SECCON Beginners CTF 2020 writeup
目次
はじめに
SECCON Beginners CTF 2020にふ゛ち (@betit0919) | Twitter, まいこー (@micheeeeell1001) | Twitterと3人で出ました。久しぶりのCTFとても楽しかったです。
解けた問題はこんな感じで異常に偏っています。自分はCryptoとRev解いて余った時間でPwnやる予定だったのですが、Revが久しぶりでなんも覚えておらず、Pwnには手をつけられず……。 Rev, Pwnを練習しなくてはいけないなあと感じました。あと、久しぶりにPython 使ってそこにもめちゃくちゃ苦労したので、Pythonにももっと慣れていきたいです。
解いた問題
2020/05/26 追記 チームメイトが解いた問題の解法はこちらです betit0919.hatenablog.com
Noisy equations
概要
- coeffs (接続するたびにランダム)とanswersが与えられる
- flag長Nとすれば、coeffs はN×Nの行列answersはN×1のベクトル
- これ以外にN×1のランダムなベクトルが設定されているが、seedは固定
- A b + r = x (A: coeffs, b: flag, r: randbits, x: answers) という式が成り立つ
解法
- seed固定なので二回取ってきて差分を取ると(A_1-A_2)b = x_1-x_2 という感じでランダム部分が消える
- 逆行列をかけると求めたいbが求まる
from pwn import * from Crypto.Util.number import * import numpy as np import json io1 = remote("noisy-equations.quals.beginners.seccon.jp", 3000) coeff1 = json.loads(io1.recvline()) ans1 = json.loads(io1.recvline()) io2 = remote("noisy-equations.quals.beginners.seccon.jp", 3000) coeff2 = json.loads(io2.recvline()) ans2 = json.loads(io2.recvline()) N = len(ans2) subcoeff = [[0 for i in range(N)] for j in range(N)] subans = [0] * N for i in range(N): for j in range(N): subcoeff[i][j] = int(coeff1[i][j]) - int(coeff2[i][j]) for i in range(N): subans[i] = int(ans1[i]) - int(ans2[i]) A = np.array(subcoeff).astype(np.float64) b = np.array(subans).astype(np.float64) flag_ascii = np.linalg.inv(A).dot(b) flag = '' for c in ans_ascii: flag += chr(round(c)) print(flag)
ctf4b{r4nd0m_533d_15_n3c3554ry_f0r_53cur17y}
入力をうまく配列に入れたり、逆行列をかけたり……Python力がなさすぎて、解法思いついてから実装するまでにめっちゃ時間かかった。
RSA Calc
概要
- nc で指定されたポートに繋ぐと、Sign, Exec, Exit を選ぶインターフェイスが与えられる
- Signを選択すると入力したdataに対するSignatureが返される
- Execを選択するとdataと signatureを入力させられ、dataに対するsignatureが正しい時のみその後の処理が実行される
- SignatureはRSA署名でN, eも普通でp,qが求められる感じではない
- dataはExecできると','で分解され、'+-*/'などの演算子と数字による演算が行われる
- 'F'の入力があるとval==1337が実行されtrueならフラグが出力される
- Sign では'F', '1337'を含む文字列を入力するとsignatureを返さないようになってる
解法
- いかにして'1000,337,+,F'とか'1337,F'みたいな実行結果が1337になるような実行文のRSA署名を得るかが問題
- m='1337,F'としたとき、例えば2mとかの署名は返してくれるので、これをうまく使う
- sign_m=md(mod N) = (2m)d/2d (mod N)みたいな感じ
- 実際には'a'でやった
from Crypto.Util.number import * import binascii as ba from pwn import * io = remote('rsacalc.quals.beginners.seccon.jp', 10001) io.recvuntil('N: ') N = int(io.recvline()) m = b'1337,F' ma = long_to_bytes(bytes_to_long(m)*bytes_to_long(b'a')) io.sendlineafter('> ', '1') io.sendlineafter('data> ', ma) io.recvuntil('Signature: ') sig_ma = io.recvline()[0:-1] print(sig_ma) io.sendlineafter('> ', '1') io.sendlineafter('data> ', 'a') io.recvuntil('Signature: ') sig_a = io.recvline()[0:-1] print(sig_a) sig_m = hex(bytes_to_long(ba.unhexlify(sig_ma)) * inverse(bytes_to_long(ba.unhexlify(sig_a)),N) % N) io.sendlineafter('> ', '2') io.sendlineafter('data> ', m) io.sendlineafter('signature> ', sig_m) print(io.recvline())
ctf4b{SIgn_n33ds_P4d&H4sh}
mask
概要
- 実行すると引数にflagを入力してくれと言われる
- flag を入れて実行すると謎の文字列が2つ返り入力が正しくないと言われる
解法
- radare2+r2dec でCの疑似コードを出してみる
while (eax < var_d4h) { eax = var_d8h; rax = (int64_t) eax; eax = *((rbp + rax - 0xd0)); eax &= 0x75; edx = eax; eax = var_d8h; rax = (int64_t) eax; *((rbp + rax - 0x90)) = dl; eax = var_d8h; rax = (int64_t) eax; eax = *((rbp + rax - 0xd0)); eax &= 0xffffffeb; edx = eax; eax = var_d8h; rax = (int64_t) eax; *((rbp + rax - 0x50)) = dl; var_d8h++; eax = var_d8h; }
片方は&= 0x75, もう一方は&= 0xffffffebで入力の各文字mask処理が行われて、それが出力されている
eax = var_d4h; rax = (int64_t) eax; *((rbp + rax - 0x90)) = 0; eax = var_d4h; rax = (int64_t) eax; *((rbp + rax - 0x50)) = 0; rax = &s1; puts (rax); rax = &var_50h; puts (rax); rax = &s1; eax = strcmp (rax, "atd4`qdedtUpetepqeUdaaeUeaqau"); if (eax == 0) { rax = &var_50h; eax = strcmp (rax, "c`b bk`kj`KbababcaKbacaKiacki"); if (eax == 0) { puts ("Correct! Submit your FLAG."); }
それぞれ"atd4`qdedtUpetepqeUdaaeUeaqau"と"c`b bk`kj`KbababcaKbacaKiacki")との比較が行われる。
ということで0x75, 0xffffffebのmaskのビットが立っているところはflagのビットの状態がわかるので、2つのorをとってflagを逆算します。
a = 'atd4`qdedtUpetepqeUdaaeUeaqau' b = 'c`b bk`kj`KbababcaKbacaKiacki' mask1 = 0x75 mask2 = 0xffffffeb ans = "" for i in range(len(a)): ans += chr(ord(a[i])&mask1 | ord(b[i])&mask2) print(ans)
ctf4b{dont_reverse_face_mask}
dont_reverse_face_mask
とか、問題文のところの
The price of mask goes down. So does the point (it's easy)!
とか、ユーモアが効いてますね。
考察した問題
yakisoba
- revしてみると、一文字ずつ判定しているところがあってそこがカオス
- ヒントにもある通り自動化しないとやってられなそう
- 1文字ずつ正しいかどうか判別がつくのでbruteforceできそうだが、実装できなかった
ghost
- ghostが書いたscriptということで、ghostscript->PostScriptで書かれている
- interpreter使ったり、実行したりしながら試したが間に合わず
2020/05/26 追記 コンテスト後に解きました
概要
- postscriptで書かれている
- ghostscriptを入れて実行すると入力を求められる
- 文字列と同じ長さの数列が返る
- 後ろの文字を変えてもそれより前は変わらない
解法
- 1文字ずつ決まるのでbruteforce
from pwn import * output = [3417, 61039, 39615, 14756, 10315, 49836, 44840, 20086, 18149, 31454, 35718, 44949, 4715, 22725, 62312, 18726, 47196, 54518, 2667, 44346, 55284, 5240, 32181, 61722, 6447, 38218, 6033, 32270, 51128, 6112, 22332, 60338, 14994, 44529, 25059, 61829, 52094] s = 'ctf4b{' len_o = len(output) while len(s) != len_o: for c in string.printable: p = process('gs chall.gs', shell=True) p.sendlinethen('details.\n', s+c) res = p.recvline(keepends=False).split(b' ')[0:-1] p.close() if list(map(int, res)) == output[:len(s)+1]: s += c print(s) break
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun!}
おわりに
時間かけた割に全然解けてなくないですか??? もっとrev, pwn問解けるようになりたい!!!