動的なインストルメンテーションは単純ではありませんが、些細なことです:American Fuzzy Lop用の別のインストルメンテーションを記述します

(*)実際には、そうではありません。

おそらく、Valgrind-ネイティブプログラムのどこでメモリリークが発生しているか、初期化されていない変数によって分岐がどこにあるかなどを知ることができるデバッガー(およびmemcheckの他に、他の操作モードもあります)について多くの人が聞いたことがあるでしょう。 内部的には、このすばらしいプログラムはネイティブコードを中間バイトコードに粉砕し、それを指示し、新しいマシンコードを生成します-すでにランタイムチェックがあります。 しかし、問題があります。ValgrindはWindowsでの作業方法を知りません。 必要なときに、検索によってDrMemoryと呼ばれる同様のユーティリティが見つかりました 。これもアナログstrace



でした。 しかし、それはそれらについてではなく、それらが構築されていることに基づいた動的計装ライブラリ、 DynamoRIOについてです。 ある時点で、私は自分のインストルメンテーションの作成という点でこのライブラリに興味を持ち、ドキュメントを探し始め、 多数の例に出会い、 呼び出し命令のカウントのような完全なインストルメンテーションが237行のコードで記述できることに驚いた32そのうちの-ライセンス、および8-説明。 いいえ、もちろん、これは「30行のJavaScriptコードでValgrindキラーを作成する」のではなく、そのようなタスクで想像できるよりもはるかに簡単です。







例として、最近Habré書かれたFuzzer American Fuzzy Lopの計装の4番目の実装を書きましょう。







AFLとは何ですか?



AFLは、鉄筋コンクリートから組み立てられた、バグや脆弱性を検索するためのガイド付きファジングツールです。 松葉杖 最も自明で効率的な方法で実装されたヒューリスティック。 したがって、libjpegの動作を観察して有効なジープを合成できるツールを見ると、このすべてがそれほど複雑ではないメカニズムに基づいて行われていることに驚かされます。 要するに、本格的なAFL操作では、実行中にエッジカバーを収集するようにターゲットバイナリをインストルメント化する必要があります。各基本ブロック( 基本ブロック 、ラベルから最も近い遷移命令までの一連の命令のようなもの)をグラフの頂点として想像してください。 BBは、BB間で制御を移す可能な方法です。 したがって、AFLは、プログラムの基本ブロック間でどのような遷移が発生し、およそ何回発生したのかに関心があります。







AFLでインストルメントする主な方法は、コンパイル段階でafl-gcc



/ afl-g++



ラッパーまたはclang



類似物を使用して静的です。 afl-gcc



ことに、 afl-gcc



は、呼び出さas



コマンドを、コンパイラーによって生成されたアセンブラーのリストを上書きするラッパーに置き換えます。 llvm mode



と呼ばれる、より高度なオプションがありllvm mode



。これはコンパイルプロセス(もちろんLLVMを使用して生成されllvm mode



)に正直に統合されるため、理論的には生成コードのパフォーマンスが向上します。 最後に、すでにコンパイルされたバイナリをファジングするためのqemu mode



がありqemu mode



-必要なインストルメンテーションを追加する1つのプロセスのエミュレーションモードでのQEMUのパッチ(最初は、このQEMUモードの動作は、ホストカーネルを使用して別のアーキテクチャ用にアセンブルされた個別のプロセスを開始することを目的としていました)







DynamoRIOとは



DynamoRIOは動的インストルメンテーションシステムです(つまり、実行時に既にコンパイル済みのバイナリを直接指示します)。x86およびx86_64アーキテクチャのWindows、Linux、Android、およびARM(リリース候補バージョン7.0はAArch64をサポートします)で実行されます。 QEMUとは異なり、他の誰かのアーキテクチャで「テキストに近い」プログラムを実行することを意図していませんが、ネイティブアーキテクチャでプログラムの動作を変更する独自のツールを簡単に作成することを目的としています。 同時に、可能な場合、目標は最適化されたコードを破損しないことです。 残念ながら、変換が行われないため、 クライアント (いわゆるカスタムインストルメンテーションライブラリ)がターゲット命令セットについて知らない方法を見つけたことがありません(基本的な命令に十分なクロスプラットフォームラッパーが存在する場合を除きます)。マシンコード->バイトコード-[instrumentation]->新しいバイトコード->インストルメントされたマシンコード "。 代わりに、各ブロードキャストベースユニットに対して、クライアントはデコードされた命令のリストを受信します。これは、便利な機能とマクロで修正および補足できます。 つまり、マシンコードでプログラミングする必要はありませんが、ほとんどの場合、x86命令(または別のプラットフォーム)のセットを知っている必要があります。







ちょっとしたボーナスとして、 Githubアカウントで他に何が面白いかを調べてみて、興味深いリポジトリDRKに出会いました。 リポジトリは放棄されており、多少古くなっているようですが、説明は印象的です。







DRKは、ロード可能なLinuxカーネルモジュールとしてのDynamoRIOです。 DRKがロードされると、すべてのカーネルモード実行(システムコール、割り込みおよび例外ハンドラー、カーネルスレッドなど)はDynamoRIOの範囲内で行われますが、ユーザーモード実行は変更されません-ユーザーをインストルメントする通常のDynamoRIOの逆です。モードプロセスであり、カーネルモードの実行には影響しません。


テストプログラム



まず、AFLの機能を見てみましょう。 いいえ、脆弱なバージョンのライブラリを使用して数時間または数日待つことはありません。 テストのために、文字NULL



