转载自:https://bbs.pediy.com/thread-223539.htm
近日,Android平台被爆出“核弹级”漏洞Janus(CVE-2017-13156),该漏洞允许恶意攻击者任意修改Android应用中的代码,而不会影响其签名。
众所周知,Android具有签名机制。正常情况下,开发者发布了一个应用,该应用一定需要开发者使用他的私钥对其进行签名。恶意攻击者如果尝试修改了这个应用中的任何一个文件(包括代码和资源等),那么他就必须对APK进行重新签名,否则修改过的应用是无法安装到任何Android设备上的。但如果恶意攻击者用另一把私钥对APK签了名,并将这个修改过的APK对用户手机里的已有应用升级时,就会出现签名不一致的情况。因此,在正常情况下,Android的签名机制起到了防篡改的作用。
但如果恶意攻击者利用Janus漏洞,那么恶意攻击者就可以任意地修改一个APK中的代码(包括系统的内置应用),同时却不需要对APK进行重新签名。换句话说,用这种方式修改过的APK,Android系统会认为它的签名和官方的签名是一致的,但在这个APK运行时,执行的却是恶意攻击者的代码。恶意攻击者利用这个修改过的APK,就可以用来覆盖安装原官方应用(包括系统的内置应用)。由此可见,该漏洞危害极大,而且影响的不仅是手机,而是所有使用Android操作系统的设备。
目前,Google将该漏洞危险等级定义为高,其影响Android 5.1.1至8.0的所有版本。基于多年以来针对移动端漏洞的技术积累和安全对抗,安天移动安全对Janus高危漏洞进行了紧急分析,并发布技术报告,全文如下。
ART虚拟机在加载并执行一个文件时,会首先判断这个文件的类型。如果这个文件是一个Dex文件,则按Dex的格式加载执行,如果是一个APK文件,则先抽取APK中的dex文件,然后再执行。而判断的依据是通过文件的头部魔术字(Magic Code)来判断。如果文件的头部魔术字是“dex”则判定该文件为Dex文件,如果文件头部的魔术字是“PK”则判定该文件为Apk文件。
另一方面,Android在安装一个APK时会对APK进行签名验证,但却直接默认该APK就是一个ZIP文件(并不检查文件头部的魔术字),而ZIP格式的文件一般都是从尾部先读取,因此只要ZIP文件尾部的数据结构没有被破坏,并且在读取过程中只要没有碰到非正常的数据,那么整个读取就不会有任何问题。
总而言之,Android在加载执行代码时,只认文件头,而安装验证签名时只认文件尾。
因此只要构造一个APK,从其头部看是一个Dex文件,从其尾部看,是一个APK文件,就可以实施攻击。很容易想到,将原APK中的classes.dex抽取出来,改造或替换成攻击者想要执行的dex,并将这个dex和原APK文件拼起来,合成一个文件,就可以利用Janus漏洞。
当然仅仅简单地将恶意dex放在头部,原apk放在尾部合起来的文件还是不能直接用来攻击。需要稍作修正。对于头部dex,需要修改DexHeader中的file_size,将其调整为合并后文件的大小。另外需要修改尾部Zip,修正[end of central directory record]中[central directory]的偏移和[central directory]中各[local file header]的偏移。
当然,Janus漏洞是针对APK文件的攻击,因此v1签名无法抵御这类攻击,而v2签名可以抵御。
▲图1 攻击文件拼接原理
具体的漏洞利用分为3步:
1. 从设备上取出目标应用的APK文件,并构造用于攻击的DEX文件;
2. 将攻击DEX文件与原APK文件简单拼接为一个新的文件;
3. 修复这个合并后的新文件的ZIP格式部分和DEX格式部分,修复原理如图1所示,需要修复文件格式中的关键偏移值和数据长度值。
最后,将修复后的文件,重命名为APK文件,覆盖安装设备上的原应用即可。
1. Google官方修复方案
对文件system/core/libziparchive/zip_archive.cc打上如下patch:
2. 修复原理
打开ZIP格式文件时,多做了一项校验,也就是检测文件的头部是不是以‘PK’标示打头。如果是,则进行正常的逻辑,否则认为该文件不是一个合法的ZIP格式文件。
Android平台上的应用签名机制是Android安全的基石。Android平台的permission机制完全依赖于应用的签名,签名机制一旦突破,所有基于Android permission构建的安全体系将崩溃。而Janus漏洞已经不是Android平台的第一例签名机制漏洞了,之前由“Bluebox”发现的Master Key漏洞和“安卓安全小分队”(安天移动安全上海团队前身)发现的第二个Master Key漏洞都是利用签名机制的漏洞,其原理是利用Android的代码中对APK验证不充分的缺陷,使得应用在安装时验证的是原dex,但执行的是另一个dex,从而达到瞒天过海、偷梁换柱的目的。
Janus漏洞的利用在原理上也类似,它将恶意dex和原apk拼接在一起,安装验证时验证的是原apk的dex,而执行时却是执行恶意dex。修复这类漏洞的原理也很简单,就是加强安装时的验证,避免不合法应被安装到系统中。针对Janus漏洞,只需简单验证一下apk文件的头部是不是‘PK’即可。如果不是,则该apk 文件一定不是一个正常的apk文件。
Janus漏洞再一次提示我们,即使像Google这样的跨国科技企业也难免在签名验证这么关键的环节上多次产生漏洞,特别是Janus漏洞从2014年就已经存在,潜伏长达3年之久,并且从Android 5.1-8.0版本都存在这个漏洞。这说明了安全问题有时是极其隐蔽的,暂时未发现安全问题,不代表安全问题不存在;更说明了安全是动态的而不是静止的。因此安全防护是一个工程化体系化的持久战,很难通过单次或短期投入就将安全问题一次性解决,在安全方面只有持续而坚定地投入,一旦发现风险或者漏洞就要以最快的速度及时修复,只有这样才能保证系统安全而稳定地运行,最大程度地规避风险和损失。
要理解Janus高危漏洞的原理,首先需要掌握一些基础知识:ZIP文件结构、DEX文件结构和Android APK签名机制。
1. DEX文件结构
▲图2 dex文件格式
Dex文件有很多部分组成(如图2),其中Dex Header最为重要,因为Dex的其他组成部分,都需要通过这个Dex Header中的索引才能找到。
Dex Header内部结构如下:
▲图3 dex header 结构
在Dex Header中(如图3),file_size规定了整个dex文件的总大小。因此,如果想在Dex文件中隐藏一些额外的数据的话,最简单地,就可以将这些数据追加到Dex文件末尾,然后再将file_size调大到合适的值即可。
2. ZIP文件结构
ZIP文件可以通过获得文件尾部的End of central directory record(EOCD record)获取central directory,遍历central directory中的每项记录得到的file data即为压缩文件的数据。
▲表1 ZIP文件结构
如表1,读取ZIP文件时,会现从最后一个记录区end of central directory record中读取central directory的偏移,然后遍历central directory中的每一项,获取每个文件的 local file header,最后通过 local file header 获取每个文件的内容。
End of central directory record结构体如下:
▲表2 End of central directory record结构
该结构(如表2)中的offset of start of central directory with respect to the starting disk number,指向了central directory的位置。
Central directory结构体如下:
▲表3 Central directory结构
该结构(如表3)中的relative offset of local header,指向了每个文件的Local File Header的位置。
因此,如需在ZIP文件中隐藏一些数据,可将这些数据简单添加到头部,然后修改End of central directory record中central directory的偏移。同时修改每个central directory中的Local File Header的偏移即可。
3.Android APK签名机制
Android APK签名机制分为两个版本:v1和v2版本。
两个版本的签名区别在于,前者是对APK中的每个文件进行签名,如果APK中某个文件被篡改了,那么签名验证将会通不过;后者则是对整个APK文件进行签名,只要APK文件的内容发生变化则签名失效。
很显然,v2版本要比v1更加严格,安全性会高很多。
但遗憾的是,Android从7.0开始才引入v2签名。之前的所有Android系统只能验证v1签名的app,即使这个app也用V2签名了。
以下是两个版本的签名对比:
▲表4 v1 v2签名对比
对于android 7.0以上,系统在校验签名是会先检查是否存在V2签名方案,若存在,则通过V2签名方案对APK进行校验,否则使用V1签名方案对APK进行校验。
▲图4 Android v2签名流程
对于android 7.0以下的系统,不支持V2签名方案,所以APK在签名时最好将两种签名方案都支持。
例子:https://github.com/TomesVWhite/BuildFakeApk