mcpField
A
anti-debugging-techniques

anti-debugging-techniques

github.com/yaklangセキュリティ2.6k3.7k 回インストール6 週間前に更新
リポジトリ

anti-debugging-techniques について

Linux と Windows のバイナリにおけるアンチデバッグ機構を分析・回避するセキュリティスキルです。検出手法、回避策、分析ツールをまとめています。

主な機能

  1. 01Linux の ptrace とプロセス検査
  2. 02Windows API と PEB の検査
  3. 03タイミングとシグナルの検出
  4. 04TLS コールバックと VEH の手法
  5. 05回避策とツールの参照情報

活用シーン

  1. 01デバッガー起動時に終了するバイナリの分析
  2. 02マルウェアのアンチデバッグ検査の調査
  3. 03CTF のアンチデバッグ課題への対応

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/yaklang/hack-skills --skill anti-debugging-techniques

インストール後、Claude Code で/anti-debugging-techniquesを入力して読み込まれたか確認します。

同梱ファイルANTI_DEBUG_MATRIX.md

詳細

スキル:アンチデバッグ技術 — 検出と回避のプレイブック

AI LOAD INSTRUCTION:Linux と Windows における高度なアンチデバッグ技術を扱います。ptrace、PEB フラグ、NtQueryInformationProcess、タイミング攻撃、シグナルベースの検出、TLS コールバック、VEH のトリック、およびそれぞれに対応する回避方法を網羅しています。基本モデルは、ユーザーモードとカーネルモードの検出の違いや、それぞれに適したパッチ方法を見落としがちです。

0. 関連するルーティング

  • バイナリが制御フローのフラット化、VM 保護、または文字列暗号化も使用している場合は、code-obfuscation-deobfuscation
  • アンチデバッグ処理がカスタム VM ディスパッチャー内にある場合は、vm-and-bytecode-reverse
  • アンチデバッグチェックをシンボリックに完全にスキップしたい場合は、symbolic-execution-tools
高度なリファレンス

以下が必要な場合は、ANTI_DEBUG_MATRIX.md も読み込んでください:

  • 技法 × OS × 検出方法 × 回避方法の完全な相互参照マトリックス
  • 技法ごとの信頼性評価と誤検知に関する注意事項
  • ツールの互換性一覧(GDB、x64dbg、WinDbg、Frida、ScyllaHide)
すぐに使える回避策
検出クラス最初の回避策代替策
ptrace ベース(Linux)LD_PRELOAD で ptrace() をフック → 0 を返すトレーサーを隠すカーネルモジュール
PEB.BeingDebugged(Windows)fs:[0x30]+0x2 の PEB バイトにパッチを適用ScyllaHide の自動パッチ
タイミングチェック(rdtsc)rdtsc の後に条件付き BP を設定し、レジスターを修正Frida で rdtsc の戻り値をフック
IsDebuggerPresent呼び出しを NOP にする/戻り値をフックx64dbg の組み込み隠蔽機能
INT 2D / UD2 例外VEH を設定して適切に処理TitanHide ドライバー

1. Linux のアンチデバッグ技術

1.1 ptrace(PTRACE_TRACEME)

古典的な自己アタッチ方法です。プロセスは ptrace(PTRACE_TRACEME, 0, 0, 0) を呼び出します。デバッガーがすでにアタッチされている場合、呼び出しは失敗します(-1 を返します)。

if (ptrace(PTRACE_TRACEME, 0, 0, 0) == -1) {
    exit(1); // debugger detected
}

回避方法:

方法手順
LD_PRELOAD シム共有ライブラリーをコンパイルします:long ptrace(int r, ...) { return 0; } を定義し、LD_PRELOAD を設定します
バイナリパッチptrace の呼び出しを NOP にするか、戻り値のチェックにパッチを適用します
GDB catchcatch syscall ptrace → 戻り時に $rax を 0 に変更します
カーネルモジュールsys_ptrace をフックして複数のトレーサーを許可します
1.2 /proc/self/status — TracerPid
FILE *f = fopen("/proc/self/status", "r");
// parse TracerPid: if non-zero → debugger attached

回避策:/proc/self に FUSE ファイルシステムをマウントするか、fopen/fread を LD_PRELOAD でフックして、TracerPid を 0 にフィルタリングします。

1.3 タイミングチェック(rdtsc / clock_gettime)

