Android干货分享Android知识Android开发经验谈

浅谈Android安全

2017-10-08  本文已影响319人  dfqin

1、网络请求——裸奔的数据

无论是网页还是APP,都不可避免与后台服务进行访问,可能从服务器获取数据或者提交数据到服务器,这时就需要客户端发起网络请求。由于http协议简单、灵活和无状态等特点,目前大部分应用都是采用的http协议进行的网络请求。一个常见的网络请求如下图:

图 1.1

1.1 无处不在的安全隐患

因为http协议是明文传输的,所以上面的用户名和密码也是明文在网络中传输的,在传输过程中的任何一个环节,攻击者都可以直接看到明文数据。当然现在很多系统都不传输和保存明文账号密码,通常会采取MD5值传输和存储,之前比较出名的CSDN数据库泄漏,就是因为密码是明文保存,黑客可以方便的拿去撞库,加重了事件的严重性。近几年MD5破解能力提高,例如黑客看到库中一个密码数量比较多,就可以尝试去爆破,加上也可以直接用密码的MD5值去撞库,所以现在通常生成MD5值时都需要加盐,例如MD5(name+pwd)作为密码存储,同样的密码生成的值是不一样的,在一定程度上提高了安全性。
攻击不一定需要用户名密码,或许可以直接打token和uid的主意。通过上图可以看到,通过上图可以看到,一旦用户登录后,服务器就通过token来标识身份。如果这个token被黑客劫持了,他就可以冒充你的身份进行攻击,如果token机制设计不合理,攻击者甚至可以直接暴力去撞token。大部分网站的机制也类似,服务器通过一个sessionId标识用户,攻击者一旦拿到这个sessionId,就可以去冒充一个合法用户。
当然,虽说这些数据明文传输,但是如果你不是网管、网络运营商,想拿到这些数据也不是那么容易,要不然也不会有那么人在公共场合开放WIFI去钓鱼了。即使抓不到别人的数据,也并不意味着什么也做不了,想想看我们能做什么?抓不到别人的数据,可以抓自己的数据,分析服务器接口,寻找设计漏洞做突破口。下面是一些应用场景:

1.2 使用https是否就万事大吉

https分单向认证和双向认证,大部分的应用场景是c/s模式,这时通常都是采用的单向认证的方式,也就是说可以保证客户端拿到的数据是后台发送的。这时想攻击确实很难了,除非你能忽悠别人安装并信任你的证书,实际上也并不是做不到,很多人蹭wifi时或者在网上下载一些资源,系统提示要信任什么东西,看都不看就点确定。这些暂时不提,上面提到的那些攻击,都依赖于对后台接口的分析和调用,对于这类攻击者来说,你使用http和https是没有区别的。
使用抓包工具时,给目标设备安装并信任装包工具的自签名证书,这时候就可以分析https请求了。下面是正常抓https请求的包和配置过证书后的抓包

图 1.2 图 1.3

1.3 使用签名和加密数据

上面可以看到,https并不能阻挡攻击者分析请求接口并发起攻击,为了增加攻击者分析请求的难度,通常可以采用两种方式:

上面说的两种方式可以同时使用,但是大家还需要考虑一个问题:如何防止攻击者获取到你的签名生成规则和加密算法,例如你加密使用的AES算法,你的秘钥放在哪里呢?

2、反编译,让你的代码没有秘密

这里主要指Android项目。大家知道,很多应用尤其是单机版游戏类,都只有iOS版本,没有Android版本。一些联网游戏,有Android版本用户数据和iOS用户数据也是隔离的。这些现象的原因,一方面是Android应用分发的混乱,另一方面,就是比较低的反编译和二次打包发布的门槛。

2.1 项目结构

