サイト内の現在位置

SECCON Beginners CTF 2026作問記 ~eBPFを読み解く~

NECセキュリティブログ

2026年7月31日

SECCON Beginners CTF(通称 CTF4b)は、CTF初心者・中級者を主な対象とした国内最大規模のセキュリティコンテストの一つです。毎年多くの学生や若手エンジニアが腕試しの場として参加しており、国内のセキュリティ人材育成において重要な役割を担っていますnew window[1]
筆者はSECCON BeginnersのWGメンバーとして活動しており、2026年6月13日(土)〜14日(日)に開催された「SECCON Beginners CTF 2026」において、Reversingカテゴリの問題「filter」を作問しました。また、入社2〜5年目の若手エンジニアたちも「wancosoba」チームとして本大会に参加しており、後半では彼らのコメントもあわせてご紹介します。

  • CTF(Capture The Flag)…フラグと呼ばれる答えを、手を動かしながら探し出し得点を競い合う競技

目次

SECCON Beginners CTF 2026 について

SECCON Beginners CTF 2026 は、Web、Pwnable、Reversing、Crypto、Misc など多岐にわたるカテゴリから問題が出題されるオンラインCTF大会です。人材育成とセキュリティ技術の底上げを目的としており、初心者が取り組みやすいだけでなく、問題を解く過程で新たな知識を自然に習得できる設計が重視されています。
今回の大会は2026年6月13日(土)〜14日(日)の2日間にわたって開催されました。CTFには国内外から多数のチームが参加し、筆者が作問した「filter」は Hard 難易度として出題されました。
NECは例年、SECCONのゴールドスポンサーとして協賛・出展しており、情報セキュリティ人材の発掘・育成、技術の実践の場の提供という目的に賛同しています。筆者自身も、WGメンバーとして問題作成の段階から携わり、日常業務で培った技術知識をCTFという形でコミュニティに還元することができました。

eBPF とは

筆者が作成した問題「filter」を解説するにあたり、まずはこの問題の核心となる技術であるeBPFについて説明します。eBPF(extended Berkeley Packet Filter)は、Linuxカーネル内でユーザーが定義したプログラムを安全に実行できる仕組みです。1990年代に開発されたBPF(Berkeley Packet Filter)を大幅に拡張したもので、Linux 3.18 以降のカーネルに標準搭載され、4.x系以降でさらに多くの機能が追加されました。
eBPFプログラムは、特定のカーネルイベントにフック(hook)されて実行されます。ユーザー空間からロードされたプログラムは、eBPF検証器による安全性チェックを経たうえで、JIT(Just-In-Time)コンパイラによってネイティブコードに変換されカーネル内で実行されますnew window[2]

図 1 eBPFのアーキテクチャ概要

主な活用領域を以下に示します。

  • ネットワーク処理の高速化・フィルタリング(tcコマンドによるトラフィック制御など)
  • システムコールの監視・制御(セキュリティ監視、LSMフック)
  • パフォーマンス分析・トレーシング
  • コンテナ・クラウドネイティブ環境のセキュリティ強化

セキュリティの観点では、eBPFはカーネルモジュールと異なり検証器による厳格な安全性チェックが行われるため、カーネルクラッシュのリスクが大幅に低減されています。

一方で、その強力な機能を悪用した事例も報告されています。例えば、eBPFを利用してカーネル空間でネットワークパケットを密かに監視・操作する手法がすでに実世界で確認されており、その中には特定のパケットシーケンス(いわゆる「ポートノッキング」)を待ち受けて密かに動作するものも確認されています。eBPFはユーザー空間の一般的な監視ツールからは見えにくい位置で動作するため、セキュリティエンジニアはその仕組みを深く理解しておく必要があります。こうした背景から、eBPFによるネットワークフィルタリングをCTFの題材とし、参加者が身をもってその動作原理を体験できる問題を作成しました。

問題「filter」の概要

配布ファイルは filter.sh という単一のシェルスクリプトです。問題文は「あるイベントが発生するとフラグが表示されるみたい。」という一文のみです。

図 2 実際の出題画面

スクリプト末尾には base64 エンコードされた zip アーカイブが埋め込まれており、展開すると filter という名のバイナリが得られます。スクリプトはこのバイナリを Linux の tc(traffic control) コマンドを用いてloopbackインターフェースのingress側にアタッチし、eBPFのネットワークフィルタとして動作させます。

#!/usr/bin/env bash
set -euo pipefail

if [[ $# -ne 1 ]]; then
  echo "Nothing to do."
  exit
fi

if [[ $EUID -ne 0 ]]; then
  echo "[!] This script needs root privilege."
  exit
fi

d() {
  local fz f
  fz=$(mktemp /dev/shm/fz.XXXXXX.ctf4b)
  f=$(mktemp /dev/shm/f.XXXXXX.ctf4b)

  tail -n 2 "$0" | head -n 1 | base64 -d > "$fz"
  unzip -q -o "$fz" -d /dev/shm >/dev/null 2>&1

  if [[ -f /dev/shm/filter ]]; then
    mv /dev/shm/filter "$f"
  fi

  rm -f "$fz"
  echo "$f"
}

case "$1" in
  start)
    tc qdisc replace dev lo clsact
    tc filter replace dev lo ingress bpf da obj "$(d)" section classifier
    ;;
  stop)
    tc qdisc del dev lo clsact 2>/dev/null || true
    rm -f /dev/shm/f.*.ctf4b
    ;;
esac
exit 0

# (以降にbase64エンコードされたzipアーカイブが埋め込まれています — 本記事では省略)

展開されるeBPFプログラムはパケットの内容を検査し、特定の条件を満たすパケットを受信した際にのみ bpf_printk 関数でカーネルのトレースバッファにフラグを出力する仕組みです。

なお、このeBPFプログラムのロードにはroot権限が必要であり、カーネルバージョンや設定(CONFIG_BPF_SYSCALL、tracefs のマウント等)への依存もあります。参加者自身が適切な動作環境を用意する必要がありますが、これは意図的な設計判断です。「解析環境そのものを整備する」こともCTFの醍醐味の一つであり、eBPFが動く環境を自前で構築する経験は、実務でのデバッグや検証にも直結します。

解法解説(Write-up)

Step 1:ELFバイナリの特定

filter.sh 末尾の base64 文字列を展開して得たバイナリに file コマンドを実行します。

$ tail -n 2 filter.sh | head -n 1 | base64 -d > fz.zip
$ unzip fz.zip
$ file filter
filter: ELF 64-bit LSB relocatable, no machine, version 1 (SYSV), ...

「no machine」と表示されますが、これは作問者が意図的にヘッダを書き換えたためであり、eBPFバイナリの本来の特徴ではありません。続いてreadelf -WS filter を実行すると classifier・license・.BTF といったeBPF特有のセクションが確認でき、これがeBPFバイナリであると推定できます。
あるいは、シェルスクリプトのtcコマンドの特徴からロードしているバイナリの種類がeBPFであることを読み取る方法もあります。さらに「loopbackのingressにアタッチされる」という情報から、ネットワークフィルタとして動作するプログラムであることも推察できます。

Step 2:Ghidraによる逆コンパイル

eBPFバイナリの解析方法はいくつかありますが、Ghidranew window[3]を用いると可読性の高い逆コンパイル結果を得られます。
Ghidraは米国家安全保障局(NSA)が開発したオープンソースの逆コンパイルフレームワークで、eBPF の ELF バイナリも解析対象として読み込むことができます。
eBPFバイナリを Ghidra にインポートし、Analyze を実行すると、C 言語に近い逆コンパイル結果が表示されます。以下はその結果をもとに整理した疑似コードです。

