<div> 
把以前學過的東西再複習總結一下,溫故而知新。由於相較其他heap,largebin 中的機制相對複雜,所以largebin attack 一直是CTF的堆漏洞利用常規而少見的一種,但是卻一直活着人們的心中。

背景知識

我們以64位,libc 2.27爲例,其實與2.23差不多,只不過2.27中,0×420以下堆塊會進入tcache,但是2.23中0×400就能進入largebin了

開始介紹large bin的相關機制。在此之前,請學習堆的申請與釋放等相關知識。

largebin 從哪裏來

在堆的機制中,free掉的chunk會進入bin,如果此chunk的大小大於fastbin,free後就會先進unsorted bin,之後,再次申請更大堆塊,unsorted bin無法滿足,heap就會進入large bin,那麼large bin的範圍是多少呢? 源碼告訴我們一切:

源碼中可以看到,MIN_LARGE_SIZE是(64 – 0)*0×10 = 0×400,也就是最小的large bin。這裏的MALLOC_ALIGNMENT其實等於SIZE_SZ,即系統位數乘以2,64位系統即 0×8乘以2=0×10,32位爲0×4乘以2=0×8。 也就是說我們可以申請0×400的chunk,然後free掉進unsorted bin ,之後申請更大chunk,就可以將0×400的堆放入largebin。 注意:此時,我們使用的是libc 2.27,所以,其實0×400到不了largebin,而是tcache的範圍,所以,在libc 2.27中,一般使用0×420,但是如果可以將0×400的tcache填滿,也是可以申請到largebin的 那麼large bin 的index如何確定呢,這裏,系統採取了相對於fastbin和smallbin更加粗獷的方式:

large bin採取了分段的存儲,比如第一個範圍就是0×400到(48<<6),即0×400到0xc00,而其中,每個鏈表之間大小差爲(1<<6)=0×40,結果如下表:
index size範圍
64 [0x400,0x440) 相差0x40
65 [0x440,0x480)相差0x40
...... ......相差0x40
96 [0xc00,0xc40)相差0x40
97 [0xc40,0xe00)相差0x1c0
98 [0xe00,0x1000)相差0x200
...... ......相差0x200
...... ......

largebin 的內部

因爲largebin,一個bin內部並不是一個size,所以需要fd_nextsize與bk_nextsize將其串起來。 那麼largebin內部是如何佈置的呢: 首先fd_nextsize指向比他小的最大heap,而bk_nextsize指向比他大的最小的heap,最後將兩條鏈條首尾相連。而fd和bk和其原來的任務一樣,都是指向和其大小相同的堆塊。那麼,畫個草圖:

那麼如果bin裏的大小都一樣的話,那麼第一個釋放的堆塊作爲此bin鏈的鏈首(這個和fastbin和tcache都不一樣),fd_nextsize與bk_nextsize都指向自己,其餘的大小相同的堆塊free的時候,fd_nextsize與bk_nextsize就都爲0了。 而fd和bk與原本作用一樣,指向上一個釋放的堆塊,但是,這裏的鏈頭始終爲第一個釋放的chunk。 做個小實驗,申請4個堆塊ABCD,AB大小爲0x450,CD大小爲0x460,如下圖:

其堆塊如下:

將其釋放至unsorted bin,釋放順序爲A B D C:

之後我們申請0x470堆塊,此時unsorted bin無法滿足,將ABCD放入large bin:

此時largebin內由fd和bk組成的鏈爲:

此時的fd_nextsize與bk_nextsize:

最後的示意圖即:

那麼large bin 是如何申請到的呢? 源碼中顯示: 首先通過申請的size找到index,進而找到指定的bin鏈 在找到bin鏈後,從最小的heap開始通過bk_nextsize找到第一個大於等於size的heap 在找到heap後,利用fd判斷下一個heap的size是否於當前heap的size,如果相等,將下一個heap返回,這樣可以減少設置鏈頭的操作 利用unlink取下得到的heap 判斷heap的size減去申請的size是否大於MINSIZE,如果大於就將其放入unsorted bin,如果小於,那麼直接分配給用戶 這就是largebin的分配過程,那麼largebin只有這一種被申請的方式嗎? 如果在申請堆塊的時候沒有找到合適heap的時候,會從topchunk開始申請,在此之前,程序會遍歷比當前index大的索引,如果在大於fastbin的範圍內,存在heap,就會從中切割申請的大小,當前heap的size減去申請的size是否大於MINSIZE,如果大於就將其放入unsorted bin,如果小於,那麼直接分配給用戶。 即,如果在上述情況下申請0x60,就會從B中分配0x60,餘下0x3e0歸入unsorted bin

這就是largebin在ptmalloc中的機制了。

large bin attack

那麼large bin attack又是怎麼進行的呢? 現在有兩種常見的利用方式: 在申請largebin的過程中,僞造largebin的bk_nextsize,實現非預期內存申請。 在largebin插入的過程中,僞造largebin的bk_nextsize以及bk,實現任意地址寫堆地址。

申請時利用