Android项目早期没有自己单独的IDE工具,是使用Eclipse加ADT插件进行开发,项目结构跟其他Java项目一致,即一个workspace表示一个工程,里面一个一个的project表示子项目。后面google推出了基于IntelliJ IDEA的Android Studio,项目结构也顺其自然的变成了Project+moduled形式。虽然目录层级有所变化,但是整体结构变化不大。
我们可以看下图,是一个典型Android项目,PermissionGrantor是项目名称,里面有两个子项目,分别是app和grantor,两个子项目图标看起来不一样,是因为他们一个是应用项目,即编译后可以生成一个可以在Android设备上运行的应用,另外一个是库项目,可以生成一个被其他项目引用的aar库。
我们看一下app项目即应用项目,红框标注的是几个比较重要的目录,libs是用来放项目依赖库的,java目录是项目的代码,res则是存放资源,包括图片、布局、字符串等

图 2.1

最下面的AndroidManifest.xml可以理解成项目的配置文件,操作系统加载一个应用,从哪里开始执行、每个页面调用那个类和都有哪些服务哪些权限,都需要在这里配置。从这里可以透露一个很重要的信息,就是应用的入口和各个模块甚至是服务的类,都可以从这里看到,而且是必须要配置到这里并且这个文件是要附带到发布APK里面的。


图 2.2

2.2 编译与反编译

2.2.1 混淆

项目编译正式包时,默认是开启混淆的,即类名方法名会被混淆成a,b,c,d的样子,增加破解着的难度。Android采用的Proguard的开源方案,只能对java类进行混淆。上面的分析中我们知道,程序的启动类(Application)和业务类(Activity)都需要注册到AndroidManifest.xml中,而混淆只能对java类进行混淆,并不支持xml这种配置文件,所以Android默认对于Application、Activity、Service、UI相关的类都是不混淆的,而一些通过字符串反射生成类的地方也不能混淆(网络数据转为本地java对象),而jni调用方法也不能混淆。下图是QQ阅读反编译后的一个Activity和数据对象。
通过上面分析,我们发现虽然Android引入了混淆技术,但是由于当前混淆方案的缺陷和Android系统框架中大量使用反射技术,使得很多重要的类不能混淆,这极大的降低了反编译后阅读源码的成本。1.3中提到的使用签名和加密算法,这时你会发现,被破解也不是特别的难,为了增加破解难度,我们可以把算法放到native代码中,即用C/C++实现算法,通过jni调用。

图 2.3 图 2.4
2.2.2 编译

项目编译成apk文件,主要分为以下几部分:

图 2.5
2.2.3 签名

APP编译成apk包后,必须对包进行签名才能在手机上安装。我们分析签名后的包会发现里面多了MANIFEST.MF、CERT.SF和CERT.RSA三个文件。我们直接用文本编辑器打开MANIFEST.MF如下图:

图 2.6

可以看到这个文件里面存储了每一个资源的SHA1摘要的base64值,这里就可以解释你直接替换了apk包里面的某个资源为什么apk无法安装,因为程序在安装时验证资源的摘要跟MANIFEST.MF中的不一致,可以判断出这个包被人动了手脚。
我们继续用文本编辑器打开CERT.SF,如下图:

图 2.7

我们会发现除了最上面多了一个SHA1-Digest-Manifest值,下面的文件跟第一个文件没区别,我们可以测试得知,这个值就是第一个文件MANIFEST.MF的SHA1摘要的base64值。所以上面提到的,替换APK资源文件后,如果你同时更新MANIFEST.MF中对应的值也不行,因为此时通不过CERT.SF的验证。那么我同时修改CERT.SF中的值呢?我们可以继续看第三个文件CERT.RSA,我们发现用文本编辑器打开,它是二进制文件,可以通过下面的命令打开此文件:

openssl pkcs7 -inform DER -in CERT.RSA -noout -print_certs -text

打开后的内容为:

图 2.8

网上有资料解析这个结构,我们不展开分析,只要知道,上面那一坨16进制编码是签名的公钥,下面那一坨是签名,即用你keystore文件中的私钥对上面的域和值进行加密得到的结果。程序安装时使用上面的公钥进行校验,如果你拿不到对方的私钥就没办法对签名进行伪造。这就回答了上面抛出的问题,替换apk包中的资源后,同时修改MANIFEST.MF和CERT.SF也无法通过验证。
但是如果我同时把上面的公钥也替换了呢?答案是当然可以通过验证啊。因为你的这一系列动作等同于对APK重新签名了,不过使用的是你自己的私钥。Android采用的是自签名的证书,所以如果有人拿到了你的APP,用自己的keystore重新签名然后发布,用户并不知道他是山寨的还是正版的。这就解释了为什么很多单机游戏都不做Android版本的,你辛苦做一个收费的游戏,攻击者很容易破解在里面加点广告,然后再二次打包发布。一些不愿意花钱买游戏的用户只需要忍受下广告就可以免费玩游戏了。通过这里的分析,我们知道攻击者很难伪造我们的签名,所以我们可以在程序中插入签名检查的代码,提高攻击门槛。