int p(struct __sk_buff *skb) {
  ...
  char flag[24];   /* flag string buffer */
  char payload[5]; /* payload buffer */
  byte tcph[20];   /* TCP header buffer */
  byte iph[20];    /* IP header buffer */
  
  __u32 index = 0;
  if ( /* (処理)TCP/IPv4プロトコルのパケットならば */ ) {
    st = bpf_map_lookup_elem((bpf_map *)0x0, &index);    // BPFマップから0番目のエントリを取得

    if (st != NULL) {
      dport  = /* (処理)TCPヘッダから宛先ポートを抽出 */;
      st_raw = *st;

      if ( tcph.syn && !tcph.ack && window == 54321 ) {
        /* (処理)マップのエントリにdport(宛先ポート)と使用済フラグをセット */
      }
      else {
        if ( /* (処理)エントリが未使用ならば */ )    return 0;
        if ( dport != /* (処理)エントリに格納されている宛先ポート番号 */ ) return 0;

        bpf_skb_load_bytes_relative(skb, thl_len + ihl_len, payload, 5, 1);  // パケットから5バイト分のペイロードを抽出
        if (extraout_var_01 < 0)  return 0;

        if (payload[0] != 'c') return 0;
        if (payload[1] != 't') return 0;
        if (payload[2] != 'f') return 0;
        if (payload[3] != '4') return 0;
        if (payload[4] != 'b') return 0;
        ...
        bpf_trace_printk((char *)0x0, 4, flag);  // flag文字列を表示
      }
    }
  }
  return 0;
}

逆コンパイル結果を読み解くと、2段階の条件を検査していることがわかります:
条件①:TCP ウィンドウサイズが 54321 の SYN パケットを受信する
条件②:同一ポートに対して、TCPペイロードの先頭5バイトが "ctf4b" であるパケットを送信する

両条件が満たされると、bpf_printk によってカーネルのトレースバッファにフラグが出力されます。

Step 3:トリガーパケット送信とフラグ確認

生のTCPパケットを組み立てられるパケット生成ライブラリを使って、条件を満たすパケットを送ります。

from scapy.all import IP, TCP, Raw, send

dport = 31337

pkt1 = IP(dst="127.0.0.1") / TCP(
    dport=dport,
    flags="S",
    window=54321,
    ...
)

pkt2 = IP(dst="127.0.0.1") / TCP(
    dport=dport,
    ...
) / Raw(b"ctf4b")

send(pkt1, verbose=False)
send(pkt2, verbose=False)
print("sent")

パケットを送ると、bpf_printkによりカーネルのトレースバッファにフラグが表示されます。

$ sudo cat /sys/kernel/tracing/trace_pipe
...
ctf4b{ebpf_m4g1c_kn0ck}

参加メンバーからのコメント

本大会には入社2〜5年目の若手エンジニアたちが「wancosoba」チームを結成して参加しました。チームは全体 166位(2,244点)を獲得し、各カテゴリにまたがる計19問を解きました。

図 3 チームの成績

チームメンバーからのコメントをご紹介します。

リーダー

私は主にPwnableとMiscの問題に挑戦しました。
本CTFでは生成AIの利用が認められていたため、問題を解く際にはChatGPTやClaude Codeなどの生成AIツールを積極的に活用し、問題の概要や解法を整理しながら取り組みました。
CTFでは知識不足のために手が止まってしまう場面も少なくありませんが、生成AIを活用することで、出題テーマや関連技術を効率よく学習しながら問題に取り組むことができます。

ここでは、シンプルではありますが、私が実践したCTFの進め方をご紹介します。
① まずは自分の力で問題と向き合い、どのような解法が考えられるかを整理しながら取り組みます。この段階で解ければ、生成AIを利用する必要はありません。
② 解法が思いつかない場合は、問題に添付されているファイルを生成AIに読み込ませ、問題の概要や重要な技術について解説してもらいます。そこで得た知識を基に、改めて自分で解法を考えます。
③ それでも解決できない場合は、問題を解くためのスクリプトを作成してもらうなど、より具体的な支援を生成AIに依頼します。これでフラグを取得できることもありますし、うまくいかない場合は生成AIと何度かやり取りを重ねながらスクリプトを改善し、フラグの取得を目指します。