始まる行をstdinに渡すと、nullポインターを逆参照する最も愚かなプログラムを作成しNULL



。 これは、もちろん、どこからともなくジープを合成したものではありませんが、待つ必要はほとんどありません。







したがって、 ここから AFL ダウンロードして収集してください。 おそらく既にご想像のとおり、GNU / Linuxでアセンブルします。 ただし、他のUnixライクなシステムやMac OS XのようなUnixも動作するはずです。 小さなプログラムを取ります:







 #include <stdio.h> #include <string.h> volatile int *ptr = NULL; const char cmd[] = "NULL"; int main(int argc, char *argv[]) { char buf[16]; fgets(buf, sizeof buf, stdin); if (strncmp(buf, cmd, 4)) { return 0; } *ptr = 1; return 0; }
      
      





コンパイルしてファジングを実行します。







 $ export AFL_PATH=~/tmp/build/afl-2.42b/ $ #   ,     $ $AFL_PATH/afl-gcc example-bug-libc.c -o example-bug-libc $ #  -    ( ) $ mkdir input $ echo test > input/1 $ #   $ $AFL_PATH/afl-fuzz -i input -o output -- ./example-bug-libc
      
      





そして、私たちは何を見ます:













どういうわけか機能しません...最後の新しいパスに注意してください。AFLは、9万1千回の発売後、新しいパスを見つけられなかったことを誓います。 実際、これは非常に論理的です。アセンブラーを呼び出す段階で静的インスツルメンテーションを使用したことを思い出します。 主な比較はlibcの関数によって行われますが、これは装備されていないため、一致する文字の数を計算することはできません。 それで、私はそれをチェックすることを決めるまで考えましたが、私たちのバイナリはstrncmp



関数をインポートしないことが判明しました。 objdump -d



の出力から判断すると、コンパイラーは、 strncmp



代わりに、インストルメンテーションをstrncmp



ループではなく、接頭部をstrncmp



命令を単に生成しました。







