
リバースエンジニアリングからソフトウェアを保護するために設計された、Windows向けのステルスで完全にsyscallを用いたC/C++ユーザーランド向けアンチデバッグライブラリ
antidbg は、Windows 向けの x64 ユーザーモード アンチデバッグ ライブラリであり、ソフトウェアをデバッグから保護するために設計されています。
このライブラリは、アンチデバッグ保護の基盤として考えてください。唯一の防御手段として考えないでください。
このライブラリは:
脅威モデルは、デバッガがこの保護システムで保護されたソフトウェアをあらゆる特権レベルから傍受する可能性を想定しており、特権レベルが高いほど検出の効果は低くなります。
このソフトウェアは、以下の方法で自明な CPL > 0 の傍受を回避するように強化されています:
RAX 内のシステムコール スプーフィングを防止します。.text セクションに対するあらゆる種類のインラインパッチを検出します。VEH、SEH、LPTOP_LEVEL_EXCEPTION_FILTER) とソフトウェア ブレークポイントを分析します。PAGE_GUARD リダイレクト動作をテストします。TLS callbacks でプロセス エントリポイントをデバッガのアタッチから保護し、スレッド開始アドレスの検査を実行します。DbgBreakPoint や DbgUiRemoteBreakin などのデバッガ エントリポイントにトラップを作成し、プロセスをクラッシュさせるかリターンさせます。チェックを実行する唯一の方法がシステムコール不可能なエクスポート関数を使用することである場合、その関数は手動でリバースエンジニアリングされ、保護スレッドのモジュール アドレス空間で実行されるように再構築されます。すべてのユーザーモード メモリ構造は、API を使用する代わりに直接メモリ イントロスペクションで走査されます。
保護ルーチンは、ユーザーモード フックに対する保護なしで一部の実行パスを明示的に残し、メモリ ハニーポットとして機能します。これらは状態比較と攻撃者の混乱のために使用されます。
保護ルーチンは疑似ランダムに実行されます。エントロピーは、純粋な ハードウェアベースの ASLR、スタックの動作、および少しの数学によって決定されます。カーネルを呼び出したり、ユーザーモード API を使用したり、ハイパーバイザによる条件付きまたは無条件の終了命令を発行したりすることはありません。
セキュリティ違反が検出されると、保護システムは STATUS_SXS_EARLY_DEACTIVATION を伴う INT 29h を呼び出すことで現在のプロセスをクラッシュさせ、すべての例外ハンドラをバイパスします。場合によっては、現在のプロセスを終了するために APC をカーネルにキューイングすることもあります。
このライブラリのメイン エントリポイント (abdg.c) にあり、順番に説明します。
要約された説明です。特定の検出は、ここで説明されているよりも多くの追加/サブチェックを実行する場合があります。
他の検出コンセプトのソースコードは antidebug\archived フォルダにあります。
1. kernel32 のエクスポート関数を使用して、PEB の BeingDebugged フィールドを読み取ります。2. エクスポート IsRemoteDebuggerPresent を呼び出して、ターゲット プロセスが自身のコンテキスト外からデバッグされているかどうかを確認します。3. INT 2D ソフトウェア割り込みを実行し、その命令に続くバイトがスキップされ、EXCEPTION_BREAKPOINT ハンドラを経由しないかどうかを確認します。4. ブレークポイント割り込み INT 3D を実行し、例外が傍受されるか、通常どおり通過するかを監視します。5. ICE/0xF1 を呼び出し、EXCEPTION_SINGLE_STEP を発生させ、デバッガがこの例外を RFlags レジスタのシングル ステップ ビットを設定して命令を実行することで生成される通常の例外と見なすかどうかを確認します。6. フラグを設定してスタック セグメント レジスタをプローブし、デバッガが からそれをクリアするかどうかを確認します。通常、デバッガは各デバッガ イベントが配信された後にトラップ フラグをクリアします。例:
#include "adbg.h"
int main() {
StartDebugProtection();
return 0;
}
例:
#include "adbg.h"
int main() {
if (isProgramBeingDebugged()) {
printf("Debugger detected.\n");
}
else {
printf("No debugger was detected.\n");
}
return 0;
}
テスト ランナー実行可能ファイルをビルドするには、-DBUILD_EXAMPLE=ON を有効にします。これにより example/main.c がコンパイルされ、コア ライブラリにエントリ ポイントを混入させることなく antidebug に対してリンクされます。
.sln ファイルを開きます)。antidebug_runner をスタートアップ プロジェクトとして設定し、Build をクリックします (または F5 を押して実行します)。プロジェクト ルートから:
cmake -B build -S . -DBUILD_EXAMPLE=ON
cmake --build build --config Release
実行可能ファイルは次の場所にあります:
build/Release/antidebug_runner.exebuild/antidebug_runner.exeデバッグ ビルドに関する注意: デバッグ モード (
--config Debug) でコンパイルすると、core/debug.cを介してコンソール/デバッガ診断ログが有効になります。リリース モードではログが完全に削除されます。
デフォルトでは、CMake は 静的ライブラリ (antidebug.lib または libantidebug.a) を生成します。動的リンク ライブラリ (DLL) をビルドするには、-DBUILD_SHARED_LIBS=ON を渡します。
cl.exe)Visual Studio ジェネレータを使用:
# Static Library (.lib)
cmake -B build -S . -A x64 -DBUILD_SHARED_LIBS=OFF
cmake --build build --config Release
# Dynamic Library (.dll + import .lib)
cmake -B build -S . -A x64 -DBUILD_SHARED_LIBS=ON
cmake --build build --config Release
clang-cl で Ninja を使用:
# Ensure clang-cl is in your PATH or run from the VS x64 Native Tools Command Prompt
cmake -B build -S . -G Ninja -DCMAKE_C_COMPILER=clang-cl -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF
cmake --build build
64 ビット LLVM-MinGW シェル (PATH に x86_64-w64-mingw32-clang) を起動し、Ninja を使用します:
# Static Library (.a)
cmake -B build -S . -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF
cmake --build build
# Shared Library (.dll)
cmake -B build -S . -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON
cmake --build build
コンパイル済みライブラリとヘッダーをローカル プレフィックスにインストールするには:
cmake --install build --prefix "C:/local/antidebug"
これにより以下が生成されます:
C:/local/antidebug/
├── bin/
│ └── antidebug.dll (if BUILD_SHARED_LIBS=ON)
├── lib/
│ └── antidebug.lib (or libantidebug.a)
└── include/
└── antidebug/
├── adbg.h
└── ...
このプロジェクトの悪意のある使用によって引き起こされたいかなる損害についても、私は責任を負いません。
BUILD ドキュメントの生成は AI によって行われました。問題があれば報告してください。
ライセンス: MIT
TFRFLAGS7. プレフィックスベースの命令フローのエッジ ケースを使用し、PREFIX REP として逆アセンブルされる 0xF3 0x64 が 0xF1 スキップを強制するかどうかを確認します。8. トラップ フラグを設定して pushfd mov dword ptr [esp], 0x100 popfd nop を呼び出した後、EXCEPTION_SINGLE_STEP ハンドラに入る代わりに nop に到達するかどうかを確認します。9. DBG_CONTROL_C と DBG_RIPEXCEPTION イベントを発生させ、例外が傍受され、SEH を経由しないかどうかを確認します。10. アタッチされたデバッグ オブジェクト ハンドルを確認し、NtQueryInformationProcess で ProcessDebugObjectHandle をクエリします。11. NtQuerySystemInformation で SystemKernelDebuggerInformation を使用してカーネル デバッガの存在をクエリし、KUSER_SHARED_DATA メモリ ページを直接読み取って KdDebuggerEnabled フィールドを確認します。さらに、カーネル タイマー ISR が非同期にティックしているかどうかを確認します。12. NT グローバル フラグを読み取り、FLG_HEAP_ENABLE_TAIL_CHECK (0x10)、FLG_HEAP_ENABLE_FREE_CHECK (0x20)、FLG_HEAP_VALIDATE_PARAMETERS (0x40) のマスクを確認します。13. ProcessDebugFlags を検査し、デバッグが有効か抑制されているかを推測します。14. プロセス ハンドルを複製し、デバッガがハンドルに触れるか、ハンドルを継承するか、またはそれらを再オープン/複製するかを確認します。保護された複製ハンドルを作成し、それを再度クリーンに複製できるか?15. 親プロセス チェーンを調べて、デバッガ ランチャーや vsjitdebugger、x64dbg などの疑わしい祖先を特定します。16. エクスポートを使用せずにデバッグ PEB フィールドを確認し、ベース (__readgsqword(0x60)) からオフセット *(BYTE*)((uintptr_t)peb + 2 で直接読み取ります。17. 現在のプロセスの ProcessDebugPort をクエリします。18. スレッド デバッグ レジスタ (Dr0–Dr7) を検査してハードウェア ブレークポイントを確認します。19. ハニーポットを配置して変更を監視することで、仮想メモリがデバッガによってヒットされたかどうかを確認します。20. プロセス ハンドルとウィンドウ ハンドルで 2 つの無効なハンドル クローズ テストを実行し、ERROR_INVALID_WINDOW_HANDLE と EXCEPTION_INVALID_HANDLE が傍受されないかどうかを監視します。21. デバッグ オブジェクトがデバッガによって傍受され、ハンドル ストリッピングが発生するかどうかを確認します。22. アクセスがデバッガによってフィルタリングまたはリダイレクトされているかどうかを明らかにする方法でプロセスを開こうとします。23. HANDLE_FLAG_PROTECT_FROM_CLOSE としてマークされたミューテックス ハンドルを直接クローズできるかどうかを確認します。24. SysDbgGetTriageDump を指定して NtSystemDebugControl を呼び出し、カーネル デバッガが呼び出しをブロックするか、呼び出しをスプーフィングするがメモリ バッファに触れないかどうかを確認します。25. 自身のスタックのメモリ読み取りがインストルメントまたは傍受されているかどうかを確認します。26. プロセスがデバッガによって作成されたホワイトリストに登録されていないジョブ オブジェクト内にあるかどうかを確認します。27. メモリ ブレークポイント スタイルのアクセス テストを使用し、通常はウォッチポイントがアクティブな場合にページ ガードまたはフォールト動作を期待します。28. ページ例外ブレークポイント シナリオをトリガーし、例外チェーンが STATUS_GUARD_PAGE_VIOLATION を正しく配信するかどうかを検査します。29. 実行タイミングを測定して、シングル ステッピング、ブレークポイント、または動的バイナリ インストルメンテーション/JIT 再コンパイルによって導入されるオーバーヘッドを検出します。30. デバッガ ツールに関連付けられたウィンドウ/クラス/タイトルを列挙して、デバッガ ウィンドウまたは UI アーティファクトを検索します。31. デバッガが自身のシングル ステッピングを実行するために DR7 の以前に設定された LBR/BTF ビットをクリアするかどうかを確認し、その結果 ExceptionInformation 配列が空になるか、またはデバッガが LBR を有効のままにして icebp によって呼び出された EXCEPTION_SINGLE_STEP を傍受することを決定した場合にカーネルモード分岐アドレスが検出されるかどうかを確認します。32. ヒープを直接走査し、0xABABABAB と 0xFEEEFEEE のマジック値を確認します。実質的に 12 と同じですが、フック可能な Heap API を使用します。33. 以前に共有されていたページがデバッガによって触れられたかどうかを確認することで、仮想メモリで Copy-On-Write が発生したかどうかを確認します。34. コンソール イベント (CTRL_C_EVENT) を送信し、デバッガがそれを傍受して制御ハンドラへの配信を変更するか、または DBG_CONTROL_C を発生させるかどうかを確認します。35. インジェクション試行のためにプロセスが外部から一時停止されているかどうかを確認します。自身のプロセスを指す NtResumeProcess への外部呼び出しを検出します。36. 異なる SE_DEBUG_PRIVILEGE 特権レベルで NtSetDebugFilterState を呼び出し、カーネル デバッガがアクセスを誤って処理するかどうかを確認します。37. デバイス オブジェクトを分析し、カーネル デバッガがファイル読み取りを傍受するかどうかも確認します。38. カーネル デバッガとカーネル自体の両方に対して ContextFlags 構造体を読み取るスレッドを競合させます。DEBUG_REGISTERS が削除されているか、Dr0 が設定されていないかどうかを確認します。39. 仮想セクションの非常に大きなビューを作成してマッピングすることで一部のデバッガをフリーズさせます。NtMapViewOfSection への呼び出しが改ざんされているかどうかを検出します。40. 未実装のシステムコール (エミュレータで一般的) を確認します。41. 4 つの連続する NOP に DR0 から DR3 までの 4 つのハードウェア実行ブレークポイントを設定し、VEH を介して結果の EXCEPTION_SINGLE_STEP 配信をカウントします。42. レガシー Windows XP/2000 での OutputDebugString の副作用、およびデバッガが傍受できる DBG_PRINTEXCEPTION_{C,WIDE_C} 例外を介してデバッガの関与をテストします。43. 自身のディスク上のイメージの最初のバイトが 0xCC であるかどうかを確認し、デバッガの下で LoadLibrary がファイルを非排他的にアクセス可能なままにする Windows ローダー ファイル ハンドル動作を悪用します。