Vectored Overloading PE Injection

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

Tip

Looking for Windows 11 LFH heap shaping and VMware Workstation PVSCSI (vmware-vmx) escape techniques?

{{#ref}} vmware-workstation-pvscsi-lfh-escape.md {{#endref}}

기술 개요

Vectored Overloading is a Windows PE injection primitive that fuses classic Module Overloading with Vectored Exception Handlers (VEHs) and hardware breakpoints. Instead of patching LoadLibrary or writing its own loader, the adversary:

  1. 정상적인 DLL(예: wmp.dll)을 기반으로 하는 SEC_IMAGE 섹션을 생성한다.
  2. 맵된 뷰를 완전히 재배치된 악성 PE로 덮어쓰되, 섹션 객체는 디스크상의 정상 이미지(benign image)를 가리키도록 유지한다.
  3. VEH를 등록하고 디버그 레지스터를 프로그래밍하여 NtOpenSection, NtMapViewOfSection, 그리고 선택적으로 NtClose에 대한 모든 호출이 사용자 모드 breakpoint를 발생시키도록 한다.
  4. LoadLibrary("amsi.dll") (또는 다른 정상 대상)을 호출한다. Windows 로더가 해당 syscall들을 호출하면 VEH는 커널 전환을 건너뛰고 준비된 악성 이미지의 핸들과 베이스 주소를 반환한다.

로더는 여전히 요청한 DLL을 맵했다고 믿기 때문에, 섹션의 백킹 파일만 확인하는 도구는 메모리에는 공격자의 페이로드가 들어있음에도 wmp.dll을 본다. 한편으로 imports/TLS 콜백은 실제 로더에 의해 여전히 해결되므로, 공격자가 자체적으로 유지해야 하는 PE 파싱 로직의 양이 크게 줄어든다.

Stage 1 – 위장된 섹션 생성

  1. Create and map a section for the decoy DLL
NtCreateSection(&DecoySection, SECTION_ALL_ACCESS, NULL,
0, PAGE_READWRITE, SEC_IMAGE, L"\??\C:\\Windows\\System32\\wmp.dll");
NtMapViewOfSection(DecoySection, GetCurrentProcess(), &DecoyView, 0, 0,
NULL, &DecoySize, ViewShare, 0, PAGE_READWRITE);
  1. 그 뷰에 섹션 단위로 악성 PE를 복사한다, SizeOfRawData/VirtualSize를 준수하고 이후 보호 설정(PAGE_EXECUTE_READ, PAGE_READWRITE 등)을 갱신한다.
  2. reflective loader가 하듯 정확히 relocations를 적용하고 imports를 해결한다. 뷰가 이미 SEC_IMAGE로 매핑되어 있으므로 섹션 정렬과 가드 페이지는 나중에 Windows 로더가 기대하는 것과 일치한다.
  3. PE 헤더 정규화:
  • 페이로드가 EXE인 경우 IMAGE_FILE_HEADER.Characteristics |= IMAGE_FILE_DLL를 설정하고 엔트리 포인트를 0으로 만들어 LdrpCallTlsInitializers가 EXE 전용 스텁으로 점프하는 것을 방지한다.
  • DLL 페이로드는 헤더를 변경하지 않아도 된다.

이 시점에서 프로세스는 백킹 객체가 여전히 wmp.dll인 RWX 가능 뷰를 소유하지만, 메모리의 바이트는 공격자가 제어한다.

Stage 2 – VEH로 로더 가로채기

  1. VEH를 등록하고 hardware breakpoints를 설정: ntdll!NtOpenSection의 주소로 Dr0(또는 다른 디버그 레지스터)를 프로그래밍하고 DR7을 설정하여 실행마다 STATUS_SINGLE_STEP가 발생하도록 한다. 나중에 동일하게 NtMapViewOfSection과 선택적으로 NtClose에 대해서도 반복한다.
  2. LoadLibrary("amsi.dll")로 DLL 로딩을 트리거한다. LdrLoadDll은 결국 실제 섹션 핸들을 얻기 위해 NtOpenSection을 호출한다.
  3. NtOpenSection에 대한 VEH 훅:
  • [out] PHANDLE SectionHandle 인수의 스택 슬롯을 찾는다.
  • 해당 슬롯에 이전에 생성한 DecoySection 핸들을 쓴다.
  • RIP/EIPret 명령으로 이동시켜 커널이 호출되지 않도록 한다.
  • 다음에는 NtMapViewOfSection을 감시하도록 hardware breakpoint를 재설정한다.
  1. NtMapViewOfSection에 대한 VEH 훅:
  • 이미 매핑된 악성 뷰의 주소로 [out] PVOID *BaseAddress(및 크기/보호 출력)를 덮어쓴다.
  • 앞서처럼 syscall 본문을 건너뛴다.
  1. (선택) NtClose에 대한 VEH 훅은 가짜 섹션 핸들이 정리되었는지 확인하여 리소스 누수를 방지하고 최종 정합성 검사를 제공한다.

syscall이 실제로 실행되지 않기 때문에 kernel 콜백(ETWti, minifilter 등)은 의심스러운 NtOpenSection/NtMapViewOfSection 이벤트를 관찰하지 못해 텔레메트리가 크게 낮아진다. 로더의 관점에서는 모든 것이 성공했으며 amsi.dll이 메모리에 있으므로 로더는 공격자의 뷰를 대상으로 import/TLS 해결을 계속 진행한다.

PoC 구현 참고 (2025)

공개 PoC는 재구현할 때 놓치기 쉬운 몇 가지 실제적인 세부사항을 보여준다:

  • HWBPs are per-thread. PoC는 LoadLibrary를 호출하기 전에 현재 스레드CONTEXT_DEBUG_REGISTERS를 설정하므로, VEH는 로더를 트리거한 것과 동일한 스레드에서 실행되어야 한다.
  • Syscall emulation: VEH는 RAX = 0을 설정하고 ntdll 스텁 내의 ret까지 RIP를 이동시킨다( 0xC3를 스캔함). 이렇게 하면 커널 전환이 발생하지 않고 NtContinue로 재개한다.
  • Output parameters: NtMapViewOfSection의 경우 VEH는 반환되는 BaseAddress, ViewSize, Win32Protect 출력을 덮어써서 로더가 매핑이 성공한 것으로 믿고 공격자의 뷰를 사용해 imports/TLS를 계속 처리하게 한다.

Minimal HWBP setup used by the PoC (x64):

CONTEXT ctx = {0};
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
GetThreadContext(GetCurrentThread(), &ctx);
ctx.Dr0 = (DWORD64)NtOpenSection;
ctx.Dr7 = 1;
SetThreadContext(GetCurrentThread(), &ctx);
AddVectoredExceptionHandler(1, VehHandler);

스텔스 변형

Recent VEH research highlights that handlers can be registered by manually manipulating the VEH list instead of calling AddVectoredExceptionHandler, which reduces reliance on user-mode APIs that may be monitored or hooked. This is not required for Vectored Overloading but can be combined with it to reduce observable API activity.

단계 3 – 페이로드 실행

  • EXE payload: 인젝터는 재배치(relocations)가 완료되면 단순히 원래 엔트리 포인트로 점프합니다. 로더가 DllMain을 호출할 것이라고 생각할 때, 커스텀 코드는 대신 EXE 스타일 엔트리를 실행합니다.
  • DLL payload / Node.js addon: 의도된 export를 해결하고 호출합니다 (Kidkadi는 JavaScript에 명명된 함수를 노출합니다). 모듈이 이미 LdrpModuleBaseAddressIndex에 등록되어 있기 때문에 이후 조회는 이를 정상적인 DLL로 인식합니다.

Node.js native addon(.node file)과 결합하면, 모든 Windows-internals 관련 작업이 JavaScript 레이어 밖에 남아 있어, 공격자는 동일한 로더를 여러 가지 난독화된 Node 래퍼와 함께 배포할 수 있습니다.

References

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기