strncmpでインストゥルメントされたメイン関数
 00000000000007f0 <.plt.got>: 7f0: ff 25 82 17 20 00 jmpq *0x201782(%rip) # 201f78 <getenv@GLIBC_2.2.5> 7f6: 66 90 xchg %ax,%ax 7f8: ff 25 8a 17 20 00 jmpq *0x20178a(%rip) # 201f88 <_exit@GLIBC_2.2.5> 7fe: 66 90 xchg %ax,%ax 800: ff 25 8a 17 20 00 jmpq *0x20178a(%rip) # 201f90 <write@GLIBC_2.2.5> 806: 66 90 xchg %ax,%ax 808: ff 25 8a 17 20 00 jmpq *0x20178a(%rip) # 201f98 <__stack_chk_fail@GLIBC_2.4> 80e: 66 90 xchg %ax,%ax 810: ff 25 8a 17 20 00 jmpq *0x20178a(%rip) # 201fa0 <close@GLIBC_2.2.5> 816: 66 90 xchg %ax,%ax 818: ff 25 8a 17 20 00 jmpq *0x20178a(%rip) # 201fa8 <read@GLIBC_2.2.5> 81e: 66 90 xchg %ax,%ax 820: ff 25 92 17 20 00 jmpq *0x201792(%rip) # 201fb8 <fgets@GLIBC_2.2.5> 826: 66 90 xchg %ax,%ax 828: ff 25 9a 17 20 00 jmpq *0x20179a(%rip) # 201fc8 <waitpid@GLIBC_2.2.5> 82e: 66 90 xchg %ax,%ax 830: ff 25 a2 17 20 00 jmpq *0x2017a2(%rip) # 201fd8 <shmat@GLIBC_2.2.5> 836: 66 90 xchg %ax,%ax 838: ff 25 a2 17 20 00 jmpq *0x2017a2(%rip) # 201fe0 <atoi@GLIBC_2.2.5> 83e: 66 90 xchg %ax,%ax 840: ff 25 aa 17 20 00 jmpq *0x2017aa(%rip) # 201ff0 <__cxa_finalize@GLIBC_2.2.5> 846: 66 90 xchg %ax,%ax 848: ff 25 aa 17 20 00 jmpq *0x2017aa(%rip) # 201ff8 <fork@GLIBC_2.2.5> 84e: 66 90 xchg %ax,%ax ... 0000000000000850 <main>: 850: 48 8d a4 24 68 ff ff lea -0x98(%rsp),%rsp 857: ff 858: 48 89 14 24 mov %rdx,(%rsp) 85c: 48 89 4c 24 08 mov %rcx,0x8(%rsp) 861: 48 89 44 24 10 mov %rax,0x10(%rsp) 866: 48 c7 c1 04 6a 00 00 mov $0x6a04,%rcx 86d: e8 9e 02 00 00 callq b10 <__afl_maybe_log> 872: 48 8b 44 24 10 mov 0x10(%rsp),%rax 877: 48 8b 4c 24 08 mov 0x8(%rsp),%rcx 87c: 48 8b 14 24 mov (%rsp),%rdx 880: 48 8d a4 24 98 00 00 lea 0x98(%rsp),%rsp 887: 00 888: 53 push %rbx 889: be 10 00 00 00 mov $0x10,%esi 88e: 48 83 ec 20 sub $0x20,%rsp 892: 48 8b 15 77 17 20 00 mov 0x201777(%rip),%rdx # 202010 <stdin@@GLIBC_2.2.5> 899: 48 89 e7 mov %rsp,%rdi 89c: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 8a3: 00 00 8a5: 48 89 44 24 18 mov %rax,0x18(%rsp) 8aa: 31 c0 xor %eax,%eax 8ac: e8 6f ff ff ff callq 820 <.plt.got+0x30> 8b1: 48 8d 3d dc 06 00 00 lea 0x6dc(%rip),%rdi # f94 <cmd> 8b8: b9 04 00 00 00 mov $0x4,%ecx 8bd: 48 89 e6 mov %rsp,%rsi 8c0: f3 a6 repz cmpsb %es:(%rdi),%ds:(%rsi) 8c2: 75 45 jne 909 <main+0xb9> 8c4: 48 8d a4 24 68 ff ff lea -0x98(%rsp),%rsp 8cb: ff 8cc: 48 89 14 24 mov %rdx,(%rsp) 8d0: 48 89 4c 24 08 mov %rcx,0x8(%rsp) 8d5: 48 89 44 24 10 mov %rax,0x10(%rsp) 8da: 48 c7 c1 2d 5b 00 00 mov $0x5b2d,%rcx 8e1: e8 2a 02 00 00 callq b10 <__afl_maybe_log> 8e6: 48 8b 44 24 10 mov 0x10(%rsp),%rax 8eb: 48 8b 4c 24 08 mov 0x8(%rsp),%rcx 8f0: 48 8b 14 24 mov (%rsp),%rdx 8f4: 48 8d a4 24 98 00 00 lea 0x98(%rsp),%rsp 8fb: 00 8fc: 48 8b 05 1d 17 20 00 mov 0x20171d(%rip),%rax # 202020 <ptr> 903: c7 00 01 00 00 00 movl $0x1,(%rax) 909: 0f 1f 00 nopl (%rax) 90c: 48 8d a4 24 68 ff ff lea -0x98(%rsp),%rsp 913: ff 914: 48 89 14 24 mov %rdx,(%rsp) 918: 48 89 4c 24 08 mov %rcx,0x8(%rsp) 91d: 48 89 44 24 10 mov %rax,0x10(%rsp) 922: 48 c7 c1 8f 33 00 00 mov $0x338f,%rcx 929: e8 e2 01 00 00 callq b10 <__afl_maybe_log> 92e: 48 8b 44 24 10 mov 0x10(%rsp),%rax 933: 48 8b 4c 24 08 mov 0x8(%rsp),%rcx 938: 48 8b 14 24 mov (%rsp),%rdx 93c: 48 8d a4 24 98 00 00 lea 0x98(%rsp),%rsp 943: 00 944: 31 c0 xor %eax,%eax 946: 48 8b 54 24 18 mov 0x18(%rsp),%rdx 94b: 64 48 33 14 25 28 00 xor %fs:0x28,%rdx 952: 00 00 954: 75 40 jne 996 <main+0x146> 956: 66 90 xchg %ax,%ax 958: 48 8d a4 24 68 ff ff lea -0x98(%rsp),%rsp 95f: ff 960: 48 89 14 24 mov %rdx,(%rsp) 964: 48 89 4c 24 08 mov %rcx,0x8(%rsp) 969: 48 89 44 24 10 mov %rax,0x10(%rsp) 96e: 48 c7 c1 0a 7d 00 00 mov $0x7d0a,%rcx 975: e8 96 01 00 00 callq b10 <__afl_maybe_log> 97a: 48 8b 44 24 10 mov 0x10(%rsp),%rax 97f: 48 8b 4c 24 08 mov 0x8(%rsp),%rcx 984: 48 8b 14 24 mov (%rsp),%rdx 988: 48 8d a4 24 98 00 00 lea 0x98(%rsp),%rsp 98f: 00 990: 48 83 c4 20 add $0x20,%rsp 994: 5b pop %rbx 995: c3 retq 996: 66 90 xchg %ax,%ax 998: 48 8d a4 24 68 ff ff lea -0x98(%rsp),%rsp 99f: ff 9a0: 48 89 14 24 mov %rdx,(%rsp) 9a4: 48 89 4c 24 08 mov %rcx,0x8(%rsp) 9a9: 48 89 44 24 10 mov %rax,0x10(%rsp) 9ae: 48 c7 c1 a8 dc 00 00 mov $0xdca8,%rcx 9b5: e8 56 01 00 00 callq b10 <__afl_maybe_log> 9ba: 48 8b 44 24 10 mov 0x10(%rsp),%rax 9bf: 48 8b 4c 24 08 mov 0x8(%rsp),%rcx 9c4: 48 8b 14 24 mov (%rsp),%rdx 9c8: 48 8d a4 24 98 00 00 lea 0x98(%rsp),%rsp 9cf: 00 9d0: e8 33 fe ff ff callq 808 <.plt.got+0x18> 9d5: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 9dc: 00 00 00 9df: 90 nop
      
      