2.3 反编译

上面已经把APK结构分析的差不多了,反编译要做的工作就是反汇编.dex和还原xml文件。我们可以使用apktool直接对一个apk进行反编译,可以得到还原后的xml文件和.smali文件。这里的smali文件是可以运行在android虚拟机上的汇编语言,读起来还是有些晦涩的,如果有读源码的需求,可以直接解压apk包,使用dex2jar工具直接反编译dex文件,可以得到反编译后的class文件,即我们能得到反编译后的java源码。我们1.3中提到的加密算法和秘钥,就可以通过分析反编译后的源码去获取。为了不让别人获取类似敏感信息,很多人选择这些信息继续向底层放到lib.so中,下面一节我们会介绍。

3、NDK与反汇编

3.1 NDK

NDK全称为Native Development Kit,翻译过来叫原生开发工具包,官方解释是可以在Android中使用C/C++的工具。我没找到这里Native的详细解释,个人理解常规Android程序是运行在虚拟机上面的,而Native指直接运行在操作系统上面的程序,可以调用操作系统的API。一般情况下我们没有必要使用NDK,官方也提到了使用native开发会增加开发过程的复杂性。但是对于一些计算密集型的应用,例如游戏、图像处理,使用NDK能提高运算性能。还有一些情况为了复用现有库或者跨平台库,也会选择NDK。上面提到的一些核心算法和秘钥,大家选择放到native层,潜意识中也是默认native的破解难度比java高,还有欺负大部分Android程序员不会写C/C++代码:)
我们可以创建一个新工程,创建时勾选上"Include C++ support",如下图:

图 3.1

这时会创建一个支持NDK的默认工程,项目里包含从C++代码中获取一个字符串的demo:

图 3.2

我们编译出apk并反编译这个程序如下图,会发现包里面多了一堆libnative-lib.so(其实只是一个库,为了支持不同的CPU架构编程出了多个版本)。

图 3.3

字符串的生成的实现放到了这个so库里面,相比较直接在java代码中生成字符串,破解起来的确麻烦了些。这个时候如果想破解拿到这个字符串,该怎么办呢?
一个简单的办法,直接写java代码,通过jni调用这个so的方法。这尼玛就尴尬了,本来封装so是为了隐藏数据,结果倒好,别人才不管你怎么实现,把你直接拿来用。为了避免这种尴尬,我们可以在so中判断当前应用的签名,前面分析过,只要别人拿不到你的keystore就没办法伪造你的签名。如果没办法直接调用这个so,只能想办法破解才能洞察到里面隐藏的秘密了,下面我们谈一下怎么来破解。

3.2 PE/ELF和反汇编

上面提到了我们需要破解so文件才能获取里面的信息,在破解之前我们要先理解so文件到底是什么。这里我们我们需要了解一种文件格式,即Windows系统中的PE(Portable Executable)和Linux系统下的ELF(Executable Linkable Format),看全称基本了解这种文件的用途。其实在Windows系统上我们平时运行的.exe安装文件和使用共(破)享(解)软件时用来覆盖的.dll动态链接库文件,都是PE格式文件。在Linux系统上(我们可以把Android理解为Linux系统)的.o目标文件、.a静态连接库和.so动态连接库,都属于ELF文件。如下图所示,一段C程序编译生成ELF文件后的结构

图 3.4

当然我们直接用文本编辑器打开so文件,看到的是如下二进制文件:

图 3.5