このような流れで取り組むことで、CTFへの心理的なハードルが下がり、実践を通じて効率よく知識や技術を身に付けることができます。今後もさまざまなCTFに積極的に参加し、継続的な自己研鑽を通じてセキュリティスキルの向上に努めていきたいと思います。 (上野 隆治)

メンバー①

社内向けや常設CTFを除くと、2度目のCTF参加でした。今回の参加でまず感じたのは、机上の理論とその実践には大きな隔たりがあることでした。
普段はリスクアセスメントをはじめとした情報セキュリティ関連業務に従事しており、セキュリティに関する基礎知識や運用面の理解については一定の経験を積んできたつもりでしたが、実際には想像以上に難しく、問題文を読んでもすぐに方針が思い浮かばない場面が多くありました。
趣味でWebアプリケーション開発について学んでいることもあり、今回は主にWebカテゴリの問題に挑戦してみましたが、限られた情報の中から手掛かりを見つけ、それをどう活用するかに苦労した印象がありました。一方で、その試行錯誤を通じて得られた経験は非常に有意義であり、座学だけでは得られない学びでもありました。
今回の参加を通じて、自身の現在地と課題を客観的に把握できたことは大きな収穫だったと感じています。最近は常設CTFやTryHackMeのような演習環境に取り組んで研鑽を重ねているので、来年にはもっと強くなってリベンジしようと思います。 (石川 雄大)

メンバー②

今回のSECCON Beginners CTFでは、特定のジャンルに絞らず、各ジャンルの比較的易しい問題を中心に取り組みました。幅広い分野の問題に触れることで、それぞれのジャンルで求められる考え方や着眼点を学ぶことができました。
問題を解く中で、実際の脆弱性につながるポイントがどこにあるのかを考える力を養うことができました。また、システムやプログラムのどの部分に着目して調査を進めるべきかを意識することで、脆弱性の発見につながる視点や解析の進め方について理解を深めることができました。
さらに、CTFというと高度な知識や特別な解析ツールが必要という印象がありましたが、初心者向けの問題も多く用意されており、基本的な知識と試行錯誤を重ねながら取り組める内容であることが分かりました。中には特別な解析ツールを使用しなくても解答できる問題もあり、自分の学習レベルに合った形で参加できたと感じています。
今回の参加を通じて、脆弱性の見つけ方や問題解決のアプローチを実践的に学ぶことができ、自身のセキュリティ学習に非常に役立つ経験となりました。今後も継続してCTFに参加し、知識だけでなく分析力や技術力の向上につなげていきたいと考えています。 (田村 優都)

メンバー③

今回のCTFは、私にとって2度目の社外CTFへの参加でした。前回と同様に、自身のセキュリティ知識や技術力の不足を実感する場面が多くありました。
その一方で、大会中に調査を進めながらではあったものの、プライベートで学習していたReversingの知識や各種ツールの活用経験を生かし、いくつかの問題を解くことができました。これまで積み重ねてきた学習の成果を実践の場で発揮できたことで、自身の成長を感じられる良い機会にもなりました。
私は自分自身の学びを重視したいという考えから、AIの利用は必要最小限に留めて挑戦したため、問題に苦戦する場面も少なくありませんでした。しかし、AIの活用度合いを参加者自身で選択できる点は本CTFの特徴の一つであり、経験者だけでなく、未経験者や初心者にとっても、それぞれのレベルに応じた形で挑戦しやすい環境が提供されていたように感じます。
また、チームの各メンバーの進捗や獲得スコアが共有される中で、先輩方が次々と得点を積み重ねていく様子に大きな刺激を受けました。自分が時間をかけて取り組んでいた問題が既に解かれていたこともあり、その技術力の高さを改めて実感するとともに、学ぶべきことの多さを認識しました。
今回の経験を通じて、自身の課題と成長の両方を実感することができました。次回はより多くの問題でチームに貢献できるように日々技術力の向上に努めていきたいと思います。 (村田 彪賀)

