feature image

2026年4月23日 | ブログ記事

CPCTF2026参加記/writeup(新入生2位)

この記事は新歓ブログリレー2026 49日目の記事です!

 traPの主催するCPCTFに出場し、新入生2位になることができたので参加記を書きます。

はじめに

CPCTFとは、traPの主催する、競技プログラミングとCTFが融合したコンテストです。競技は48時間開催されました。詳細なルールなどはトップページをご確認ください。と思ったけどコンテストサイトが1年後以降には見られなさそう(現時点で2025が見れない)なので、軽くまとめておきます。

問題について

問題は全部で56問出題されました。48時間をフルに使っても解ききれるか怪しいくらい十分な問題量が用意されています。
 問題にはジャンルとレベル(難易度)が設定されています。ジャンルは

の全部で9種類あります。
 難易度は5段階に設定されており、Lv1~5となっています。
Lv5以外の問題にはヒントがついていて、ヒントを開いても点数が減ることはありません。

点数について

Lv1の問題は固定の点数で、それ以外の問題では解いた人数が多いほど得点が下がります。つまり問題の配点はだんだん下がっていきますが、点数の低下は既に解いた人にも反映されるため、問題を解く順番や速度は点数に関係ありません。
 基本的に各問題100回まで提出できますが、問題によっては減点ラインが設定されており、規定の回数を超える提出を行うとその問題の点数が10分の1にされます。
 なお競プロの問題についてはyukicoder上で出題されるため、ジャッジもyukicoder上で行われます。誤答回数に制限はありません。ACすると解説を開くことができ、解説にフラグが掲載されているので、それをコンテストサイトに提出すると点数が得られる形です。

AIについて

AIの使用は競プロの問題のみ制限されています。CTFの問題では制限なく使うことができます。

商品について

商品については以下の通りです。僕が狙っていたのは主に新入生賞でした。

商品一覧。かなり豪華

ルールについてはこんなもんでしょうか。

自己紹介

キモい位置に自己紹介を入れておきます。東京科学大学の26B(26年度新入生)のblueberryです。AtCoderは青です。CTFは1か月前まで未経験でした。何問か解いてみたりはしていましたが、コンテストに出場するのは初めてでした。

コンテスト結果と感想

3,758点で全体18位、新入生2位となりました。全56問のうち、CTFの問題で解けなかった問題が1問、競プロの問題で解けなかった問題が3問で、それ以外の52問を解くことができました。
 競プロの問題の方が残ってるのはびっくりですが、CTF初心者でこの成績をとれて嬉しいです。新入生1位にはなれなかったものの、かなり満足できる結果になりました。

新入生の得点遷移のグラフ。最終的に2位なのが僕です


 48時間コンテストに出場するのも初めてで、休憩をどう入れるかとか初めてで難しくてとっても疲れましたが、すごく楽しかったです。途中1位の瞬間も何度かあり、抜かしたり抜かし返されたり熱い戦いができて良かったです。
 さて、感想はこんなもんですかね。以下ネタバレを多く含みます、というか全てがネタバレなのでよろしくお願いします。あとたぶん超長いので飛ばし読みするのが良いと思います。

基本方針

実際の立ち回りを振り返る前に基本方針について。

まあ結構当たり前のことしか言っていないと思います。

振り返り

 ここからは時系列順にやっていたことを振り返ります。

開始前

 5-6限まで授業がありました。即帰宅して17:14から仮眠を取り、起きたら20:42でした。ちなみにコンテスト開始は20:00だったので普通に遅刻しています。まあ解くスピードやスタートダッシュは点数には関係ないので、ちゃんと睡眠が取れたということで良かったと思います。色々準備して、解き始めたのは21:07でした。この時点で新入生順位は18位でした。

Sign up for traP (Lv.1/PPC)

 とりあえずPPCを解けるところまで解くことにします。文字列が特定の条件を満たしているかどうかを判定する問題です。一瞬だけ正規表現が頭をよぎるものの、調べずに扱えるほど慣れていないので普通に条件式を書きました。

 フラグをコンテストサイトに提出しようと思ったのですが、自分が出遅れていることを思い出したのでフラグの提出はいったん保留することにしました。(21時18分)

