# 浅谈zip格式处理逻辑漏洞

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2015-10-22T02:21:00Z
Canonical: https://wepostx.com/topics/1128

> 乌云历史资料归档
>
> **原始作者：** 路人甲
> **原始编号：** superkieran-wooyundrops:843
> **原始发布时间：** 2015-10-22 10:21
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

前言：zip压缩格式应用广泛，各个平台都有使用，Windows平台使用来压缩文件，Android平台使用来作为apk文件的格式。由于zip文件格式比较复杂，在解析zip文件格式时，如果处理不当，可能导致一些有意思的逻辑漏洞，本篇文章将挑选有意思的漏洞进行解析。

# 一、文件扩展名欺骗漏洞

很早之前，国外安全研究人员[爆料Winrar 4.x版本存在文件扩展名欺骗漏洞](http://www.exploit-db.com/papers/32480/)，黑客可以通过该漏洞诱骗受害者执行恶意程序。该漏洞的主要原理是：Winrar在文件预览和解压缩显示文件名使用的是不同结构体的字段导致的。

### 1.1 zip格式文件的结构
在了解漏洞的原理前，先熟悉下zip格式的文件结构。
如果一个压缩包文件里有多个文件，可以认为每个文件都是被单独压缩，然后再拼成一起。
一个 ZIP 文件由三个部分组成：压缩源文件数据区+压缩源文件目录区+压缩源文件目录结束标志，如下图：

![原文图片](/media/2016/06/d70ce58370ab5c89f1754a877bb1c2eb)

1）文件头（压缩源文件目录区）在文件末尾，即图1中的File Header，记录了索引段的偏移、大小等等。
2）数据段（压缩源文件数据区）在文件开头，即图1中的Local Header，记录了数据的一些基本信息，可以用来跟File Header中记录的数据进行比较，保证数据的完整性。
3）Local Header还包含了文件被压缩之后的存储区，即图1中的Data区域。
4）图2和图3为Local Header（图2中的ZIPFILERECORD）和File Header（图3中的ZIPDIRENTRY）的数据对比，两者数据是一致的。

![原文图片](/media/2016/06/653394ae845af7480b9bd816a2d12e1e)

![原文图片](/media/2016/06/3e5ed063f022f75c146a347dda2aad16)

### 1.2 漏洞产生原因
Winrar在文件预览的时候使用的是ZIPDIRENTRY下面的deFileName字段来显示文件名，解压缩的时候使用的是ZIPFILERECORD下面的frFileName字段来显示文件名。如果将deFileName字段文件扩展名改成jpg、gif等图片的文件扩展名，可以欺骗用户运行恶意程序。
Winrar文件预览示意图：

![原文图片](/media/2016/06/5ccf793864e939c97126b1bb917391c6)

用户看到的是jpg图片，打开的确实exe文件，真坑啊！
Winrar解压缩文件示意图：

![原文图片](/media/2016/06/8cebcb7e891de79591215183bb43797e)

解压缩之后显示的exe，两处显示的不一样。

# 二、Android Master Key漏洞

之前，国外安全研究人员爆出[第三个Android Master Key漏洞](http://www.saurik.com/id/19)，该漏洞的主要原理是：android在解析Zip包时，没有校验ZipEntry和Header中的FileNameLength是否一致。

### 2.1 zip文件格式的结构
在了解漏洞的原理前，还是先熟悉下zip格式的文件结构。
如果一个压缩包文件里有多个文件，可以认为每个文件都是被单独压缩，然后再拼成一起。
一个 ZIP 文件由三个部分组成：压缩源文件数据区+压缩源文件目录区+压缩源文件目录结束标志，如图1所示：
1）文件头（压缩源文件目录区）在文件末尾，即图1中的File Header，记录了索引段的偏移、大小等等。
2）数据段（压缩源文件数据区）在文件开头，即图1中的Local Header，记录了数据的一些基本信息，可以用来跟File Header中记录的数据进行比较，保证数据的完整性。
3）Local Header还包含了文件被压缩之后的存储区，即图1中的Data区域。
4）图2和图3为Local Header（图2中的ZIPFILERECORD）和File Header（图3中的ZIPDIRENTRY）的数据对比，两者数据是一致的。

![原文图片](/media/2016/06/2ea4c1f987e865986266fad7c5e2e345)

![原文图片](/media/2016/06/c8924913d203524d26913362558db4d1)

### 2.2 漏洞产生原因
先来看一下是如何定位到Local Header中的Data数据：

```text
off64_t dataOffset = localHdrOffset + kLFHLen + get2LE(lfhBuf + kLFHNameLen) +
```
Data的偏移是通过Header的起始偏移+Header的大小（固定值）+Extra data的大小+文件名的大小，如下图

![原文图片](/media/2016/06/1fcadace053c190dacf61eee277482b7)

回头看一下，java在获取Data偏移的处理，在读取Extra data的长度的时候，它已经预存了文件名在FileHeader中的长度。

```text
// We don't know the entry data's start position. // All we have is the position of the entry's local // header. At position 28 we find the length of the // extra data. In some cases this length differs // from the one coming in the central header. RAFStream rafstrm = new RAFStream(raf, entry.mLocalHeaderRelOffset + 28); DataInputStream is = new DataInputStream(rafstrm); int localExtraLenOrWhatever =
```
漏洞就在这里产生了，如果Local Header中的FileNameLength被设成一个大数，并且FileName的数据包含原来的数据，File Header中的FileNameLength长度不变，那么底层C++运行和上层Java运行就是不一样的流程。

```text
C++ Header 64k Name Data +--------> +----------------------> +----------> length=64k classes.dex dex\035\A... dex\035\B... +--------> +---------> +---------->
```
如上面所示，底层C++的执行会读取64k的FileName长度，而Java层由于是读取File Header中的数据，FileName的长度依旧是11，于是Java层校验签名通过，底层执行会执行恶意代码。

## Replies
