SECCON CTF 2022 Finals 参加記
2月11日と2月12日に行われたSECCON CTFの国内決勝にチームDouble Lariatとして参加し、12チーム中5位という成績を得ることができました。競技中にしたことなどについて書いていきます。
Double Lariatは5位入賞でした!チームも運営の皆様もありがとうございました! @_k4non @Iwancof_ptr @miso_2324 pic.twitter.com/4MRAK4txcY
— Satoooon (@Satoooon1024) 2023年2月12日
CTFについて
一日目の11:00~18:00と、二日目の10:00~17:00で開催されました。一日目終了後も合わせると全体の競技時間は30時間です。
決勝はKing of the HillとJeopardyを組み合わせた形式で行われました。
King of the Hillとは、あるものについて他のチームと攻防や競争を行って得られるポイントを競う形式です。ポイントを得られる条件や配点は問題によって様々で、例えば一定時間ごとに現在の順位に応じて各チームにポイントが配られたりします。Kothの問題については取り組めるのはその日限りで、一日目に1問、二日目に2問出題されました。
Jeopardyはいつもの形式で、問題を解くとポイントが貰える形式です。ジャンルはPwn, Web, Reversing, Crypto, Miscが出題されました。これは二日通して取り組めることができます。
Miscは別室でチャレンジする物理問で、競技開始前にどのタイミングで物理問に挑戦するか決定し、2時間前に問題のハンドアウトが配られて、制限時間50分の間に解くという形式でした。
配点はKotHとJeopardyの合計です。
チーム名について
スタッフの方にチーム名の由来について聞かれることがありました。(プロレスの技ではないです)
Double Lariatは元々はkanonさんとSekaiCTFに参加するときに組んだチームで、チーム名を決めるときに「SekaiCTFなのでボカロにちなんだチーム名にしよう」という話になり、そこからDouble Lariatというチーム名になりました。
前日
予選が終わってからもちょくちょくCTFには出たりしていましたが、SECCONに向けてこれといった対策はしてませんでした。
一応ルールを読んでKotHの配点はJeopardyと同程度くらいになるのかなと予想し、KotHを優先的に解く方針を固めてはいました。
1日目
一日目に出題されたKotHの問題です。この問題は次のような構成になっています。
Pythonで書かれたメモリ安全でない独自言語のコンパイラのプログラムが各チームに用意される
範囲外参照などの未定義動作を含むコードが実行できてしまう
言語機能は簡単なもので、コマンド実行などの機能はない
他のチームのコンパイラを実行できる環境が用意されている
他のチームのコンパイラに悪意のあるコードを渡して任意コード実行をし、動作環境内にあるフラグを奪取してスコアサーバーに提出すると得点が貰える
- この得点は1時間区切りのラウンドが終わるまでは再度得ることはできず、ラウンドが終了するとフラグが更新され再度提出できるようになる
自分のチームのコンパイラは、プログラムをアップロードして更新することができる
- 一回更新すると次に更新できるようになるまで1時間更新が制限される
5分ごとに各チームのコンパイラにランダムなテストケースを用いたジャッジが行われ、テストケースに合格すると点数が貰える。
- しかし、他のチームに攻撃されるとこの点数が一時的に貰えなくなる
つまり、正常性を保ちつつ攻撃を受けないように自分のコンパイラを修正しながら、相手のコンパイラのふさいでいないバグを見つけて攻撃するという流れになります。
11:21
misoさんが配列からintにキャストするとバグるのを発見しました。このあたりでだいたいの担当を決めて、自分はコンパイラの修正に取り掛かりました。
11:50
misoさんが任意コード実行できるコードを作成しました。そこから色々手間取っていたようで、もっと情報交換をうまくできていたら11時中に頑張ってexploitできたかなと反省しています。
12:45
コンパイラの修正に手間取っててこのままだと時間が溶けそうだと感じたので、バグを直接直さずに防御機構を入れてexploitの難易度を高くする方針に切り替えました。
このときDockerの起動スクリプトを実行すると手元のrealpathがぶっ壊れて止まるという謎現象が発生していて、コンパイラのテストスクリプトを書こうとしても不安定でめちゃくちゃ手間取っていました。マジで何だったんだろう……
12:52
misoさんがexploitに成功し、他のチームのフラグを全て得ることができました。国内では自チームが一番最初にexploitできたチームだったようです。
15:19くらい
コンパイラをCanaryとFlow Controlの実装をしたものに更新しました。Canaryはそのまま関数のプロローグとエピローグを編集するだけで簡単にできて、Flow Controlは関数の先頭に固定値をつけて、関数を呼ぶときに固定値部分をチェックしてから本体を実行させることで実装しました。
関数プロローグ:
self.code.text([f'{func_name}:', f'db 0xbb', f'db 0xbb', f'db 0xbb', f'db 0xbb', f'db 0xbb', f'db 0xbb', f'db 0xbb', f'db 0xbb', f'mov r13, {CANARY}', f'push r13', f'push rbp', f'mov rbp, rsp'])
関数エピローグ:
label = self.code.label() self.code.text([ "pop rbp", "pop r13", f"mov r12, {CANARY}", f"cmp r13, r12", f"je {label}", f"mov rax, [0]", f"{label}:", "ret" ])
関数コール:
label = self.code.label() self.code.text([ f"mov r13, [rax]", f"mov r12, {FUNC_PREFIX}", f"cmp r13, r12", f"je {label}", f"mov rax, [0]", f"{label}:", f"add rax, 8" ]) self.code.text([f"sub rsp, {0x10+n_args*0x10}"]) self.code.text(["call rax"]
これらは思いつきで実装しましたが、CanaryはOOB系の脆弱性が多いのであまり意味が無く、Flow Controlは固定値なのでシェルコードの先頭にそれを入れればいいだけの話であるため、かなりお粗末なものでした。(思いついた時点では他のチームはコンパイルしたアセンブリを確認できないのだと勘違いしていました)
多少は効果があったようですが、16:22にはids-TeamCCにexploitされ、最終的には数チームにexploitされていました。
prefixの値をランダムにしたり、アセンブリの難読化やデバッグ妨害をうまくやっていればよかったなと思っています。他のチームもnopなどを入れたり、何らかのチェックを入れていたり工夫をしていたようです。
そこからはSecLangを考えながらJeopardyを見たりして1日目が終了しました。SecLangで取ったリードが大きく、1日目終了時点ではDouble Lariatは1位通過でした。
ホテルに入ってからは持ち帰ったJeopardyの問題を見ていました。深夜まで粘ってeasylfi
だけ解けましたが、他の問題は何もわからなかったです。
2日目
2日目にはHeptarchyとWitchQuizというKotHが出題されました。
Heptarchyは様々な言語のバイナリが渡されるので、元のソースコードにできるだけ近づけたソースコードを書いて提出し、それをコンパイルしたバイナリと元のバイナリの差分の小ささを競う問題でした。これはmisoさんに担当してもらいました。
WitchQuizは様々な問題について優れた解を提出し、そのスコアを競う競プロのマラソンとCryptoが組み合わさったような問題でした。これはCrypto担当のkanonさんに担当してもらいました。
その後、物理問に挑戦する時間が来ました。どうやらLANケーブルを切断して盗聴し、加えてDH鍵交換のMITMをしろという問題のようで、これはkanonさんとiwancofさんに担当してもらいました。(これは解けませんでした)
その間kanonさんがWitchQuizの担当から外れるのですが、このとき自分が少しでもWitchQuizやるべきだったなと反省しています。
自分は2日目はJeopardyのWeb、とりわけbabybox
をずっと見ていましたが、結局解けず2日目は何もできませんでした。
最終的には1日目のリードを取り返され、Double Lariatは5位という結果でした。ぎりぎり表彰圏内で、2万円の賞金を頂きました。
感想
初めてのオンサイトCTFどころか大規模なリアルイベントに参加するのは初めてで緊張していましたが、めちゃくちゃ楽しかったです。SECCONの運営に関わった皆様に感謝します。
5位という結果でしたが、これはチームメイトのおかげで取れた順位だと思います、本当にありがとうございました。反対に自分は貢献できたところがあまり無くて悔しいので、次回はもっと貢献できるように頑張りたいと思います。