同じ、ただし計装なし
 0000000000000630 <.plt.got>: 630: ff 25 82 09 20 00 jmpq *0x200982(%rip) # 200fb8 <strncmp@GLIBC_2.2.5> 636: 66 90 xchg %ax,%ax 638: ff 25 8a 09 20 00 jmpq *0x20098a(%rip) # 200fc8 <__stack_chk_fail@GLIBC_2.4> 63e: 66 90 xchg %ax,%ax 640: ff 25 92 09 20 00 jmpq *0x200992(%rip) # 200fd8 <fgets@GLIBC_2.2.5> 646: 66 90 xchg %ax,%ax 648: ff 25 aa 09 20 00 jmpq *0x2009aa(%rip) # 200ff8 <__cxa_finalize@GLIBC_2.2.5> 64e: 66 90 xchg %ax,%ax ... 0000000000000780 <main>: 780: 55 push %rbp 781: 48 89 e5 mov %rsp,%rbp 784: 48 83 ec 30 sub $0x30,%rsp 788: 89 7d dc mov %edi,-0x24(%rbp) 78b: 48 89 75 d0 mov %rsi,-0x30(%rbp) 78f: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 796: 00 00 798: 48 89 45 f8 mov %rax,-0x8(%rbp) 79c: 31 c0 xor %eax,%eax 79e: 48 8b 15 6b 08 20 00 mov 0x20086b(%rip),%rdx # 201010 <stdin@@GLIBC_2.2.5> 7a5: 48 8d 45 e0 lea -0x20(%rbp),%rax 7a9: be 10 00 00 00 mov $0x10,%esi 7ae: 48 89 c7 mov %rax,%rdi 7b1: e8 8a fe ff ff callq 640 <.plt.got+0x10> 7b6: 48 8d 45 e0 lea -0x20(%rbp),%rax 7ba: ba 04 00 00 00 mov $0x4,%edx 7bf: 48 8d 35 ce 00 00 00 lea 0xce(%rip),%rsi # 894 <cmd> 7c6: 48 89 c7 mov %rax,%rdi 7c9: e8 62 fe ff ff callq 630 <.plt.got> 7ce: 85 c0 test %eax,%eax 7d0: 74 07 je 7d9 <main+0x59> 7d2: b8 00 00 00 00 mov $0x0,%eax 7d7: eb 12 jmp 7eb <main+0x6b> 7d9: 48 8b 05 40 08 20 00 mov 0x200840(%rip),%rax # 201020 <ptr> 7e0: c7 00 01 00 00 00 movl $0x1,(%rax) 7e6: b8 00 00 00 00 mov $0x0,%eax 7eb: 48 8b 4d f8 mov -0x8(%rbp),%rcx 7ef: 64 48 33 0c 25 28 00 xor %fs:0x28,%rcx 7f6: 00 00 7f8: 74 05 je 7ff <main+0x7f> 7fa: e8 39 fe ff ff callq 638 <.plt.got+0x8> 7ff: c9 leaveq 800: c3 retq 801: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 808: 00 00 00 80b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
      
      





興味深いことに、 strncmp



strncmp



れていstrncmp



正直に呼び出されるため、AFL自体が最適化をオンにしたstrncmp



afl-gcc



によってコンパイルされたコード内の膨潤したPLTについては、明らかに、forkserverによって呼び出される関数が表示されます。これも記述しますが、最初に最初に記述します。 さて、これを見なかったふりをして、ライブラリ関数なしで単純に例を書き換えてみましょう:







 #include <stdio.h> volatile int *ptr = NULL; const char cmd[] = "NULL"; int main(int argc, char *argv[]) { char buf[16]; fgets(buf, sizeof buf, stdin); for (int i = 0; i < sizeof cmd - 1; ++i) { if (buf[i] != cmd[i]) return 0; } *ptr = 1; return 0; }
      
      





コンパイル、実行、... tadam!













フォークサーバーを書く



すでに述べたように、AFLは、調査中のプログラムの基本ブロック間の遷移に関する情報を収集する計装の可用性を想定しています。 ただし、 afl-gcc



によって追加された最適化: forkserverがあります。 その意味は、 fork



- execve



バンドルを使用してプログラムを再起動することではなく、毎回動的リンクなどを実行し、fuzzerプロセス内でfork



を作成してから、インストルメント済みプログラムに実行します。 調査したプログラムをどのように再起動しますか? ポイントは、この実行中のプロセスをテストしないことです。 代わりに、無限ループでファザーからのコマンドを待機し、 fork



作成し、子プロセスが完了するまで待機し、結果についてコールバックするコードを実行します。 しかし、出芽プロセスはすでに入力データを実際に処理し、エッジカバレッジに関する情報を収集します。 afl-gcc



場合、そのロジックを正しく理解していれば、forkserverはインストルメント済みコードに初めてアクセスしたときに起動します。 llvm mode



の場合、 遅延forkserver llvm mode



もサポートされます-この方法では、動的リンクだけでなく、検討中のプロセスの標準である初期化もスキップできます。これにより、プロセス全体を桁違いにスピードアップできますが、考慮すべきいくつかのニュアンスがあります:









Llvmモードは永続モードもサポートします 。このモードでは、複数のテストケースが同じ発芽プロセス内で連続して実行されます。 おそらく、これによりオーバーヘッドコストはさらに削減されますが、次のテスト例でのプログラム実行の結果は、現在の例だけでなく起動履歴にも依存する危険性があります。 ところで、記事の執筆を開始した後、 WindowsAFLポートもインストルメンテーションにDynamoRIOを使用し、永続モードのみを使用するという情報に出会いました。 さて、 fork



サポートなしで他に何が残っていますか?







そのため、フォークサーバーをDynamoRIOで作成する必要がありますが、まず、ファザープロセスとの相互作用に必要なプロトコルを理解する必要があります。 上記のAFL作者のブログへのリンクから何かを収集できますが、 llvm_mode/afl-llvm-rt.oc



llvm_mode/afl-llvm-rt.oc



