简介
“Janus”漏洞是Google在12月发布的安卓系统的安全公告中披露的,由移动安全公司GuardSquare 的研究团队发现,漏洞编号:CVE-2017-13156。该漏洞的可以绕过Andorid的签名校验机制,攻击者可以利用漏洞通过升级的方式对App进行篡改。
由于安卓系统中App的安全和可信都是建立在签名和校验基础之上,“Janus”漏洞绕过了系统的签名校验,可以说是突破了安卓整个安全机制,导致安卓整个安全体系的沦陷。
一旦攻击者将植入恶意代码的仿冒的App投放到安卓商店等第三方应用市场,就可替代原有的App做下载、更新。网友安装这些仿冒App后,不仅会泄露个人账号、密码、照片、文件等隐私信息,手机更可能被植入木马病毒,进而或导致手机被ROOT,甚至被远程操控。
影响范围
- 安卓5.0-8.0的各个版本系统;
- 使用安卓Signaturescheme V1签名的App APK文件。
由于,signature scheme V2需要对App进行重新发布,而大量的已经存在的App APK无法使用V2校验机制,所以为了保证向前兼容性,V1的校验方式的还被保留,故该漏洞影响范围较大。
漏洞分析
漏洞原理
1、安卓在4.4中引入了新的执行虚拟机ART,这个虚拟机经过重新的设计,实现了大量的优化,提高了应用的运行效率。与“Janus”有关的一个技术点是,ART允许运行一个raw dex,也就是一个纯粹的dex文件,不需要在外面包装一层zip。而ART的前任DALVIK虚拟机就要求dex必须包装在一个zip内部且名字是classes.dex才能运行。当然ART也支持运行包装在ZIP内部的dex文件,要区别文件是ZIP还是dex,就通过文件头的magic字段进行判断:ZIP文件的开头是‘PK’, 而dex文件的开头是’dex’.1
代码部分
2、APK本质是一个ZIP文件,ZIP文件的读取方式是通过在文件末尾定位central directory, 然后通过里面的索引定位到各个zip entry,每个entry解压之后都对应一个文件。1
代码部分
因此:
- 对于上述1,ART通过文件头判断是否为dex文件,然后按照dex的格式进行解析,而如果在dex文件之后附加其他数据,完全不影响其工作机制
- 对于上述2,系统在解析一个APK文件时,是从文件末尾寻找central directory标志,然后依次展开解析的。如果在一个ZIP文件头部追加任意数据,也完全不影响整个APK解析过程。(ZIP/APK文件中数据的偏移需要改动)
所以,攻击者构造一个(恶意dex+合法APK)文件,并修改好APK中部分数据的偏移,如下图,则可以同时满足上述两个条件。而上述1为Android执行代码的入口,即ART会执行我们构造的恶意dex;上述2完成解析后会对APK进行签名校验,实际校验时,参与校验运算的数据均为合法APK的数据。
简而言之,构造的畸形文件既可以通过签名校验部分,又可以执行攻击者构造的任意代码,巧妙的绕过Android的应用安全机制。
漏洞利用
- 攻击者可以向APK文件的开始位置放置一个攻击的DEX文件A;
- 安卓系统在安装时用ZIP的读取机制从末尾开始进行文件的读取,读取到了原始的APK内容,并且以V1的方式进行校验,认为这个文件是正常的,没有篡改,APK安装成功;
- 在运行时,Android的ART虚拟机从文件头开始读取,发现是一个DEX文件,直接执行,攻击文件A被最终执行。
利用代码如下,来自V-E-O的github
https://github.com/V-E-O/PoC/tree/373104bea150895e10879e87fcecfa0318c82647/CVE-2017-13156
1 | #!/usr/bin/python |
需要注意的是,ART在执行恶意dex时,由于需要和真实APK中的AndroidManifest.xml相对应,多数情况下受到很多限制,例如:
- 用来构造畸形文件的APK必须是被攻击APP的新版本,否则Android系统会拒绝安装
- 恶意dex的包名、入口类等需要与被攻击APP完全一致
- 恶意dex需要实现被攻击APK的provider
标签里的android:name类需要实现 - setContentView的资源文件id需要和被攻击APP一致
- 通过重打包的方式构造攻击dex文件市,需要考虑原APK为多dex的情况,因为此漏洞只能执行一个恶意dex文件
漏洞修复
修复代码如下,在解析APK的时候对文件头(LocalFileHeader::kSignature)进行了验证,如果文件头不对,停止解析。
https://android.googlesource.com/platform/system/core/+/9dced1626219d47c75a9d37156ed7baeef8f6403%5E%21/#F01
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27diff --git a/libziparchive/zip_archive.cc b/libziparchive/zip_archive.cc
index 78de40a..d0bbd72 100644
--- a/libziparchive/zip_archive.cc
+++ b/libziparchive/zip_archive.cc
@@ -441,6 +441,22 @@
return -1;
}
}
+
+ uint32_t lfh_start_bytes;
+ if (!archive->mapped_zip.ReadAtOffset(reinterpret_cast<uint8_t*>(&lfh_start_bytes),
+ sizeof(uint32_t), 0)) {
+ ALOGW("Zip: Unable to read header for entry at offset == 0.");
+ return -1;
+ }
+
+ if (lfh_start_bytes != LocalFileHeader::kSignature) {
+ ALOGW("Zip: Entry at offset zero has invalid LFH signature %" PRIx32, lfh_start_bytes);
+
+ android_errorWriteLog(0x534e4554, "64211847");
+
+ return -1;
+ }
+
ALOGV("+++ zip good scan %" PRIu16 " entries", num_entries);
return 0;