1. 程式人生 > >雙倍快樂的堆溢出-unlink漏洞

雙倍快樂的堆溢出-unlink漏洞

inux 用戶 應該 ron height 定位 truct 標誌位 tail

Linux下堆的unlink漏洞

參考文章:https://blog.csdn.net/qq_25201379/article/details/81545128

首先介紹一下Linux的堆塊結構:

struct malloc_chunk {
INTERNAL_SIZE_T prev_size;
INTERNAL_SIZE_T size;
struct malloc_chunk *fd;
Struct malloc_chunk *bk;
}

0x01、其中前兩個結構體成員組成了堆塊的塊首:1、prev_size字段僅在該堆塊是空閑時有意義,代表了前一個堆塊的size(包括塊首的大小在內),註意國內很多相關blog中將prev解釋成“後一個”堆塊,這非常的別扭,此處我們將prev當作前一個。 2、size字段表示該堆塊的大小,由於堆塊的大小必須是8字節的整數倍,因此size字段的後三個二進制位是不表示大小的,因此作為其他標誌位,我們需要知道的是,最後一位表示prev堆塊是否空閑,若prev空閑,則最後一位為0。 3、至於fd和bk,也是在空閑堆塊中才有意義的數據,應用在雙鏈表中表示前後堆塊(指向塊首),而在in_use堆塊中它們是用戶數據。

0x02、關於malloc的返回值:malloc返回的指針是用戶態指針,是指向chunk_body的,不包括塊首,而malloc的參數表示的size也是用戶態size

0x03、鏈表安全檢查:Linux的堆內存管理有一個很重要的機制,會檢查 p->bk->fd==p && p->fd->bk==p,我們不討論宏中沒有這個機制的漏洞利用

0x04、漏洞利用詳解:我們不先講原理,直接看過程來體會

技術分享圖片

我們需要至少兩個堆塊,堆內存分布如上,此處不要引起誤會,雖然每個chunk都列出了fd和bk字段,但是只是為了方便讀者參考,並不代表這些堆塊是空閑的

首先malloc p堆塊和引線堆塊,那麽如果放到空閑鏈表中看,p就是引線堆塊的prev_chunk,但是此時兩個堆塊都是空閑的。

往下方是高地址這個不用多說,我們在p堆塊中打一個溢出,踩到引線堆塊,怎麽踩呢?要把引線堆塊的prev_size覆蓋成p堆塊的用戶區大小,把引線堆塊的size字段的最後一個二進制位弄成0,這樣就造成了p堆塊是free態的假象;此外在這個溢出過程中,由於寫操作是從p發起的,要順便看一下能否寫到p_user的2和3個的偏移,也就是p_user[2]和p_user[3],如果能,就把它們分別覆蓋成fake的fd和bk,不能的話也不慌,尋找一下有沒有別的可行寫操作或者可以從更低地址堆塊打來溢出。

那麽fake的fd和bk應該改成多少呢?註意改了以後還要過鏈表安全檢查的!我們來看一個巧奪天工的構造:

fd = &p - 3*size(int); bk = &p - 2*size(int)

我們來分析一下這個小小的藝術品奇妙在哪裏:

首先我們需要清楚結構體的尋址是按偏移來尋址的,然後我們來看一下free函數的具體實現:

FD = p->fd;
BK = p->bk;
FD->bk = BK;
BK->fd = FD;

其實就是一個很簡單的雙向鏈表拆卸,不多說;

然後我們來看,fd = &p - 3*size(int),bk = &p - 2*size(int) ,所以FD=&p - 3*size(int) , BK=&p - 2*size(int)

FD->bk按照偏移尋址,就是FD+3*size(int)==&p,FD->bk==p,同理 BK->fd==p,這樣一來就繞過了安全檢查

檢查通過就按照上述代碼來free,第三行等價於p=&p - 2*size(int)

但是第四行又把值覆蓋回來了,最終執行完畢後就變成了p=&p - 3*size(int)

回到本例中,此時如果程序執free(引線堆塊),那麽由於檢查到引線堆塊的size最後一位是0,因此認為p堆塊空閑,進行合並,觸發p的unlink

現在大家應該就明白“引線堆塊”這個名字是怎麽來的了,free相當於點火,堆塊就是引線,觸發prev的unlink爆炸

還記得我們已經將引線堆塊的prev_size覆蓋成了prev用戶區的大小,因此會造成一種假象,認為prev用戶區的起始就是prev的塊首起始,因此做unlink時,進行fd和bk操作的時候,fd和bk就能成功定位到之前的fake值!

這樣一來,在沒有觸發檢查報警的情況下,成功將指針p_user劫持到了存放p_user自己的內存往上三個單位的內存處:

技術分享圖片(圖中p為p_user)

此時p_user=&p_user - _3

但是,此時在受害程序視角上,堆塊p並非空閑態,也就是說,此時程序可能繼續以指針p_user為接口繼續進行讀寫操作。此時,便可以為所欲為。

這裏舉例一個具體操作:

比如接下來存在p堆塊的寫操作,原程序中正常的堆塊操作是,首先寫p[3],然後寫p[0]

假定我們現在已經知道了libc在內存中的映像地址,即得知了free_got(free_plt)、system_got

那麽我們就可以在寫操作中執行p[3]=&free_got,p[0]=system_got

這兩個操作分別實現的效果是,p[3]指向p即p[0],將p[0]的值即p的值改成了&free_got,之後p[0]=system_got,就相當於實現了&free_got=system_got

這樣一來,在程序執行任何free操作的時候,都會被劫持到system函數,get shell

當然有一個問題忘了提到,就是偽造fd和bk的時候,我們需要事先知道&p的值,這個估計需要具體的內存泄露,不再贅述。

希望對各位pwn??們有所裨益,有不當之處歡迎指正!

(尊重版權,轉載請註明出處,謝謝!)

雙倍快樂的堆溢出-unlink漏洞