メンバー④

今回の大会では、主にCryptoやReversingカテゴリの問題に取り組みました。
個人でのCTF参加経験はあるものの、チーム戦への参加は今回が2回目であり、チームメンバーとの効果的な連携という面ではまだ慣れない部分も少なくありませんでした。そのような状況の中でも計4問を解くことができたことは、チームに一定の貢献ができたと感じています。

スクリプトのコーディングにおいては知識の不足を痛感する場面が多く、この課題を補うために生成AIを積極的に活用しました。
例えばRSA暗号に関する問題では、与えられたパラメータ(n1、n2、e、c)を提示した上で「これらの値を用いたRSAの計算スクリプトを出力してほしい」と問い合わせ、生成されたコードを基に解法を導きました。このように、問題の性質を把握した上で自身に不足する実装知識を補完する手段として生成AIを実践的に活用し、問題解決に役立てることができました。
今回の参加を通じて、自身の技術的課題を改めて認識する機会を得ました。今後もCTFへ積極的に参加し、継続的な技術研鑽に努めてまいります。 (國方 稜太)

メンバー⑤

今回の参加では自身の学習を目的とし、若干Reversingに比重を置きつつ、基本的には各ジャンルの基礎的な問題に取り組みました。結果としてチームの得点に貢献できたのは難易度の低い1問に留まりましたが、問題解決に有用なツールや技術的な知識や考え方など多くのことを学ぶ良い機会でした。

生成AIの活用にあたっては、解法そのものを生成AIに委ねるのではなく、問題の概要を共有した上で「自身が提示した解法方針に対する評価やヒントの提示を求める」という形で利用しました。実際に質問を繰り返すことで正解にたどり着けた問題も多く、学習目的としても、実際の問題解決の手段としても、双方で生成AIのパワフルさを感じました。

チームとして参加し、他メンバーの解答速度や得点の獲得を目の当たりにすることで、今の自分にはまだまだ勉強が必要なことを改めて実感しました。今後もオンラインのCTFに随時参加することで、力を伸ばしていければと思います。 (滝浦 一樹)

まとめ

本記事では、SECCON Beginners CTF 2026 で作問した「filter」について、eBPFの概要・設計思想・解法をご紹介しました。また若手エンジニアたちが「wancosoba」チームとして大会に挑んだ様子もお伝えしました。

作問側として最も難しかったのは「Hard 問題らしい難しさを保ちつつ、解けた瞬間に明確な学びがある設計にする」というバランスです。eBPFはカーネル空間・ネットワーク・バイナリ解析が交差する技術領域であり、CTFという枠組みで体験できる内容として非常に適していると感じています。

「作問する」「挑戦する」というそれぞれの立場でCTFに向き合うことが、チーム全体のセキュリティスキル底上げにつながると改めて実感しました。本記事を通じてeBPFやCTFへの興味を持っていただけたなら幸いです。

参考文献

執筆者紹介

祐野 雅範(ゆうの まさのり)
担当領域:リスクハンティング
専門分野:サイバーインテリジェンス

上野 隆治(うえの りゅうじ)
担当領域:マネージドセキュリティサービス
専門分野:PDNS(Protective DNS)、脆弱性診断

石川 雄大(いしかわ ゆうだい)
担当領域:リスクハンティング

國方 稜太(くにかた りょうた)
担当領域:サイバーセキュリティ人材育成

田村 優都(たむら ゆうと)
担当領域:マネージドセキュリティサービス

村田 彪賀(むらたひょうが)
担当領域:セキュリティコンサルティング

滝浦 一樹(たきうら かずき)
担当領域:セキュリティシステム提案

アクセスランキング