Modulo Equation(Lv.1/PPC)

最小値が 以下なので普通に全探索すればよい。(21時22分)

I Love DAG(Lv.2/PPC)

頂点番号が小さい方から大きい方に辺を張るようにすればサイクルを作らないようにグラフを構築することができる。(時間不明)

01 String(Lv.2/PPC)

0を右に広げていくことと1を左に広げていくことができる。00...11のような長さNの列があったときその列はN+1パターンに変化させることができる。これをいい感じにやると通る。(21時44分)

Digit Products 2(Lv.3/PPC)

面白い。N桁の正整数Xを特定する問題。クエリを投げると2桁の積を知ることができる。1桁目は必ず0以外なので、1桁目を特定することを考える。なんやかんや罠があるが、それを頑張って回避していくと通る。なんと、11WAしている。(22時19分)

解いているときのつぶやき(Discordの自分のみのサーバーにて)

GCD Knapsack(Lv.3/PPC)

主客転倒をすれば、重みの最大公約数がWであるような選び方の価値の総和を求めることができる。この時点で6位に。(22時32分)

DualCast(Lv.1/Crypto)

このへんでPPCから離れてCTFを解くことに。long_to_bytesなのでbytes_to_longすればよい。(22時46分)

Hidden(Lv.1/Reversing)

stringsを実行すると答えが出る。(22時49分)

Killionaire(Lv.1/Pwn)

AIに相談してみると、負の値を入力すると良いと言われる。ncコマンドで接続し、適当なデカい(小さい)負の値を入れると答えが出力される。(22時51分)

L0v3 PDF(Lv.1/Forensics)

PDFをそのまま開くとdummyと表示される。stringsを実行してみると答えが見える。(22時53分)

mirage(Lv.1/Web)

ヒントの通りDevToolを開くと答えが書いてある。(22時54分)

ssh(Lv.1/Shell)

ヒントがかなり親切。ncコマンドで接続し、cat flag.txtすると答えが見れる。(22時56分)

Buffer Visualizer(Lv.2/Pwn)

この時点でLv1を解き終わり、ここからLv2。AIに聞くと入力すべき値がわかるのでそのようにすると解ける。(23時8分)

Flag in Flags(Lv.2/Forensics)

検索/AIに相談すると、便利な画像解析ツールAperi'Solveが出てくる。それに投げて眺めると、小さくフラグが書いてあるのを見つけることができる。もっとわかりやすく書かれているかなと思っていたので見つけるのに時間がかかった...(23時14分)

Hidden Recipe(Lv.2/Web)

AIと相談する。SQLインジェクションの手段を色々提案されるので、色々試していると答えを得ることができる。(23時40分)

Very Exciting(Lv.2/Crypto)

AIと相談する。xorして向こうのseedを0にすることができ、それをすると数値で答えが返ってくる。これを数値から文字に直せば答えが得られる。(0時16分)

1,0,7(Lv.3/Crypto)

RSA暗号を解くらしい。ほとんど解き方を知らないのでAIに聞いてみる。ヒントにNの形が特徴的なのでこれを利用することができると書いてあるので、それを伝えつつ復元コードを書くように要求すると復元コードを書いてくれる。ここで5位に。(0時32分)

Brack Stack Query 2(Lv.2/PPC)

PPCに戻る。復元できるような形でデータを保持しながら進めていくと解ける。(1時4分)

Let's Remove script tag(Lv.2/Web)

AIに聞くと、上手い感じでファイルを作ってそれを開くと任意のJavaScriptコードが実行できること(XSS Payload)がわかる。AIの言う通りの手順で実行するとフラグを得ることができる。(1時11分)

Omikuji(Lv.2/Reversing)

まずはfileコマンドを実行すると、C++でコンパイルされたものであることがわかる。AIに聞いてみるとC++のデコンパイルツールGhidraを提案されるため、それをインストールして実行し、解析結果の全文をAIに再び投げるとフラグが返ってくる。(1時26分)

ssh2(Lv.2/Shell)

いくつかのコマンドが禁止されている状態でcat flag.txtと同じ効果を得るコマンドを探す問題。AIに聞くとgrepなどを提案されるので実行してみるとフラグを得られる。(1時32分)

Secret Recipe(Lv.2/Forensics)

ヒントにもある通り、Wiresharkをインストールしてpcapファイルを解析してみる。TCP Streamを追うと画像ファイルが含まれており、ダウンロードしてそれを開くとフラグが書かれている。(2時3分)

Z(Lv.2/Web)

例のごとくAIに相談してみる。開発者ツールのコンソールからfetchでplanをplemiumに切り替えることができ、フラグを得られる。(2時11分)

Insert Maze(Lv.3/PPC)

このタイミングよりも前に問題を読んでなんとなく問題概要を把握していた。insertする操作は2回使えば確実にゴールに至ることができるので、H+Wが答えの上界。insertを1度も使わずにゴールする方法は単にBFSをすれば求めることができる。問題はinsertを1度だけ使うパターン。ゴールと同じ行または列にいるときはそこからinsertを使えばたどり着くことができるので、それを実装して提出するもWA。
細かい条件を場合分けして詰めるのは良くなさそうなのでスマートな解法を考える。マス目を2倍して、insert操作を最初から全行全列に対して使用しておくことにする。このとき、奇数番目の行,列からは2マス先に移動できる、というふうにすると良い感じに軽い実装で解くことができる。この時点で3位に。(2時21分)

CPCTF jail(Lv.3/Shell)

C,P,T,Fおよび記号のみを使ってかつ指定の文字数以内の適切なコマンドを実行し、flag.txtを読む問題。空白も使えないのがポイントで、cat flag/flag.txtを実行するためにc?t f????f????t?tなどを実行したいのだがそれができないということで、*を使ってうまく当たるのを目指すことになる。この問題はAIで解くのが少し難しかった。なぜならAIは文字数の制約や使える文字種の制約を厳密に守ってくれないことがあるからだ。そこもいい感じに調整しつつ、いろんなコマンドを試していたら最終的に*だけでなく$や<も使ってなんとかフラグを得ることが出来た。かなり時間がかかった。(3時17分)

Hello LaTeX3!!!(Lv.3/Misc)

TeXファイルが与えられ、欠けている命令を補完する問題。AIにぶん投げるとすぐに答えが返ってきて、さすがにそんなすぐわかるわけないだろうと思いながら半信半疑で投げると通る。この時点で2位に。(3時29分)
この辺でそろそろ寝るべきかなあと思い始めるが、全く眠くなく疲れもあまり感じなかったので続行することに。

Anomaly 2(Lv.3/Crypto)

ふたたびRSA暗号を解く。AIに復元コードを書いてもらうとフラグを得ることができる。というかそれどころか復元結果をダイレクトに出してくれた。(3時39分)
(この辺しばらくAI頼りでおもんなくてすみません...)

campaign(Lv.3/Pwn)

Cのコードが与えられる。AIによると入力受け取りでまずいことをしていて、うまいことをやるとどうにかなるらしい。書式文字列攻撃という脆弱性があって、各変数のアドレスなどを特定したあとに入力に特定の文字列を送ることでメモリが書き換えられる、らしい。Pythonでpwntoolsを使って攻撃するコードを書いてもらい、それを実行するとフラグが得られる。(4時28分)

Tar me(Lv.3/Web)

問題名にあるように、tar形式のバイナリを送信したらそれが展開されるらしい。そこにコードを仕込むことでサーバー上で任意のコマンドを実行できる。何度かAIとやり取りしていると突然AIがフラグを示してきて、それを提出すると通る。(4時44分)

Template Playground(Lv.4/Web)

AIがかなりweb分野に強いような気がしたのでLv.4に手を出してみることにする。とはいえこの辺からAIのRate Limitに到達し始め、ログがあまり残っていない...
fetchを書き込めるので、webhookを作ってそこにフラグが送信されるように頑張ると、できる。(5時22分)
外が明るくなってきた...

Bitwise Scrumble(Lv.4/Crypto)

なんと全くログが残っていない...最終的にはAIが復元コードを書いてくれてフラグを得ることが出来たはず。(5時42分)

credentials(Lv.4/Forensics)

gitに関する問題。面白かった。gitを使って削除してもログから完全に削除はされておらず、適切なコマンドを実行するとファイルを閲覧することができる。(5時47分)
さすがにこの辺りで寝ることを決意する。寝る前に、PPCの残っている分野について軽く考察を整理しておくことにした。

Night View(Lv.3/OSINT)

chal_55e58f3b2fb358e0733204a6acc7545150a321a5133281f89250094f28be5e82
超面白かった。ヒントによると、写真に写っているのはレインボーブリッジらしい。レインボーブリッジが左奥から右前に向かって移動しているので、Google Mapを参照しつつこの写真が撮られた位置はレインボーブリッジの北西または南東であることがわかる。南東には建物があまりないので北西なきがする。少しだけ見えている道路の向きから大まかな位置がわかり、Google Earthで周りの建物を眺めてみると、写真の真ん中下あたりにある文字が書かれた建物、真ん中右あたりにあるちょっとかけた正方形みたいな建物が見つかるので、あとはこの画角になりそうな建物かつ高さのある建物を探すと特定できる。
Way IDの取得にはやや苦労した。最初はNominatimを使おうとしていたが、うまくいかず、最終的にはhttps://overpass-api.de/api/interpreter を使った。(11時16分)

Out of World(Lv.3/Reversing)

とりあえずfileとstringsを実行し、AIに投げるとフラグが返ってくる。(11時25分)

digest(Lv.3/Forensics)

ログも記憶もない...おそらくAIに攻撃コードを書かせたと思う。(12時12分)

viGor(Lv.4/Reversing)

与えられたものはGoのバイナリらしい。AIと相談するとGoReSymというものを提案されるのでインストールし、grepするとmain.flagCheckerのアドレスが分かる。いちいちAIが「3文字のパスワードの候補はこれ!」とか「vから始まるかも!」みたいなメタ読みをしてくるがそれを無視しつつ、一旦gdbを動かしてみる。disasを実行すると、AIが突然「3文字の絵文字などを試してみてください」と言ってくる。絵文字を入力してみると、確かにそれっぽい文字列が返ってくるが、まだフラグではない。いくつかの絵文字を試してそれをAIに教えると、最終的に「(printf "\xf0\x9f\xa5\xb0\xf0\x9f\x98\xa1\xf0\x9f\x98\xad"; echo) | ./viGor」でフラグが得られる。(12時19分)
ちなみに妙にdigestからviGorを解くまでの時間が早いように見えるが、これは問題を何問か並列して解いているからだと思う。

Damaged Report(Lv.4/Misc)

TeXファイルとdocker imageが与えられるので一旦開いてみる。AIに聞きつつDockerを起動し、.texファイルを作成して実行してみると、なぜか知らないがコマンドを打ち込める状態になる。texなのに何かが実行されていることに驚きつつ色々試してみるとフラグを得ることが出来た。(12時38分)

CPCTF Market(Lv.4/Web)

面白かった。botが自分のページを開くのでそこにコードを用意しておき、それを実行させるということができる。mXSSというらしい。AIに手順を聞きつつ言う通りにすると最終的にフラグが得られる。(13時5分)

coding agent(Lv.4/Pwn)

Rate Limitに到達しておりほとんどログが残っていない...(13時29分)
この辺からだんだん疲れが出てくる。というかAIがひたすら嘘をついてくるのがかなり精神に来る。「これなら完璧に動きます」「これが最終形です」「進捗100%です」「これが落ちることは絶対にありません」「完璧に原因がわかりました」などなど...パーソナライズによって無効化できていたものもあったが、無料版が終わるとまたそういう言葉遣いをし始めてかなりきつかった。

JankenMaster(Lv.3/Crypto)

