チームzer0pts
でSECCON Beginnners CTF 2019に参加し、5477点を獲得して1位でした。初心者としては上の方にいることが確認できてよかったと思います。チームメイトが優秀だったので、私は易しい問題ばかり解いていました。
- [Crypto 115pts(192 solves)] [warmup]So Tired
- [Crypto 223pts(96 solves)] Party
- [Reversing 57pts(414 solves)] [warmup]seccompare
- [Reversing 186pts(120 solves)] Leakage
- [Reversing 293pts (62 solves)]Linear Operation
[Crypto 115pts(192 solves)] [warmup]So Tired
最強の暗号を作りました。 暗号よくわからないけどきっと大丈夫!
File: so_tired.tar.gz
yoshikingが取り組んでいましたが、なぜか解けないようだったので代わりにやりました。ダウンロードしたファイルをみると、base64でエンコードされていることがわかるので、base64 -d encrypted.txt
として復号すると表示不能文字の列になりました。file
コマンドに食べさせてやるとzlibで圧縮されていることがわかりました。zlib-flate --uncompress
で伸長またbase64文字列になったので、この時点で次のスクリプトを書きました。
from base64 import b64decode from zlib import decompress data = open("encrypted.txt", "rb").read() i = 0 while True: try: if i % 2 == 0: data = b64decode(data) else: data = decompress(data) i += 1 except: print(data) break
フラグは ctf4b{very_l0ng_l0ng_BASE64_3nc0ding}
でした。zlibどこいった
[Crypto 223pts(96 solves)] Party
Let's 暗号パーティ
File: party.tar.gz
encrypt.py
をみると秘密分散法っぽいコードになっていることがわかります。ただし、剰余をとっていません。
from flag import FLAG from Crypto.Util.number import bytes_to_long, getRandomInteger, getPrime def f(x, coeff): y = 0 for i in range(len(coeff)): y += coeff[i] * pow(x, i) return y N = 512 M = 3 secret = bytes_to_long(FLAG) assert(secret < 2**N) coeff = [secret] + [getRandomInteger(N) for i in range(M-1)] party = [getRandomInteger(N) for i in range(M)] val = map(lambda x: f(x, coeff), party) output = list(zip(party, val)) print(output)
剰余をとっていない場合にはラグランジュ補完によって各項を求めるのはできなさそうなので普通にz3で解きました。
from z3 import * M = 3 def f(x, coeff): y = 0 for i in range(len(coeff)): y += coeff[i] * pow(x, i) return y s = Solver() coeff = [Int("x:{}".format(i)) for i in range(M)] for c in coeff: s.add(0 < c) s.add(c < (1 << 512)) encrypted = eval(open("encrypted", "r").read()) for x in encrypted: s.add(f(x[0], coeff) == x[1]) r = s.check() if r == sat: m = s.model() print(m) else: print(r) exit()
結果を見ると多項式の係数は↓で、x0を文字列に直してやるとctf4b{just_d0ing_sh4mir}
とフラグが得られました。
[x:2 = 8559415203809303629563171044315478022492879973152936590413420646926860552595649298493153041683835412421908115002277197166850496088216040975415228249635834, x:1 = 6759741750199108721817212574266152064959437506612887142001761070682826541920627672362291016337903640265385249474489124882116454124173716091800442011015857, x:0 = 175721217420600153444809007773872697631803507409137493048703574941320093728]
[Reversing 57pts(414 solves)] [warmup]seccompare
https://score.beginners.seccon.jp/files/seccompare_44d43f6a4d247e65c712d7379157d6a9.tar.gz
IDAとかで開くと 74,66,34,62,7B,35,74,72,31,6E,67,73,5F,31,73,5F,6E,30,74,5F,65,6E,30,75,67,68,7D
というバイト列がみえるのでえいって戻すと tf4b{5tr1ngs_1s_n0t_en0ugh}
になっているのでどこかでこぼした c
を補完して勝ち
[Reversing 186pts(120 solves)] Leakage
https://score.beginners.seccon.jp/files/leakage_80a8c3c2bd63254a033ea21093944b1e.tar.gz
IDAで見た感じそんなに複雑そうではないcrackmeだったので、angrに投げたら通りました。
import angr, claripy p = angr.Project("leakage") flag = claripy.BVS("sim_flag", 8 * 0x22) state = p.factory.entry_state(args=[p.filename, flag]) for c in flag.chop(8): state.add_constraints(c >= " ") state.add_constraints(c <= "~") sim = p.factory.simulation_manager(state) sim.explore(find=0x4006AE, avoid=[0x4006BC]) print(sim.run()) import code code.interact(local=locals())
フラグは ctf4b{le4k1ng_th3_f1ag_0ne_by_0ne}
だったので、本当はちゃんと解析する問題だったのだと思います。
[Reversing 293pts (62 solves)]Linear Operation
https://score.beginners.seccon.jp/files/linear_operation_a45530bbfc995ac99f30e026276674aa.tar.gz
こちらも普通のcrackmeのようでしたが、IDAでみてみると無駄に長くて規則的な処理をしていたので、問題名と合わせてもangrの領域だと判断して投げました。
import angr, claripy p = angr.Project("linear_operation") flag = claripy.BVS("sim_flag", 8 * 0x63) state = p.factory.entry_state(stdin=flag) for c in flag.chop(8): state.add_constraints(c >= " ") state.add_constraints(c <= "~") sim = p.factory.simulation_manager(state) sim.explore(find=0x40CF78, avoid=[0x40CF86]) print(sim.run()) import code code.interact(local=locals())
フラグはctf4b{5ymbol1c_3xecuti0n_1s_3ffect1ve_4ga1nst_l1n34r_0p3r4ti0n}
でした。こちらは想定解法だったようです。