December 18, 2017

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

前言

CVE-2017-13156是今年Android爆出的最为严重的一个漏洞,这个漏洞允许攻击者绕过Android系统V1的签名,用篡改过的apk覆盖原有的应用,攻击者的代码可以访问原应用所有的数据。影响范围是Android 5.0+。这个漏洞的本质原因是ART允许直接运行一个dex,同时也允许运行一个里面包含dex的zip文件。而因为dex文件格式和zip文件格式的问题,一个文件可以同时是合法的zip文件和合法的dex文件。正是因为这种二义性,这个漏洞也被称作Janus漏洞,Janus是罗马神话中的双面神,具有前后两个面孔。对于特殊修改过的apk文件,PackageManagerService把它当作合法的apk安装,而ART把它视作合法的dex执行,这就是一个文件的两个视角。github上已经有人放出了这个漏洞的PoC,代码虽然很简单,但是看懂需要一点背景知识,下面就从zip和dex的文件结构上来解释这段代码。

Zip文件结构

Zip File Structure
Zip文件最重要的一个部分就是末尾的Central Directory,这个部分可以理解为Zip文件真正的文件头,解析zip文件都是从这里开始解析的,它包含了zip文件每一个entry的摘要信息。这个部分的结构如下:
Central Directory Structure
这里很容易看出来,Central Directory里有多个File header,而每一个File data前面也有一个local file header,前者是后者的加强版,包含了更详细的数据。File header结构如下,markdown制表比较麻烦,这里从维基百科上截了一张图:
File header
这里需要注意几个重要的字段,file header总是以magic number 0x02014b50开头,file header的第42~46字节是对应的local file header的偏移,解析zip文件的时候就是根据这个字段找到每一个文件entry的。
End of central directory record的结构很固定,图片同样取自维基:
EOCD
EOCD是以magic number 0x06054b50开头的一个数据块,其中第12~16字节包含了central directory的长度,16~20字节包含了central directory起始位置的绝对偏移量,为了理解后续的内容,请特别留意这三个字段。
看完上面几张图,我们可以很容易地得出zip文件的解析流程:

  1. 根据magic number 0x06054b50找到End of central directory record
  2. 根据End of central directory record中第16~20字节找到central directory的起始位置
  3. 以magic number 0x02014b50为标记位,遍历central directory中每一个file header
  4. 根据file header中的元数据找到local file header的位置
  5. 根据local file header的元数据解析file data

zip的这个特性使得解压工具在不需要读取完整文件内容的情况下就可以展示整个压缩包的结构。zip文件实际上的文件头是在尾部的,因此头部可以任意拼接数据,只要修改好相应的偏移量,这个文件还是一个合法的zip文件,比如自解压程序的实现,就是在头部拼上了exe文件。zip格式只要求了应用必须从central directory开始解析文件,但是并没有要求检查整个文件是否以0x02014b50开头,一般情况下,这并不会有什么问题,但是结合ART的机制和dex的文件结构,就导致了Janus漏洞。

Dex文件结构

Dex文件结构没必要了解得那么细致,跟本文有关的只有一个文件头,这里我从Android官网找了一张图,截得不全,但是对于本文足够了:
Dex file header
dex文件总是以magic number DEX_FILE_MAGIC开头,这个值是**{ 0x64 0x65 0x78 0x0a 0x30 0x33 0x38 0x00 },写成ASCII的表示就是我们平时看到的"dex\n038\0"**。我们只需要关注dex文件头的前4个字段,第一个是magic number,占8个字节,第二个是dex的adler32校验码,占4个字节,第三个是SHA-1的签名,占20个字节,第四个字段是dex文件的总长度,占4个字节。理论上来说,同时修改这几个字段,我们就可以保持一个dex文件是合法的。

PoC

好了,有了这些前置知识,就可以来看一下脚本了,脚本我加好了注释,应该很容易懂。

官方修复

PackageManagerService安装应用的时候,会使用PackageParser这个类去解析apk包,而Android 5.0以上的PackageParser解析apk的时候,使用的是StrictJarFile类,这个类其实是一个jni的wrapper,真正的实现要看zip_archive.cc.我们先来看一下Google对于这个漏洞的修复:

Google在解析zip文件的Central Directory的时候加上了对文件头的判断,如果不是以特定的magic number开头,则返回错误。而在这之前,zip_archive.cc是直接解析Central Directory的,并没有验证文件头部几个字节,使用PoC脚本修改过的apk文件可以顺利完成解析,覆盖掉原有应用。

后记

后面还会有两篇,分别分析ART为什么会先执行修改后拼在头部的dex文件和Android V2签名为什么不受这个漏洞的影响。时间关系,先写了这一篇。