初級堆溢出-unlink漏洞


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、漏洞利用詳解:我們不先講原理,直接看過程來體會

(圖中8byte有誤,應該是16byte,圖片不方便換了,聊以填坑。。)

我們需要至少兩個堆塊,堆內存分布如上,此處不要引起誤會,雖然每個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_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🐶們有所裨益,有不當之處歡迎指正!

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM