概要
- DEF CON CTF Qualifier 2026 で、Team Enu は 73 位の成績を収めました。
- 本記事では、今年の変更点や、いくつかの問題を解説します。
はじめに
こんにちは、研究開発部の末廣です。 CTF イベント DEF CON CTF Qualifier 20261 に、 NTT グループ有志と、募集2に応募いただいた学生の合同チーム Team Enu3 として参加しました。今年のコンテスト開催期間は 2026/05/23 から 2026/05/25 の 48 時間でした。
結果、全 686 チーム中 73 位の成績を収めました。

完全自動化チームの禁止と AI 利用ポリシーの自己宣告
今年から、DEF CON CTF の運営チームが交代しました4。昨年までの 4 年間は Nautilus Institute というチームが運営でした。今年は Benevolent Bureau of Birds というチームが運営です。
今年の DEF CON CTF Qualifier では参加ルールにも変化がありました。AI/LLM/Agent Policy について Fully or primarily autonomous teams are not allowed として、自動化されたチームは禁止されています。一方で人間が主導する限りでは各種ツールを利用できます。
各種チームは AI 利用ポリシーを自己申告しました。次の 3 種類のいずれかから選択できます。
No AIな、AI を全く使わないポリシーLow AIな、AI をコーディング補助にのみ使い、問題を解くためには使わないポリシーHuman-led AIな、人間が主導して問題を解くために AI を使うポリシー
なお、チームが選択する AI 利用ポリシーは、順位付け等には一切影響しません。
最終的な順位表5を見ると、上位 100 チームうちの約 9 割が Human-led AI を自己申告しています。Low AI を自己申告しているチームの最高順位は 46 位、No AI を自己申告しているチームの最高順位は 71 位です。
問題の出題形式
運営チームが交代した影響か、出題される問題の形式にも変化がありました。今年に出題された問題形式の詳細を記述します。
- Jeopardy 形式
- オンライン開催でよくある形式です。
- コンテスト開始直後では、問題文にフラグそのものが記述されている Sanity Check 問題を含めて、5 問出題されました。
- その後、徐々に出題問題が増えていき、最終的に Sanity Check 問題を含めて 19 問出題されました。そのうち 2 問は revenge 問題というもので、作問者が想定していない方法で解けてしまった問題を、作問者の想定解法でのみ解けるように変更されたものです。
- 今回の CTF では、各種問題に Binary Exploitation、Reverse Enginnering、Web Exploitation、Cryptography、Miscellaneous ジャンルが 1 つ以上設定されていました。
- King of the Hill 形式 (略称 KotH 形式)
- 少なくともここ何年かでは、DEF CON CTF Qualifier では出題されていなかった形式です。DEF CON CTF Finals では出題されています。
- 今年は Qualifier 時点から出題がありました。コンテスト開始直後から 1 問出題され、最終的には合計 3 問出題されました。
- KotH 形式の問題では、ティックと呼ばれる一定時間ごとに、チーム間で何らかの指標を競います。ティック単位で、上位のチームほど高得点を得られます。
- そのため全体として、問題出題当初から継続して上位であるほど有利になります。反対に、終盤になって圧倒的首位に立てたとしても、残りティックが少ない場合は全体として恩恵が少ないです。
- LiveCTF 形式
- ジャンルそのものとしては、2023 年から出題されています。しかし今年は、出題方針等が大きく異なっていました。
- 昨年までは、独立した問題が 4 時間ごとに合計 6 問出題されて、他チームよりも早く解くほど高得点を得られる形式でした。詳細は、2023 年の LiveCTF 形式紹介記事6や、2024 年の Writeup 記事7、2025 年の Writeup 記事8をご参照ください。
- 今年は、前述の KotH 形式のように、チーム間で争う形式が合計 24 時間続く形式でした。4 時間ごとの合計 6 フェーズに区切られており、フェーズごとにルールの変更等はあるものの、全体的な問題としては 1 つです。詳細は後述します。
今年は、CTF 開始から約 25 時間後に全問題が出題されました。それ以降は、Jeopardy 形式の 19 問、LiveCTF 形式の 1 問、King of the Hill 形式の 3 問の合計 23 問に取り組む必要がありました。

