
軽量な動的インストルメンテーションライブラリ
Copyright 2020 Google LLC
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
## TinyInstとは?
TinyInstは軽量な動的計装ライブラリであり、プロセス内の選択したモジュールのみを計装し、残りのプロセスはネイティブに実行させることができます。理解しやすく、ハックしやすく、ハックに使いやすいことを目的としています。すべてのターゲットと互換性があるように設計されているわけではありません(詳細は後述)。
### [DynamoRIO](https://dynamorio.org/)や[PIN](https://software.intel.com/en-us/articles/pintool)との比較は?
TinyInstは、DynamoRIOやPINのような複雑な計装フレームワークの代替としてではなく、より軽量なソリューションで十分なシナリオのための代替手段です。TinyInstは、ターゲットがwell-behaved(後述の意味で)であることを前提としていますが、これはより複雑なフレームワークでは当てはまりません。したがって、[以前DynamoRIOで行われたように](https://www.slideshare.net/MaximShudrak/fuzzing-malware-for-fun-profit-applying-coverageguided-fuzzing-to-find-bugs-in-modern-malware)、マルウェアに対してTinyInstを正常に実行できる可能性は低いでしょう。一方、計装する必要のないモジュールが原因で他のフレームワークで動作しないターゲットがあり、その計装対象モジュールがwell-behavedである場合、TinyInstでは動作する可能性があります。TinyInstではプロセスの大部分がネイティブに実行されるため、プロセスの起動時間が短くなり、ターゲットプロセスが計装を必要としないモジュールで多くの時間を費やす場合には、他のソリューションよりも優れたパフォーマンスを発揮する可能性があります。
### [Mesos](https://github.com/gamozolabs/mesos)や[TrapFuzz](https://github.com/googleprojectzero/p0tools/tree/master/TrapFuzz)との比較は?
TinyInstは完全なバイナリ書き換えソリューションであり、ターゲットモジュール内の任意の動作を変更できます。これにより、例えば基本ブロックだけでなくエッジカバレッジを抽出することが可能です。また、TinyInstは基本ブロックを特定するためにIDA Proなどの他のソフトウェアに依存しません。
### TinyInstはどのオペレーティングシステムをサポートしていますか?
TinyInstはWindows(x86およびx64)、macOS(x64およびARM64)、Linux(x64およびARM64)、Android(ARM64)で動作します。各オペレーティングシステムの追加の注意事項と制限については、対応するディレクトリのREADMEを参照してください。
### TinyInstと互換性のあるターゲットは?
TinyInstは、すべての計装対象モジュールが以下の意味でwell-behavedであると仮定しています:
- 自己修正コードがないこと
- スタック上のリターンアドレスがプログラムから直接アクセスされることがないこと
OR/AND(設定に依存)
- スタックの先頭より前(ESP/RSPが指すアドレスより低いアドレス)にデータが格納されることがないこと。この条件は、`-stack_offset`フラグを使用して「(ESP/RSP - arbitrary_offset)より前にデータがない」という条件に緩和できます。
TinyInstはまた、ターゲットプロセスでDEP/NXが有効になっている必要があります。そうでない場合は、`-force_dep`フラグを使用して強制的に有効にできます。ただし、ターゲットが正しく機能するためにDEPをオフにする必要があるという稀なケースでは、強制的に有効にすると誤動作を引き起こす可能性があります。
### パフォーマンスオーバーヘッドは?
画像デコードに関する初期の測定結果によると、デフォルトのTinyInst設定でwell-behavingな64ビットターゲットの場合、パフォーマンスオーバーヘッドはクライアントなしで約15%、カバレッジ収集のサンプルクライアントを使用すると約20%でした。これには、モジュールの初期計装によって生じるタイムアウトは含まれていないことに注意してください。詳細については、以下のパフォーマンスのヒントを参照してください。
## TinyInstのビルド
1. ターミナルを開き、ビルド環境を設定します(例:Windowsではvcvars64.bat / vcvars32.batを実行)
2. ソースを含むディレクトリに移動します
3. 以下のコマンドを実行します(ビルド対象のIDEのバージョンとプラットフォームに応じてジェネレータを変更してください)
#### Windows```
mkdir build
cd build
cmake -G "Visual Studio 16 2019" -A x64 ..
cmake --build . --config Release
mkdir build cd build cmake -G Xcode .. cmake --build . --config Release
#### Linux```
mkdir build
cd build
cmake ..
cmake --build . --config Release
mkdir build cd build cmake -DCMAKE_TOOLCHAIN_FILE=</path/to/android/ndk>build/cmake/android.toolchain.cmake -DANDROID_NDK=</path/to/android/ndk> -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM= .. cmake --build . --config Release
注意点1: 64ビットビルドは、WindowsおよびLinuxオペレーティングシステム上で32ビットターゲットに対しても動作します。
注意点2: 64ビットWindows上で32ビットビルドを作成する際、環境が適切に設定されておらずライブラリが不足しているために問題が発生していますか? `cmake --build`を実行する代わりに、生成された.slnファイルをVisual Studioで開き、そこからビルドしてください。また、64ビットビルドは32ビットターゲットでも動作するため、32ビットビルドを作成する必要はないかもしれないことに注意してください。
## TinyInstの使用
TinyInstは主に、他のプログラム内でライブラリとして使用されることを意図しています。
TinyInstクライアントは、TinyInstクラスのサブクラスとして記述されます。クライアントは必要なAPIメソッドをオーバーライドできます。APIメソッドは以下で定義されています。
クライアントが作成された後、コマンドラインオプションを指定して初期化する必要があります。呼び出しは以下の通りです。
`void init(int argc, char **argv);`
コマンドラインオプションは以下で定義されており、クライアントは独自のオプションを定義することもできます。その後、インストルメントされたプログラムを実行および制御するために、以下の関数を使用できます。
`DebuggerStatus Run(int argc, char **argv, uint32_t timeout);`
`DebuggerStatus Attach(unsigned int pid, uint32_t timeout);`
これらの関数は、プログラムを実行するか(指定されたコマンドラインを使用)、既に実行中のプログラムにアタッチします。ターゲットメソッドが指定されていない場合、ターゲットはプログラムが終了するか、クラッシュするか、タイムアウト(ミリ秒単位で指定)が経過するまで実行を続けます。ターゲットメソッドが定義されている場合、TinyInstはターゲットメソッドが入力されたとき、およびターゲットメソッドが戻るときに戻り、呼び出し元が追加のタスクを実行できるようにします。
`Run`および`Attach`がターゲットプロセスがまだ生きている間に戻った場合、以下の関数を使用してプロセスを終了するか、実行を継続できます。
`DebuggerStatus Kill();`
`DebuggerStatus Continue(uint32_t timeout);`
TinyInstには、以下のように呼び出すことができるカバレッジバイナリの例が付属しています。
`<options> -- <target command line>`
Windowsでの例:
`litecov.exe -instrument_module notepad.exe -coverage_file coverage.txt -- notepad.exe`
## インストルメンテーションAPI
### デバッガイベントコールバック
これらのコールバックは情報提供のみを目的としており、クライアントはその間にインストルメントされたコードを出力すべきではありません。クライアントは、これらのイベントを自身で処理する前に、スーパークラスで定義された同じハンドラを呼び出す必要があります。
`OnProcessCreated`
ターゲットプロセスが作成されたか、アタッチされたときに呼び出されます。
`OnProcessExit`
ターゲットプロセスが終了したときに呼び出されます。
`OnProcessEntrypoint`
プロセス(メインバイナリ)のエントリポイントに到達したときに呼び出されます。
`OnTargetMethodReached`
ターゲットメソッドが定義されている場合、ターゲットメソッドに初めて到達したときに呼び出されます。
`OnModuleLoaded`
モジュールがロードされたときに呼び出されます。インストルメントされたモジュールだけでなく、すべてのモジュールに対して呼び出されます。
`OnModuleUnloaded`
モジュールがアンロードされたときに呼び出されます。インストルメントされたモジュールだけでなく、すべてのモジュールに対して呼び出されます。
`OnException`
例外が発生したときに呼び出されます。クライアントは、例外が処理された場合はtrueを返すか、親クラスの同じメソッドの結果を返す必要があります。
### インストルメンテーションコールバック
これらのコールバック中、クライアントは`WriteCode()`を呼び出すことでターゲットにコードを追加できます。クライアントは、挿入されたコードで破壊されるコンテキスト(レジスタやフラグなど)を保存および復元する責任があることに注意してください。
`InstrumentBasicBlock`
特定のベーシックブロックで実行されるコードを挿入するために使用できます。
`InstrumentEdge`
特定のエッジで実行されるコードを挿入するために使用できます。注意: パフォーマンス上の理由から、このコールバックは非決定的エッジ(条件付きジャンプなど)と間接ジャンプ/コール(例:`call rax`)でのみ発行されます。次のベーシックブロックが前のベーシックブロックから常に既知であるエッジ(例:`jmp offset`、`call offset`)では、コールバックは発行されません。
`InstrumentInstruction`
命令を変更したり、その前にコードを挿入するために使用できます。戻りコードに応じて、元の命令はコールバック後に出力されるか、されません。
### その他のコールバック
`OnModuleEntered`
別のモジュールからインストルメントされたモジュールに制御フローが移行したときに呼び出されます。
`OnModuleInstrumented`
モジュールがインストルメントされたときに呼び出されます。これは通常、プロセスエントリポイントに到達したとき(ターゲットメソッドが定義されていない場合)、またはターゲットメソッドに到達したとき(定義されている場合)に発生します。クライアントはここでインストルメンテーション関連のデータを初期化できます。
`OnModuleUninstrumented`
インストルメンテーションデータが無効になり、クリアする必要があるときに呼び出されます。これはモジュールがアンロードされたことと同じではないことに注意してください。デフォルトでは、インストルメンテーションはモジュールのアンロード/再ロードをまたいで持続します。このコールバックは、クライアント内のインストルメンテーション関連データをクリアするために使用できます。
### フックAPI
上記の汎用APIに加えて、TinyInstは個々の関数の動作を検査および変更するのに適したフッキングAPIも実装しています。このAPIは[別のページ](https://github.com/googleprojectzero/TinyInst/blob/master/hook.md)に文書化されています。
## コマンドラインオプション
### インストルメンテーション関連
`-instrument_module [module name]` インストルメントするモジュールを指定します。複数の`-instrument_module`オプションを指定して、複数のモジュールをインストルメントできます。
`-instrument_transitive [module name]` `-instrument_module`と似ていますが、他のインストルメントされたモジュールから入力されたコードのみがインストルメントされて実行されます。主にmodule1->module2->module1のような呼び出しの最適化として使用され、module2モジュール全体をインストルメントすることが重要ではなく、module2->module1のエントリが速度低下を引き起こしている場合に使用します。
`-indirect_instrumentation [none|local|global|auto]` 間接ジャンプ/コールに使用するインストルメンテーションを指定します。
`-patch_return_addresses` - リターンアドレスを元の値に置き換えます。指定された`-indirect_instrumentation`メソッドを使用してリターンがインストルメントされるようにします。
`-generate_unwind` - インストルメントされたコード用のスタックアンワインドデータを生成します(C++例外処理を高速化するため)。一部の古いWindowsバージョンでは正しく動作しない可能性があることに注意してください。
`-persist_instrumentation_data` (デフォルト = true) モジュールのアンロード/再ロード時にモジュールを再インストルメントしません。モジュールが以前ロードされたのと同じアドレスにロードされた場合にのみ機能します。
`-instrument_cross_module_calls` (デフォルト=true) 複数の`-instrument_module`モジュールが指定され、一方が他方を呼び出す場合、例外を発生させずに(速度低下の原因となる)他方のモジュールのインストルメントされたコードにジャンプします。
`-stack_offset` (デフォルト=0) スタックにコンテキストを保存するとき、スタックの先頭(スタックポインタの前)にこのバイト数を変更せずに残します。
`-patch_module_entries [off|data|code|all]` 以前に検出されたエントリポイントへのポインタを検索し、それらをインストルメントされた対応物に置き換えることにより、過剰なモジュールエントリによる速度低下を解決しようとします。フラグの値は、これらのポインタを検索する場所を制御します。警告: これを有効にすると、ターゲットに不安定性が生じる可能性があります。
### デバッグ関連
`-trace_debug_events` - デバッガイベント(モジュールのロード、例外など)を表示します。
`-trace_basic_blocks` - 実行されるベーシックブロックを表示します。
`-trace_module_entries` - インストルメントされたコードへのすべてのエントリを表示します。