<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/tips/7326

            0x00 背景


            本文寫給對堆溢出無的放矢的童鞋,分為如下幾部分:

            一.經典的unlink利用方法簡介
            二.在當今glibc的保護下如何繞過進行unlink利用
            

            建議閱讀本文之前先對glibc的malloc.c有所了解

            你可以在這里在線看到所有的malloc.c的源碼

            0x01 第一部分


            首先簡要介紹一下堆chunk的結構

            我們可以在malloc.c中找到關于堆chunk結構的代碼

            #!c
            struct malloc_chunk {
            
                  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
                  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */
            
                  struct malloc_chunk* fd;         /* double links -- used only if free. */
                  struct malloc_chunk* bk;
            
                  /* Only used for large blocks: pointer to next larger size.  */
                  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
                  struct malloc_chunk* bk_nextsize;
                };
            

            這指明了一個heap chunk是如下的結構

            #!c
            +-----------+---------+------+------+-------------+
            |           |         |      |      |             |
            |           |         |      |      |             |
            | prev_size |size&Flag|  fd  |  bk  |             |
            |           |         |      |      |             |
            |           |         |      |      |             |
            +-----------+---------+------+------+-------------+
            

            如果本chunk前面的chunk是空閑的,那么第一部分prev_size會記錄前面一個chunk的大小,第二部分是本chunksize,因為它的大小需要8字節對齊,所以size的低三位一定會空閑出來,這時候這三個位置就用作三個Flag(最低位:指示前一個chunk是否正在使用;倒數第二位:指示這個chunk是否是通過mmap方式產生的;倒數第三位:這個chunk是否屬于一個線程的arena)。之后的FD和BK部分在此chunk是空閑狀態時會發揮作用。FD指向下一個空閑的chunk,BK指向前一個空閑的chunk,由此串聯成為一個空閑chunk的雙向鏈表。如果不是空閑的。那么從fd開始,就是用戶數據了。(詳細信息請參考glibcmalloc.c部分,在此不再多做解釋。)

            首先,為了方便,我直接引用一位外國博主的漏洞示例程序,以便繼續解釋

            #!c
            /* 
             Heap overflow vulnerable program. 
             */
            #include <stdlib.h>
            #include <string.h>
            
            int main( int argc, char * argv[] )
            {
                    char * first, * second;
            
            /*[1]*/ first = malloc( 666 );
            /*[2]*/ second = malloc( 12 );
                    if(argc!=1)
            /*[3]*/         strcpy( first, argv[1] );
            /*[4]*/ free( first );
            /*[5]*/ free( second );
            /*[6]*/ return( 0 );
            }
            

            這個程序在[3]處有很明顯的堆溢出漏洞,argv[1]中的內容若過長則會越界覆蓋到second部分。

            簡單給出此程序的堆結構

            #!c
            +---------------------+   <--first chunk ptr
            |     prev_size       |
            +---------------------+
            |     size=0x201      |          
            +---------------------+   <--first                  
            |                     |
            |     allocated       |         
            |      chunk          |      
            +---------------------+   <--second chunk ptr                
            |    prev_size        |         
            +---------------------+                     
            |    size=0x11        |         
            +---------------------+   <--second                  
            |     Allocated       |         
            |       chunk         |     
            +---------------------+   <-- top                  
            |     prev_size       |            
            +---------------------+                     
            |    size=0x205d1     |           
            +---------------------+                      
            |                     |
            |                     |
            |                     |
            |        TOP          |   
            |                     |
            |       CHUNK         |    
            |                     |
            +---------------------+
            

            此處不贅余介紹exploit具體代碼,只介紹利用方法.

            只要我們通過溢出構造,使得second chunk

            #!c
            prev_size=任意值
            size=-4(因為最低位的flag沒有設置,所以prev_size是什么值是無所謂了)
            [email protected]([email protected]?定技術”)
            bk=shellcode地址
            

            在我們的payload將指定位置的數值改好后。下面介紹在[4][5]行代碼執行時發生的詳細情況。

            第四行執行free(first)發生如下操作

            1).檢查是否可以向后合并

            首先需要檢查previous chunk是否是空閑的(通過當前chunk size部分中的flag最低位去判斷),當然在這個例子中,前一個chunk是正在使用的,不滿足向后合并的條件。

            2).檢查是否可以向前合并

            在這里需要檢查next chunk是否是空閑的(通過下下個chunk的flag的最低位去判斷),在找下下個chunk(這里的下、包括下下都是相對于chunk first而言的)的過程中,首先當前chunk+當前size可以引導到下個chunk,然后從下個chunk的開頭加上下個chunksize就可以引導到下下個chunk。但是我們已經把下個chunksize覆蓋為了-4,那么它會認為下個chunkprev_size開始就是下下個chunk了,既然已經找到了下下個chunk,那就就要去看看size的最低位以確定下個chunk是否在使用,當然這個size-4,所以它指示下個chunk是空閑的。

            在這個時候,就要發生向前合并了。即first chunk會和 first chunk的下個chunk(即second chunk)發生合并。在此時會觸發unlink(second)宏,想將second從它所在的bin list中解引用。

            具體如下

            #!c
            BK=second->bk(在例子中bk實際上是shellcode的地址)
            FD=second->fd ([email protected] - 12)
            FD->bk=BK
            /*shellcode的地址被寫進了FD+12的位置,[email protected][email protected]*/
            BK->fd=FD 
            

            執行unlink宏之后,再調用free其實就是調用shellcode,這時就可以執行任意命令了。

            但是,在現如今,glibc已經不這么簡單了,為了使堆溢出不那么容易就被利用,它加入了許多新的保護措施,如何繞過也就是要在第二部分中討論的內容。

            0x02 第二部分


            以glibc中的代碼作為示例,首先拿出最新版本的unlink宏。

            #!c
            1413    /* Take a chunk off a bin list */
            1414    #define unlink(AV, P, BK, FD) {                                            
            1415        FD = P->fd;                                                                      
            1416        BK = P->bk;                                                                      
            1417        if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                      
            1418          malloc_printerr (check_action, "corrupted double-linked list", P, AV);  
            1419        else {                                                                      
            1420            FD->bk = BK;                                                              
            1421            BK->fd = FD;                                                              
            1422            if (!in_smallbin_range (P->size)                                      
            1423                && __builtin_expect (P->fd_nextsize != NULL, 0)) {                      
            1424                if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)              
            1425                    || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    
            1426                  malloc_printerr (check_action,                                      
            1427                                   "corrupted double-linked list (not small)",    
            1428                                   P, AV);                                              
            1429                if (FD->fd_nextsize == NULL) {                                      
            1430                    if (P->fd_nextsize == P)                                      
            1431                      FD->fd_nextsize = FD->bk_nextsize = FD;                      
            1432                    else {                                                              
            1433                        FD->fd_nextsize = P->fd_nextsize;                              
            1434                        FD->bk_nextsize = P->bk_nextsize;                              
            1435                        P->fd_nextsize->bk_nextsize = FD;                              
            1436                        P->bk_nextsize->fd_nextsize = FD;                              
            1437                      }                                                              
            1438                  } else {                                                              
            1439                    P->fd_nextsize->bk_nextsize = P->bk_nextsize;                      
            1440                    P->bk_nextsize->fd_nextsize = P->fd_nextsize;                      
            1441                  }                                                                      
            1442              }                                                                      
            1443          }                                                                              
            1444    }
            1445    
            1446    /*
            

            我們可以看到我們最大的阻礙是下面的這部分代碼

            #!c
            if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                     
                  malloc_printerr (check_action, "corrupted double-linked list", P);
            

            這段代碼被添加到了unlink宏中,所以現在再調用unlink宏的時候,chunk指針P->fd->bk(即代碼中的大寫FD->bk)應該還是p指針自己。對于BK->fd != p這部分也是同樣的道理。

            在第一部分的利用方法中,我們修改了

            #!c
            p->[email protected]
            p->bk=shellcode_adress
            

            我們在此記FD=p->fd , BK=p->bk,再去看FD->bk已經是[email protected]了,這部分是不能滿足要求的。再看BK->fd已經是shellcode+16了,所以如上文的利用方法已經不能成功了。之所以還加以介紹,是因為這會使我們理解第二部分變得又快又好。

            如果繞過還是要根據這段保護代碼來談。我們勢必需要構造合適的條件的來過掉這行代碼,那么就要找一個指向p的的已知的地址,然后根據這個地址去設置偽造的fd和bk指針就能改掉原p指針。

            以64bit為例,假設找到了一個已知地址的ptr是指向p(p指向堆上的某個地方)的,通過堆溢出,我們可以做如下的修改。

            #!c
            p->fd=ptr-0x18
            p->bk=ptr-0x10
            

            布置好如此結構后,再觸發unlink宏,會發生如下情況。

            #!c
            1.FD=p->fd(實際是ptr-0x18)
            2.BK=p->bk(實際是ptr-0x10)
            3.檢查是否滿足上文所示的限制,由于FD->bk和BK->fd均為*ptr(即p),由此可以過掉這個限制
            4.FD->bk=BK
            5.BK->fd=FD(p=ptr-0x18)
            

            這時候再對p進行寫入,可以覆蓋掉p原來的值,例如我們用合適的payload[email protected]寫入。p就變成了[email protected],那么再改一次p,把[email protected]改為shellcode的地址或者說system的地址都可以。之后再調用free功能,就可以任意命令執行。

            為了方便,在這邊拿出一個最近的wargame出現的一個邏輯非常簡單的程序作為漏洞示例程序,可以在此下載

            首先簡單介紹這個Binary的功能以及基本情況

            開啟的保護
            RELRO    STACK CANARY    NX          PIE     RPATH    RUNPATH    FILE
            No RELRO No canary found NX enabled  No PIE  No RPATH No RUNPATH shellman
            
            基本功能
            1.顯示已經建立的堆塊中存儲的內容
            2.建立一個新的堆塊,大小和內容又用戶決定
            3.對一個已經分配的堆塊做編輯,這個地方沒有限制大小,若太長可造成堆溢出
            4.釋放一個已經分配的堆塊
            
            存放的堆塊的基本邏輯結構
            .bss:00000000006016C0 ; __int64 usingFLAG[]
            .bss:00000000006016C0 usingFLAG       dq ?                    ; DATA XREF: main+38o
            .bss:00000000006016C0                                         ; .text:0000000000400A90o ...
            .bss:00000000006016C8 ; __int64 LEN[]
            .bss:00000000006016C8 LEN             dq ?                    ; DATA XREF: new+B5w
            .bss:00000000006016C8                                         ; delete+79w
            .bss:00000000006016D0 ; __int64 content[]
            .bss:00000000006016D0 content         dq ?                    ; DATA XREF: new+BCw
            

            程序有一個全局數組會存儲好每一個經過malloc分配的堆塊返回的指針。以及在全局數組中存儲長度以及本塊是否正在使用的標志。

            如何利用

            按照前文所介紹的,我們希望使用Unlink的方法去利用這個堆溢出漏洞。首先,我們要找一個指向堆上某處的指針。因為存儲malloc返回指針的全局數組的存在,這讓我們的利用變得異常的簡單。因為bss段的地址也是固定的,我們可以知道,從而設置滿足需要的bk和fd指針,下面介紹具體步驟。

            1.我們可以首先分配兩個長度合適的堆塊。(如下圖所示)

            chunk0                malloc返回的ptr        chunk1        malloc返回的ptr
            |                     |                     |             |
            +-----------+---------+---+---+-------------+------+------+----+----+------+
            |           |         |   |   |             |      |      |    |    |      |
            |           |         |   |   |             | prev | size&|    |    |      |
            | prev_size |size&Flag|   |   |             | size | flag |    |    |      |
            |           |         |   |   |             |      |      |    |    |      |
            |           |         |   |   |             |      |      |    |    |      |
            +-----------+---------+---+---+-------------+------+------+----+----+------+
            

            這時候這兩塊的fd和bk區域其實都是空的,因為他們都是正在使用的

            2.對第一塊進行編輯,編輯的過程中設置好第零塊的bk和fd指針并溢出第一塊,改好第一塊的chunk頭的控制信息(如下圖所示)

            chunk0                malloc返回的ptr           chunk1        malloc返回的pt
            |                     |                        |             |
            +-----------+---------+----+----+----+----+----+------+------+----+----+------+
            |           |         |fake|fake|fake|fake| D  | fake | fake |    |    |      |
            |           |         |prev|size| FD | BK | A  | prev | size&|    |    |      |
            | prev_size |size&Flag|size|    |    |    | T  | size | flag |    |    |      |
            |           |         |    |    |    |    | A  |      |      |    |    |      |
            |           |         |    |    |    |    |    |      |      |    |    |      |
            +-----------+---------+----+----+----+----+----+------+------+----+----+------+
                                  |--------new_size--------|
            

            我們為了欺騙glibc,讓它以為堆塊零malloc返回的指針(我們后文中簡記為p)出就是chunk0指針,所以我們偽造了prev_size和size的部分,然后溢出堆塊1,改掉第1個堆塊的prev_size,數值應該是上圖所示new_size的大小;另外第1塊的size部分還要把prev_inuse的flag給去掉。如此就做好了unlink觸發之前的準備工作

            3.刪掉chunk1,觸發unlink(p),將p給改寫。

            在刪除堆塊1時,glib會檢查一下自己的size部分的prev_inuse FLAG,發現到到比較早的一個chunk是空閑的(實際是我們偽造的),glibc希望將即將出現的兩個空閑塊合并。glibc會先將chunk0從它的Binlist中解引用,所以觸發unlink(p)。

            1).FD=p->fd(實際是0x6016D0-0x18,因為全局數組里面指向p的那個指針就是0x6016D0)
            2).BK=p->bk(實際是6016D0-0x10)
            3).檢查是否滿足上文所示的限制,由于FD->bk和BK->fd均為*6016D0(即p),由此可以過掉這個限制
            4).FD->bk=BK
            5).BK->fd=FD(p=0x6016D0-0x18)
            

            4.對p再次寫入,[email protected]

            [email protected][email protected]實地址,進而算出libc的基址來過掉ASLR。

            6.根據已經算出的libc基址再次算出system函數的真實[email protected]? (如果沒有libc,可以考慮簡歷多個chunk,[email protected]??面的函數,這樣在list時,我們可以得到兩個libc函數的真實地址,根據其偏移,便可以找出服務器上的libc,若保護再夠復雜無法改got,我們還可以構造ropchain,同樣利用這樣的方式,把ropchain丟進全局數組中)

            7.因為free已經變成了system,只要再建立一個內容為/bin/sh的塊,再刪掉,就可以得到shell,由此全部利用完成。

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

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

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

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

                      亚洲欧美在线