
Windows x64 実行ファイル向け静的バイナリインストルメンテーションツール
peafl64 は、Windows の x64 PE を対象とした静的インストルメンテーションツールです。
静的インストルメンテーションとは、実行可能ファイルを編集し、特定の場所にコードを追加する手法です。
このインストルメンテーションは、バイナリ内のすべての基本ブロックの先頭にコードを追加し、実行フローを AFL 互換の方法で記録します。
これにより、ソースコードにアクセスすることなく、ユーザーモード(WinAFL を使用)およびカーネルモード(kAFL を使用)でバイナリをファジングできます。
Windows バイナリをファジングする方法は他にもありますが、このプロジェクトでは静的インストルメンテーションが最速の方法であるため、これに焦点を当てました。
このプロジェクトは、wmliang による pe-afl ツールをベースに、x64 サポートを追加したものです。
インストルメンテーションスクリプトには IDA の解析出力が必要です。
これを作成するには、IDA 内で提供されている ida_dumper.py スクリプトを実行してください。
このスクリプトには IDA 7 以上と python3.8 以上が必要です。
usage: pe_afl.py [-h] [-n] [-cb] [-tf] [-te THREAD_ENTRY] [-nt NTOSKRNL] [-e ENTRY] [-l ENLARGE] [-v] [-lf] pefile ida_dump
positional arguments:
pefile Target PE file for instrumentation
ida_dump dump.json from IDA (created by ida_dumper.py)
optional arguments:
-h, --help show this help message and exit
-n, --nop Instrument with NOPs for testing
-cb, --callback Instrument with a callback, which is in the helper driver that's written in C
-tf, --thread-filter Driver instrumentation that filters only on thread ID (must use "-te" with this option)
-te THREAD_ENTRY, --thread-entry THREAD_ENTRY
The address (RVA) of the thread's initialization function
-nt NTOSKRNL, --ntoskrnl NTOSKRNL
ntoskrnl.exe path for offset extraction (non-optional if instrumenting a driver)
-e ENTRY, --entry ENTRY
Inject code on entry point, ie. -e9090
-l ENLARGE, --enlarge ENLARGE
Enlarge factor for sections, default=4
-v, --verbose Print debug log
-lf, --logfile Print log to pe-afl64.log rather than stream
NOP によるユーザーモードバイナリのインストルメンテーション
PS pe-afl-64> python .\pe_afl.py -n C:\Work\cmd.exe C:\Work\cmd.exe.dump.json
[*] User-mode binary is being instrumented
[*] Single-thread instrument is on
[*] Preparing new sections
[*] Added section .text^
[*] Added section .cov
[*] Expanding relative jumps
[*] Expanded 3874 of 14353 branches
[*] Building address map
[*] Updating relative instructions
[*] Updating relocations...
[*] Updating Export table
[*] Updating load config
[*] Updating exception records
[*] Updating the PE headers
[*] Finalizing...
[*] Creating instrumented code
[*] Writing address mapping to C:\Work\cmd.exe.mapping.txt
[*] Updating jump tables
[*] Updated .text
...
[*] Removing temporary PE files
[*] Fixing PE checksum
[*] Instrumented binary saved to: C:\Work\cmd.instrumented.exe
プロセス ID フィルタリングによるカーネルモードバイナリのインストルメンテーション
python .\pe_afl.py -l 6 -nt "C:\Work\ntoskrnl.exe" "C:\Work\driver.sys" "C:\Work\driver.sys.dump.json"
スレッド ID フィルタリングと冗長出力によるカーネルモードバイナリのインストルメンテーション
python .\pe_afl.py -v -tf -te 0x40000 -l 6 -nt "C:\Work\ntoskrnl.exe" "C:\Work\ntoskrnl.exe" "C:\Work\ntoskrnl.exe.dump.json"
まず、お使いのマシンが BIOS と UEFI のどちらで起動しているかを確認する必要があります。
Hyper-V マシンの場合: Gen 1 マシンは BIOS ベース、Gen 2 マシンは UEFI ベースです。
マシンが BIOS ベースの場合は、winload.exe にパッチを適用する必要があります:
ImgpValidateImageHash を見つけますmov eax, edi を xor eax, eax に置き換えますbcdedit /set path \Windows\system32\winload2.exe を実行しますマシンが UEFI で起動している場合は、EfiGuard ユーティリティを使用して winload.efi にパッチを適用します。
Hyper-V マネージャーを使用する場合のコマンド早見表:
FS1:
cd EFI/Boot
Load EfiGuard.Dxe.efi
FS0:
\EFI\Boot\bootx64.efi
次に、任意のドライバーをインストルメントします。Windows マシンでインストルメント済みドライバーを読み込むには署名が必要で、 自己署名証明書でも OS の要求を満たすのに十分です:
# 管理者権限の PowerShell ターミナルで
$c = New-SelfSignedCertificate -Type CodeSigningCert -KeyUsage DigitalSignature -Subject 'CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US'
Set-AuthenticodeSignature .\driver.instrumented.sys -Certificate $c -Force
インストルメントしたドライバーがシステムによって既に使用されている場合は、次のコマンド(管理者として実行)を使用してドライバーのファイルを置き換えます:
set NAME=mydriver.sys
icacls %NAME% /save C:\windows\temp\%NAME%.icacls
takeown /F %NAME%
icacls %NAME% /grant Everyone:F
move %NAME% %NAME%.bak
move instrumented_driver.sys %NAME%
icacls . /restore C:\windows\temp\%NAME%.icacls
次に、以下を実行します:
bcdedit /set recoveryenabled no
bcdedit /set nointegritychecks on
shutdown -t 0 -r
WinAFL との統合は、提供されているヘッダーを使用してハーネスをコンパイルすることで行います。
ヘッダーと一緒に example.c があり、これはそれらの使用方法を示すサンプルプログラムです。
提供されているヘッダーは、WinAFL が既に提供しているヘッダーをわずかに変更したもので、Syzygy と呼ばれる別の静的バイナリインストルメンテーションツールと統合するためのものです。
kAFL との統合方法は非常にシンプルです。
通常、kAFL ハーネスは仮想マシン上で実行され、特別な「ハイパーコール」を使用してファザーのフロントエンドと通信します。
これらのハイパーコールは、カバレッジデータを IntelPT からロードして AFL ビットマップとして解析することを含む、さまざまな処理をファザーに指示します。
peafl64 によって IntelPT トレースが不要になるため、カバレッジデータをファザーに送信する方法を準備する必要があります。
そこで、VM 内で実行される(ユーザーモード)ハーネスがヘルパードライバーを使用して収集したカバレッジデータを送信できるようにする「ハイパーコール」を qemu と kvm に拡張しました。
これは特に ESXi 上のセットアップに関するものですが、AWS などの他の仮想化プラットフォームにも関連するはずです。
セットアップは非常に単純です:
install.sh qemu ステップの代わりに install.sh qemu_sbi を実行しますkAFL と peafl64 を使用してファジングするには、ファジングマシンをセットアップする必要があります:
その他は、私たちの kAFL フォークでのファジングは通常の kAFL でのファジングと同じです。
手順の概要:
まず、IDA を使用して、対象の命令と場所を見つけて解析します。
この解析により、そのすべての情報を json 形式で含む dump.json ファイルが作成されます。
instrument.py にはインストルメンテーションロジックが含まれており、フロー自体は process_pe 関数で概説されています。
バイナリをインストルメントするには、その実行可能セクションを複製します。そこにすべてのインストルメントコードが配置されます。
次に、すべての相対命令を処理し、それらをどのように処理する必要があるか、また処理する必要があるかどうかを判断します。
アドレス X からアドレス Y への短い jmp があるとします。X と Y の間にインストルメンテーションコードを挿入すると、ターゲットはアドレス Z(Z = Y + len(instrumentation))になります。
つまり、ジャンプを変更する必要があります。
アドレス Z が短い jmp の範囲外になった場合、ジャンプを short から far に変換する必要があります。(instrument.py:expand_relative_instructions)
例えば:
short jmp 0x57 (eb 55) -> near jmp 0x604 (e9 ff 05 00 00)これは call、loop、条件付きジャンプなどの命令にも当てはまります。
処理が必要なもう 1 つの一般的なケースは、x64 アセンブリで導入された rip 相対命令です:
call [rip+0x1000]リロケーションテーブル、エクスポートテーブル、ロード構成、TLS ディレクトリ、例外レコードを、インストルメントされたコードを指すように更新します。
更新は、元のセクションのすべてのアドレスを、対応するインストルメント済みのアドレスに変更することで行われます。(instrument.py:update_addr)
PE 構造への変更(追加されたセクション、変更されたエントリポイントと PE サイズ)を反映するようにヘッダーが更新されます。(instrument.py:update_pe_headers)
さらに、peafl64 は Windows カーネルをインストルメントすることもできます。これを実現するために、Dynamic Value Relocation Table(DVRT)と SSDT を解析・更新し、PatchGuard の詳細を処理します。
DVRT はコンパイラが生成するテーブルで、PE のロード時に変更が必要なアドレスの位置を記述しています。
これは KASLR を改善するために使用され、Spectre の脆弱性を緩和するのに役立ちます(1、2、3)。
DVRT は、テーブルの構造を模倣した一連のクラスを使用して解析されます(drt.py; instrument.py:get_updated_dynamic_relocs)