**
安全对一些涉及到直接的金钱交易或个人隐私相关的应用的重要性是不言而喻的。Android系统由于其开源的属性,市场上针对开源代码定制的ROM参差不齐,在系统层面的安全防范和易损性都不一样,Android应用市场对app的审核相对iOS来说也比较宽泛,为很多漏洞提供了可乘之机。市场上一些主流的app虽然多少都做了一些安全防范,但由于大部分app不涉及资金安全,所以对安全的重视程度不够;而且由于安全是门系统学科,大部分app层的开发人员缺乏安全技术的积累,措施相对有限。
本文将重点分析Android APP面临的安全问题、防范措施以及一系列安全技术方案的实施。
**
Android病毒就是手机木马,主要是一些恶意的应用程序。比如去年央视曝光的一款名为“银行悍匪”的手机银行木马,模仿真正的手机银行软件,通过钓鱼方式获取用户输入的手机号、身份证号、银行账号、密码等信息,并把这些信息上传到黑客指定服务器。盗取银行账号密码后,立即将用户账户里的资金转走。手机木马有的独立存在,有的则伪装成图片文件的方式附在正版App上,隐蔽性极强,部分病毒还会出现变种,并且一代比一代更强大。
这些病毒有一些通用的特征:
虽然java代码一般要做混淆,但是Android的几大组件的创建方式是依赖注入的方式,因此不能被混淆,而且目前常用的一些反编译工具比如apktool等能够毫不费劲的还原java里的明文信息,native里的库信息也可以通过objdump或IDA获取。因此一旦java或native代码里存在明文敏感信息,基本上就是毫无安全而言的。
即反编译后重新加入恶意的代码逻辑,从新打包一个APK文件。重打包的目的一般都是上面提到和病毒结合,对正版apk进行解包,插入恶意病毒后重新打包并发布,因此伪装性很强。截住app重打包就一定程度上防止了病毒的传播。
这个几乎是目前针对性最强的一种攻击方式了,一般通过进程注入或者调试进程的方式来hook进程,改变程序运行的逻辑和顺序,获取程序运行的内存信息,也就是用户所有的行为都被监控起来,这也是盗取帐号密码最常用的一种方式。
当然 hook行为不一定完全是恶意的,比如有些安全软件会利用hook的功能做主动防御(比如 LBE和我们移动安全实验室最新的apkprotect线上加固产品)。一般来说,hook需要获取root权限或者跟被hook进程相同的权限,因此如果你的手机没有被root,而且是正版apk的话,被注入还是很困难的。
传输过程最常见的劫持就是中间人攻击。很多安全要求较高的应用程序要求所有的业务请求都是通过https,但是https的中间人攻击也逐渐多了起来,而且我们发现在实际使用中,证书交换和验证在一些山寨手机或者非主流ROM上面存在一些问题,让https的使用碰到阻碍。
支付密码一般是通过键盘输入的,键盘输入的安全直接影响了密码的安全。键盘的安全隐患来自三个方面:
之前做过一套安全键盘的方案,就是自定义话键盘+数字布局随机化。但是随机化的键盘很不符合人性的操作习惯。所以之后的随机话也去掉了。
还需要说明下,还有一种更为安全的方式就是现在trustzone的标准已经有GlobalPlatform_Trusted_User_Inteface,也就是说在trustzone里实现安全界面的一套标准,如果安全键盘在trustzone里弹出,则黑客不管通过啥手段都无法拿到密码,是目前最为安全的方式,但是trustzone依赖于设备底层实现,如果设备不支持trustzone,或者trustzone不支持GlobalPlatform_Trusted_User_Inteface标准,我们也无能为力。*
由于现在hybrid app的盛行,Webview在app的使用也是越来越多,Android 系统Webview存在一些漏洞,造成js提权。最为著名的就是传说中js注入漏洞和webkit xss漏洞,下面的章节我们会详细介绍。
这个最多的就是防重放攻击和注入攻击,这个不在本次文文讨论范围内。
从本质上讲,https就是一个双向身份验证的过程,但是由于Android设备太多太乱,Android设备证书也不统一,一般只有客户端验证服务器端证书,而服务器端在https层是不会验证客户端证书的,所以实际应用场景是一个单向身份验证的过程。
schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
这个是利用google API来对https进行校验,主要检查四个方面的内容:
如果服务器所使用的根证书是自签名的,或者签名机构不在设备的信任证书列表中,这样使用httpclient进行https连接就会失败。解决这个问题的办法有几种:
最简单的做法就是httpclient不开启证书校验,信任所有证书的方式。这种办法相对来说简单很多,但安全性就相当于http一样差,黑客可以轻易伪造证书进行中间人攻击,造成用户交易数据等敏感信息泄露。现在大部分的手机浏览器为了兼容各个证书参差不齐的网站,就是采取的这样的方式,但是也有些主流浏览器对非根的证书,只检查host和有效期,如果失败,给用户提示,还能继续浏览吗还是中断了的提示来保证用户体验的同时来提升安全性。
采用了覆盖google默认的证书检查机制(X509TrustManager)的方式,在发起https连接之前将服务器证书加到httpclient的信任证书列表中,这个相对来说比较复杂一些,很容易出错,而且每个网站的自定义证书都不一样,很难找到统一的办法对所有非根证书进行证书检查。
支付密码只是一个比较有代表性的数据,它其实是代表了客户端的一些敏感数据,比如银行卡号,手机号,密码,cvv,有效期等。特别对比密码和cvv这样的强敏感数据,为了提高应用程序的性能和防止别人的破解。
我们采用了RSA和AES两套加密方式对这些数据进行加密,如下图:
首先我们会生成x位的随机秘钥,要加密的数据data用该随机秘钥去加密,最后将秘钥进行Base64编码,此时的数据才是我们要上传到服务器的敏感数据,大家都知道AES是一个对称加密算法,服务端必须知道秘钥才能解密。
但是我们的秘钥是一个随机的,服务端是怎么知道的呢,如上图所示,我们将这个随机秘钥进行了RSA公钥加密,加密后的数据也进行了一下Base64编码,并上传到了服务器,因为服务器有RSA的私钥,所以服务端就可以拿到AES加密的那个随机秘钥了。
由于我们AES每次都是通过随机秘钥去加密,并没有一个固定的秘钥,所以AES的加密是安全的,另外因为RSA是非对称加密,我们的so只存储了RSA的公钥,所以也是安全的。
总之,只要服务端私钥不泄露就可以保证数据的安全性。
数据不管是传输还是存储都需要加密,加密算法不健壮会容易被破解或者模拟,太健壮导致通讯服务器撑不住高并发和高流量,所以加解密算法的选取也很重要。我们这里顺便解释下我们对加密算法的选取考虑。
为什么采用RSA加密呢?因为RSA是非对称加密,客户端只拿到了公钥,它只能用来加密,但是没法用来解密,这样就可以保证数据的安全,因为没有私钥是没法解密的。
为什么不都用RSA加密?既然RSA加密这样安全,为什么不都用RSA加密的,因为RSA加密也是有一些弊端:
- RSA的加解密对性能开销很大,所以不建议大量使用,以增加服务端压力;
- RSA对加密的数据长度有限制,具体长度是与RSA秘钥位数有关系,所以对于比较长的数据RSA没法加密。
不管RSA加密还是AES加密都进行了一下Base64编码,这个是需要注意的地方,因为RSA和AES加密出来的都是二进制流,在转化成字符串时候可能出现空格造成数据的截断,所以必须编码一下不要让它出现空格。
应用加固包括病毒扫描模块,防注入,防调试,防篡改模块四个模块,目前行业内已经出现了很多的应用加入解决方案,入爱加密、梆梆加密、百度加密、360应用加固等等。
病毒扫描模块和目前市面大多的安全卫士类app一样,摒弃了传统的依赖本地病毒库的杀毒引擎,采用了百度手机卫士的云扫描杀毒引擎。云扫描杀毒引擎在云端有一个定期更新的病毒库,扫描主要是通过收集本地安装的 App的信息,通常是apk里包名、签名、so、dex等信息,上传到云端, 云端通过比较程序签名和病毒库中的签名判断是否为病毒,然后将扫描结果返回给本地。
现在有些病毒扫描还结合人工智能深度学习利用服务器端的病毒数据库进一步查询可疑程序。还有些扫描作了一些主动防御,通过监控敏感api进行防御,例如监控以下敏感操作:更改浏览器主页,注册开机启动的行为,应用程序的内存注入等。
调试指当前 app被其他程序使用特定方法(调试器、 ptrace等)跟踪劫持,被调试后的app的一切行为都可以被其他程序查看和修改, 大家可以联想下平时通过gdb调试程序。
反调试功能分两个步骤:首先检测当前 app是否正在被调试。如果 app正在被调试,则返回调试器所在进程的进程名。 如果 app没有被调试,则保护该 app不再被其他程序调试。
保护app不被调试的方法有两种:
我们知道一个进程只允许有一个调试器,所以在进程起来的同时,会fork
一个daemon(守护进程),并ptrace被保护的app进程,守护进程会拦截调试器的入口,保证其他程序无法再调试当前app。守护进程一旦激活,就会一直存在,直到当前应用退出,
如果强行关闭守护进程,当前 App也会随之关闭。这里要注意信号的处理。
注入是指当前 App进程被其他程序使用特定方法(ptrace、 dlopen等)插入不属于当前 App的模块。一般来讲,注入不是目的,只是手段,所以注入后的模块一般都具有特殊行为,如收集 App的隐私数据,使用 Hook等方法劫持 App的正常运行流程等。其中 Hook是最主要的安全风险。
从技术手段来讲,注入的前提是需要调试的。所以如果当前 App已经被保护为不可被调试,那么理论上就不存在被注入的风险。但是,如果反调试模块激活得较晚,那么在激活之前, App还是存在被注入的可能。反注入功能就是检测出当前 App被哪些模块注入和劫持(Hook)了。
Android平台中 hook的方法分为两大类: Java Hook和 So Hook. 其中 Java Hook分为静态成员
Hook和函数 hook,可以通过检查vm dex 内存的fields域和method域来判断是否有java注入。So Hook
也是所有linux系统的hook方式,分为 GOT Hook、 Symbol Hook和 Inline Hook. Inline
Hook一般是通过汇编注入的方式, Symbol
Hook一般是采用LD_PRELOAD的方式给函数加hook,这两种注入目前都不太能防,主要防的还是GOT hook.
笔者就曾经在若干年前通过GOT hook原理在不刷机、没有root权限的情况下,在系统里植入了su程序,把系统给强行root了。GOT
Hook的原理是简单说就是通过cat
/proc/pid/maps拿到app进程的so加载地址,然后通过分析GOT表拿到so里各个函数的偏移地址,计算得出函数入口的绝对地址,然后把该地址得函数替换成注入函数。所以防native注入的方法是:通过app
进程空间,查看加载的库是不是都在/system或/data/data/app底下,如果不是,则一定有注入。
传统判断 App是否盗版的方法,业界惯用做法是事先收集大量正版应用的信息做白名单,然后利用当前 App的包名、签名等信息去做匹配。该方法的准确度依赖于白名单库的大小,并且需要网络连接。反篡改功能根据重新打包的 App和正版 App的dex的排列特征来判定 App是否被重新打包,速度快、精确度高。
针对Android特有的一些语法和设计,也存在被攻击的危险,通常我们代码在正式发布前都会进行安全扫描,安全扫描最主要是扫面以下点:
这个比较简单了,不允许打印敏感数据,再在发版前必须关闭打印日志的开关。
为了启动另个一个应用程序的Activity,我们经常会使用一些隐式的Intent,如果里面包含一些敏感信息,第三方app只要注册相同的Intent Filter,就有可能截获到敏感信息,所以发送隐式Intent,必须要指定接收方和权限。
Android 包括四大组件:Activitie、Service、Content Provider、Broadband Receiver,它们每一个都可以通过外面隐式的Intent方式打开,所以这些组件只要不是对外公开的必须在manifest里面注明exported为false,禁止其他程序访问我们的组件,对于要和外部交互的组件,应当添加一下访问权限的控制,还需要要对传递的数据进行安全的校验。
这种情况主要出现在对外开放的组件中,因为对外开放的组件可以接收Intent,一般里面会包含一些数据,当我们没有对这些数据判空时候,app一般会crash,攻击者可能发送数据为空的Intent来攻击。
这个问题最早是可以追溯到2011年的一篇paper《Attacks on WebView in the Android System》http://www.cis.syr.edu/~wedu/Research/paper/webview_acsac2011.pdf ,这篇文章指出了addJavascriptInterface的方式在功能上带来的一些风险,比如你的app里实现了一个读写文件的类,然后使用addJavascriptInterface接口允许js调用,那么就可能导致攻击者直接调用这个class实现读写文件的操作。这种方式是最原始的风险,并没有直接指出getClass()方法的利用。后来百度安全组在2013年也正式的发布这个漏洞的高危工单:http://security.baidu.com/risk/findNewsDetail.do;jsessionid=B09405787822801D67251543B2B8C5CB.security_client-web01?id=336。这个漏洞主要是通过getClass()的方法直接调用java. lang. Runtime接口执行系统命令,让任何js具有app相同的操作权限!
解决办法主要是两个:
钓鱼这个事一直都安全界最常用的攻击,也是最难通过技术手段解决的一类问题,而且钓鱼的手段也是千奇百怪,要防钓鱼除了让用户提高安全意识,不点击来路不明的链接外,技术层面可以做到如下两点:
主要是由于 JS 的 XmlHttpRequest 可以读取本地文件,从而读取到app data 数据库目录下的webviewCookiesChromium.db, 这个db通常是系统存放cookie的地方,相当于变相的为读取cookie开了权限,具体可以看一下乌云的链接:乌云链接
弱加密:
很多Android应用的加密存储方式都比较简单,只是一个简单的md5, 很容易被破解。
签名校验方向:
每一个apk都会有个签名,签名只有这个开发者才拥有,如果别人修改了代码,也必须要签名才能运行,但是修改者的签名与官方签名是不一致的,我们在so里面存储了应用程序官方签名的hashcode值,在应用启动后so就会检查签名是不是官方的签名,如果不是,应用程序直接关闭,so里面的加密函数也都不起作用。
/*
* @author zhoushengtao(周圣韬)
* @since 2015年6月15日 19:19:22
* @weixin stchou_zst
* @blog http://blog.csdn.net/yzzst
* @交流学习QQ群:341989536
* @私人QQ:445914891
/