痔録

おしりいたい

SECCON Beginners CTF 2019 write up

チームsproutsで参加しました. 1221pts/80thでした. 僕はうち216ptsでした.
言い訳をすると, バイトで開始が遅れてる間にOLIETくんが解けるReversing全部解いてました. f:id:leno3s:20190526202342p:plain

OLIETくんのWriteup

oliet.hatenablog.jp

[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ファイルがいっぱい出てくる. ので繋げて読むとフラグ. f:id:leno3s:20190526202635p:plain

僕が解いてないやつとか

[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

cdから復元できる攻撃あったか...?
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解けた.

f:id:leno3s:20190526235330p:plain
図1. コピペに頼るせいで変数名統一されておらずにエラーが出る図
方針は上記で合っていて, ちなみに使った実装も上のブログで出した物と同様だった. 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解けた. f:id:leno3s:20190527235622p:plain

問題だが, Nを失くしちゃった, という旨と共に$ nc 133.242.17.175 1337が与えられる.
実際に接続してみると, 暗号文cが表示された後, プロンプトの待機状態になり, 整数を入力できる(3回まで, 非整数値を入力すると"Bye."). 最後に, dが渡される.
ここからが自力では分からなかったが, この入出力は暗号化オラクルであるらしい. 実際に適切な値を入力すると, 入力値を平文mとしてRSAの暗号化処理c := m^{e} (\mathrm{mod}\ n)をし, cを出力している事が確認できる. f:id:leno3s:20190528000115p:plain 上の画像では, 0, 1を入力したときにそのままの値が出てくるが, 2の時には入力値とは異なる値が出力されている事が確認できる.

ここで, m=-1の場合を考えてみる.

    \begin{align}
      (-1)^{e} \equiv -1 \equiv -1 + n\ (\mathrm{mod}\ n)
    \end{align}
  と変形できるが, この時の出力されるc-1+nである事が確認できる. (RSA公開鍵暗号において, eは必ず奇数である, 特に素数を用いる場合が多い. e\phi (n)が互いに素である必要があるが, \phi (n)は必ず偶数であるため.)
よって, -1を入力し, 出力されたcに1を足す事でnが求まる. これにより(c, d, n)の組が揃い, 単純に復号する事ができる.

実際にやってみる. f:id:leno3s:20190528004100p:plain

#!/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解法を考え, 理解した瞬間は感動した. すごい. ちなみにe=\mathrm{0x}10001=65537だったらしい.