出題された問題のうち、 LiveCTF 形式の問題は GitHub で公開されています9。それ以外の Jeopardy 形式や KotH 形式の問題については、本記事を執筆している 2026/06/03 時点では未公開のようです。
以降、いくつかの問題を解説します。
[KotH, Miscellaneous] shelldiet
本問題は、次の処理を行う amd64 形式のシェルコードを作成し、提出する問題です。
- 配布ファイルから分かる点として、
/flagファイルの内容を表示すること - 配布ファイルからでは分からず問題サーバーへ接続して初めて分かる点として、シェルコードの実行開始から
/flagファイルの読み込みまでに 1 秒程度は遅らせること- ローカル環境では正常に
/flagファイルを表示できるシェルコードでも、遅延を入れていない場合は問題サーバー相手だとNOT YET SCORED: your shellcode was too fast, try reading /flag again shortly表示となる状況でした。
- ローカル環境では正常に
本問題は KotH ジャンルです。ティックごとの順位付けとして、次の基準が使われました。
- シェルコードの、バイト単位での総和を計算します。本記事ではその総和を「コスト」と記述します。
- 例えば
syscall命令を使う場合、当該命令の機械語は0f 05です。つまりsyscall命令を 1 回使うたびに、0x0f + 0x05である20のコストがかかりまます。
- 例えば
- 前述の処理を行うシェルコードを提出すると、そのシェルコードのコストが計算されます。チームごとに、もっとも小さなコストが登録されます。
- ティックごとに、その時点での全チームでコストを比較して順位付けをし、高順位のチームほど高い点数を得られます。
- なお競技時間中では、各チームの順位のみが分かりました。各チームのシェルコードのコストは分からない状況でした。
なおシェルコードが実行される時点では、rax レジスタは 0x1337000 に、rbx、rcx、rdx、rsi、rdi、rbp、r8 ~ r15 レジスタは 0 に設定されます。また、シェルコードは読み書き実行可能なメモリへ書き込まれて実行されます。
コンテスト終了後に判明した上位チームの方針
前述のように、コンテスト時間中では他チームのシェルコードのコストは分かりませんでした。そのため、コンテスト中では自チームが下位 1/4 付近にある状態でも、上位がどのくらい短くしているのかも分からない状況でした。
本コンテストでも、コンテスト終了後には運営や参加者が感想等を言い合う場があります。私がそこで目にしたのは、上位 30 チームのコストがわずか 23 以下という状況でした。どうやら、rax レジスタが 0x1337000 を指すことと、add byte ptr [rax], al のコストが 0 であることを利用して、シェルコードで自己書き換えを行いつつ目的の処理を実現したようです。
自チームでの取り組み
前述の通り、上位チームではシェルコードのコストを 23 など、極めて小さい値に抑えています。そのコストには全く近づけていないものの、自チームでの取り組みを記述します。何かの参考になれば幸いです。
そもそも誤解していた点として、前述の /flag ファイル内容の表示等を、シェルコード中ですべて行う必要があると思っていました。しかしコンテスト後に判明した上位チームの取り組みからすると、提出するシェルコードでは stager としての処理やシェル起動のみを行い、追加の入力で /flag 表示等を行うように感じます。今となっては、より幅広い視点で取り組めていればと悔やむばかりです。
さて自チームでは pwntools ライブラリに含まれる、 cat 相当の処理を行うシェルコード10から出発しました。
/* push b'/flag\x00' */ mov rax, 0x101010101010101 push rax mov rax, 0x101010101010101 ^ 0x67616c662f xor [rsp], rax /* call open('rsp', 'O_RDONLY', 'rdx') */ push SYS_open /* 2 */ pop rax mov rdi, rsp xor esi, esi /* O_RDONLY */ syscall /* call sendfile(1, 'rax', 0, 0x7fffffff) */ mov r10d, 0x7fffffff mov rsi, rax push SYS_sendfile /* 0x28 */ pop rax push 1 pop rdi cdq /* rdx=0 */ syscall
前述のシェルコードだけで、コストは 4330 にもなります。次のようにシェルコードを変更することで、コストを減少させました。
sendfileの第 4 引数は大きい値であればいいので、固定値を使わないよう変更しました。- 各命令について、同等の効果を持つ、よりコストの小さい命令に変更しました。
/flagという文字列そのもののコストが大きいので、実行時に構築するよう変更しました。- 特にいくつかの
add系命令はコストが極めて小さいため、うまく利用するよう心掛けました。add [rcx], alのコストは1です。add cl, [rdx]のコストは12です。add al, Nのコストは4+Nです。
- 特にいくつかの
また、前述の通り、シェルコードが実行されてから /flag を表示するまでに待ち時間を設ける必要があります。その方法としてビジーループを使う方法と nanosleep システムコールを使う方法の 2 つの方法を思いつきました。実装してコストを比較すると nanosleep 命令を使うほうがコストが少なくなったので、そちらを採用しました。
最終的に、コストが 2694 を達成したシェルコードを次に示します。
// 実行が早すぎると「NOT YET SCORED: your shellcode was too fast, try reading /flag again shortly」となり、フラグを得られません。意図的に遅くする必要があります。 // 1秒Sleepでもフラグを得られます。 // ビジーループよりもコストが小さいnanosleepを使用します。 // ナノ秒位置はほぼなんでもいいので、後に備えてraxを変更しておきます。 mov eax, SYS_nanosleep /* 0x23 */ push rax /* struct timespec::tv_nsec。[0, 999'999'999]の必要があるようで、シェルコードのアドレスを指すままのraxは使えないようです。 */ push 1 /* struct timespec::tv_sec。あとで [rdx] 領域としても使い回します。 */ // `push rsp; pop rdi` (545f) は `lea rdi, [rsp]` (488d3c24) や `mov rdi, rsp` (4889e7) よりもコストが小さいです。 push rsp pop rdi // rsiはシェルコード呼び出し時点で0です。nullptrで大丈夫です。 syscall // [rdx]を1にします。上のpush 1箇所を使いまわします。 // `push rsp` (54) は `push rdi` よりもコストが小さいです。 push rsp pop rdx // 上のpush nsec箇所の領域に "/flag" を構築します。 push rsp pop rcx // 偶然、下位1バイトが 0xF8 にならないように祈ります。 add cl, [rdx] add cl, [rdx] add cl, [rdx] add cl, [rdx] add cl, [rdx] add cl, [rdx] add cl, [rdx] add cl, [rdx] // nanosleepの戻り値により、raxは現状0です。SYS_open等に備えて今のうちに2にします。 add al, 0x02 // rbxはシェルコード呼び出し前に0に設定されます。blを問題なく使えます。 add bl, [rdx] /* bl := 1 */ // '/', 0x2F , ここには、nsecで使用した0x23が残っています。 add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al // 'f', 0x66 add cl, [rdx] add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al // 'l', 0x6C add cl, [rdx] add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al // 'a', 0x61 add cl, [rdx] add [rcx], bl add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al // 'g', 0x67 add cl, [rdx] add [rcx], bl add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al add [rcx], al // call open("/flag", 0) // `mov eax, 2` (b802000000) は `push 2; pop rax` (6a0258) よりもコストが小さいです。 // eaxは既に2です。 SYS_openです。 // rdiを、調整した箇所に合わせ直します。 // 値を捨てる場所は、reg/opcodeが小さいrcxにします。raxの値を保持する必要があるため、`pop rax`は使えません。 // `pop rcx; push rsp; pop rdi` (59545f) は `lea rdi, [rcx]` (488d39) や `xchg rdi, rcx` (4887cf) よりもコストが小さいです。 pop rcx push rsp pop rdi // rsiは継続して0です。O_RDONLYであり、このまま使えます。 syscall // call sendfile(1, fd, 0, 大きな値) // 大きな値として固定値を使おうとすると `mov r10d, 0x01000000`(41ba00000001) や `push 0x01000000; pop r10` (6800000001; 415a)などでコストが大きいです。 // 変わりに、スタックのアドレスを指している、適当に大きな値を持つレジスタの値を使用します。 // `lea r10,[rcx]` (4c8d11)のコストが小さいです。 lea r10, [rcx] // `xchg esi, eax`(96) は `mov esi, eax`(89c6)よりもコストが小さいです。 // なおここで得られるfdは3であるため固定値を使う方法もありますが、`mov edi, 3`(be03000000)などはコストが大きいです。 xchg esi, eax // ここまででesiは0であり、xchg後のeaxは0です。 // `mov al, 0x28` (b028) は `mov eax, 0x28` (b828000000) や `push 0x28; pop rax` (6a2858) よりもコストが小さいです。 mov al, SYS_sendfile /* 0x28 */ // `mov edi, 1` (bf01000000) は `push 1; pop rdi` (6a015f) よりもコストが小さいです。 mov edi, 1 // rdxを0にします。`cdq`(99)は`mov edx, 0` (ba00000000)よりもコストが小さいです。 cdq syscall // exit等は不要です
シェルコードとは無関係の場所で詰まったので、併せて紹介します。本問題の問題サーバーへは、ncat --ssl shelldiet.ctfwithbirds.com 1337 のように TLS で接続する必要があります。シェルコードの提出を自動化するために、pwntools ライブラリの remote 関数11にssl=True を指定して使用しました。また、シェルコードを与えた後に、入力がもうないことを示すために shutdown メソッド12 を使用しました。
どういうわけかそのような状況では、shutdown メソッドを呼び出した後の受信内容が文字化けするような挙動になりました。つまり、正しいシェルコードを送信してフラグを得られるはずの状況であっても、肝心のフラグ内容を得られない状況でした。
その挙動をチームメイトに相談すると、「shutdown メソッドを呼び出した後は、受信するデータが TLS 復号される前の、生の TCP データになっているかもしれない」との助言がありました。試しに shutdown メソッドを呼び出すのではなく、適当な時間だけ sleep することで入力を区切るようにしてみると、正常にフラグ内容等を受信できました。ライブラリの挙動を理解することの重要性が身に沁みました。
本問題全体としては、最小コストの順位表ではコスト 2694 で 113/147 位になりました。また、本問題の KotH としての累計スコアは 76/147 位になりました。
LiveCTF
前述のように、今年の LiveCTF は昨年までとは大きく異なるものでした。今年のルール等を簡単に説明すると、次のようになります。
- コンテスト開始前から、ルール箇所で Live CTF の時間帯が明記されます。
- コンテスト開始から 8 時間経過時点で、LiveCTF の情報や、フェーズ 1 用の配布ファイルが提供されます。
- コンテスト開始から 12 時間経過時点で、LiveCTF のフェーズ 1 が始まります。
- 以降は 4 時間ごとにフェーズが代わります。フェーズごとに配布ファイルが変化します。
- 最終的に、コンテスト開始から 36 時間経過時点のフェーズ 6 の終了とともに、LiveCTF 全体としても終了します。
- 各チームは、配布ファイルや公開ドキュメントを通じて、bot のプログラムを作成して提出します。bot プログラムは、独自のスタックマシン型の機械語で実装します。詳細は割愛します。
- 3 分ごとに bot のゲームが行われます。
- 各ゲームでは、各チームから提出された bot と、 NPC 用 bot を合わせた 1000 個の bot が 1 つのマップに集まります。
- 各 bot がプログラム通りに移動や攻撃を行います。他の bot を倒すことで、得点を得られます。
- フェーズ 5 以降では、マップの一部の領域が「King of the Hill」に設定されます。その領域に位置取れていると、高い得点を得られます。
- フェーズごとに、そのフェーズでの各チームの合計得点で順位付けをし、上位から順に高い点数を得られます。
昨年同様に今年も、LiveCTF の Live Stream が行われました15。その中でいくつかのゲームの内容が紹介されているので、そちらをご参照いただくとルールを理解しやすいと思います。
- フェーズ 1 の振り返り配信の 7:57 からをご覧いただくと、ゲームの見た目や概念等が分かりやすいと思います。
- フェーズ 6 の振り返り配信 9:45 からをご覧いただくと、「King of the Hill」の概念等が分かりやすいと思います。
自チームでの取り組み
ドキュメントを早期から読んでいましたが、他の問題等の兼ね合いもあり、LiveCTF で作成した bot プログラムは簡単なものにとどまりました。具体的には「斜めに少しずつ移動しながら反対方向を攻撃」や、「初期位置から 4 方向に回転しながら攻撃」のようなものです。そのような簡易的なものでも、ゲーム次第ではそれなりの点数を得られました。
しかし、マップを大きく移動するアルゴリズムを実装できていなかったこともあり、フェーズ 5 で「King of the Hill」ルールが追加されてからは、上位から大きく引き離されてしまいました。
全体として、全 6 フェーズある自チームの順位は次の結果になりました。
- フェーズ 1: 39/63 位
- フェーズ 2: 31/75 位
- フェーズ 3: 32/85 位
- フェーズ 4: 64/89 位
- フェーズ 5: 72/90 位
- フェーズ 6: 70/92 位
おわりに
今回紹介した問題以外にも、いくつかの問題に筆者は取り組んでいました。取り組みがいのある問題ばかりでしたが難易度が高く、残念ながら得点を得られませんでした。
非常に難しい問題が出題される中でも、チームメンバーが懸命に励んでくれました!取り組まれた皆様お疲れ様でした!
- https://ctftime.org/event/3205/↩
- https://x.com/NFLaboratories/status/2049035890513555617↩
- https://team-enu.github.io/↩
- https://www.defcon.org/html/links/dc-news.html#ctforg↩
- https://bbbctf.com/scoreboard↩
- https://blog.nflabs.jp/entry/2023/06/01/132700↩
- https://blog.nflabs.jp/entry/2024/05/10/160000↩
- https://blog.nflabs.jp/entry/2025/05/16/120000↩
- https://github.com/Live-CTF/LiveCTF-DEFCON34↩
- https://docs.pwntools.com/en/stable/shellcraft/amd64.html#pwnlib.shellcraft.amd64.linux.cat↩
- https://docs.pwntools.com/en/stable/tubes/sockets.html#pwnlib.tubes.remote.remote↩
- https://docs.pwntools.com/en/stable/tubes.html#pwnlib.tubes.tube.tube.shutdown↩
- https://github.com/Live-CTF/LiveCTF-DEFCON34/blob/bfde75fec2d0e6cccd252986cc724c824c39d3e8/bot-game/frontend/src/views/DocsView.vue↩
- https://github.com/Live-CTF/LiveCTF-DEFCON34/blob/bfde75fec2d0e6cccd252986cc724c824c39d3e8/bot-game/runner/docs/stream%20reference.md↩
- https://www.youtube.com/playlist?list=PLmnqtYqOCF7_fL9tYTomLnl_PeVmv4x_-↩