Upgrade to Pro — share decks privately, control downloads, hide ads and more …

eBPFを用いたAndroid向けデバッガ「eDBG」のx86_64 Linuxへの移植

Avatar for Arata Arata
January 31, 2026
4

eBPFを用いたAndroid向けデバッガ「eDBG」のx86_64 Linuxへの移植

Avatar for Arata

Arata

January 31, 2026
Tweet

Transcript

  1. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 GDBでリモートデバッグするためのプロトコル QEMU, BitVisor, cloud-hypervisorなどが対応している 補足1:

    GDB Remote Protocolとは #5 GDB Remote Protocol Host B eDBG デバッグ対象の プログラム 制御 Host A GDB 命令の例: • レジスタの値取得 • メモリの値取得 • BPを設定 • 継続実行 ※BP: BreakPoint
  2. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 「フックポイント」通過時にユーザーコードを実行できるLinuxの機能 フックポイントの例: • syscalls ◦

    システムコール • kprobe / kretprobe ◦ カーネル空間のコード • uprobe / uretprobe ◦ ユーザー空間のコード 補足2: eBPFとは #6 https://ebpf.io/ja/what-is-ebpf/
  3. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 openatシステムコールが呼ばれた時に、 どのコマンドが何をopenしたのか表示するeBPFプログラムを実行 補足2: eBPFの使用例 #7

    # bpftrace -e ' tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args.filename)); }' Attaching 1 probe... node /proc/1062521/cmdline cpuUsage.sh /etc/ld.so.cache [...]
  4. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 「helper関数」を呼び出すことでOSの情報を取得・変更できる • bpf_probe_{read, write}_user() ◦

    ユーザー空間のメモリ読み書き • bpf_probe_read_kernel() ◦ カーネル空間のメモリ読み込み • bpf_send_signal() ◦ プロセスにシグナルを送信 • bpf_current_task(), bpf_task_pt_regs() ◦ プロセスのstruct task_struct, struct pt_regsを取得 ◦ 汎用レジスタの値の取得に使える 補足2: eBPFで実行できる機能 #8
  5. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 以下の2つが実現できればデバッガはおおむね実装できる • プロセスを特定の場所で停止・再開させる ◦ 「特定の場所で」:

    フックポイントで場所を指定 ◦ 「停止・再開させる」: SIGSTOP, SIGCONTをプロセスに送信 • 停止した時点でのプロセスの状態を調べる ◦ レジスタ: eBPFでstruct pt_regsを取得 ◦ メモリ: process_vm_{read, write}v()システムコールを利用 ◦ その他: /proc/{pid}/ 以下を利用 実装: 基本的な実装方針 #9
  6. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 実装: 実装した機能 #10 機能 GDB

    eDBG エントリポイントでのブレーク ptrace(PTRACE_TRACEME) eBPF + tracepoint ソフトウェアブレークポイント ptrace(PTRACE_POKETEXT) eBPF + uprobe ハードウェアブレークポイント ptrace(PTRACE_POKEUSER) eBPF + perf_event システムコールキャッチ ptrace(PTRACE_SYSCALL) eBPF + tracepoint シングルステップ実行 ptrace(PTRACE_SINGLESTEP) eBPF + uprobe 継続実行 ptrace(PTRACE_CONT) eBPF + uprobe
  7. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 実装: 実装した機能 (cont’d) #11 機能

    GDB eDBG レジスタの読み込み ptrace(PTRACE_GETREGSET) eBPF レジスタの書き込み ptrace(PTRACE_SETREGSET) 未実装 メモリの読み込み process_vm_readv() メモリの書き込み process_vm_writev()
  8. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 使用するフックポイント: uprobe プログラムの特定のアドレスでフック 実行するeBPFプログラム: 1.

    bpf_send_signal(SIGSTOP)でプロセスを止める 2. レジスタ等の状態を親プロセスに送る 実装: ソフトウェアブレークポイント #14
  9. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 使用するフックポイント: sys_enter, sys_exit システムコールが呼ばれる直前、直後をフック 実行するeBPFプログラム:

    1. bpf_send_signal(SIGSTOP)でプロセスを止める 2. レジスタ等の状態を親プロセスに送る 注意: sys_enterでシステムコールの引数等は取得できるが、    ブレークはできない 実装: システムコールキャッチ #16
  10. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 1. 現在停止しているアドレスの命令を取得 ◦ ディスアセンブルにはcapstoneを使用 2.

    命令と現在の状態から1命令実行後のアドレスを予測 3. 予測したアドレスにブレークポイントを仕掛けて継続実行 予測ロジック: • RET: スタック上のリターンアドレスでブレーク • CALL: 呼び出し先でブレーク • JUMP/条件付きJUMP: 分岐条件をチェック、分岐先でブレーク 実装: シングルステップ実行 #17
  11. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 各eBPFプログラムで以下の処理を行う 1. bpf_get_current_task()でプロセスのstruct task_structを取得 ◦

    セグメントレジスタ、浮動小数点レジスタが見える 2. bpf_task_pt_regs()でプロセスのstruct pt_regsを取得 ◦ 汎用レジスタが見える 3. 取得した値をeBPF Mapでデバッガに送る 実装: レジスタの読み込み #19
  12. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 • 補助ベクタの取得 ◦ /proc/{pid}/auxvの内容を返す ◦

    エントリポイント、ベースアドレスなどの情報が含まれる • ホストでのfile操作(open, readlink, pread, close) ◦ /proc/{pid}/cwdやライブラリ等を読む • デバッグ対象プログラムのファイルパスの取得 • デバッグ対象プロセスのpidの取得 • 各セクションのオフセットの取得 実装: その他実装したRemote Protocolの機能 #21
  13. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 以下の手法でptraceを検出するプログラムanti-ptraceを作成し、 GDBとeDBGで検知結果を確認した • /proc/self/statusのTracerPidの値をチェック ◦

    0以外の値 → デバッグされている可能性がある • ptrace(PTRACE_TRACEME)の戻り値をチェック ◦ EPERMエラーが返る → デバッグされている可能性がある 評価: ptraceに対するanti-debugの回避 #23
  14. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 $ ./anti-ptrace pid = 340401

    [check_tracerpid] /proc/self/status ... OK [check_ptrace_traceme] ptrace(PTRACE_TRACEME) ... OK 評価: ptraceに対するanti-debugの回避(結果) #24 そのまま実行 → 検知されなかった ⭕
  15. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 評価: ptraceに対するanti-debugの回避(結果) #25 $ gdb

    -nx -q ./anti-ptrace (gdb) r [...] pid = 341440 [check_tracerpid] /proc/self/status ... TRACED (TracerPid: 341379) [check_ptrace_traceme] ptrace(PTRACE_TRACEME) ... TRACED GDBでデバッグ → 検知された ❌
  16. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 評価: ptraceに対するanti-debugの回避(結果) #26 $ cargo

    run -- ./testdata/anti-ptrace (別ウィンドウのgdbからアタッチ、継続実行) [...] pid = 342918 [check_tracerpid] /proc/self/status ... OK [check_ptrace_traceme] ptrace(PTRACE_TRACEME) ... OK eDBGでデバッグ → 検知されなかった ⭕
  17. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 それぞれ3回計測を行い、その平均を算出した 評価: GDBとのパフォーマンス比較(結果) #30 実行時間

    [秒] BPなし GDBserver eDBG loop_func 0.070 12.4 11.0 loop_syscall 0.071 17.3 8.76 GDBと比較して高速化を達成した • loop_func: 約1.13x 高速化 • loop_syscall: 約1.97x 高速化
  18. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 明確な理由は不明 • int3を使う点はGDB, eDBG (uprobe)ともに同じ

    ◦ この点では大きな差異は生じないはず • 継続実行でシステムコールの発行回数が違う可能性 ◦ GDB: ptrace(POKETEXT → SINGLESTEP → POKETEXT → CONT) ◦ eDBG: kill(SIGCONT) • eDBGはBPを削除してもint3にパッチしたままで、i-cacheが効いてい る可能性 考察: loop_funcで高速化を達成した要因 #31
  19. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 eBPFでは特定のシステムコール呼び出しに限ってSIGSTOPを送ることが 可能なためと思われる • ptrace(PTRACE_SYSCALL)はすべてのシステムコールでブレークする ◦

    ブレークさせるかの判定はユーザー空間で行われる • eBPFでは特定のシステムコールでのみブレークさせられる ◦ ブレークさせたいシステムコール番号をeBPF Mapで共有している ◦ sys_exitでこのeBPF Mapを見て必要な場合にのみSIGSTOPを送る ◦ ブレークさせるかの判定はカーネル空間で行われる プログラムの特性によってはさらに高速化の恩恵を得られる可能性がある 例: キャッチしたいシステムコールの呼び出し頻度が少ないプログラム 考察: loop_syscallで高速化を達成した要因 #32
  20. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 • システムコールの呼び出し直前でブレークできない • レジスタの値の変更ができない •

    マルチプロセス・マルチスレッドへの対応が難しい ◦ 任意の時点でプロセス・スレッドにアタッチして即座に状態を 取得できる必要がある ◦ eBPFは受動的に情報を取得するので、原理的に難しい ◦ 例: スレッド1でブレーク→スレッド2も一緒に停止するため、 発火させられるフックポイントがない=状態を取得できない • 実行のために強い権限が必要 課題: eBPF由来の制約が大きい #33
  21. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 • eBPFベースのデバッガ「eDBG」をx86_64 Linuxへ移植した ◦ GDB

    Remote Protocolへの対応を新規に追加した ◦ ブレークポイントの追加・削除のオーバーヘッドを削減した • GDBと比較して検知されにくく、低オーバーヘッドである ◦ ptraceを使用しないため、一部のanti-debugを回避できる ◦ eBPFを使用して、ブレークポイントのオーバーヘッドを抑えられる • インタラクティブにデバッグを行うための機能は一部実装が難しい ◦ eBPFがトレース目的の機能であるため リポジトリ: https://github.com/arata-nvm/eDBG-x64 まとめ #34
  22. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 • 実はあまりない ◦ eBPFのポータブル性やGDBのマルチアーキ対応のおかげ •

    レジスタセットの違いの対応は必要 ◦ x86_64とARM64ではレジスタセットが異なる ◦ struct pt_regsの構造も異なる ◦ アーキごとに適切な場所を読む 付録: x86_64 Linux移植にあたっての課題 #35
  23. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 • GDBはブレークポイントの追加・削除を頻繁に行う ◦ プログラムが実行中の間のみブレークポイントを設定する ◦

    そのために毎回実行前にBP追加、実行後にBP削除を行う • eBPFプログラムのアタッチ・デタッチ操作は重い ◦ GDBからの指示そのままに操作を行うと非常に動作が遅くなる ◦ 対処として、基本デタッチを行わず、eBPF MapでBPの有効/無効 を制御することで高速化した ◦ 一時的にBPを追加するシングルステップ実行は依然として遅い 付録: GDBとeBPFの相性が悪い点 #36
  24. 2026/01/30 情報システム実験B「バイナリプログラムの解析」 最終発表会 | 安藤慎 • GDB Remote Protocolのサポートのためgdbstubクレートを利用した ◦

    多数のコマンドへの対応、アーキテクチャの定義など手厚い • サーバーとの通信を1文字づつ行うよう実装されている ◦ この点がパフォーマンス上のネックとなっていた ◦ Buffered IOを実装したところ、実行時間が大幅に改善された ◦ 例: loop_syscall ▪ 改善前: 49.8 秒 ▪ 改善後: 8.76 秒 付録: gdbstubクレートにおける改良 #37