2 点間の経過時間を測定します。デバッガーでのシングルステップ実行は、目立った遅延を引き起こします。

rdtsc
mov ebx, eax       ; save low 32 bits
; ... protected code ...
rdtsc
sub eax, ebx
cmp eax, 0x1000    ; threshold
ja  debugger_detected

回避策:2 回目の rdtsc の後にハードウェアブレークポイントを設定し、比較を通過するように eax を変更します。または、Frida を使ってタイミング関数を置き換えます。

1.4 シグナルベースの検出(SIGTRAP)
volatile int caught = 0;
void handler(int sig) { caught = 1; }
signal(SIGTRAP, handler);
raise(SIGTRAP);
if (!caught) exit(1); // debugger swallowed the signal

デバッガーがアタッチされている場合、SIGTRAP はハンドラーに配信されず、デバッガーによって消費されます。回避策:GDB で handle SIGTRAP nostop pass を使い、シグナルを転送します。

1.5 /proc/self/maps と LD_PRELOAD の検出

デバッガーや計装に特徴的な、挿入されたライブラリーやメモリー領域をチェックします。

FILE *f = fopen("/proc/self/maps", "r");
while (fgets(buf, sizeof(buf), f)) {
    if (strstr(buf, "frida") || strstr(buf, "LD_PRELOAD"))
        exit(1);
}

回避策:fopen("/proc/self/maps") をフックしてフィルタリング済みの内容を返すか、Frida のエージェントライブラリーの名前を変更します。

1.6 環境変数のチェック

一部の保護機能は、LD_PRELOAD、LINES、COLUMNS(GDB のターミナルが設定)、またはデバッガー固有の環境変数をチェックします。

回避策:起動前に疑わしい環境変数の設定を解除するか、getenv() をフックします。


2. Windows のアンチデバッグ技術

2.1 IsDebuggerPresent / CheckRemoteDebuggerPresent
if (IsDebuggerPresent()) ExitProcess(1);

BOOL debugged = FALSE;
CheckRemoteDebuggerPresent(GetCurrentProcess(), &debugged);
if (debugged) ExitProcess(1);

回避策:kernel32!IsDebuggerPresent をフックして 0 を返すか、PEB に直接パッチを適用します。

2.2 PEB フラグ
フィールドオフセット(x64)デバッグ時の値通常の値
BeingDebuggedPEB+0x0210
NtGlobalFlagPEB+0xBC0x70 (FLG_HEAP_*)0
ProcessHeap.FlagsHeap+0x400x400000620x00000002
ProcessHeap.ForceFlagsHeap+0x440x400000600
mov rax, gs:[0x60]    ; PEB
movzx eax, byte [rax+0x02]  ; BeingDebugged
test eax, eax
jnz debugger_detected

回避策:4つすべてのフィールドをゼロにします。ScyllaHide が自動的にこれを行います。

2.3 NtQueryInformationProcess
情報クラス値デバッグ時の戻り値
ProcessDebugPort0x070 以外のポート
ProcessDebugObjectHandle0x1E有効なハンドル
ProcessDebugFlags0x1F0(反転されています!)

回避策:ntdll!NtQueryInformationProcess をフックし、情報クラスごとにクリーンな値を返します。

2.4 ハードウェアブレークポイントの検出
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
GetThreadContext(GetCurrentThread(), &ctx);
if (ctx.Dr0 || ctx.Dr1 || ctx.Dr2 || ctx.Dr3)
    ExitProcess(1);

回避策:GetThreadContext をフックして DR0~DR3 をゼロにするか、事前に NtSetInformationThread(ThreadHideFromDebugger) を使用します(皮肉なことに、これはアンチデバッグ手法そのものです)。

2.5 INT 2D / INT 3 / UD2 の例外トリック

INT 2D はカーネルのデバッグサービス割り込みです。デバッガーがない場合は STATUS_BREAKPOINT が発生します。デバッガーがある場合は動作が異なり(バイトがスキップされます)。

xor eax, eax
int 2dh
nop          ; debugger may skip this byte
; ... divergent execution path ...

回避策:VEH で処理するか、割り込み命令にパッチを適用します。

2.6 TLS コールバック

TLS コールバックは main() / WinMain() より前に実行されます。ここに配置されたアンチデバッグチェックは、デバッガーの初期ブレークより前に実行されます。

