Single-header direct syscall library for Windows x64.
Resolves syscall numbers at runtime using HellsGate and HalosGate, writes minimal stubs into an RX memory pool, and exposes them as standard Nt* function pointers. Calls bypass ntdll entirely — EDR hooks on ntdll stubs are never executed.
Full writeup: SSN (System Service Number)
Walks the PEB to find ntdll, parses its export table, and resolves SSNs for all Zw* functions using HellsGate (direct byte read) with HalosGate fallback (neighbor derivation). Writes 11-byte stubs into a single VirtualAlloc pool, patches each stub with its resolved SSN, then flips the pool to PAGE_EXECUTE_READ. One VirtualAlloc call at init — everything else goes through direct syscalls.
| Function | Zw* resolved |
|---|---|
NtAllocateVirtualMemory |
ZwAllocateVirtualMemory |
NtFreeVirtualMemory |
ZwFreeVirtualMemory |
NtWriteVirtualMemory |
ZwWriteVirtualMemory |
NtReadVirtualMemory |
ZwReadVirtualMemory |
NtProtectVirtualMemory |
ZwProtectVirtualMemory |
NtQueryVirtualMemory |
ZwQueryVirtualMemory |
NtCreateThreadEx |
ZwCreateThreadEx |
NtOpenProcess |
ZwOpenProcess |
NtOpenThread |
ZwOpenThread |
NtSuspendThread |
ZwSuspendThread |
NtResumeThread |
ZwResumeThread |
NtQuerySystemInformation |
ZwQuerySystemInformation |
NtDuplicateObject |
ZwDuplicateObject |
NtWaitForSingleObject |
ZwWaitForSingleObject |
NtClose |
ZwClose |
Drop syscalls.h into your project. Call DirectSyscall_Init() once, then call any Nt* pointer directly.
#include "syscalls.h"
int main(void) {
if (!DirectSyscall_Init())
return 1;
PVOID addr = NULL;
SIZE_T size = 0x1000;
NTSTATUS st = NtAllocateVirtualMemory(
NtCurrentProcess(), &addr, 0, &size,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE
);
if (!NT_SUCCESS(st)) return 1;
SIZE_T freeSize = 0;
NtFreeVirtualMemory(NtCurrentProcess(), &addr, &freeSize, MEM_RELEASE);
return 0;
}Compile with -masm=intel:
x86_64-w64-mingw32-gcc -o program.exe main.c -Wall -O2 -masm=intelEnable verbose logging during development:
x86_64-w64-mingw32-gcc -DSYSCALLS_DEBUG -o program.exe main.c -Wall -O2 -masm=intel1. Add the typedef:
typedef NTSTATUS (NTAPI *pNtYourFunction)(HANDLE, /* params */);2. Add the function pointer:
static pNtYourFunction NtYourFunction = NULL;3. Register the stub in Stubs_Init:
ok &= _sc_writeStub(slot++, "ZwYourFunction", (PVOID *)&NtYourFunction);4. Increment SC_STUB_COUNT:
#define SC_STUB_COUNT 16 // was 15Requires mingw-w64.
make| Target | Output | Description |
|---|---|---|
make |
stub_test.exe + ssn_test.exe |
Debug builds |
make release |
stub_test_release.exe |
Silent release build of stub_test |
make clean |
— | Remove build artifacts |
Exercises all registered stubs: alloc, write, read, protect, query, free, open process, close. Prints pass/fail per test and an exit summary.
=== Direct Syscall Stub Test ===
[+] DirectSyscall_Init: ntdll @ 0x00007FFD1B3B0000
[+] DirectSyscall_Init: resolved 450 syscalls, 0 hooked
[+] DirectSyscall_Init: pool @ 0x000001C0AB230000 (165 bytes RWX)
[+] DirectSyscall_Init: 15 stubs written
[+] DirectSyscall_Init: pool flipped to PAGE_EXECUTE_READ
FUNCTION SSN STUB ADDR
---------------------------------------- ------ ------------------
NtAllocateVirtualMemory 0x0018 0x000001C0AB230000
NtFreeVirtualMemory 0x001E 0x000001C0AB23000B
...
[+] PASS NtAllocateVirtualMemory
[+] PASS NtWriteVirtualMemory
[+] PASS NtReadVirtualMemory
[+] PASS NtProtectVirtualMemory
[+] PASS NtQueryVirtualMemory
[+] PASS NtFreeVirtualMemory
[+] PASS NtOpenProcess
=== 7 passed / 0 failed ===
Standalone diagnostic. Resolves and prints every Zw* SSN from ntdll — useful for verifying syscall numbers on a target or identifying which stubs an EDR has hooked.
FUNCTION SSN ADDRESS METHOD
--------------------------------------------- ------ ------------------ -----------
ZwAccessCheck 0x0000 0x00007FFD1B3B10E0 HellsGate
ZwAllocateVirtualMemory 0x0018 0x00007FFD1B3B1270 HellsGate
...
[+] 450 syscalls resolved | 0 hooked stubs
- Windows x64 — x86 is not supported
- mingw-w64 —
x86_64-w64-mingw32-gcc - One translation unit —
syscalls.husesstaticglobals. Include it in exactly one.cfile per binary
- x64 only — stub template and
GS:[0x60]PEB offset are x64-specific - One Win32 call at init —
VirtualAllocis called once for the stub pool; everything else uses direct syscalls - HalosGate last resort — if every single
Zw*stub is hooked (extremely rare), falls back to using the sorted index as the SSN, which may be inaccurate
| Technique | Original research |
|---|---|
| HellsGate | @smelly__vx & @am0nsec |
| HalosGate | @trickster012 |
MIT