
하드웨어 브레이크포인트(DR0-DR7) 기반 패치리스 유저 모드 후킹 및 텔레메트리 계측 엔진(AMSI, WLDP 및 ETW PoC).
하드웨어 브레이크포인트(CPU 디버그 레지스터) 기반 함수 후킹을 기존의 인메모리 코드 패칭의 대안으로 보여주는 보안 연구 개념 증명(POC).
목적 및 범위
이 저장소는 Windows 내부 구조에 대한 방어적 보안 연구, 레드팀/퍼플팀 교육, 탐지 엔지니어링, 학술 연구를 위해서만 게시되었습니다. 공격자가 프로세서 디버그 레지스터를 악용하여 사용자 모드 보안 텔레메트리를 무력화할 수 있는 방법과, 그에 못지않게 중요한, 그러한 기법을 탐지하기 위해 방어자가 무엇을 모니터링해야 하는지를 보여줍니다. 작성자는 이 코드의 오용에 대해 책임을 지지 않습니다. 명시적 허가 없이 시스템에 이 기법을 사용하는 것은 불법이며, 대부분의 관할권에서 관련 컴퓨터 사기 및 남용 법률을 위반합니다. 소유하거나 명시적 서면 허가를 받은 환경이 아닌 곳에는 배포하지 마십시오.
mora_hwbp.c는 대상 프로세스(예: PowerShell 호스트)에 로드/인젝션되면, 프로세스 내 모든 스레드의 아키텍처 디버그 레지스터(DR0–DR7)에 저장된 CPU 하드웨어 브레이크포인트를 통해서만 네 개의 사용자 모드 함수를 후킹하는 DLL을 구현합니다:
프로세스별 **벡터 예외 처리기(VEH)**는 디버그 레지스터가 발생시키는 EXCEPTION_SINGLE_STEP(0x80000004) 오류를 수신하고, 예외 컨텍스트를 다시 작성하여 원래 함수의 성공적인 반환 경로를 시뮬레이션한 다음 실행을 재개합니다 — 모두 실행 가능한 메모리의 단일 바이트도 수정하지 않고 수행됩니다.
이 기법은 공격적 관점과 방어적 관점 모두에서 특히 흥미롭습니다:
.text 섹션(전통적인 인라인 후킹, EAT/IAT 패칭, Etwp* 스터빙)을 찾는 EDR/HIPS 무결성 검사를 우회합니다.GetThreadContext/SetThreadContext syscall 패턴)를 남깁니다.기존의 사용자 모드 후킹 방식 — 인라인 디토어(5–14바이트 덮어쓰기), 임포트 주소 테이블(IAT) 후킹, 익스포트 주소 테이블(EAT) 후킹 — 은 공통된 약점을 공유합니다: 무결성 스캐너와 ETW가 관찰할 수 있는 메모리를 수정한다는 점입니다.
최신 AV/EDR 제품은 다음을 구현합니다:
pageguard/guard-page 트릭, PAGE_EXECUTE_READWRITE로의 VirtualProtect 전환, 섹션 해시 불일치를 감지하는 커널 콜백 및 사용자 모드 무결성 검사.하드웨어 브레이크포인트는 이 모든 것을 우회합니다:
.text에서 스캔할 것이 없습니다.SetThreadContext를 통해 스레드별로 설정되며, 무결성 스캐너가 사용하는 전형적인 "메모리 수정됨" 신호를 트리거하지 않습니다.이 POC는 현대 Windows 보안 스택에서 가장 널리 신뢰받는 세 가지 사용자 모드 보안 기본 요소인 AMSI(Antimalware Scan Interface), WLDP(Windows Lockdown Policy), ETW(Event Tracing for Windows) 를 대상으로 이 기법의 효율성과 탐지 가능성을 탐구합니다.
AMSI는 애플리케이션(PowerShell, Office, VBScript, .NET 호스트 등)이 등록된 맬웨어 방지 공급자에게 콘텐츠 스캔을 요청할 수 있게 하는 Windows 플랫폼 통합 지점입니다. 두 가지 진입점이 특히 중요합니다:
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`에 0이 아닌 브레이크포인트가 설정된 상태로 실행되는 스레드는 명령 포인터가 해당 주소에 도달하거나(데이터 접근이 구성된 조건과 일치하거나) 할 때마다 오류가 발생합니다. 상태 레지스터 `DR6`는 어떤 브레이크포인트가 발생했는지 기록합니다.
하드웨어 브레이크포인트는 **컨텍스트 민감형**입니다. 즉, 스레드의 `CONTEXT` 구조체에 저장되며 설정된 스레드에만 적용됩니다. 따라서 견고한 구현은 프로세스의 **모든 스레드**에 브레이크포인트를 설정하고(새 스레드에는 지속적으로 다시 적용해야) 합니다.
### 5.2 디버그 레지스터 레이아웃(DR0–DR7)
`DR7`은 브레이크포인트 활성화와 동작을 제어하는 비트 필드입니다:
| Bits | Field | Meaning |
|--------|--------|-----------------------------------------------------|
| `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바이트) |
네 개의 브레이크포인트 모두 단일 바이트에 대한 **실행(명령 인출)** 조건으로 구성되며, 이는 함수 진입점 후크에 적합한 조건입니다.
### 5.3 Vectored Exception Handler(VEH)
브레이크포인트가 트리거되면 프로세서가 `#DB` 예외를 발생시킵니다. x64 Windows에서 `ntdll` 디스패치 루틴은 스레드의 구조적 예외 처리기(SEH) 체인보다 먼저 프로세스 전역 **VEH 체인**을 통해 이를 라우팅합니다. 이 프로젝트의 핸들러는:
1. **필터링** — `EXCEPTION_SINGLE_STEP`(`0x80000004`)만 처리하고, 나머지는 모두 `EXCEPTION_CONTINUE_SEARCH`로 전달됩니다.
2. **매칭** — `ExceptionAddress`를 네 개의 알려진 함수 주소와 비교합니다.
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]
AMSI_RESULT_CLEAN (0)을 6번째 매개변수(pResult, [RSP+0x30])에 씁니다.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`** (`RCX, RDX, R8`에 처음 3개 인자):```
HRESULT WldpIsClassInApprovedList(const GUID* classId, PBOOL isApproved, DWORD evalCriteria);
RCX RDX R8
TRUE를 *isApproved에 기록합니다 (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);
- Returns `ERROR_SUCCESS (0)` in `RAX` without touching any output parameter.
- 결과: ETW 공급자는 후킹된 프로세스에서 **어떤** 이벤트도 수신하지 않으므로 스크립트 실행, 모듈 로드, 프로세스 생성 및 AMSI 원격 측정 로깅이 억제됩니다.
### 5.5 스레드 관리 및 후크 지속성
디버그 레지스터는 스레드별로 존재하므로 엔진은 후크를 지속적으로 유지해야 합니다:
1. **즉시 후킹** — `DllMain`(또는 `InstallHook`)은 `SetHwbpOnThread(GetCurrentThread())`로 호출 스레드를 후킹합니다.
2. **전체 스레드 스윕** — `HookAllThreads()`는 `TH32CS_SNAPTHREAD` 스냅샷을 통해 프로세스의 모든 스레드를 열거하고, 각 외부 스레드를 일시 중단(`SuspendThread`)한 후 중단점을 적용(`SetHwbpOnThread`)하고 다시 재개한 다음 핸들을 닫습니다. 일시 중단은 `GetThreadContext`와 `SetThreadContext` 사이의 컨텍스트 전환 중에 스레드가 오류를 일으키는 경합을 방지합니다.
3. **지속성 모니터** — `MonitorThreadProc`는 `Sleep(500)`으로 루프하며 500ms마다 `HookAllThreads()`를 호출합니다. 이는 **제거된 모든 중단점을 다시 설정**하고(예: 외부 `SetThreadContext` 호출, 디버깅 도구, 스레드 종료/생성) **초기 후크 이후 생성된 스레드를 포함**합니다.
4. **동기화** — `HookAllThreads`는 `CRITICAL_SECTION`(`g_HookLock`) 하에서 실행되므로 모니터 스레드와 초기 후킹 루틴이 컨텍스트 전환을 서로 간섭하지 않습니다.
5. **정리 해제** — `UninstallHook`는 모니터를 중지하고 모든 스레드에서 `DR0–DR7`을 지운 다음 VEH 등록을 해제합니다.
> **"후크 지속성" 질문에 대한 명시적 답변:** 그렇습니다. 중단점이 어떤 스레드에서 제거되면(다른 에이전트, 디버거 또는 EDR에 의해) 모니터 스레드가 **500ms 이내에 다시 적용합니다**. 또한 DLL 로드 후 생성된 모든 스레드는 한 번의 모니터 주기 내에 후킹됩니다. 이 특정 엔진을 무력화하는 유일한 확실한 방법은 모니터 스레드를 종료하고 *그리고* 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"
플래그 설명:
| Flag | 용도 |
|---|---|
/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를 사용하여 네 개의 중단점 모두 스크립트 내용이 실행됨에 따라 히트를 보고하는지 확인하십시오.
이 POC는 이중 목적을 가집니다: 공격적으로 효과적인 동일한 특성이 방어자가 정확히 찾아내야 할 대상이기도 합니다.
GetThreadContext(CONTEXT_DEBUG_REGISTERS)를 폴링하고 승인된 디버거 프로필 외부의 0이 아닌 DR0–DR3가 있는 모든 스레드를 감사합니다.Microsoft-Windows-Kernel-Process + Thread 추적을 활성화하고 보안 관련 프로세스를 대상으로 하는 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). |
0이 아닌 DR0–DR3 | CONTEXT_DEBUG_REGISTERS에 알려진 디버거 워크플로 외부의 사용자 모드 주소가 포함된 모든 스레드. |
DR7 로컬 활성화 비트 (L0–L3) 및 R/W = 00 | 디버거가 관리하지 않는 스레드의 실행 전용 중단점 — 강력한 이상 징후. |
EXCEPTION_SINGLE_STEP 볼륨 | 프로세스의 VEH에서 발생하는 높은 비율의 #DB 오류(0x80000004). |
| First-chance VEH 등록 | #DB 폭풍 직전에 새로 추가된 VEH(AddVectoredExceptionHandler). |
TH32CS_SNAPTHREAD + SuspendThread/ResumeThread | 반복적인 스레드 열거 + 일시 중단 패턴 (500ms 모니터에서 사용). |
이전에 로드되지 않은 경우 LoadLibraryW를 통한 wldp.dll/amsi.dll 로드 | 대상 프로세스에서의 비정상적 모듈 로드. |
EtwEventWrite 미도달 | 예상 ETW 이벤트 부재 (스크립트 실행 중 PowerShell 운영 로그가 조용함). |