回避策:x64dbg では、「Break on TLS Callbacks」オプションを設定します。WinDbg では、sxe ld を使ってモジュールの読み込み時にブレークします。

2.7 NtSetInformationThread(ThreadHideFromDebugger)
NtSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, NULL, 0);

この呼び出しの後、スレッドはデバッガーから見えなくなり、ブレークポイントとステップ実行が何も表示されずに機能しなくなります。

回避策:ThreadInfoClass == 0x11 の場合に NOP を実行するよう、NtSetInformationThread をフックします。

2.8 VEH ベースの検出

デバッガー固有の動作(シングルステップフラグ、デバッガーのセマンティクスを伴うガードページ違反)について EXCEPTION_RECORD をチェックする Vectored Exception Handler を登録します。

回避策:VEH のロジックを理解し、例外チェーンが非デバッグ実行時と同じように動作することを確認します。


3. 高度な多層テクニック

3.1 自己デバッグ(fork + ptrace)

プロセスは子プロセスを fork し、その子プロセスが ptrace を介して親プロセスにアタッチします。外部デバッガーがすでにアタッチされている場合、子プロセスの ptrace は失敗します。

pid_t child = fork();
if (child == 0) {
    if (ptrace(PTRACE_ATTACH, getppid(), 0, 0) == -1)
        kill(getppid(), SIGKILL);
    else
        ptrace(PTRACE_DETACH, getppid(), 0, 0);
    _exit(0);
}
wait(NULL);

回避策:fork() の戻り値にパッチを適用するか、監視用の子プロセスを終了またはデタッチします。

3.2 マルチプロセスのデバッグ検出

親プロセスと子プロセスが互いのデバッグ状態を協調して確認し、相互監視パターンを形成します。

回避策:両方のプロセスにアタッチします(GDB の follow-fork-mode を使うか、2つのデバッガーインスタンスを使用します)。

3.3 複数のチェックポイントを用いたタイミングベースの検出

複数の関数にタイミングチェックを分散させ、累積したずれを比較します。合計値がしきい値を超えたままになるため、単一のパッチでは失敗します。

回避策:Frida の Interceptor.replace を使い、すべてのタイミングソース(rdtsc、clock_gettime、QueryPerformanceCounter)を、制御された値を返すように置き換えます。

3.4 ナノマイト / INT3 パッチ

元の条件付きジャンプが INT3(0xCC)に置き換えられます。親デバッガープロセスが各 INT3 を処理し、条件を評価して、子プロセスの EIP を適切に設定します。

回避策:すべての INT3 ハンドラーをトレースして元のジャンプテーブルを再構築し、バイナリにパッチを適用します。


4. 対抗ツール

ツールプラットフォーム機能
ScyllaHideWindows(x64dbg/IDA/OllyDbg)PEB への自動パッチ、NtQuery* のフック、スレッドの隠蔽、タイミングの修正
TitanHideWindows(カーネルドライバー)すべてのユーザーモードチェックをカーネルレベルで隠蔽
Fridaクロスプラットフォームスクリプトベースで任意の関数をフックし、タイミングを偽装
LD_PRELOAD shimsLinux読み込み時に ptrace、getenv、fopen を置き換え
GDB scriptsLinuxcatch syscall、条件付き BP、レジスターの修正
Qilingクロスプラットフォームフルシステムエミュレーション、すべてのハードウェアチェックを回避

5. 体系的な回避方法

Step 1: Static analysis — identify anti-debug calls
  └─ Search for: ptrace, IsDebuggerPresent, NtQuery, rdtsc,
     GetTickCount, SIGTRAP, INT 2D, TLS directory entries

Step 2: Classify each check
  ├─ API-based → hook or patch the call
  ├─ Flag-based → patch PEB/proc fields
  ├─ Timing-based → spoof time source
  ├─ Exception-based → forward/handle exception correctly
  └─ Multi-process → handle both processes

Step 3: Apply bypass (order matters)
  1. Load ScyllaHide / set LD_PRELOAD (covers 80% of checks)
  2. Handle TLS callbacks (break before main)
  3. Patch remaining custom checks (Frida or binary patch)
  4. Verify: run with breakpoints, confirm no premature exit

Step 4: Validate bypass completeness
  └─ Set BP on ExitProcess/exit/_exit — if hit unexpectedly,
     a check was missed → trace back from exit call

6. 判断フローチャート