在申請largebin的時候,從最小的heap開始,利用bk_nextsize尋找合適的heap,如果找到了合適的heap,就將其取出,那麼如果我們將bk_nextsize指向其他地方,就可以申請到其他地方。看下源碼:
 if ((victim = first (bin)) != bin
           && (unsigned long) chunksize_nomask (victim)
             >= (unsigned long) (nb))
             {
               victim = victim->bk_nextsize;
               while (((unsigned long) (size = chunksize (victim)) <
                       (unsigned long) (nb)))
                 victim = victim->bk_nextsize; //尋找堆塊
                 
 ......
 ​
               remainder_size = size - nb;
               unlink (av, victim, bck, fwd); //unlink
所以只需要在目標地方符合unlink,那麼就可以通過檢查了。舉個例子:申請一個largebin,之後free,再申請一個大堆塊使之前堆塊進入large bin: 申請A和底下分割的0x20,之後freeA,申請B和C。

之後,我們開始利用,這裏提供兩種思路吧,但是其本質都是修改bk_nextsize。 直接將bk_nextsize指向B的頭 將bk_nextsize指向B的內容 第一種和第二種本質上一樣,但是第二種可以順便利用unlink,向bss段上寫一個bss段地址,這裏假設bss段上存着堆地址,如果PIE開啓了,就不好操作了,這裏我們關掉PIE,演示第二種漏洞利用。修改A的bk_nextsize,假設此時有UAF:也就是將 bk_nextsize = B+0x10 ,而B+0x10一般存在bss段上,所以,此時B上僞造以一個的 fd = bss - 0x18 , bk= bss - 0x10 的堆塊,這都屬於unlink常規操作。注意僞造堆塊的fd_nextsize要等於0,因爲unlink有檢查。 之後注意對於僞造堆塊檢查,即檢查下一個堆塊的pre_size。最後得到:

此時申請large bin ,就會得到fake B的內存區域。

插入時利用

在申請大堆塊後,unsorted bin不滿足size要求,會將其放入large bin,此時,我們可以通過一些辦法,達到一次任意地址寫堆地址的目的,看代碼:
              victim_index = largebin_index (size);
               bck = bin_at (av, victim_index); //這個是main_arena的地址
               fwd = bck->fd;//這是最大size的鏈首
 ​
               /* maintain large bins in sorted order */
               if (fwd != bck)
                 {
                   /* Or with inuse bit to speed comparisons */
                   size |= PREV_INUSE;
                   /* if smaller than smallest, bypass loop below */
                   assert (chunk_main_arena (bck->bk));
                   if ((unsigned long) (size)                //bck->bk是最小size的鏈首
               < (unsigned long) chunksize_nomask (bck->bk)) //如果當前申請的size小於最小szie
                     {
                       fwd = bck;
                       bck = bck->bk;
 ​
                       victim->fd_nextsize = fwd->fd;
                       victim->bk_nextsize = fwd->fd->bk_nextsize; 
                       fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;//這裏不好整
                     }
                   else //如果當前申請的size不是最小的
                     {
                       assert (chunk_main_arena (fwd));
                       while ((unsigned long) size < chunksize_nomask (fwd)) //從最大塊開始尋找一個小於szie的鏈
                         {
                           fwd = fwd->fd_nextsize;
               assert (chunk_main_arena (fwd));
                         }
 ​
                       if ((unsigned long) size
               == (unsigned long) chunksize_nomask (fwd)) // 如果找到的鏈和申請的size相同
                         /* Always insert in the second position.  */
                         fwd = fwd->fd; 
                       else//如果不同,則說明應該插在這個鏈前面
                         {
                           victim->fd_nextsize = fwd;//小的鏈在victim上
                           victim->bk_nextsize = fwd->bk_nextsize;//這裏如果可以控制
                           fwd->bk_nextsize = victim;
                           victim->bk_nextsize->fd_nextsize = victim;//這裏能寫一個victim
                         }
                       bck = fwd->bk;
                     }
                 }
               else
                 victim->fd_nextsize = victim->bk_nextsize = victim;
             }
 ​
           mark_bin (av, victim_index);
           victim->bk = bck;
           victim->fd = fwd;
           fwd->bk = victim;
           bck->fd = victim;//最後這裏,還可以有一次寫,如果fwd->bk可控
 ​
可以看到,這段代碼中有兩次可以寫入victim的地方,由於其比較簡單,沒啥檢查,所以可以直接搞起: 搞到一塊large bin 然後搞到一塊更大的large bin,且其index一樣。在得到這個large bin之前,先改寫原本就在large bin中的堆塊的bk或bk_nextsize。 舉個例子: 首先申請3個chunk,free A堆塊,之後申請更大的chunk

此時A在large bin內,之後修改A的bk或bk_nextsize:

修改上述的bk和bk_nextsize,後續就可以達到在target1和target2中寫堆B地址的目的。 之後,free chunk B,然後申請大於等於0x480的堆塊,比如申請malloc(0x470),就可以達到漏洞利用目的了。

寫在最後

如有錯誤歡迎指正: [email protected]

參考

[1] https://ray-cp.github.io/archivers/ptmalloc_argebin_attack 、*本文作者:白裏個白sofr,轉載請註明來自FreeBuf.COM
相關文章