ファイルを見つける方が簡単です。 たくさんの興味深いものがありますが、最初に__afl_start_forkserver



関数を見てみましょう-コメントも__afl_start_forkserver



て、すべてがそこで詳細に説明されています。 永続モードには興味がありません。そうでなければ、すべてが明確です。既知の番号を持つ2つのファイル記述子が与えられます。一方から読み取り、他方に書き込みます。 必要なもの:







  1. ソースからの引用: 電話をかけ、親に大丈夫だと伝えます 。 任意の4バイトを書き込みます。
  2. 4バイトを読み取ります。 私たちの場合(永続モードなし)には不変なchild_stopped == 0



    があるように見えるため、正確に読み取ったものは重要ではありません。
  3. ファザーとの通信のために、子プロセスを作成し、その中のファイル記述子を閉じます。
  4. 子プロセスのPIDで4バイトをファイル記述子に書き込み、それ(プロセス)が完了するのを待ちます。
  5. 待機した後、戻りコードでさらに4バイトを書き込み、ステップ2に進みます。


そして、実際にクライアントを書くことになりました。 ここでは、ドキュメンテーションの例は数百行しか取らないが、ドキュメンテーションを読む価値があるという余談をする必要があります。 たとえば、 ここから始めることができます。特にここでは、すべきでないことについて書かれています。 たとえば、よく知られている文学的なキャラクターを言い換えると、「計装クライアントは非常に奇妙なものです:そこにあるように見えますが、なくなっているようです」、これはドキュメントではクライアントの透明性と呼ばれます :特に、システムライブラリのコピーを使用する必要があります(プライベートローダーが役立つもの)、またはDynamoRIO APIを使用します(メモリの割り当て、コマンドラインオプションの解析など)。 API関数のドキュメントにも重要な情報があります。たとえば、 dr_register_bb_event関数の説明には、11項目の短いリストが示されており、インストルメンテーション後に結果として生じる命令のシーケンスを満たす必要があります。







DynamoRIOのクライアントのビルドを管理するには、CMakeを使用することをお勧めします-CMakeを使用します。 ドキュメントでこれを行う方法について読むことができますが、さらに興味深い質問に進みます。 たとえば、遅延フォークサーバーを作成するには、調査中のプログラムでその起動場所を何らかの方法でマークし、DynamoRIOでこのマークを見つける必要があります(ただし、調査中のプログラム内の通常の関数の呼び出しとしてフォークサーバーを作成することを妨げるものはないようですしかし、それは面白くありませんか?)、幸いなことに、そのような機能はDynamoRIOに組み込まれており、 注釈と呼ばれます。 クライアントの開発者は、調査中のプログラムにリンクする必要のある特別な静的ライブラリを収集し、必要な場所でこのライブラリから関数を呼び出す必要があります。通常の実行中は何も実行しませんが、DynamoRIOで実行されると、特定の定数に置き換えられるか、関数呼び出し。







私は公式の教科書を繰り返さず、ヘッダーファイルをDynamoRIO配信からコピーし、注釈の名前と署名の下に2つのマクロの「呼び出し」を作り直し(このファイルはテスト中のプログラムに接続する必要があります)、またソースコードを作成することを提案しますマクロへの1回の呼び出し。これにより、注釈用のスタブの実装が生成されます(テスト対象のプログラムにリンクする必要がある静的ライブラリがそこから構築されます)。







forkserverの実装については、 libc



コピーからfork



を呼び出すのがおそらくあまり正しくないことを除いて、すべてが非常に簡単です:たとえば、forkserver実装の機能に関する上記の記事のAFLの著者は、 libc



PIDをキャッシュすると述べています。 調査中のアプリケーションのlibc



コピーからfork



呼び出すと、発生したfork



呼び出しについて知ることができ、DynamoRIOは私もそれに気づくでしょう。 だから私は次のようなものを書かなければならなかった







 module_data_t *module = dr_lookup_module_by_name("libc.so.6"); EXIT_IF_FAILED(module != NULL, "Cannot lookup libc.\n", 1) fork_fun_t fork_ptr = (fork_fun_t)dr_get_proc_address(module->handle, "fork"); EXIT_IF_FAILED(fork_ptr != NULL, "Cannot get fork function from libc.\n", 1) dr_free_module_data(module);
      
      





したがって、プログラムの起動時に従来のforkserver起動モードをサポートするには、この時点でlibcがすでに利用可能であることを確認する必要があります。







これをテストしようとすると、奇妙な動作に遭遇しました。テスト中のプログラムは起動しましたが、forkserverは起動しませんでした。 dr_client_main



印刷を追加しdr_client_main



-表示されませんでした。 ドラフトコードのアセンブリディレクトリに切り替えます。 うーん...それは動作します。 nm -D



の出力を、動作中および動作しないインストルメンテーションライブラリと比較しましたdr_client_main



関数があります。 もう一度比較します-本当にあります... drrunオプション-verbose -debug



指定されてい-verbose -debug



。 , , , . QtCreator .../build-afl-dr-Desktop_3fb6e5-



, "" — .../build-afl-dr-Desktop-



. , . , , , , , , , … (, - .)







:







 $ ~/soft/DynamoRIO-Linux-6.2.0-2/bin64/drrun -c libafl-dr.so -- ./example-bug Running forkserver... Cannot connect to fuzzer. 1 $ ~/soft/DynamoRIO-Linux-6.2.0-2/bin64/drrun -c libafl-dr.so -- ./example-bug 198<&0 199>/dev/null Running forkserver... xxxx 1 Incorrect spawn command from fuzzer.
      
      





