Janus漏洞(CVE-2017-13156)分析


文章目录
  1. 1. 背景
  2. 2. 基础知识
    1. 2.1. Android app签名校验简单介绍
    2. 2.2. 签名方案
    3. 2.3. apk签名校验方式
  3. 3. 漏洞原理
  4. 4. 漏洞复现
  5. 5. 影响范围
  6. 6. 漏洞危害
  7. 7. 参考链接

背景

Android系统要求每一个Android应用程序必须要经过数字签名才能够安装到系统中,也就是说如果一个Android应用程序没有经过数字签名,是没有办法安装到系统中的。Android通过数字签名来标识应用程序的作者和在应用程序之间建立信任关系,不是用来决定最终用户可以安装哪些应用程序。这个数字签名由应用程序的作者完成,并不需要权威的数字证书签名机构认证,它只是用来让应用程序包自我认证的。然而Google在本月4日发布的Android安全公告中编号为CVE-2017-13156,这个漏洞被称为Janus漏洞,利用该漏洞可修改app而不影响原始签名。

基础知识

Android app签名校验简单介绍

在Android源码中“frameworks/base/core/java/android/content/pm/PackageParser.java”中。PackageParser类的collectCertificates方法会对APK进行签名校验,并且遍历APK中的所有文件,对每个文件进行校验。下面是该方法的源码:
img
img
APK是一个ZIP格式的文件,因此使用ZIP相关的类进行读写。在上面代码中调用了loadCertificates,这个方法会返回一个二维数组,当APK中的文件签名校验失败,loadCertificates方法会返回一个空数组(可能是null,可能是数组长度为0),那么依照上面代码的逻辑如果数组为空则会抛出异常。
loadCertificates方法的代码见下:
img
其中is是JarFile.JarFileInputStream类的对象。loadCertificates调用了readFullyIgnoringContents,在readFullyIgnoringContents中会调用JarFile.JarFileInputStream.read(JarFile类在“libcore/luni/src/main/java/java/util/jar/JarFile.java”文件中)
读取APK中一项数据,在read方法中会校验读取到的数据项的签名,如果签名校验失败,则会抛出SecurityException类型的异常,即签名校验失败。
上面代码调用了StrictJarFile.getCertificateChains方法,在/ libcore / luni / src / main / java / java / util / jar / StrictJarFile.java里面。下面是它的代码:
img
上面的isSigned在下面的代码里面获取的:
img
当证书读取成功,而且APK经过了签名,isSigned为true。
回到StrictJarFile.getCertificateChains中,当isSigned为true时会调用JarVerifier.getCertificateChains方法,下面是它的代码:
img
verifiedEntries的声明:
img
verifiedEntries是一个键值对,键是APK中经过了签名的文件名,如:classes.dex文件,值是证书数组。如果向已经签过名的APK中新添加一个文件然后安装这个APK,当程序逻辑执行到JarVerifier.getCertificateChains中时,在verifiedEntries里面无法找到新添加的文件名(因为这个新文件是在APK签名之后添加),那么JarVerifier.getCertificateChains方法将返回null。
以上就是整个对于app签名的简单分析。

签名方案

参考google官方文档获得:
APK 签名方案
Android 支持两种应用签名方案,一种是基于 JAR 签名的方案(v1 方案),另一种是 Android Nougat (Android 7.0) 中引入的 APK 签名方案 v2(v2 方案)。
JAR 签名(v1 方案)
v1 签名并非保护整个apk文件,例如 ZIP 元数据就不受保护。再者,APK 验证程序需要处理大量不可信(尚未经过验证)的数据结构,而不受签名保护的数据不在处理的行列中。这会导致相当大的受攻击面。此外,APK 验证程序必须解压所有已压缩的条目,而这需要花费更多时间和内存。为了解决这些问题,Android 7.0 中引入了 APK 签名方案 v2。
APK 签名方案 v2(v2 方案)
Android 7.0 中引入了 APK 签名方案 v2(v2 方案)。该方案会对 APK 的内容进行hash处理和签名,然后将生成的“APK 签名分块”插入到 APK 中。
在验证期间,v2 方案会将 APK 文件视为 Blob,并对整个文件进行签名检查。对 APK 进行的任何修改(包括对 ZIP 元数据进行的修改)都会使 APK 签名作废。这种形式的 APK 验证不仅速度要快得多,而且能够发现更多种未经授权的修改。
同时,新的签名格式向后兼容,使用这种新格式签名的 APK(必须也使用v1) 可在更低版本的 Android 设备上进行安装(会直接忽略添加到 APK 的额外数据)。

apk签名校验方式

为了提高兼容性,应同时采用 v1 和 v2 这两种方案对应用进行签名。与只使用v1 方案签名的应用相比,经过 v2 方案签名的应用能够更快速地安装到 Android Nougat 以及更高版本的设备上。但是更低版本的 Android 平台会忽略 v2 签名,这就需要应用包含 v1 签名。
img
验证程序会对照存储在“APK 签名分块”中的 v2 签名对 APK 的全文件hash进行验证。该hash涵盖除“APK 签名分块”(其中包含 v2 签名)之外的所有内容。在“APK 签名分块”以外对 APK 进行的任何修改都会使 APK 的 v2 签名作废。v2 签名被删除的 APK 也会被拒绝,因为 v1 签名指明相应 APK 带有 v2 签名,所以 Android Nougat 及更高版本会拒绝使用 v1 签名验证 APK。

漏洞原理

Janus漏洞主要是向APK里面添加额外的DEX文件。一方面,APK文件是zip文件,可以包含初始时的任意字节,在它的zip条目(实际情况下更多是在zip条目里面)。但是JAR签名方案只考虑了zip入口。当计算或者校验apk的签名的时候,它忽略了其他字节。另一方面,在字符串、类、方法定义等常规部分之后,一个DEX文件可以包含任意的字节。综上所述,一个文件可以同时包含一个APK文件和一个DEX文件。
img
另一个关键点是Dalvik / ART虚拟机方面。android运行加载APK文件,提取它的DEX文件,然后运行它的代码。在实践中,虚拟机可以加载和执行APK文件。当它得到APK文件时,它仍然会查看header的magic字节来判断它是什么类型的文件,如果找到一个DEX类型的,就会将文件载入为DEX文件,否则就会将文件加载为一个APK文件,其中包括一个带有DEX的zip文件。因此,既可以读取DEX文件又可以读取APK文件。
所以攻击者可以利用着这种二元性,在不影响其签名的情况下将一个恶意DEX文件预先添加到APK文件里面,然后,android运行时接受APK文件作为一个合法的早期版本的应用程序的有效更新,但是Dalvik虚拟机从注入的DEX文件中加载代码,从而实现攻击。

漏洞复现

img
原来的apk主要代码
img
img
修改了dex文件,将Log.i(“janus”,”点击事件”);修改为Toast.makeText(getApplicationContext(),”I steal your car.”,Toast.LENGTH_LONG).show();
img
img
安装合并好的1.apk
img
img
测试
img
攻击完成

影响范围

该漏洞仅影响基于 JAR 签名的方案(v1 方案)。不影响自Android 7.0 以来引入的 APK 签名方案 v2(v2 方案)。

漏洞危害

可被添加恶意dex文件可以进行一些敏感性的操作,例如窃取用户数据、监控手机等高危操作。

参考链接

https://source.android.com/security/apksigning/v2#verification
https://www.guardsquare.com/en/blog/new-android-vulnerability-allows-attackers-modify-apps-without-affecting-their-signatures