ChatGPTでCTF攻略(PartⅡ 決戦SECCON14編)
SECCON14 予選 Writeup(Ez Flag Checker / Breaking Out)
はじめに
- 日本最大級のCTFである SECCON14予選 に参加してきました
- 結果は 267位(817チーム中)
- ChatGPT Plus(5.2)のみ使用
- 本Writeupも作成してもらいました(笑)
実運用環境や第三者システムに対して同様の行為を行うことは、許可なく実施すると法令・規程違反になる可能性があります。
目次
(1)Ez Flag Checker
概要
「flag checker(入力された文字列が正しいflagか判定するプログラム)」を解析して、 “正しいflagを逆算する”タイプの問題です。
アプリケーションの挙動(ブラックボックス確認)
$ chmod +x chall $ ./chall Enter flag: test wrong :(
解析方針
- Step 1形式チェック(長さ / prefix / suffix)を特定して入力の型を確定
- Step 2変換関数(暗号化/難読化)と比較対象(期待値)を見つける
- Step 3変換を反転(復号)して中身を復元
- Step 4実行で最終確認(
correct flag!)
具体的なペイロード(再現手順)
① 形式チェック突破用(“まず比較処理まで到達” する入力)
多くのflag checkerは、暗号チェックの前に「形式チェック」で即NGにします。 まずは prefix/suffix を満たし、長さも正しくすることで、比較処理まで到達します。
SECCON{AAAAAAAAAAAAAAAAAA}
※中身(Aの個数)はバイナリの要求長に合わせて調整します。ここは「比較処理まで到達する」ためのダミー入力です。
② “差分観測” 用(位置ずらし・1文字変更テスト)
XOR系の可逆変換では、特定位置だけ変えると差分が局所的に出ることが多く、解析の手掛かりになります。
SECCON{AAAAAAAAAAAAAAAAA!}
SECCON{AAAAAAAAAAAAAAAA!!}
SECCON{AAAAAAAAAAAAAAA!!!}
③ 復号スクリプト(可逆変換を反転)
変換関数の式(例:out[i] = in[i] XOR (key[i%16] + i))と期待値(暗号文)が分かれば、同じXORで復号できます。
下は “同種の可逆変換” を反転する雛形です。
# 例:XORでの復号(雛形) # key / cipher はバイナリ解析で取得した値を入れる key = b"expand 32-byte k" cipher = bytes([0x03,0x15,0x13,0x03,0x11,0x55,0x1f,0x43,0x63,0x61,0x59,0xef,0xbc,0x10,0x1f,0x43,0x54,0xa8]) keystream = bytes(((key[i % 16] + i) & 0xff) for i in range(len(cipher))) plain = bytes(c ^ k for c, k in zip(cipher, keystream)) print(plain.decode())
④ 取得できたflag
SECCON{flagc29yYW5k<b19!!}
(2)Breaking Out
概要
ブロック崩し風のWebゲームで、問題文のヒントは 「There is something at stage 100」。 つまり “100面” に特別なデータがある想定です。
脆弱性(CTF的な弱点)
本問の“脆弱性”はサーバ侵入ではなく、 ゲームの核心データと復号ロジックがクライアント側JSに同梱されている点です。
- ゲームを頑張って100面まで到達しなくても、JS解析でstage100のデータを抽出できる
- 圧縮/暗号化されていても、復号ロジックが同梱なら “時間の問題”
具体的なペイロード(再現手順)
① stage100(ステージ定義)を取り出す
DevTools(F12)→ Console で、ステージ定義が配列として存在する場合は直接参照できます。 (CTF配布のソースでは stage 情報がJS内に保持されていました)
// 例:0始まりの配列なら stage100 は [99] const stage100 = stages[99]; console.log(stage100);
② ブロック配置(layout)を 0/1 に落とす
const layout = stage100.layout; // 例:2次元配列 const bin = layout.map(row => row.map(cell => cell ? 1 : 0)); console.log(bin);
③ 画像化(Canvasに描画してQRとして読み取る)
stage100 のレイアウトがQRコード状の二値画像になっていたため、Canvasに描画して読み取りました。
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const size = bin.length; // 正方形を想定
canvas.width = canvas.height = size;
// 1セル=1px(小さければ後で拡大してもOK)
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
ctx.fillStyle = bin[y][x] ? "black" : "white";
ctx.fillRect(x, y, 1, 1);
}
}
document.body.appendChild(canvas);
fillRect(x*scale, y*scale, scale, scale) にするとQRリーダーが認識しやすくなります。
④ QRの中身(最終ペイロード=flag)
SECCON{H4ve_y0u_3ver_p14yed_Atari?_SQiOIVX6HPtRekE1vTn4}
学び
共通して言えること
- 入力検証は “どこで何を比較しているか” を見つけると勝ち筋が見える
- クライアントサイドは攻撃者が自由に観測/改変できる前提で設計する
- 難読化・圧縮・暗号化は、復号ロジックも同梱なら “時間の問題”
実務に活かす観点(チェックリスト)
- クライアントに「秘密情報」や「判定の最終権限」を置いていないか?
- ライセンスキー/フラグ/トークンのような “価値のあるもの” をフロントに埋めていないか?
- 「検証」や「権限確認」をサーバ側でやっているか?
- ローカル保存(localStorage等)の値を信用していないか?
※本Writeupは攻撃の考え方を中心にまとめています。
参考:解法に至ったプロンプト(スクリーンショット)
参考として、2問の回答を導いたプロンプト(やり取り)をスクリーンショットした画像を掲載します。



コメント
コメントを投稿