エクスプロイト生成のための強化学習
数ヶ月前、CTFの決勝戦で、私たちのチームはpwnバイナリで3時間立ち往生した。スタックカナリア、NX、部分PIE、目立たない関数におけるちょうど72バイトのオーバーフロー。あらゆることを試した。何も効かなかった。午前03:14、私たちの一人がほぼ冗談で、AFL風のbitflipsで入力を変異させて何も考えずに再送する5行のスクリプトを起動した。20分後、そのスクリプトは純粋な統計的幸運でflagを獲得した。
その夜、私たちはある問いを胸に帰路についた:もし、愚かなプロセスの代わりに、失敗するたびに学習するプロセスがあったらどうだろうか?
なぜRLが制御フローハイジャックに適合するのか
エクスプロイトの探索空間は過酷だが一様ではない。ほとんどの入力は同じ振る舞いを生み出し、狭い領域だけが望む機械状態へと導く:命令レジスタが制御下のメモリを指す状態。その部分空間を特徴づけるのは情報の勾配である:RIPの破壊に近づく入力は観測可能な副作用を示す — 部分的に上書きされたレジスタ、入力バイトと一致するスタック値、無効だが制御されたバイトに近いアドレスへのジャンプ。
それは、適切に設計された報酬関数が捉えられるものに他ならない。
最先端
AvgerinosとBrumleyは2011年に、シンボリック実行に基づく最初のエンドツーエンドシステムAEG(NDSS 2011)を発表した。Mayhem(Cha et al., S&P 2012)が解析をスケールさせた。両者とも準決定論的だが、シンボリック実行の古典的な代償を払う:パス爆発と詰まったSMTソルバ。
BöttingerはDQNを用いたDeep Reinforcement Fuzzing(2018)でファジングをMDPとして定式化した。PwnGPT(Shao et al., ACL 2025)はo1-previewによりエクスプロイト生成率を26.3%から57.9%へ引き上げたと報告している。そしてPPO(Schulman et al., 2017)+RLHF(Ouyang et al., 2022)は、バイナリ環境上でエージェントを訓練することが真剣な議論となるほど成熟した。
私たちのセットアップ
ptrace上のGymスタイルの環境とeBPFサイドカー。観測は次を含む密なテンソルである:
- 16個の汎用レジスタ(x86_64)の状態、正規化済み。
- RSP周辺のスタックウィンドウ(256バイト)を生バイトとして。
- 制御フラグ:
NX、canary_present、RIP_corrupted、register_controlled[]。 - eBPF(各ベーシックブロック上のuprobe)から取得した最後のカバレッジトレースのハッシュ。
行動空間は入力バイト上で離散的である。報酬は新規カバレッジ(正値、逓減)、エクスプロイト可能性ヒューリスティック、長さペナルティを組み合わせる。
import gym
from gwaihir.tools.rl_env import BinaryPwnEnv
class CTFPwnEnv(BinaryPwnEnv):
def step(self, action):
mutated = self.apply_action(self.current_input, action)
trace = self.run_with_ebpf(mutated)
new_bbs = len(trace.basic_blocks - self.coverage_seen)
cov_reward = np.log1p(new_bbs) * 0.3
self.coverage_seen |= trace.basic_blocks
expl = 0.0
if trace.rip_controlled_bytes > 0:
expl += 5.0 + 0.1 * trace.rip_controlled_bytes
if trace.canary_leaked: expl += 2.5
if trace.tainted_syscall_args: expl += 1.5
reward = cov_reward + expl - 0.001 * len(mutated)
done = trace.rip_fully_controlled or self.steps > 4096
return self.observe(trace), reward, done, {"trace": trace}
PPO対DQN
私たちはDQNから始めた。行動空間が大きくなるとすぐに、DQNは脆くなった:エクスプロイト可能性報酬が巨大で孤立した値をトリガーするたびに、ターゲットネットワークが発散した。PPOはポリシー比のクリッピングにより、それらのスパイクを崩壊することなく吸収する。GAE(λ=0.95)、エントロピー係数0.01、バッチサイズ4096のPPOは、RTX 4090上で24時間の安定した訓練を実現した。
あまり語られない詳細:時間の大部分は勾配ではなく、バイナリの実行に費やされる。各ステップにはfork、ptrace、eBPF収集、パースが必要である。SubprocVecEnvでGPUあたり32 envsを並列化したが、それでもボトルネックはI/Oだった。
10個のCTFバイナリ上での結果
セット:baby 2個、canary leak必須3個、format string 2個、最小ROPチェーン2個、heap(UAF)1個。エージェントは、バイナリごとにゼロから訓練され、10個中7個を解いた。失敗した3個のうち:heapのもの(予想通り)、存在しないgadgetを必要としたROPの1個、そしてポリシーがアドレスを永遠にリークする局所最小値で立ち往生したformat string。
純粋なangrとの比較:RLは5勝、2分、3敗(angrはheapとformat-writeをより早く解いた)。優れたツールではなく、補完的なツールである。
厳しい制約
- バイナリごとに1つのポリシー。チャレンジ間で知識を転移しない。
- シニア人間が数分で解くチャレンジに数時間の訓練時間。
- 脆い報酬シェイピング。カスタムアロケータや非標準の緩和策がヒューリスティックを壊す。
- パスが決定論的に導出可能な場合、シンボリック実行は依然として勝つ。
真のトレードオフ:安価な広範囲カバレッジ(RL)対 高価な深い推論(シンボリック)。Gwaihir内でそれらを並列に走らせる。
次に来るもの
3つの戦線:(a) exploit-dbとCTF writeupでの事前訓練、(b) RLHFスタイルの報酬学習、(c) エージェントをコーディネーターLLMのサブツールとして統合する。
今のところ訓練を続ける。次にチームが午前3時に3時間立ち往生したとき、flagを見つけるのが運ではなく — エージェントであってほしい。
参考文献
- Avgerinos, T. et al. (2011). AEG: Automatic Exploit Generation. NDSS.
- Cha, S.K. et al. (2012). Unleashing Mayhem. IEEE S&P.
- Böttinger, K. et al. (2018). Deep Reinforcement Fuzzing. IEEE SPW. arXiv:1801.04589.
- Schulman, J. et al. (2017). PPO. arXiv:1707.06347.
- Ouyang, L. et al. (2022). InstructGPT/RLHF. NeurIPS.
- Shao, Y. et al. (2025). PwnGPT. ACL.