根据ELF文件格式的定义,可以把这个二进制文件还原为图3.4右边的结构。这些细节很复杂我们也没必要去全部弄清楚,我们可以使用一些命令直接查看ELF文件。以图3.2中的C++代码生成的so为例,我们输入:

readelf -a libnative-lib.so 

可以看到结果:

图 3.6

内容很长,不了解ELF格式的话很难看懂(我这里在使用“greadelf”是因为使用的mac系统自己安装的类似工具),我们可以先大概看一下,这里的“节头”其实就是对应的图3.4中的section,我们可以查看第12个segment——.rodata:

图 3.7

我们发现了之前辛苦藏到so中的字符串"Hello from C++"。如果想看代码实现,可以使用objdump命令,能看到反汇编后的代码。
现在我们可以理解ELF文件已经是机器指令了,我们如果想看一些代码逻辑,要么像机器一样读机器指令,要么把这些机器指令反编译成人类方便阅读的汇编代码(汇编语言人类也很难读的好吧),这个过程我们就是反汇编。
当然真正去破解一些so文件时,使用上面的办法效率太低,这是可以去使用一些专业的破解工具,里面集成了很多很强大的功能,可以大大提高工作效率,例如使用IDA直接把so打开,我们可以很方便的定位到jni函数:

图 3.8

从这个例子的分析,我们为了增加破解难度,可以动态注册jni函数并且自定义函数名,避免破解者一眼就找到Java_com_xxx这样的native函数。另外一点就是要隐藏在字符串,不可以直接明文写在代码中,至少做个拼接吧,或者添加一定的逻辑动态生成。大家可以想一下,还有没有其他的办法增加破解难度?

4、加壳与脱壳

通过上面的分析我们可以知道,无论是编译java代码生成的dex文件,还是编译C/C++代码生成的so文件,反编译成本都不是特别的高,如果想增加破解难度,还能做什么呢?其实这个问题一直存在,大家想想从十几年前的PC时代,大部分人使用的都是windows,当时大家使用的office、photoshop有几个不是破解版的?因为当时网络不发达,很多游戏和工具软件都是本地验证授权的,再加上法律监管空白,软件破解行业甚是红火。
在当时就有了加壳脱壳这个说法,看一些破解软件的文章时,第一步基本都是脱壳。加壳直观理解就是给程序加一层壳,可以用来对原程序进行资源压缩、防调试、防注入、防反编译,也就是说通过一个壳把原来的程序保护了起来。我们知道一个常规Android程序它的所有代码都在dex文件中,程序启动时要先把这个dex文件载入到内存中,所以如果要加壳的话,主要工作就是把原dex文件加密或者隐藏起来,放一个新的壳dex到apk中,程序启动时运行这个壳dex,然后这个壳dex在运行时再加载原dex,用一张图表示如下:

图 4.1

其实这时候我们发现,破解原dex的工作变成了破解壳dex了,最终的原理是一样的,早期的梆梆、360等公司的加固方案都被人破解,网上能查到破解步骤。当然他们的加固方案升级很快,网上看的破解方案对于新的加固方案基本已经无效。我们使用腾讯的乐固加密一个不包含so库的APK后反编译后可以看到如下:

图 4.2 图 4.3

我们可以看到程序一开始就加载了libshella.so库,这里我们可以推测我们的原dex被加密为了mix.dex和mixz.dex,dex的动态还原方案的实现放到了libshell.so库中,即dex的加壳方案放到了so实现,再次佐证了大家公认so的破解难度比较高。这里我测试发现这两个so使用IDA反汇编工具已经无法直接打开了,很显然这个so是加过壳的。关于so的脱壳,我也是小白,就不班门弄斧了,这里乐固.so V2.8版本的脱壳方案(乐固目前最新是V2.10),可以证明上面我们的推测是正确的,大家有兴趣可以看一下https://bbs.pediy.com/thread-217556.htm

参考文章:
http://blog.csdn.net/luoshengyang/article/details/8744683
http://blog.csdn.net/jiangwei0910410003/article/details/50402000
http://www.cnblogs.com/LiuYanYGZ/p/5574602.html
http://www.cnblogs.com/EliteDci/p/5578901.html

上一篇下一篇

猜你喜欢

热点阅读