超きつかった。AIに聞いてみると、まず全探索を提案される(!?)よい乱数シードを引けば行けます!みたいノリでひたすら全探索をするコードを出してくる。当然それで解けるわけもなく、改善してね~といろいろ指示してみるがなかなかうまくいかない。前述のとおり「これなら完璧に動き、フラグを確実に獲得することができます」などと言われ続けて2~3時間が経過していた。
そこでふと自分でコードを読んでみて、seedが0になるように入力を入れたらあっさり解くことができた。なんだったんだこの時間は...と思った。(16時52分)

RangeSum RangeUpdate RangeSqrt(Lv.4/PPC)

JankenMasterを通した後、PPC分野の問題をいくつか考察していた。SegmentTreebeats!を使えばよさそうだが、よくわからないので(青コーダーさん!?)遅延セグ木で解く方向でデータの持たせ方をいろいろ考えてみる。sqrtをとった値ごとに数と総和を管理していくことを考えたがこれだと各ノードが持つデータが10^3くらいになってしまい当然TLE。sqrtをとった時の総和の変化は5回で止まることを利用し、「i回sqrt操作が実行されたときに総和がいくつ変化するか」を管理しておけば各ノードが持つデータの量を大幅に削減することができる。なんか地味にTLEしたので定数倍を改善してAC。sqrtの誤差が怖かった。sqrtlを使ったが、これで誤差が出ないかを知らなかったので心配だった。(20時22分)
この時点で、あと1問通せば1位になれるという状態だったので粘ってみることにした。

Sum of Prod of Root(Lv.3/PPC)

さて、2乗までをどう処理するか考え、とりあえず紙に書いて考察してみる。するとそれっぽい数列が現れるのでOEISに突っ込むと、4乗和と2乗和に分解できることがわかる。あとはそれぞれの和の公式を使ってあとはいい感じにmodintで実装すると解くことができる。(21時19分)
この時点で1位となったが、ryokuくんがPPCを解かずに潜伏していることから全く油断できない状態だった。かなり疲れていたので夜ご飯を食べてシャワーを浴び、寝ようと思ったが、なんと3位になっていた。ryokuくんがPPCを出し始め、nopoさんも何問かを通したことで抜かされていた。とはいえほとんど僅差だったので1位を取り返しに行くことにした。

QRRRRRRRRRR(Lv.3/Misc)

破損したQRコードが与えられるのでそれを復元する問題。このタイミングより前に手を付けていた。strong-QR-decoderとQRazyBoxがヒントに書いてあるのでそれぞれインストールしてみる。strong-QR-decoderはPython2が要求されていて、すぐに用意できなかったのでQRazyBoxを使うことに。Brute-force Format Into Patternを使った後にExtract QR Informationをしてみると、うまく読み込めない。その旨をAIに伝えると誤り訂正レベルとマスクパターンを変えることを提案されるのでいろいろ試してみるが全くうまくいかない。
改めてこの問題に戻ってきた際は、何もせずに一旦Extract QR Informationしてそのデータを直接AIにぶん投げると、なんとフラグが返ってくる。さすがにびっくりした。いくつかの箇所が破損しているデータを勝手に復元したっぽい?(23時3分)
1位に復帰したもののまだまだ油断できないので解き進める。mod N Jankenがかなり通されていたのでそれを進めるが、しばらくAIとやり取りを続けても全く光明が見えず...諦めて寝ることにした。(0時35分)
6時に目が覚め、ランキングを見るとまだ1位を維持しているので二度寝した。10時に再び目が覚めたので起床した。この時点で残っている問題は7問あり、PPCが3問とForensicsが1問の4問が解けそうな感じがしていたのでそれを進めていくことにした。

Physical CTF(Lv.5/Web)

このあたりからはGitHub Copilot CLIを並列に並べ、残っている問題たちをそれぞれ割り当てて解いてもらっている間に自分がPPCの考察をする、という分業を行っていた。Physical CTFはCopilot CLIで解けた唯一の問題で、解いといてね~と言ったあとにしばらくした後に見たら「これをコピペしてブラウザで実行して」と指示されたのでファイルに保存するように伝え、実際に実行してみるとフラグが得られた。(13時14分)