, - . , AFL. dumb mode ( ), - forkserver:







 $ AFL_DUMB_FORKSRV=1 $AFL_PATH/afl-fuzz -i input -o output -n -- ./example-bug ... -   Fork server handshake failed -- ,   ... $ AFL_DUMB_FORKSRV=1 $AFL_PATH/afl-fuzz -i input -o output -n -- ~/soft/DynamoRIO-Linux-6.2.0-2/bin64/drrun -c libafl-dr.so -- ./example-bug ... -   Timeout while initializing fork server (adjusting -t may help) $ #   strace- ... $ AFL_DUMB_FORKSRV=1 $AFL_PATH/afl-fuzz -i input -o output -n -m 2048 -- ~/soft/DynamoRIO-Linux-6.2.0-2/bin64/drrun -c libafl-dr.so -- ./example-bug ...  ,     ( -m)
      
      











, . .







: forkserver , SIGSEGV. , , , , fork



libc



, , . , extension droption



. , dr_option_t<T>



, , , dr_client_main



droption_parser_t::parse_argv(...)



(, C++). , 7.0 RC1, 6.2.0-2 , CMake droption



- . . , , , , drutil



.







:







afl-annotations.h
 // Based on dr_annotations.h from DynamoRIO sources #ifndef _AFL_DR_ANNOTATIONS_H_ #define _AFL_DR_ANNOTATIONS_H_ 1 #include "annotations/dr_annotations_asm.h" /* To simplify project configuration, this pragma excludes the file from GCC warnings. */ #ifdef __GNUC__ # pragma GCC system_header #endif #define RUN_FORKSERVER() \ DR_ANNOTATION(run_forkserver) #ifdef __cplusplus extern "C" { #endif DR_DECLARE_ANNOTATION(void, run_forkserver, ()); #ifdef __cplusplus } #endif #endif
      
      





afl-annotations.c
 #include "afl-annotations.h" DR_DEFINE_ANNOTATION(void, run_forkserver, (), );
      
      





afl-dr.c
 #include <dr_api.h> #include <droption.h> #include <stdint.h> #include <unistd.h> #include <sys/wait.h> #include "afl-annotations.h" static const int FROM_FUZZER_FD = 198; static const int TO_FUZZER_FD = 199; typedef int (*fork_fun_t)(); #define EXIT_IF_FAILED(isOk, msg, code) \ if (!(isOk)) { \ dr_fprintf(STDERR, (msg)); \ dr_exit_process((code)); \ } static droption_t<bool> opt_private_fork(DROPTION_SCOPE_CLIENT, "private-fork", false, "Use fork function from the private libc", "Use fork function from the private libc"); static void parse_options(int argc, const char *argv[]) { std::string parse_err; if (!droption_parser_t::parse_argv(DROPTION_SCOPE_CLIENT, argc, argv, &parse_err, NULL)) { dr_fprintf(STDERR, "Incorrect client options: %s\n", parse_err.c_str()); dr_exit_process(1); } } static void start_forkserver() { // For references, see https://lcamtuf.blogspot.ru/2014/10/fuzzing-binaries-without-execve.html // and __afl_start_forkserver in llvm_mode/afl-llvm-rt.oc from AFL sources static bool forkserver_is_running = false; uint32_t unused_four_bytes = 0; uint32_t was_killed; if (!forkserver_is_running) { dr_printf("Running forkserver...\n"); forkserver_is_running = true; } else { dr_printf("Warning: Attempt to re-run forkserver ignored.\n"); return; } if (write(TO_FUZZER_FD, &unused_four_bytes, 4) != 4) { dr_printf("Cannot connect to fuzzer.\n"); return; } fork_fun_t fork_ptr; // Lookup the fork function from target application, so both DynamoRIO // and application's copy of libc know about fork // Currently causes crashes sometimes, in that case use the private libc's fork. if (!opt_private_fork.get_value()) { module_data_t *module = dr_lookup_module_by_name("libc.so.6"); EXIT_IF_FAILED(module != NULL, "Cannot lookup libc.\n", 1) fork_ptr = (fork_fun_t)dr_get_proc_address(module->handle, "fork"); EXIT_IF_FAILED(fork_ptr != NULL, "Cannot get fork function from libc.\n", 1) dr_free_module_data(module); } else { fork_ptr = fork; } while (true) { EXIT_IF_FAILED(read(FROM_FUZZER_FD, &was_killed, 4) == 4, "Incorrect spawn command from fuzzer.\n", 1) int child_pid = fork_ptr(); EXIT_IF_FAILED(child_pid >= 0, "Cannot fork.\n", 1) if (child_pid == 0) { close(TO_FUZZER_FD); close(FROM_FUZZER_FD); return; } else { int status; EXIT_IF_FAILED(write(TO_FUZZER_FD, &child_pid, 4) == 4, "Cannot write child PID.\n", 1) EXIT_IF_FAILED(waitpid(child_pid, &status, 0) >= 0, "Wait for child failed.\n", 1) EXIT_IF_FAILED(write(TO_FUZZER_FD, &status, 4) == 4, "Cannot write child exit status.\n", 1) } } } DR_EXPORT void dr_client_main(client_id_t id, int argc, const char *argv[]) { parse_options(argc, argv); EXIT_IF_FAILED( dr_annotation_register_call("run_forkserver", (void *)start_forkserver, false, 0, DR_ANNOTATION_CALL_TYPE_FASTCALL), "Cannot register forkserver annotation.\n", 1); }
      
      







