メモリ内コードパッチの代替として、ハードウェアブレークポイント(CPUデバッグレジスタ)ベースの関数フックを実証するセキュリティリサーチ用Proof-of-Concept(POC)。
目的と範囲
このリポジトリは、防御的なセキュリティリサーチ、レッドチーム/パープルチーム教育、検知エンジニアリング、およびWindows内部構造の学術研究のためにのみ公開されています。攻撃者がプロセッサのデバッグレジスタを悪用してユーザーモードのセキュリティテレメトリを無効化する方法と、同様に重要なこととして、防御側がそのような手法を検知するために何を監視すべきかを示しています。著者は、このコードの誤用について一切の責任を負いません。明示的な許可なくシステムに対してこの手法を使用することは違法であり、ほとんどの法域で関連するコンピュータ詐欺および悪用防止法に違反します。所有していない環境、または明示的な書面によるテスト許可を得ていない環境にこれを展開しないでください。
mora_hwbp.c は、ターゲットプロセス(例: PowerShellホスト)に読み込まれる/インジェクションされると、プロセス内のすべてのスレッドのアーキテクチャデバッグレジスタ(DR0–DR7)に格納されたCPUハードウェアブレークポイントのみを通じて、4つのユーザーモード関数をフックするDLLを実装します。
プロセスごとの**ベクタード例外ハンドラ(VEH)**が、デバッグレジスタによって発生するEXCEPTION_SINGLE_STEP(0x80000004)フォールトを受け取り、例外コンテキストを書き換えて元の関数の成功戻りパスをシミュレートし、実行を再開します — これらはすべて、実行可能メモリを1バイトも変更することなく行われます。
このため、この手法は攻撃面と防御面の両方から特に興味深いものとなっています。
.textセクションを探すEDR/HIPSの整合性チェック(古典的なインラインフック、EAT/IATパッチ、Etwp*スタブ化)を迂回します。GetThreadContext/SetThreadContextのsyscallパターン)を残します。従来のユーザーモードフック手法 — インライン detours(5〜14バイトの上書き)、インポートアドレステーブル(IAT)フック、エクスポートアドレステーブル(EAT)フック — には共通の弱点があります。それは、整合性スキャナとETWが観測できるメモリを変更するということです。
最新のAV/EDR製品は以下を実装しています。
pageguard/guard-pageトリック、PAGE_EXECUTE_READWRITEへのVirtualProtect遷移、セクションハッシュの不一致を検出するカーネルコールバックとユーザーモード整合性チェック。ハードウェアブレークポイントは、これらすべてを回避します。
.text内にスキャンすべきものはありません。SetThreadContextを介してスレッド単位で設定され、整合性スキャナが使用する古典的な「メモリ変更」シグナルをトリガーしません。このPOCは、現代のWindowsセキュリティスタックで最も広く依存されている3つのユーザーモードセキュリティプリミティブ、AMSI(Antimalware Scan Interface)、WLDP(Windows Lockdown Policy)、**ETW(Event Tracing for Windows)**に対する、この手法の有効性と検出可能性を探求します。
AMSIは、アプリケーション(PowerShell、Office、VBScript、.NETホストなど)が登録済みのマルウェア対策プロバイダーにコンテンツスキャンを要求できるようにするWindowsプラットフォーム統合ポイントです。主要な関心対象となるエントリポイントは2つあります。
AmsiScanBuffer(HANDLE hamsiContext, PVOID buffer, ULONG length, LPCWSTR contentName, HANDLE hamsiSession, AMSI_RESULT *pResult)AmsiScanString(HANDLE hamsiContext, LPCWSTR string, LPCWSTR contentName, HANDLE hamsiSession, AMSI_RESULT *pResult)返されるAMSI_RESULTをAMSI_RESULT_CLEAN (0)に強制することで、スクリプトエンジンはコンテンツが検査され無害であると認識し、実行は中断されることなく続行されます。
WLDPは、Windows Defender Application Control(WDAC / Device Guard)のポリシー評価を実装します。WldpIsClassInApprovedListは、指定されたCOMクラス(GUIDで識別)が現在のポリシーの下で許可されているかどうかを回答します。AMSIは内部でWLDPに問い合わせて、特定のスクリプト/コンテンツクラスが「信頼済み」(承認リスト内)であるかどうかを判断します。関数がクラスを承認済みとして報告した場合、AMSIはそのコンテンツタイプに対する追加の精査をスキップする可能性があります。
このDLLは、isApproved出力パラメータ(RDX)をTRUEに設定し、S_OKを返すことで、評価されたクラスが信頼済みであるかのように見せかけます。
ntdll.dll内のEtwEventWriteは、システム上のほぼすべてのETWイベント出力の中心的なユーザーモードシンクです。これを抑制すると、セキュリティ監視に関連する広範な副作用が生じます。
Microsoft-Windows-DotNETRuntime)このDLLは、実際の関数を実行せずにERROR_SUCCESS (0)を返すだけです。
┌──────────────────────────────────────────────────────────────────────────┐ │ Target Process (e.g. powershell.exe) │ │ │ │ ┌──────────────────────────┐ ┌──────────────────────────────────┐│ │ │ mora_hwbp.dll │ │ CPU / Windows ││ │ │ │ │ ││ │ │ DllMain / InstallHook │ │ Thread A Thread B ││ │ │ │ │ │ ┌────────┐ ┌────────┐ ││ │ │ ▼ │ │ │ DR0..3 │ │ DR0..3 │ ││ │ │ Resolve exports │ │ └────────┘ └────────┘ ││ │ │ (amsi/wldp/ntdll) │ │ ││ │ │ │ │ │ #DB (single-step) ││ │ │ ▼ │ │ exception ──► Windows Dispatch ││ │ │ AddVectoredException │ │ │ ││ │ │ Handler(VEH) │ │ ▼ ││ │ │ │ │ │ ┌─────────────────────────────┐ ││ │ │ ▼ │ │ │ VectoredHandler (ours) │ ││ │ │ SetHwbpOnThread(ALL) │ │ │ • match #DB address │ ││ │ │ │ │ │ │ • rewrite context (RIP/RSP)│ ││ │ │ ▼ │ │ │ • spoof return value (RAX) │ ││ │ │ MonitorThread ◄──┐ │ │ │ • continue execution │ ││ │ │ (re-hook every │ │ │ └─────────────────────────────┘ ││ │ │ 500ms) └──────┘ │ ││ │ └──────────────────────────┘ └──────────────────────────────────┘│ └──────────────────────────────────────────────────────────────────────────┘
**高レベルのフロー:**
1. DLL がターゲットプロセスにロードされます(任意のインジェクション手法を使用 — [使用方法](#injection--usage-example) を参照)。
2. `DLL_PROCESS_ATTACH` 時(またはエクスポートされた `InstallHook` 経由)に、ターゲットのエクスポートが `GetProcAddress` で解決されます(必要に応じて `LoadLibraryW` でモジュールロードを強制)。
3. プロセス内の**最初の**ハンドラとして **Vectored Exception Handler** が登録されます(`AddVectoredExceptionHandler(1, ...)`)。
4. **現在のスレッド**が即座にフックされ、次にプロセス内の**既存の全スレッド**が `CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)` で列挙され、フックされます。
5. **モニタースレッド**が500msごとに起動し、**新しく作成されたスレッド**を含むすべてのスレッドにブレークポイントを再適用します。これにより、フック後にスレッドが生成されたり、ブレークポイントが外部からクリアされたりしても、フックの永続性が保証されます。
6. フックされた関数がいずれかのスレッドで呼び出されると、CPU は `#DB` シングルステップ例外を発生させます。Windows はそれを VEH にディスパッチし、VEH は良性の戻りをシミュレートして実行を継続します。
### サンプルデバッグ出力

*図 1 — Sysinternals DebugView で取得したサンプル診断出力。各行は、解決されたターゲットアドレスと、対応するデバッグレジスタのライブヒットカウンターを報告します。*
---
## 技術的な詳細
### 5.1 x64 上のハードウェアブレークポイント
x86/x64 では、各 CPU が**4つのハードウェアデバッグアドレスレジスタ**(`DR0`–`DR3`)と制御レジスタ(`DR7`)を提供します。`DR0`–`DR3` にゼロ以外のブレークポイントが設定された状態で実行されるスレッドは、命令ポインタがそのアドレスに達する(またはデータアクセスが設定された条件に一致する)たびにフォールトを発生させます。ステータスレジスタ `DR6` は、どのブレークポイントが発火したかを記録します。
ハードウェアブレークポイントは**コンテキスト依存**です。スレッドの `CONTEXT` 構造体に格納され、設定されたスレッドにのみ適用されます。そのため、堅牢な実装ではプロセスの**すべてのスレッド**にブレークポイントを設定し(新しいスレッドには継続的に再適用し)なければなりません。
### 5.2 デバッグレジスタのレイアウト(DR0–DR7)
`DR7` はブレークポイントの有効化と動作を制御するビットフィールドです:
| ビット | フィールド | 意味 |
|--------|--------|-----------------------------------------------------|
| `0` | `L0` | ブレークポイント0(DR0)のローカル有効化 |
| `2` | `L1` | ブレークポイント1(DR1)のローカル有効化 |
| `4` | `L2` | ブレークポイント2(DR2)のローカル有効化 |
| `6` | `L3` | ブレークポイント3(DR3)のローカル有効化 |
| `8` | `LE` | レガシーローカル有効化(互換性のため維持) |
| `9` | `GE` | レガシーグローバル有効化(互換性のため維持) |
| `16–17`| `R/W0` | BP0 のアクセスタイプ(`00` = 命令実行) |
| `18–19`| `Len0` | BP0 の長さ(`00` = 1バイト) |
| `20–21`| `R/W1` | BP1 のアクセスタイプ(`00` = 命令実行) |
| `22–23`| `Len1` | BP1 の長さ(`00` = 1バイト) |
| `24–25`| `R/W2` | BP2 のアクセスタイプ(`00` = 命令実行) |
| `26–27`| `Len2` | BP2 の長さ(`00` = 1バイト) |
| `28–29`| `R/W3` | BP3 のアクセスタイプ(`00` = 命令実行) |
| `30–31`| `Len3` | BP3 の長さ(`00` = 1バイト) |
4つのブレークポイントはすべて、**1バイトに対する実行(命令フェッチ)** に設定されます。これは関数エントリフックに適切な条件です。
### 5.3 Vectored Exception Handler(VEH)
ブレークポイントがトリガーされると、プロセッサは `#DB` 例外を発生させます。x64 Windows では、`ntdll` のディスパッチルーチンは、スレッドの構造化例外ハンドラ(SEH)チェーンの前に、プロセス全体の **VEH チェーン** を通してルーティングします。このプロジェクトのハンドラは:
1. **フィルタリング** — `EXCEPTION_SINGLE_STEP`(`0x80000004`)のみを処理し、それ以外はすべて `EXCEPTION_CONTINUE_SEARCH` にフォールスルーします。
2. **マッチング** — `ExceptionAddress` を4つの既知の関数アドレスと比較します。
3. **コンテキストの書き換え**:
- `RIP = *(RSP)` → リターンアドレスをポップして元の呼び出し元に「戻る」。
- `RSP += 8` → `ret` をシミュレート(x64 シングル命令アンワインド)。
- `RAX = 0` → `S_OK` / `ERROR_SUCCESS`(成功リターンコード)を偽装。
- `DR6 &= ~0xF` → ブレークポイントステータスビットをクリアし、後でスプリアス状態なしに命令を再実行できるようにする。
4. **出力パラメータを変更**([5.4](#54-per-component-interception-logic) を参照)。
5. **`EXCEPTION_CONTINUE_EXECUTION` を返す**。これにより Windows は変更されたコンテキストでスレッドを再開します。つまり、実行は *呼び出し元* で再開され、実際のターゲット関数は**決して実行されません**。
各インターセプトサイトはさらに SEH の `__try/__except` ガードでラップされているため、不正または予期しないスタックレイアウトによってプロセスがクラッシュすることはありません。これは、敵対的/堅牢化されたターゲットに対する堅牢性の考慮事項です。
### 5.4 コンポーネント別インターセプトロジック
**DR0 — `AmsiScanBuffer`**(x64、最初の6引数は `RCX, RDX, R8, R9, [RSP+0x28], [RSP+0x30]`):```
HRESULT AmsiScanBuffer(HANDLE, PVOID, ULONG, LPCWSTR, HANDLE, AMSI_RESULT* pResult);
[RSP+0x28] [RSP+0x30]
pResult、[RSP+0x30])に AMSI_RESULT_CLEAN (0) を書き込みます。RAX に S_OK (0) を返します。DR1 — AmsiScanString (x64、最初の5つの引数は RCX, RDX, R8, R9, [RSP+0x28]):```
HRESULT AmsiScanString(HANDLE, LPCWSTR, LPCWSTR, HANDLE, AMSI_RESULT* pResult);
[RSP+0x28]
- 5番目のパラメータ(`pResult`、`[RSP+0x28]`)に`AMSI_RESULT_CLEAN (0)`を書き込みます。
- `RAX`に`S_OK (0)`を返します。
**DR2 — `WldpIsClassInApprovedList`**(最初の3つの引数は `RCX, RDX, R8`):```
HRESULT WldpIsClassInApprovedList(const GUID* classId, PBOOL isApproved, DWORD evalCriteria);
RCX RDX R8
*isApproved に TRUE を書き込みます (RDX 経由)。RAX に S_OK (0) を返します。DR3 — EtwEventWrite (最初の4つの引数は RCX, RDX, R8, R9):```
ULONG EtwEventWrite(HANDLE RegHandle, PCEVENT_DESCRIPTOR EventDescriptor,
ULONG UserDataCount, PEVENT_DATA_DESCRIPTOR UserData);
- `RAX` で `ERROR_SUCCESS (0)` を返し、いかなる出力パラメータにも触れません。
- 結果: ETW プロバイダーはフックされたプロセスから**いかなる**イベントも受け取らず、スクリプト実行、モジュール読み込み、プロセス作成、AMSI テレメトリのログ記録が抑制されます。
### 5.5 スレッド管理とフックの永続性
デバッグレジスタはスレッドごとに存在するため、エンジンはフックを継続的に維持する必要があります。
1. **即時フック** — `DllMain`(または `InstallHook`) は `SetHwbpOnThread(GetCurrentThread())` を使用して呼び出し元スレッドをフックします。
2. **全スレッドスイープ** — `HookAllThreads()` は `TH32CS_SNAPTHREAD` スナップショットを介してプロセス内のすべてのスレッドを列挙し、各外部スレッドを一時停止し(`SuspendThread`)、ブレークポイントを適用し(`SetHwbpOnThread`)、再開して、ハンドルを閉じます。一時停止により、`GetThreadContext` と `SetThreadContext` の間のコンテキストスイッチ中にスレッドがフォールトする競合を防ぎます。
3. **永続性モニター** — `MonitorThreadProc` は `Sleep(500)` でループし、500 ミリ秒ごとに `HookAllThreads()` を呼び出します。これにより、**削除されたブレークポイントが再設定され**(例: 外部の `SetThreadContext` 呼び出し、デバッグツール、スレッドの終了/作成など)、**初期フック後に作成されたスレッドもカバーされます**。
4. **同期** — `HookAllThreads` は `CRITICAL_SECTION`(`g_HookLock`) の下で実行されるため、モニタースレッドと初期フックルーチンがコンテキストスイッチをインターリーブすることはありません。
5. **クリーンなティアダウン** — `UninstallHook` はモニターを停止し、すべてのスレッドの `DR0–DR7` をクリアし、VEH の登録を解除します。
> **「フックの永続性」の質問への明示的な回答:** はい — ブレークポイントが任意のスレッドから剥離された場合(別のエージェント、デバッガー、EDR など)、モニタースレッドは**500 ミリ秒以内に再適用します**。さらに、DLL 読み込み後に作成されたスレッドは、1 回のモニターサイクル内でフックされます。この特定のエンジンを確実に無効化する唯一の方法は、モニタースレッドを終了し、*かつ* VEH をクリアし、*かつ* 同じ時間枠内でレジスタを剥離することです — あるいは、最初から `SetThreadContext` を拒否するアンチデバッグを使用することです。
---
## エクスポート API
| エクスポート | シグネチャ | 動作 |
|-------------------|------------------------------------|-----------------------------------------------------------------------|
| `InstallHook` | `BOOL WINAPI InstallHook(void)` | ターゲットを解決し、VEH を登録し、すべてのスレッドをフックし、モニターを開始します。 |
| `UninstallHook` | `BOOL WINAPI UninstallHook(void)` | モニターを停止し、すべてのスレッドのブレークポイントをクリアし、VEH を削除します。 |
| `GetStats` | `void WINAPI GetStats(void)` | 現在のフック状態を出力します(`OutputDebugStringA` 経由)— アドレス、ヒットカウンター。 |
ヒットカウンター(`g_HaveAmsiBuf`、`g_HaveAmsiStr`、`g_HaveWldp`、`g_HaveEtw`) は `InterlockedIncrement` で維持され、デバッグ出力に公開されます。これは、ラボ環境で傍受が実際に発生していることを検証するのに役立ちます。
`DllMain` 自体が `DLL_PROCESS_ATTACH` 時にフックシーケンス全体を実行するため、エクスポートは実行時の(アン)ロードシナリオにおけるオプションの便宜機能です。
## ビルド手順
**要件:** Windows 10/11 x64、Visual Studio Build Tools(`icx.exe`)、SDK。
DLL をコンパイルします(x64):```bat
icx.exe /nologo /O3 /MT /EHsc "mora_hwbp.c" /link /DLL /out:"mora_hwbp.dll" /LIBPATH:"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\lib"
Flags explained:
| Flag | Purpose |
|---|---|
/O3 | 最大最適化(関数コード、必須ではありません) |
生成されるのは mora_hwbp.dll で、ターゲットプロセスに読み込むことができます。
この DLL は、AMSI/WLDP/ETW を使用するプロセスに読み込む必要があります — PowerShell ホストが標準的なテスト環境です。読み込みには、標準的な DLL インジェクション手法を使用できます。Reflective/LoadLibrary インジェクション を使用した、最小限で自己完結型のデモは、小さな C ローダーで実行できます。```bat
rem Run from an x64 developer prompt (example with a generic loader)
loader.exe mora_hwbp.dll powershell.exe
または、手動のラボチェックを行う場合は、お好みのツールでインジェクションし、その後PowerShellから検証します:```powershell
# 1. Inject mora_hwbp.dll into the PowerShell process (via external tool).
# 2. Verify that classic AMSI test vectors now return clean.
"AmsiTestSample:7e72c3ce-861b-4339-8740-0ac1484c1386"
ラボ検証専用。 デバッガーまたは
GetStats/OutputDebugStringを使用して、スクリプトコンテンツの実行中に4つのブレークポイントすべてがヒットを報告することを確認してください。
このPOCには二重の目的があります。攻撃的に有効なのと同じ特性は、まさに防御側が探すべきものです。
GetThreadContext(CONTEXT_DEBUG_REGISTERS) をポーリングし、承認済みデバッガープロファイル外で非ゼロの DR0–DR3 を持つスレッドを監査する。Microsoft-Windows-Kernel-Process +スレッドトレースを有効にし、セキュリティ関連プロセスを標的とする NtGetContextThread/NtSetContextThread をアラートする。.text の整合性だけに頼らず、行動ベースの検出(EtwEventWrite より下位のETWコンシューマーフック、カーネルETW、AMSIコンシューマー再チェック)に依存する。SetThreadContext を、高重大度の明示的なシグナルとして扱う。RCX/RDX/R8/R9、続いて [RSP+0x20…])を前提としています。x86版では [EBP+…] 形式のパラメータ再構築が必要になります。Sleep(500) の反復間には(意図的に小さな)ウィンドウがあります。極端に高速なスレッド生成と積極的なストリッピングが組み合わさると、理論的には数百ミリ秒間モニターを追い越す可能性があります。OutputDebugStringA ベースのステータス — 診断はデバッグ出力チャネルに依存します。完全にストリップされた/ヘッドレスの環境では、ラボで観察するためにデバッガーをアタッチするか、出力をリダイレクトする必要があります。このプロジェクトはMITライセンスの下でライセンスされています。詳細は LICENSE ファイルを参照してください。
このプロジェクトは教育および防御研究目的のみで公開されています。セキュリティベンダー、ブルーチーム、または検出エンジニアの方は、このリポジトリの内容を使用して、ハードウェアブレークポイントベースの回避に対する検出範囲を改善することをお勧めします。この手法が実環境で悪用されていることを発見した場合は、組織の責任ある開示プロセスと、関連するベンダー/当局のチャネルを通じて報告してください。
ご自身の責任で使用してください。この手法を無断使用すると、該当する法律に違反する可能性があります。
| レジスタ | フック対象関数 | モジュール | 目的 |
|---|
DR0 | AmsiScanBuffer | amsi.dll | AMSIコンテンツスキャンを無効化 |
DR1 | AmsiScanString | amsi.dll | AMSI文字列スキャンを無効化 |
DR2 | WldpIsClassInApprovedList | wldp.dll | WLDPクラス承認を強制(Device Guard / WDAC) |
DR3 | EtwEventWrite | ntdll.dll | ETWイベントトレーシングを抑制 |
/MT | CRT 静的リンク(ランタイム DLL 依存なし) |
/EHsc | C++/SEH 例外処理(__try に必要) |
/DLL | エクスポートテーブルを持つ DLL を生成 |
| アーティファクト | 観測可能な兆候 |
|---|
GetThreadContext / SetThreadContext 呼び出し | 他のプロセス/スレッドに対する高頻度のデバッグレジスタコンテキストスイッチ(カーネルETW: Microsoft-Windows-Kernel-Process/Thread API)。 |
非ゼロの DR0–DR3 | 既知のデバッガーワークフロー外のユーザーモードアドレスを CONTEXT_DEBUG_REGISTERS に含むスレッド。 |
R/W = 00 の DR7 ローカル有効化ビット(L0–L3) | デバッガーが管理していないスレッド上の実行専用ブレークポイント — 強い異常。 |
EXCEPTION_SINGLE_STEP の量 | プロセスのVEHに起因する高率の #DB フォールト (0x80000004)。 |
| ファーストチャンスVEH登録 | #DB ストームの直前に追加された新しいVEH(AddVectoredExceptionHandler)。 |
TH32CS_SNAPTHREAD + SuspendThread/ResumeThread | 繰り返されるスレッド列挙+一時停止のパターン(500msモニターで使用)。 |
以前にロードされていない場合の LoadLibraryW 経由の wldp.dll/amsi.dll ロード | ターゲットプロセス内での異常なモジュールロード。 |
EtwEventWrite に到達しない | 期待されるETWイベントの欠如(スクリプト実行中、PowerShell運用ログにイベントが記録されない)。 |