Solution 21: Dynamic API Resolution
Overview
Rule 7 only inspects the PE Import Address Table (IAT) — the list of functions statically linked at compile time. By resolving NtAllocateVirtualMemory dynamically at runtime via GetProcAddress, the function never appears in the IAT.
Solution
// resolve_dynamic.c - bypasses static import detection
#include <windows.h>
#include <stdio.h>
typedef NTSTATUS (NTAPI *pNtAllocateVirtualMemory)(
HANDLE, PVOID*, ULONG_PTR, PSIZE_T, ULONG, ULONG);
int main() {
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
pNtAllocateVirtualMemory NtAlloc =
(pNtAllocateVirtualMemory)GetProcAddress(ntdll, "NtAllocateVirtualMemory");
PVOID base = NULL;
SIZE_T size = 4096;
NtAlloc(GetCurrentProcess(), &base, 0, &size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
printf("Allocated at %p via dynamic resolution\n", base);
return 0;
}
Compile without linking ntdll.lib:
cl.exe resolve_dynamic.c /link kernel32.lib
Why It Works
The compiled PE only imports GetModuleHandleA and GetProcAddress from kernel32.dll. NtAllocateVirtualMemory is resolved at runtime via a function pointer — it never appears in the import table that Rule 7 scans.
A real EDR would:
- Hook
GetProcAddressto monitor dynamic resolution - Use kernel callbacks to intercept the actual syscall
- Monitor ETW telemetry for the allocation event
How to Verify
- Start the EDR:
.\edr_agent.exe --profile crowdstrike --verbose --no-kill - Run a program that statically imports
NtAllocateVirtualMemory— should trigger Rule 7 - Run
resolve_dynamic.exe— should show[OK]with no detection
MostShittyEDR