, , . , , AFL, . , afl-gcc



afl-as.c



, afl-as.h



. AFL , , , technical details . ,







 cur_location = <COMPILE_TIME_RANDOM>; shared_mem[cur_location ^ prev_location]++; prev_location = cur_location >> 1;
      
      





( AFL). , , , 1 — . 64- shared memory, llvm_mode/afl-llvm-rt.oc



.







, DynamoRIO, . , ( , , / ) DynamoRIO . , dr_register_bb_event



. , , thread-local , , . , -, , :







 //  dr_client_main: lock = dr_mutex_create(); dr_register_thread_init_event(event_thread_init); dr_register_thread_exit_event(event_thread_exit); dr_register_bb_event(event_basic_block); dr_register_exit_event(event_exit);
      
      





:







 typedef struct { uint64_t scratch; uint8_t map[MAP_SIZE]; } thread_data; static void event_thread_init(void *drcontext) { void *data = dr_thread_alloc(drcontext, sizeof(thread_data)); memset(data, 0, sizeof(thread_data)); dr_set_tls_field(drcontext, data); } static void event_thread_exit(void *drcontext) { thread_data *data = (thread_data *) dr_get_tls_field(drcontext); dr_mutex_lock(lock); for (int i = 0; i < MAP_SIZE; ++i) { shmem[i] += data->map[i]; } dr_mutex_unlock(lock); dr_thread_free(drcontext, data, sizeof(thread_data)); }
      
      





… . , -, , DynamoRIO , , thread-specific memory pool. -, --- thread_data



, tls field.







, : event_basic_block(void *drcontext, void *tag, instrlist_t *bb, bool for_trace, bool translating)



, . , — instrlist_t *bb



. ( ) , , , amd64 aka x86_64. DynamoRIO , dr_register_bb_event



. , basic block:







DR constructs dynamic basic blocks, which are distinct from a compiler's classic basic blocks. DR does not know all entry points ahead of time, and will end up duplicating the tail of a basic block if a later entry point is discovered that targets the middle of a block created earlier, or if a later entry point targets straight-line code that falls through into code already present in a block.

, , — !