Binary exits/crashes under debugger?
│
├─ Crashes immediately before main?
│  └─ TLS callback anti-debug
│     └─ Enable TLS callback breaking in debugger
│
├─ Crashes at startup?
│  ├─ Linux: check for ptrace(TRACEME)
│  │  └─ LD_PRELOAD hook or NOP patch
│  └─ Windows: check IsDebuggerPresent / PEB
│     └─ ScyllaHide or manual PEB patch
│
├─ Crashes after some execution?
│  ├─ Consistent crash point → API-based check
│  │  ├─ NtQueryInformationProcess → hook return values
│  │  ├─ /proc/self/status → filter TracerPid
│  │  └─ Hardware BP detection → hook GetThreadContext
│  │
│  ├─ Variable crash point → timing-based check
│  │  └─ Hook rdtsc / QueryPerformanceCounter
│  │
│  └─ Crash on breakpoint hit → exception-based check
│     ├─ INT 2D / INT 3 trick → handle in VEH
│     └─ SIGTRAP handler → GDB: handle SIGTRAP pass
│
├─ Debugger loses control silently?
│  └─ ThreadHideFromDebugger
│     └─ Hook NtSetInformationThread
│
├─ Child process detects and kills parent?
│  └─ Self-debugging (fork+ptrace)
│     └─ Patch fork() or handle both processes
│
└─ All basic bypasses applied but still detected?
   └─ Multi-layer / custom checks
      ├─ Use Frida for comprehensive API hooking
      ├─ Full emulation with Qiling
      └─ Trace all calls to exit/abort to find remaining checks

7. CTF と実環境でのパターン

一般的なCTFのアンチデバッグパターン
パターン頻度簡単な回避方法
単独の ptrace(TRACEME)非常によくあるLD_PRELOAD のワンライナー
IsDebuggerPresent + NtGlobalFlagよくあるScyllaHide
ループ内でのrdtscによるタイミング計測そこそこある比較しきい値をパッチ
signal(SIGTRAP) + raiseそこそこあるGDBのシグナル転送
fork + ptraceウォッチドッグまれだが厄介子プロセスを終了するか、forkをパッチ
NanomiteによるINT3置換まれ(高度)ジャンプテーブルを再構築
実環境での保護
プロテクター主なアンチデバッグ機能推奨ツール
VMProtectPEB + タイミング計測 + ドライバーレベルTitanHide + ScyllaHide
Themida多層PEB + SEH + タイミング計測ScyllaHide + 手動パッチ
Enigma ProtectorIsDebuggerPresent + CRCチェックx64dbg + ScyllaHide
UPX(カスタム)通常なし(パッキングのみ)標準的なアンパック
カスタム(マルウェア)多種多様解析にはFrida + Qiling

8. クイックリファレンス — 回避チートシート

Linuxワンライナー
# LD_PRELOAD anti-ptrace
echo 'long ptrace(int r, ...) { return 0; }' > /tmp/ap.c
gcc -shared -o /tmp/ap.so /tmp/ap.c
LD_PRELOAD=/tmp/ap.so ./target

# GDB: catch and bypass ptrace
(gdb) catch syscall ptrace
(gdb) commands
> set $rax = 0
> continue
> end
Fridaによるアンチデバッグ回避(クロスプラットフォーム)
// Hook IsDebuggerPresent (Windows)
Interceptor.replace(
  Module.getExportByName('kernel32.dll', 'IsDebuggerPresent'),
  new NativeCallback(() => 0, 'int', [])
);

// Hook ptrace (Linux)
Interceptor.replace(
  Module.getExportByName(null, 'ptrace'),
  new NativeCallback(() => 0, 'long', ['int', 'int', 'pointer', 'pointer'])
);

// Timing spoof
Interceptor.attach(Module.getExportByName(null, 'clock_gettime'), {
  onLeave(retval) {
    // manipulate timespec to hide debugger delay
  }
});
x64dbg ScyllaHideのクイックセットアップ
  1. Plugins → ScyllaHide → Options
  2. チェック:PEB BeingDebugged、NtGlobalFlag、HeapFlags
  3. チェック:NtQueryInformationProcess(すべてのクラス)
  4. チェック:NtSetInformationThread(HideFromDebugger)
  5. チェック:GetTickCount、QueryPerformanceCounter
  6. 適用 → デバッグセッションを再起動

コメント