浅谈Android安全

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去钓鱼了。即使抓不到别人的数据,也并不意味着什么也做不了,想想看我们能做什么?抓不到别人的数据,可以抓自己的数据,分析服务器接口,寻找设计漏洞做突破口。下面是一些应用场景:

  • 微信投票刷票。微信中充斥着各种投票活动,什么萌娃大赛、最美警察、舞蹈之星。基本都是一个链接甩过来,打开先提示获取你的公开信息(昵称、头像等),确认后通常是一个90年代风格粗糙的网页,找到对应编号的参赛者后,勾选提交。做这些操作前,设置好你的代理服务器,看看投票时发送的请求参数,多半情况下你会发现一些问题,用postman验证你的想法,然后差不多花半个小时,就能写一个刷票工具。这时你可以思考下,怎么设计才能让投票程序更健壮。
  • 微博xss事件。微博API本身是开放的,通过xss漏洞,注入调用关注、转发、私信API的js脚本。
  • 微信、点评、微博等机器人。在很多行业,大众点评网站中商户的好评差评,对商户的生意和口碑影响很大,于是出现了很多水军刷好评和恶意诋毁对手的事件,人肉养号和刷评论很显然成本比较高,加上活跃度越高的用户级别越高,级别越高的用户评论时对商户打分的权重也越高,所以当时有很多养机器人小号做不法勾当,直到后面公司投入很大资源成立专门的诚信团队这种现象才好转。微信公众号也有同样问题,一个名人公众号发篇文章才几百的阅读量,这样多没面子啊,花点钱找水军刷刷量吧,印象中微信针对这个事也从政策和技术上整顿过好几次了。微博就不多说了,我甚至怀疑官方的态度,水清则无鱼嘛。甚至连苹果APP Store都不能幸免,不过只能人肉和众包的形式。

1.2 使用https是否就万事大吉

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

图 1.2
图 1.3

1.3 使用签名和加密数据

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

  • 使用签名。即给你的请求参数添加一个签名,后台服务接收到请求时,先验证签名,签名不正确的话,则不予处理。签名规则五花八门,大致策略就是根据请求参数做一些运算最后生成一个唯一的字符串当做sign,微信支付的签名大家可以做一个参考:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3
  • 数据加密。post到服务器和从服务器返回的数据都做加密,这样的话即使攻击者拿到你的数据,他不知道你的加密算法就无能为力了。

上面说的两种方式可以同时使用,但是大家还需要考虑一个问题:如何防止攻击者获取到你的签名生成规则和加密算法,例如你加密使用的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文件,主要分为以下几部分:

  • xml 编译为二进制文件。xml中所有字符串都会被放到一个资源池中,原来使用字符串的地方会被替换成资源的地址。所以xml编译为二进制不仅可以减少占用的空间,而且可以提高加载速度。

  • 生成resource.arsc资源索引表

  • java生成dex文件

图 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

补充: 这是一年前的文章,当时认为加固是比较安全的方案,毕竟逆向so成本非常高,后面发现使用像xposed框架这样的hook技术,类似于降维打击,可以绕过加固技术轻松获取到dex文件。目前的乐固、360等大厂加固都可以绕过,从原理上看,加固技术对于这种hook技术获取dex也没有什么好办法,作为APP的作者,需要加强hook方面的防御来提高加固技术的安全性,后面有时间的话我会写一篇文章介绍这方面的攻防。

参考文章:
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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容