今年もtheoremoon, yoshikingとでInterKosenCTF*1を開催しました。 前と変わらず初心者〜中級者向けを対象にしました。(どっちかというと中級者向けの問題が多いと思います。)
今年は直前までサーバー周りが燃えていて炭になりそうだったんですが、theoremoon*2の運用力により無事開催できました。
チーム優勝はproが集まったHypwnLabでした🎉
個人優勝はproの広田空さんでした🎉
問題のソースコードや解法は以下のgithubリポジトリにまとまっています。 実装や想定解が気になる方はご利用ください。
他のメンバーの開催記:
運営とのお約束:
競技中に解けなかった問題は是非復習してください。 復習しないと競技中に調べた分しか学びがありません。 writeupが書けると更に良いです!
スコアサーバは @theoremoon がすべて書きました。えらい。 一方私は「raceがあるから直してー」とか「スコアの遷移グラフ付けてねー」とか下請けに無茶を言うおじさんみたいな役をしていました。
問題サーバも @theoremoon がすべて設計しました。すごい。 一方私は「Discordに解答状況投げたいねー」とか「tcpdumpでパケット監視したいなー」とか下請けに無茶を言うおじさんみたいな役をしていました。
今年は問題をいい感じにデプロイしてくれたり、サーバーが動いているかを自動で確認してくれるbotも作ってくれました。さすが。 例年私は問題の生存確認とかパケット監視をしていたのですが、仕事が減ったのでよく寝ていました。
去年は個人スポンサーを集めて資金繰りをしたのですが、今年はオンラインCTFで勝ち取ったDigitalOceanを使いました。 タダでじゃんじゃん使えるのは良いですが、AWSと若干仕様が違って詰まることも多かったらしいです。(他人事🙄)
今年は初心者の道標としてwarmupとlunaticだけ難易度表示をして、あとは難易度を書きませんでした。 例年非想定解等で予想より簡単になったり、逆に思ったより解かれなかったりすることが多いからです。 どの問題も初心者にとっては「やるだけ」ではなく、何かしら新しい知識・技術が身につくように設計したつもりです。(ほんまか?)
私の作った問題の概要:
ジャンル | 問題名 | 想定難易度 | テーマ |
---|---|---|---|
pwn | babysort | warmup | 範囲外参照 |
Fabled of aeSOP | easy | FILE Stream Exploit | |
pash | medium | 権限昇格 | |
authme | hard | バッファオーバーフロー | |
confusing | lunatic | Type Confusion, Heap Corruption | |
reversing | in question | warmup | 元祖簡単rev, statically linked |
trilemma | medium | ライブラリ同士の競合, パッチ | |
stratum | lunatic | xmmレジスタ周りの愉快な命令たち | |
web | maze | lunatic | prototype pollution |
graphviz++ | medium | OS Command Injection | |
misc | readme | medium | procfs, file descriptor |
limited | easy | Network Forensics, SQL injection, 中国人剰余定理 | |
Tip Toe | hard | チート |
pwn
babysort
簡単な範囲外参照の問題です。 メモリ上でSortExperiment構造体が次のようになっています。
| ... | +--------+ | elm[3] | +--------+ | elm[4] | <-- cmp[-1] +--------+ | cmp[0] | +--------+ | cmp[1] | +--------+ | |
渡した関数ポインタ(cmp[i]
)をqsortが呼ぶ事に気づけば、cmp[-1]
等でelm
に入れた値を使って任意のアドレスを呼べます。
作る時は意識していませんでしたが、pwntoolsなどを使わずにncだけで解けるというのも初心者向けポイントだったと思います。
ちなみにwin
関数が用意されていなくても解けます。*3
作問チェックのwriteup:
pash
ssh
の引数にコマンドを渡すと実行してくれるので、pashを起動せずにbash等を起動できます。
シェルが起動できるのですが、フラグはadmin権限でしか読めないのでsetgidされているpashを使って権限昇格する必要があります。
想定解としては
- フラグへのシンボリックリンクを
flag.txt
以外の名前で貼って、それをpashからcatする。 whoami
などのファイル名でフラグを読むバイナリを送り、$PATHの優先順位を変えてpashからwhoamiを叩く。
の2つを用意していました。 (というか後者を許しつつ前者を防げる権限設定を思いつかなかったのでそのまま出しました。) いずれもsetgidの効果によりEGID(実効グループID)が引き継がれ、EUID/EGIDだけあればファイル読み込みが可能という内容でした。
なお、sftpのオプションでシンボリックリンクを貼る手法もあったそうです。 シラナカッタ......
ちなみに参加者の解法を監視していましたが、私が見ていたときに解いた人は全員シンボリックリンクを使っていました。
PATHで解いた人もいたようです。
作問チェックのwriteup:
Fables of aeSOP
FILE構造体のvtableを書き換える最近流行り(もう流行ってない?)のFILE Stream Exploitの最も簡単な問題です。 bssセクションに自明なバッファオーバーフローがあり、FILE構造体へのポインタが書き換えられます。 偽のFILE構造体を指すようにポインタを書き換え、偽のvtableにwin関数のアドレスを書いておけばfclose時にシェルが取れます。
この問題ではlibc-2.23なのでこの手法が使えますが、libc-2.27以降では_IO_str_jumps
を使う必要があるので注意してください。
そんでlibc-2.31以降では_dealloc_buffer
がfree
に置き換えられてるから無理なんじゃないかな。
作問チェックのwriteup:
authme
入出力周りの知識が要求されるBOF問です。 fgetsによる自明なバッファオーバーフローがありますが、認証後にexit関数が呼ばれるためリターンアドレスを書き換えても意味がありません。
ユーザー名のfgetsでBOFを起こした後、パスワードのfgetsが失敗すればexitでなくreturnしてくれるので、fgetsにNULLを返させる必要があります。 fgetsはreadに失敗したらNULLを返してくれるので、相手側のreadに関わるソケットを閉じます。
writeの方は生きているのでその後の出力が受け取れるのですが、readが死ぬのでシェルを取っても意味がありません。 そこで、各接続でROPでユーザー名とパスワードをリークし、それらを使って正しく認証に成功すればプログラムの仕様上シェルが起動します。
作問チェックのwriteup:
confusing
Type Confusion + Heapの問題です。 WebKitのJSValueで定義される8バイトのunion型を真似た構造を持っています。
- 文字列のポインタは普通に48-bitのポインタとして持つ
- 32-bit整数は上位16-bitを0xFFFFにして保持する
- double型の小数は248を足して保持する
- nullやundefined等は小さい値(ポインタとしてあり得ない値)として保持する
この仕様のうち次の箇所が実装されていません。
The scheme we have implemented encodes double precision values by performing a 64-bit integer addition of the value 248 to the number. After this manipulation no encoded double-precision value will begin with the pattern 0x0000 or 0xFFFF. Values must be decoded by reversing this operation before subsequent floating point operations my be peformed.
ということで、今回の問題では248が足されていませんでした。 この操作をする理由もJSValue.hのコメントに書かれています。
This range of NaN space is represented by 64-bit numbers begining with the 16-bit hex patterns 0xFFFE and 0xFFFF - we rely on the fact that no valid double-precision numbers will begin fall in these ranges.
double型では0x00007fff12345678のような文字列ポインタを表現できてしまうので、248を足して0x00027fff12345678のようにする必要があります。 整数についても同様です。 一方IEEE754の仕様上、248を足した後に上位16ビットが0xFFFFや0x0000になることはないので、型を区別できることが保証されます。
今回は変なdouble値を入力すれば間違えてstringと判別してしまうので、任意アドレスのreadとfreeが可能になります。 GOTからlibcとheapのアドレスをリークし、fake chunkをfreeしていい感じにtcacheを壊せば任意アドレスのwriteが作れます。
これはチームメンバーがdouble freeまでやって、解けないよ〜ってなってたので唯一作問チェックwriteupがありません。 ので想定解solutionを見てください。
web
maze
Prototype Pollutionを出そうと思って作った問題です。 作ってみて始めて知ったのですが、CTFでprototype pollutionを出すと汚染が参加者全員に影響するので作りにくいですね。 今回はリクエストごとに別のnodeを立ち上げるという設定でこれを回避しました。
deepcopy使うプログラムって何かな→A*探索とか?→とりあえず迷路ソルバ書こ→なんかいい感じにRCE埋め込めた
みたいなノリで出した問題です。 なんか思ったより難しかったらしく、zer0pts CTFに回せば良かったなぁ :pien: ってなりました。
作問チェックのwriteup:
rev
in question
自明なアルゴリズムを持つフラグチェッカーです。 アセンブリをちゃんと読んでほしかったので、gdbやghidra, angrなどがファイルを識別できないようにしたり、IDAのデコンパイラが誤った結果を出すように工夫しました。
作問チェックのwriteup:
trilemma
バイナリのパッチを書かせることを目的とした問題です。 emperor, citizen, slaveの3つのライブラリがあるのですが、互いに同時にロードできないようになっています。 (問題名は「三すくみ」という意味です。)
複数のライブラリが競合する原因はいろいろあるのですが、今回はmmapで確保するアドレスが同じになっていることが原因です。 たぶんほとんどの人が考えたのは
- ライブラリチェックをパッチして消す
- mmapのアドレスを変える
だと思います。 今回RC4もどきを使ったのですが、アルゴリズムがmmapのアドレスに依存するようにしたのでアドレスを変更してはいけません。
ということで、私が作った際の想定解としては
- RC4もどきを読んで実装する(あまりやってほしくない)
- angr等で競合部分をフックし、実際には別領域を使わせることで競合を回避する
でした。 しかし、作問チェックの際にパッチを消して3つのライブラリのうち2つを使って、いいかんじにgccnにビルドオプションを渡すと各フラグが得られることが分かりました。 ただ、今回の出題目的はパッチを書くことだったので、これは良しとしました。
作問チェックのwriteup:
stratum
本来zer0pts CTFのeasyあたりに出す予定でしたが、そっち用には別のアイデアが出たのでこれをInterKosenCTFに回しました。 内容としては、IntelのSIMD謎命令を読ませる問題です。 xmmレジスタの闇を知ってほしくて作りました。 愚直に読んでz3とかで解くのが想定です。
作問チェックのwriteup:
misc
limited
LIMIT句にSQLiできる際に得られるデータ数が少ない時にどうするか、というのをテーマにしたpcap問です。 ふるつきが中国人剰余定理使うSQLi出したいけど1ビットずつリークできるから成立しねぇって言ったのでforensics問にしました。
作問チェックのwriteup:
graphviz++
ただのOS Command Injectionなのになぜか2番目にsolve数が少ない問題になりました。 dot経由でm4を呼んでいて、そこにinjectionができるという2段階になっていて気付きにくかったのかもしれませんね。
作問チェックのwriteup:
readme
SECCON Beginners 2020で出したreadmeの続きです。
あちらは/proc/self/cwd
を開くという非常に単純な問題でしたが、今回は/proc/self/fd/X
を開くというもう一歩踏み込んだ問題になっています。
さらに、selfが禁止されているので、/proc/mounts
など/proc/self
以下にリンクされているファイルを使い、2段リンクを踏む必要があります。
作問チェックのwriteup:
Tip Toe
今回はチート問を作ろうと思ったのですが、Windowsマシンがないためこれしか作れませんでした。 2日だけWindowsマシンがある東京の家に行く機会があったので、その2日で爆速コーディングしてこのゲームを作りました。 HSPは短期間で3Dゲームが作れて有能。 何年も触っていない間に物理計算付き3Dエンジンが搭載されていて便利でした。(ドキュメントが少なくてつらかったが)
解法としては
- ゲーム中に登場する文字列を探して付近の文字列を調べる
- デバッグモードの存在が分かるので、デバッグモードで起動する
- 正解のタイルやタイマーの値などが表示される
- ゴール前でCheat Engine等でタイマーの値を書き換えてゴールする
本当はデバッグモードなしで座標をいじる問題にしたかったのですが、HSPのメモリ構造を調べやすい日本チームが有利になりそうなのでやめました。 同様に、日本語のHSP製exeのデコンパイラが出回っているので、デコンパイルに失敗する命令列を調べて埋め込みました。
作問チェックのwriteup:
これを書いているまでに回収したアンケートの結果をまとめようと思います。
集計
- クオリティ:みんな3以上の評価をくれました。ウレシイネ......
- 難易度:適切と難しいが半分ずつくらいでした。難しすぎるという人は現状観測していません。
- 開催時間:6割くらいが適切、他は長いもしくは長すぎる、という感じでした。次は24時間にするかもです。
意見等
- InterKosenCTFというタイトルは何?KosenCTFとの差は?
- スコアサーバーで解いた問題と解いてない問題が区別しにくい
- 思ってたけど言い出せなかった...次は見やすくなると思います
- 問題が落ちているログは別のチャンネルにしてほしかった
- 思ってたけど言い出せなかった...次はそうなると思います
- stratumがrevしなくても良かった
- 確認したつもりですがダメでした。rev担当いないんで簡単な問題しかなくゴメンナサイ。
- ある程度時間が経ったら/はじめからヒントが欲しい
- ヒントが欲しい気持ちはよく分かりますが、基本的に今後もヒントは出さないつもりです
- まず、既に誰かが解いた問題にヒントを出すと不平等になるのでできません
- 誰も解いていなくても、もしかしたら解ける直前かもしれないので基本的に出したくないです
- 作問チェック時にヒントが必要なら問題文に書くようにしています(Tip Toeとか)
- CTFにおいてヒントを出して良いかどうかは判断が難しい問題で、ここに書くには余白が少なすぎる
- ○○に関する問題を出してほしい
- ちゃんとアンケートに答えてくれたで賞として次回検討します
一生懸命問題に取り組んでもらえると、頑張って作った甲斐があります。 みなさんのwriteupを楽しみにしていますので、じゃんじゃん投稿してください。 参加者の皆様、ありがとうございました!