. , , :







 static dr_emit_flags_t event_basic_block(void *drcontext, void *tag, instrlist_t *bb, bool for_trace, bool translating) { instr_t *where = instrlist_first(bb); reg_id_t tls_reg = DR_REG_XDI, offset_reg = DR_REG_XDX; dr_save_arith_flags(drcontext, bb, where, SPILL_SLOT_1); dr_save_reg(drcontext, bb, where, tls_reg, SPILL_SLOT_2); dr_save_reg(drcontext, bb, where, offset_reg, SPILL_SLOT_3); dr_insert_read_tls_field(drcontext, bb, where, tls_reg); //     dr_restore_reg(drcontext, bb, where, offset_reg, SPILL_SLOT_3); dr_restore_reg(drcontext, bb, where, tls_reg, SPILL_SLOT_2); dr_restore_arith_flags(drcontext, bb, where, SPILL_SLOT_1); return DR_EMIT_DEFAULT; }
      
      





tls_reg



. COMPILE_TIME_RANDOM. , event_basic_block



: for_trace



translating



. , , , DynamoRIO . , , , for_trace = true



, . , , , translating = true



— , , , translating = false



. , DR_EMIT_STORE_TRANSLATIONS



DR_EMIT_DEFAULT



, , . , by design, , . , basic block.







 void *app_pc = dr_fragment_app_pc(tag); uint32_t cur_location = ((uint32_t)(uintptr_t)app_pc * (uint32_t)33533) & 0xFFFF;
      
      





, ASLR , . , " ", sysctl -w kernel.randomize_va_space=0



.







, . . API , . .:







  instrlist_meta_preinsert(bb, where, XINST_CREATE_load(drcontext, opnd_create_reg(offset_reg), OPND_CREATE_MEM64(tls_reg, offsetof(thread_data, scratch)))); instrlist_meta_preinsert(bb, where, INSTR_CREATE_xor(drcontext, opnd_create_reg(offset_reg), OPND_CREATE_INT32(cur_location))); instrlist_meta_preinsert(bb, where, XINST_CREATE_store(drcontext, OPND_CREATE_MEM32(tls_reg, offsetof(thread_data, scratch)), OPND_CREATE_INT32(cur_location >> 1))); instrlist_meta_preinsert(bb, where, INSTR_CREATE_inc(drcontext, opnd_create_base_disp(tls_reg, offset_reg, 1, offsetof(thread_data, map), OPSZ_1)));
      
      





, , , -- , Segmentation fault. , XINST_CREATE_load



XINST_CREATE_store



:







 $ ~/soft/DynamoRIO-Linux-6.2.0-2/bin64/drrun -c libafl-dr.so --private-fork -- ./example-bug Cannot get SHM id from environment. Creating dummy map. Running forkserver... Cannot connect to fuzzer. ^C $ #  load  store $ ~/soft/DynamoRIO-Linux-6.2.0-2/bin64/drrun -c libafl-dr.so --private-fork -- ./example-bug Cannot get SHM id from environment. Creating dummy map. <Application /path/to/example-bug (5058). Tool internal crash at PC 0x00005605e72bbeaa. Please report this at your tool's issue tracker. Program aborted. Received SIGSEGV at pc 0x00005605e72bbeaa in thread 5058 Base: 0x00005605e71c5000 Registers:eax=0x0000000000000001 ebx=0x00007ff6dfa12038 ecx=0x0000000000000048 edx=0x0000000000000000 esi=0x0000000000000049 edi=0x0000000000000005 esp=0x00007ff6dfa0ebb0 ebp=0x00007ff6dfa0ebc0 r8 =0x0000000000000003 r9 =0x0000000000000005 r10=0x0000000000000000 r11=0x00005605e72b70ef r12=0x0000000000000000 r13=0x0000000000000000 r14=0x000000000000000c r15=0x00005605e7368c50 eflags=0x0000000000010202 version 6.2.0, build 2 -no_dynamic_options -client_lib '/path/to/libafl-dr.so;0;"--private-fork"' -code_api -stack_size 56K -max_elide_jmp 0 -max_elide_call 0 -early_inject -emulate_brk -no_inline_ignored_syscalls -native_exec_default_list '' -no_native_exec_managed_code -no_indcall2direct 0x00007ff6dfa0ebc0 0x0000020803000000>
      
      





どうする , . -debug -loglevel 1 -logdir /tmp/dynamorio/



, - :







 ERROR: Could not find encoding for: mov (%rdi)[8byte] -> %rdx SYSLOG_ERROR: Application /path/to/example-bug (5192) DynamoRIO usage error : instr_encode error: no encoding found (see log) SYSLOG_ERROR: Usage error: instr_encode error: no encoding found (see log) (/dynamorio_package/core/arch/x86/encode.c, line 2417)
      
      





, : , — .







, , :







 $ $AFL_PATH/afl-fuzz -i input -o output -m 2048 -- ~/soft/DynamoRIO-Linux-6.2.0-2/bin64/drrun -c libafl-dr.so -- ./example-bug afl-fuzz 2.42b by <lcamtuf@google.com> [+] You have 4 CPU cores and 1 runnable tasks (utilization: 25%). [+] Try parallel jobs - see docs/parallel_fuzzing.txt. [*] Checking CPU core loadout... [+] Found a free CPU core, binding to #0. [*] Checking core_pattern... [*] Checking CPU scaling governor... [*] Setting up output directories... [+] Output directory exists but deemed OK to reuse. [*] Deleting old session data... [+] Output dir cleanup successful. [*] Scanning 'input'... [+] No auto-generated dictionary tokens to reuse. [*] Creating hard links for all input files... [*] Validating target binary... [-] Looks like the target binary is not instrumented! The fuzzer depends on compile-time instrumentation to isolate interesting test cases while mutating the input data. For more information, and for tips on how to instrument binaries, please see docs/README. When source code is not available, you may be able to leverage QEMU mode support. Consult the README for tips on how to enable this. (It is also possible to use afl-fuzz as a traditional, "dumb" fuzzer. For that, you can use the -n option - but expect much worse results.) [-] PROGRAM ABORT : No instrumentation detected Location : check_binary(), afl-fuzz.c:6894
      
      





… … ? : strace



, , drrun



. , afl-fuzz



, ? , , AFL — ? , , , , afl-fuzz.c:6894



, :







 f_data = mmap(0, f_len, PROT_READ, MAP_PRIVATE, fd, 0); // ... if (!qemu_mode && !dumb_mode && !memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) { // ... FATAL("No instrumentation detected"); }
      
      





-, : AFL __AFL_SHM_ID



— , . - , echo -ne "__AFL_SHM_ID\0" >> /path/to/drrun



, , : , , AFL_SKIP_BIN_CHECK



:







 $ #  -d      $ AFL_SKIP_BIN_CHECK=1 $AFL_PATH/afl-fuzz -i input -o output -m 2048 -d -- ~/soft/DynamoRIO-Linux-6.2.0-2/bin64/drrun -c libafl-dr.so -- ./example-bug
      
      











, AFL , , , total paths: 12



, 4-5. , , libc. example-bug



, , (, DynamoRIO, ). …







最適化



… , . , , " ". module_data_t *



, dr_client_main



, event_basic_block



, " ":







 module_data_t *main_module; //  dr_client_main: main_module = dr_get_main_module(); //  event_basic_block: if (!opt_instrument_everything.get_value() && !dr_module_contains_addr(main_module, pc)) { return DR_EMIT_DEFAULT; }
      
      





80 ( 2 ), output/queue



test



NU



— , .







, DynamoRIO -thread_private



, . tls field , , , immediate-:







 if (dr_using_all_private_caches()) { instrlist_meta_preinsert(bb, where, INSTR_CREATE_mov_imm(drcontext, opnd_create_reg(tls_reg), OPND_CREATE_INTPTR(dr_get_tls_field(drcontext)))); } else { dr_insert_read_tls_field(drcontext, bb, where, tls_reg); }
      
      





-thread_private



( -c libafl-dr.so



, DynamoRIO, ), 5 . , if (0 && dr_using_all_private_caches())



, — , DynamoRIO . :)







, : -disable_traces



— , , , , , , . … 10-15 . , , , , , test case-.







, , "" forkserver-: example-bug.c









 ungetc('1', stdin); char ch; fscanf(stdin, "%c", &ch); RUN_FORKSERVER();
      
      





… 15 . , , : , - libc forkserver, , - .







. :









… . , : " API / ?" , ", , !" — , . , , , , — , .







参照:









UPD: , ( qemu_mode



). API , 5. , , .








All Articles