Authorized Whale(Lv.5/Forensic)

8GBもある謎のファイルが与えられるのでそれを頑張って解析する問題。stringsやgrepを色々試しまくってひたすらその結果をAIに送信し続けていると、急にAIが興奮し始める。

とんでもないお宝を発見しました!これまでの苦労が報われる瞬間です。
あなたが貼り付けた Docker コンテナのメタデータ(JSON)の中に、フラグへの鍵が完全に露出しています。

またいつものように嘘をついているんじゃないかと思いつつ半信半疑で言われた手順を試してみるとフラグを得ることができた。(17時29分)
正直もう解けるCTFの問題は残っていないかなと思いつつ惰性でAIを動かしていたので、解くことができたときは本当にびっくりしたし嬉しかった。AIに投げれば解けるみたいな簡単な話でもなくて、これに関してはひたすらAIとのやり取りを続けて粘り勝ちした感じがして嬉しかった。
なおこのあたりでryoku君が1位に浮上しており、1位を取り返すのが非常に厳しそうな状況になってくる。逆転するためにはあと2問ほど通さないといけないが、PPCはここまでの空き時間(というかAIを動かしつつ並列で)ずっと考察していて解けていないので残り少ない時間で解くのはだいぶ絶望的だった。しかし解けると思っていなかったAuthorized WhaleがAIで解けたことから、Mod N Jankenも行けるのでは?と思って解きに行くことに。

Mod N janken(Lv.5/Crypto)

この問題は2日目から結構ずっと考えていた。AIが全探索を提案して来たりしたので、一旦自分でコードを読んで解法を考えていた。nextrand関数に線形性?があり、自分で用意する方の数列も同じ動かし方をすれば同じ系列の乱数が作れるとか、64bitの整数なので120個の数があればxor基底が存在しそうとか色々考えたが、結局自力では解く手段を思いつけなかった。CTFの問題の中ではこの問題を一番考えたと思う...
それはそうと、Authorized Whaleを解くことができたのはGeminiだったので、同じくGeminiに、Authorized Whaleを解いたときと同じような流れで聞いてみる。並行してdiary(Lv.5/Pwn)も同じタイミングで同じように解き進めていた。AIが提示したコマンド等を実行し、それが実行されている間にもう片方の問題へ。実行が終わったらその結果をAIに貼り付けて次のコマンドを実行、という動きを交互に繰り返していた。しばらくやり取りを続けていたらついに答えにたどり着いた。(18時18分)

コンテスト終了まで

この時点で残り1時間50分ほどしか残っていなかった。しかもこのタイミングでGeminiがRate Limitに到達し、取れる手段が完全になくなってしまった。一応PPCの考察はしてみるものの、かなり絶望的だなあと思っていた。
そのまま終了まで結局何も成果を得ることができなかった...(TwitterやtraQでryokuくん~~とつぶやく化け物になっていた)

コンテスト終了直後

新入生2位がほぼ確定していそうだったので、traPの人たちや友達に結果を報告して祝ってもらった。ryoku君は潜伏したりしなかったりを良い感じにやっていたらしい。まあとにかく48時間のうち結構な割合を頭を使って過ごしたので疲れていた。興奮が抜けなくてその日は夜寝るのが遅くなってしまった...

まとめ

 1位にはなれなかったもののかなり高い順位を取ることができ嬉しかったです。また問題の量が多く、面白い問題も多く、作問陣の方々はすごいなぁと思いました。僕も来年は作問側に回ると思うので頑張りたいです。

明日の投稿者は@n3さんです!お楽しみに!

blueberry1001 icon
この記事を書いた人
blueberry1001

26B 競プロ(AtCoder青)/ゲーム制作(Unity)/動画編集(AviUtl)など

この記事をシェア

このエントリーをはてなブックマークに追加
共有
記事一覧 タグ一覧 Google アナリティクスについて 特定商取引法に基づく表記