# バイナリ書き換え
## 定義
バイナリ書き換えは、実行ファイルまたはメモリへロードされた機械語命令を解析・置換し、ソースコードの変更や再コンパイルなしに実行経路を変更する技術である。
静的リンク前後のファイルへ適用する方式と、起動時・実行時にメモリ上のコードへ適用する方式があり、命令長、制御フロー、再配置、自己書き換え、アーキテクチャ規約が正しさを左右する。(Source: [[@2023__USENIX-ATC__zpoline - a system call hook mechanism based on binary rewriting]])
## zpoline の方式
[[zpoline]] は、x86-64 の二バイト `syscall`/`sysenter` を同じ長さの `callq *%rax` へ置換する。
システムコール番号が格納された `rax` を仮想アドレス 0 からのトランポリンへの分岐先として利用するため、二バイトの命令位置へ長い絶対アドレスを挿入する必要がない。
トランポリン内の `nop` 列を通過した後にフック関数へ到達し、`callq` が保存した呼び出し元アドレスから元のプログラムへ戻る。(Source: [[@2023__USENIX-ATC__zpoline - a system call hook mechanism based on binary rewriting]] §2.1–§2.2)
書き換えは `procfs` から得た実行可能メモリ領域を走査して行い、セットアップ中だけ対象コードを変更可能にする。
処理はメモリ上のコードバイナリに対して行うため、プログラムの実行ファイルは変更されず、`libzpoline.so` と `zpoline_loader` で動的・静的リンクの双方へ適用できる。(Source: [[@2023__USENIX-ATC__zpoline - a system call hook mechanism based on binary rewriting]] §2.3.1)
## 正しさと安全性の制約
- 置換後の命令が元の命令と同じ長さでなければ、隣接命令を壊す。
- 置換対象を取りこぼすと網羅性が失われ、書き換え後の制御フローを誤ると予期しないアドレスへジャンプする。
- セットアップ後に生成されたコードや vDSO の命令は既定では対象外になる。
- 仮想アドレス 0 を使うため、NULL ポインタの読み書き・実行終了を XOM とビットマップで別途維持する必要がある。
- x86-64 の命令整列と非整列アドレスジャンプに依存するため、ARM などへそのまま移植できない。
- バイナリ書き換え自体はセキュリティ強化を意味せず、必要なら `seccomp` や MPK を併用する。
## 横断的知見
- **同じ命令書き換えでも、観測のためのプローブと挙動変更のためのフックでは安全性の基準が異なる**: DTrace は無効時のゼロ・プローブ効果と絶対安全なプローブ実行を目指す一方、zpoline はシステムコールをユーザー空間処理へ振り向ける。前者は測定による変形を抑える設計、後者は既存プログラムの意味を変える設計であり、同じ「ソース変更不要」だけではリスクを比較できない。(Source: [[@2004__USENIX-ATC__Dynamic Instrumentation of Production Systems]], [[@2023__USENIX-ATC__zpoline - a system call hook mechanism based on binary rewriting]])
- **書き換えの適用粒度は、網羅性と保守コストの交換条件になる**: zpoline はシステムコール命令を直接対象にするため libc の内部呼び出しを含めて網羅性を狙えるが、命令解析・アーキテクチャ・生成コードの制約を負う。uprobe は関数境界へ適用しやすい反面、シンボルや ABI の変化に弱い。命令単位と関数単位は、単純な優劣ではなく必要な制御粒度で選ぶべきである。(Source: [[@2023__USENIX-ATC__zpoline - a system call hook mechanism based on binary rewriting]], [[@2023__OReillyJapan__詳解 システム・パフォーマンス 第2版 - Chapter 14 Ftrace]])
- **バイナリ書き換えは互換性の責任を実行時へ移す**: zpoline はアプリケーションの再コンパイルを不要にして既存ソフトウェアへユーザー空間 OS サブシステムを接続するが、NULL アクセス維持、再帰回避、vDSO、動的コード、命令規約をランタイム側で処理する必要がある。ソースを変更しない利点は、実装責任が消えることではなく、アプリケーションからフック機構へ移ることを意味する。(Source: [[@2023__USENIX-ATC__zpoline - a system call hook mechanism based on binary rewriting]], [[@2025__OSDI__Extending Applications Safely and Efficiently]])
- **低オーバーヘッドの評価には、命令置換コストとフック本体のコストを分ける必要がある**: zpoline は `getpid` で 41 nsを報告し、NULL 実行検査を外すと 40 nsである一方、`ptrace`・`int3`・SUD はスケジューリングやシグナル処理を含む。したがって「バイナリ書き換えは速い」という比較は、初期書き換え時間、毎回の分岐、フック本体、カーネル越境をどこまで含めるかを明示して初めて意味を持つ。(Source: [[@2023__USENIX-ATC__zpoline - a system call hook mechanism based on binary rewriting]], [[@2004__USENIX-ATC__Dynamic Instrumentation of Production Systems]])
## 未解決の問い
- JIT、自己書き換え、共有ライブラリの遅延ロードを含む現代アプリケーションで、網羅的書き換えと実行時安全性を両立できるか。
- x86-64 以外の命令長・分岐・戻りアドレス規約を持つ CPU で、zpoline と同じ二バイト置換の設計原理を再構成できるか。
- バイナリ書き換え後のコードを、制御フロー完全性、メモリ保護、信頼できるフックの範囲という観点から自動検証できるか。
- 低オーバーヘッドを保ったまま、システムコールエミュレーション、サンドボックス、テレメトリ収集を一つのフック機構で安全に分離できるか。
- `LD_PRELOAD`、uprobe、eBPF、バイナリ書き換えを、対象の網羅性・変更能力・性能・保守性の共通指標で比較できるか。
## 関連
- [[zpoline]] — システムコール命令を二バイト置換する実装。
- [[システムコールフック]] — フック位置と方式を比較する概念。
- [[動的計装]] — 実行直前・実行中のコードへ処理を差し込む上位概念。
- [[uprobe]] — ユーザー空間関数へ適用する Linux の動的プローブ。
- [[ゼロコード計装]] — ソースコード変更なしに処理を挿入する設計軸。
## 出典
- [[@2023__USENIX-ATC__zpoline - a system call hook mechanism based on binary rewriting]]
- [[@2004__USENIX-ATC__Dynamic Instrumentation of Production Systems]]
- [[@2025__OSDI__Extending Applications Safely and Efficiently]]