SECCON Beginners CTF 2019 write up
チームsproutsで参加しました. 1221pts/80thでした. 僕はうち216ptsでした.
言い訳をすると, バイトで開始が遅れてる間にOLIETくんが解けるReversing全部解いてました.
OLIETくんのWriteup
[Web] katsudon
Rails 5.2.1で作られたサイトです。 https://katsudon.quals.beginners.seccon.jp クーポンコードを復号するコードは以下の通りですが、まだ実装されてないようです。 フラグは以下にあります。 https://katsudon.quals.beginners.seccon.jp/flag
以下のサイトへアクセスするとフラグがあるらしい. 実際にアクセスするとbaes64+hashのような何かがある.
BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVU--0def7fcd357f759fe8da819edd081a3a73b6052a
base64部分をデコードしてみる.
$ echo BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVU | base64 -d
I%ctf4b{K33P_Y0UR_53CR37_K3Y_B453}:ET
これWebか...?
(追記: 識者いわくkatsudon okawariの方が想定解だったらしい)
[Reversing] seccompare
https://score.beginners.seccon.jp/files/seccompare_44d43f6a4d247e65c712d7379157d6a9.tar.gz
実行ファイルが落ちてくる.
第一引数とフラグの文字列をstrcmp()に渡しているが, そのその際rdiにフラグがセットされている.
gdbかltraceで見ればいいと思う. 僕はgdbで見た.
ctf4b{5tr1ngs_1s_n0t_en0ugh}
[Reversing] Leakage
https://score.beginners.seccon.jp/files/leakage_80a8c3c2bd63254a033ea21093944b1e.tar.gz
gdbで見ると, 入力した文字列について, is_correctで比較している事がわかる.
is_correctの動作を観察してみると, 入力文字長が0x22 = 34
であることを確認したのち,
call 0x4006d0 <convert>
の後にraxに比較する文字を1文字入れ(というかconvertの返り値がraxに入っている), 入力のn文字目と比較, を繰り返している事がわかる.
ので, ctf4b{aaaa...}
というような文字列を入力しておいて, 1文字ずつ修正, 入力する事でフラグを得られた.
ctf4b{le4k1ng_th3_f1lag_0ne_by_0ne}
[Crypto] So Tired
最強の暗号を作りました。 暗号よくわからないけどきっと大丈夫!
File: so_tired.tar.gz
問題のファイルを展開するとbase64っぽいテキストがある. 実際にデコードするとzlib compressed dataらしい.
$ cat encrypted.txt | base64 -d | file -
/dev/stdin: zlib compressed data
このzlibを展開したところまたbase64テキストが出てきたので, デコードするとまたzlib compressed dataだった. (手作業で8回くらいやった)
#!/usr/bin/env·python3 import zlib import base64 b = open('encrypted.txt').read() for i in range(500): z = base64.b64decode(b) b = zlib.decompress(z) print(b)
b'ctf4b{very_l0ng_l0ng_BASE64_3nc0ding}'
はい. (実際にはREPL上でやっていて, エラーが起きたタイミングでprint(b)
した. ので上の回数が合ってるかは知らん.)
[Misc] Containers
Let's extract files from the container. https://score.beginners.seccon.jp/files/e35860e49ca3fa367e456207ebc9ff2f_containers
バイトから帰る前にチームメイトが既に解いていた.
$ file
してもよくわからないので$ binwalk
とか$ foremost
してみると,
ASCII文字1文字が書かれた連番のpngファイルがいっぱい出てくる. ので繋げて読むとフラグ.
僕が解いてないやつとか
[Web] Ramen
チームメイトのWeb担当が解いた. 単純なSQLiらしい.
'or'' union SELECT last_insert_id(),last_insert_id() --
を入れるとテーブルの一覧が出るので,
その中からunion使ってflagのあるテーブル連結すればいいらしい.
[Pwnable] *
Pwnなんもわからん事がわかった.
shellcoderはcall rdx
, OneLineはcall rax
している箇所があり, 怪しいなぁとは思ったが何も分からなかった.
OneLineは実際に指定したアドレスに飛ばす事は出来たが, 多分そうじゃない. ので諦めた.
[Reversing] Linear Operation
is_correct関数長すぎィ!自分、草いいすか?
最初の方で入力がctf4b{(56文字入る)}
な事を確認しているのは分かったが, その後の処理長すぎて何を見たら良いのか
何も分からなかった.
[Crypto] Go RSA
c
とd
から復元できる攻撃あったか...?
ncの接続後に整数入れるとなんか出力されるけどこれがよく分からなかった.
[Crypto] Bit Flip
ncするたび, m
のランダムな1ビットだけ反転してRSAで暗号化したものが得られる.
また, この反転されるビットはm
のビット長の1/4より下位である.
実際の処理はbitflip.pyにあり, その内容からe=3
, n=(長いので略)
である事がわかる.
e
が小さいので最初Low public-exponent attackか?と思ったが, どうやらm
が十分に大きいようで,
適用できなかった.
その後適当にRSAの攻撃を調べていたら, Coppersmith's short pad attackという物が見つかった.
これは, m
の上位ビットがlen(bin(n))//pow(e, 2)程度合致している(c1, c2, n, e)の組に適用できる.
この問題のn
は1024bitsなので適用できると考えたが, 拾ってきた実装が上手く動かなかった. 悲しいね.
(というのは, https://betit0919.hatenablog.com/entry/2019/05/26/165827を読んでいて気付いたのだが,
異なるビットの位置の差異がe^2 = 9
より小さい必要がある. 後でやります...)
[Misc] Dump
ネットワークパケットが渡される. webshellに向かってls -al
した結果のような物(と/home/ctf4b/flag)が見えたが
肝心のwebshellどこやねんという気持ちになって分からなかった.
最終的にチームメイトが解いていたが, 上手くやるとパケットから画像が取り出せるらしい.
[Misc] Sliding puzzle
競プロ. ソケット通信の部分のコードだけ書いて, あとは競プロの民が解いた.
追記(19/05/26)
Bit Flip解けた. 方針は上記で合っていて, ちなみに使った実装も上のブログで出した物と同様だった. inaz2.hatenablog.com
反転したbit位置が偶然9桁以内収まるのを待つ必要があるので, 繰り返す部分にはEnter押すだけでいいようにした. 具体的には以下のようなシェルスクリプトを置いておき, 失敗するたびに上の図1のように実行していた. (これでシェルスクリプトって言っていいのかは謎)
nc 133.242.17.175 > nc.log nc 133.242.17.175 >> nc.log
あとは$ sh ./nc.sh && sage solover.py < nc.log
とかやればよい. (上の画像を見るとどうせしばらく使わんからとsageをpathに追加してないのがバレる)
上の図1で失敗しているprintを修正して実行すると, ctf4b{b1tfl1pp1ng_1s_r3lated_m3ss4ge} DUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMIY
が得られる.
追記 (19/05/28)
Go RSA解けた.
問題だが, を失くしちゃった, という旨と共に$ nc 133.242.17.175 1337
が与えられる.
実際に接続してみると, 暗号文が表示された後, プロンプトの待機状態になり, 整数を入力できる(3回まで, 非整数値を入力すると"Bye."). 最後に, が渡される.
ここからが自力では分からなかったが, この入出力は暗号化オラクルであるらしい. 実際に適切な値を入力すると, 入力値を平文としてRSAの暗号化処理をし, を出力している事が確認できる.
上の画像では, 0, 1を入力したときにそのままの値が出てくるが, 2の時には入力値とは異なる値が出力されている事が確認できる.
ここで, の場合を考えてみる.
と変形できるが, この時の出力されるがである事が確認できる. (RSA公開鍵暗号において, は必ず奇数である, 特に素数を用いる場合が多い. とが互いに素である必要があるが, は必ず偶数であるため.)
よって, -1を入力し, 出力されたに1を足す事でが求まる. これによりの組が揃い, 単純に復号する事ができる.
実際にやってみる.
#!/usr/bin/env python3 import codecs c = 18115525754707139247737911291079251015437886046824081894326069879183338686386831965083944455490302856924438972804102557211914041415916695525483840160263376766064903718826749107093832967322507099851575610601875351034662417918548789355304620557795980960827104773522234489047031012850613853078629790754912822386123877893152687558377470454221776300175235717030894742931132275324623086858960614138894594750435302300599934845888263775524718824690321411581765233365101101343228441540927101936040730136899922703915755085690801429962855693535554144212753599517289073383827310737516704869686779546066216528686774284182800149367 n = 19230413236215593516981780036574340206180267968093886508442679292227379103152261902186410411818746662353389048895399615967667936194946498523967201987872797073747338298250295774005218990597010562236430347595668174278928218368912660995360034411350013181586257644115577004816499030775983961763620429362792687334191628151347050130895017287615792312965309996415680670873547471696399932010453146246904838448703974001663916780692600806141083869194806679430239004402405243275582385242550855157742276686882808084128732672972274717521904626041479613981471591527671085541293993692942750129915766979565141263349085528159778380258 + 1 d = 12657324951666933822856189078805418910444360270865280504572736224568109998833879631547263370679363657586947678611348670741433451267302625392218887421592092628182022137621874033103271836953059304097390168513766915874938548111164938045620801140851643630329505618758404102717007220219919193350863341944144916014395151363006132944262569929025851874958157434330682759963932480480287216517053266529733255160619504067036647400774703853620408067897800287140150785773850414527502599792758669985621522683934608508643995372589652968559263833779114329202057205549519905314114334589390027687281982112791551360035436465610719511553 m = pow(c, d, n) print(codecs.decode('%0512x'%m, 'hex_codec'))
実行すると, b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ctf4b{f1nd_7he_p4ramet3rs}'
が得られた.
終了後に作問者のツイートをを見て-1解法を考え, 理解した瞬間は感動した. すごい. ちなみにだったらしい.