2017年7月31日GuardSquare向Google报告了一个签名漏洞并于当天收到确认。Google本月修复了该漏洞,编号CVE-2017-13156。
经过360CERT分析确认,该问题确实存在,影响较为严重。攻击者可以绕过签名验证机制构造恶意程序更新原有的程序。
0x01 事件概述
该漏洞产生的根源在于将DEX文件和APK文件拼接之后校验签名时只校验了文件的APK部分,而虚拟机执行时却执行了文件的DEX部分,导致了漏洞的发生。由于这种同时为APK文件和DEX文件的二元性,联想到罗马的二元之神Janus,将该漏洞命名为Janus漏洞。
0x02 事件影响
影响Android5.0-8.0的各个版本和使用安卓V1签名的APK文件。
0x03 事件详情
技术细节
Android支持两种应用签名方案,一种是基于JAR签名的方案(v1方案),另一种是 Android Nougat(7.0)中引入的APK签名方案v2(v2方案)。
v1签名不保护APK的某些部分,例如ZIP元数据。APK验证程序需要处理大量不可信(尚未经过验证)的数据结构,然后会舍弃不受签名保护的数据。这会导致相当大的受攻击面。此外,APK 验证程序必须解压所有已压缩的条目,而这需要花费更多时间和内存。为了解决这些问题,Android 7.0中引入了APK签名方案v2。在验证期间,v2方案会将APK文件视为 Blob,并对整个文件进行签名检查。对APK进行的任何修改(包括对ZIP元数据进行的修改)都会使 APK 签名作废。这种形式的APK验证不仅速度要快得多,而且能够发现更多种未经授权的修改。
如果开发者只勾选V1签名不会有什么影响,但是在7.0上不会使用更安全的V2签名验证方式;只勾选V2签名7.0以下无法正常安装,7.0以上则使用了V2的方式验证;同时勾选V1和V2则所有机型都没问题。此次出现问题的是V1签名方案。简单地说,把修改过的dex文件附加到V1签名的apk文件之前构造一个新的文件,V1方案只校验了新文件的apk部分,而执行时虚拟机根据magic header只执行了新文件的dex部分。
我们来看一下已经公布的 POC (https://github.com/V-E-O/PoC/tree/master/CVE-2017-13156) 的原理。janus.py接受dex文件和apk文件作为输入,组合起来输出。
读取dex文件:
读取apk文件:
Apk其实就是一个zip。简单地说zip文件格式由文件数据区、中央目录结构和中央目录结束节组成。
其中中央目录结束节有一个字段保存了中央目录结构的偏移。代码中搜索中央目录结束节的固定结束标记x06054b50定位到中央目录结构的偏移,将其加上dex文件的大小,因为我们要把dex文件插到apk前面。
接下来依次更新中央目录结构数组中的deHeaderOffset字段也就是本地文件头的相对位移字段。通过deHeaderOffset字段可以直接获取到对应文件的文件数据区结构的文件偏移,就可以直接获取到对应文件的压缩数据了。同样也是因为dex文件插在前面了所以直接加上dex文件的大小。
最后更新dex部分的file_size字段为整个dex+apk的大小,使用alder32算法和SHA1算法更新checksum和signature字段。
下面做一个非常简单的测试。 在APK文件中写一个弹出Hello的toast,同时采用V1签名方案签名:
安装到手机上:
将编译好的apk解压得到dex文件,baksmali.jar反编译dex文件得到smali代码,将hello随便改成另外一个字符串:
用smali.jar回编译成dex文件,使用提供的脚本把dex文件和原来的apk打包生成out.apk:
安装到手机上成功通过了签名校验并且执行了修改的dex中的代码:
在我android8.0没有打补丁的手机上如果采用了V2签名方案不受该漏洞影响,更新不了原来正常的程序:
补丁分析
补丁非常简单,强制校验了zip的frSignature:
0x04 修复建议
1、开发者在开发应用程序时勾选V2签名方案
2、各厂商应及时发布补丁,确保用户尽快更新系统
3、用户应在正规的应用市场下载程序
0x05 时间线
2017-12-4 Google发布12月安全公告
2017-12-8 公布POC
2017-12-12 360CERT进行分析并发布预警公告
0x06 参考文档
https://source.android.com/security/bulletin/2017-12-01
https://source.android.google.cn/security/apksigning/v2?hl=zh-cn