Vectored Overloading PE Injection

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

Tip

Procurando técnicas de escape para Windows 11 LFH heap shaping e VMware Workstation PVSCSI (vmware-vmx)?

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

Visão geral da técnica

Vectored Overloading é uma primitiva de injeção PE do Windows que funde o clássico Module Overloading com Vectored Exception Handlers (VEHs) e hardware breakpoints. Em vez de patchar LoadLibrary ou escrever seu próprio loader, o adversário:

  1. Cria uma seção SEC_IMAGE referenciada por uma DLL legítima (por exemplo, wmp.dll).
  2. Sobrescreve a view mapeada com um PE malicioso totalmente realocado, mas mantém o objeto de seção apontando para a imagem benigna no disco.
  3. Registra um VEH e programa os registradores de debug para que cada chamada a NtOpenSection, NtMapViewOfSection e, opcionalmente, NtClose gere um breakpoint em user-mode.
  4. Chama LoadLibrary("amsi.dll") (ou qualquer outro alvo benigno). Quando o Windows loader invoca esses syscalls, o VEH pula a transição para o kernel e retorna os handles e endereços base da imagem maliciosa preparada.

Como o loader ainda acredita ter mapeado a DLL solicitada, ferramentas que apenas verificam os arquivos de backing das sections vêem wmp.dll mesmo que a memória agora contenha o payload do atacante. Enquanto isso, imports/TLS callbacks ainda são resolvidos pelo loader genuíno, reduzindo significativamente a quantidade de lógica de parsing de PE personalizada que o adversário precisa manter.

Etapa 1 – Construir a seção disfarçada

  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. Copy the malicious PE into that view section by section, honouring SizeOfRawData/VirtualSize and updating protections afterwards (PAGE_EXECUTE_READ, PAGE_READWRITE, etc.).
  2. Apply relocations and resolve imports exactly as a reflective loader would. Because the view is already mapped as SEC_IMAGE, section alignments and guard pages match what the Windows loader expects later.
  3. Normalize the PE header:
  • If the payload is an EXE, set IMAGE_FILE_HEADER.Characteristics |= IMAGE_FILE_DLL and zero the entry point to keep LdrpCallTlsInitializers from jumping into EXE-specific stubs.
  • DLL payloads can keep their headers unchanged.

At this point the process owns a RWX-capable view whose backing object is still wmp.dll, yet the bytes in memory are attacker-controlled.

Etapa 2 – Sequestrar o loader com VEHs

  1. Register a VEH and arm hardware breakpoints: program Dr0 (or another debug register) with the address of ntdll!NtOpenSection and set DR7 so every execution raises STATUS_SINGLE_STEP. Repeat later for NtMapViewOfSection and optionally NtClose.
  2. Trigger DLL loading with LoadLibrary("amsi.dll"). LdrLoadDll will eventually call NtOpenSection to obtain the real section handle.
  3. VEH hook for NtOpenSection:
  • Locate the stack slot for the [out] PHANDLE SectionHandle argument.
  • Write the previously created DecoySection handle into that slot.
  • Advance RIP/EIP to the ret instruction so the kernel is never called.
  • Re-arm the hardware breakpoint to watch NtMapViewOfSection next.
  1. VEH hook for NtMapViewOfSection:
  • Overwrite the [out] PVOID *BaseAddress (and size/protection outputs) with the address of the already mapped malicious view.
  • Skip the syscall body just like before.
  1. (Optional) VEH hook for NtClose verifies that the fake section handle is cleaned up, preventing resource leaks and providing a final sanity check.

Because the syscalls are never executed, kernel callbacks (ETWti, minifilter, etc.) do not observe the suspicious NtOpenSection/NtMapViewOfSection events, drastically lowering telemetry. From the loader’s point of view everything succeeded and amsi.dll is in memory, so it proceeds with import/TLS resolution against the attacker’s bytes.

PoC implementation notes (2025)

The public PoC shows a few practical details that are easy to miss when re-implementing the technique:

  • HWBPs are per-thread. The PoC sets CONTEXT_DEBUG_REGISTERS on the current thread before calling LoadLibrary, so the VEH must run on the same thread that triggers the loader.
  • Syscall emulation: the VEH sets RAX = 0 and advances RIP to the ret inside the ntdll stub (it scans for 0xC3) so the kernel transition never happens, then resumes with NtContinue.
  • Output parameters: for NtMapViewOfSection, the VEH overwrites the returned BaseAddress, ViewSize, and Win32Protect outputs so the loader believes the mapping succeeded and continues with imports/TLS using the attacker’s view.

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);

Variação Stealth

Pesquisas recentes sobre VEH destacam que handlers podem ser registrados por meio de manipulação manual da lista VEH em vez de chamar AddVectoredExceptionHandler, o que reduz a dependência de user-mode APIs que podem ser monitoradas ou hooked. Isso não é necessário para Vectored Overloading, mas pode ser combinado com ele para reduzir a atividade de API observável.

Etapa 3 – Executar o payload

  • EXE payload: O injector simplesmente salta para o ponto de entrada original assim que as relocations são concluídas. Quando o loader acha que chamaria DllMain, o código customizado, em vez disso, executa a entrada no estilo EXE.
  • DLL payload / Node.js addon: Resolve e chama o export pretendido (Kidkadi expõe uma função nomeada para JavaScript). Como o módulo já está registrado em LdrpModuleBaseAddressIndex, buscas subsequentes o tratam como a DLL benigna.

Quando combinado com um Node.js native addon (.node file), todo o trabalho pesado dos internals do Windows permanece fora da camada JavaScript, ajudando o agente de ameaça a distribuir o mesmo loader com muitos wrappers Node ofuscados diferentes.

Referências

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks