Skip to content

sebafvs/directsyscall

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

syscalls.h

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)


How it works

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.


Included 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

Usage

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=intel

Enable verbose logging during development:

x86_64-w64-mingw32-gcc -DSYSCALLS_DEBUG -o program.exe main.c -Wall -O2 -masm=intel

Adding a syscall

1. 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 15

Building the tests

Requires 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

stub_test.exe

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 ===

ssn_test.exe

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

Requirements

  • Windows x64 — x86 is not supported
  • mingw-w64x86_64-w64-mingw32-gcc
  • One translation unitsyscalls.h uses static globals. Include it in exactly one .c file per binary

Limitations

  • x64 only — stub template and GS:[0x60] PEB offset are x64-specific
  • One Win32 call at initVirtualAlloc is 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

Prior art

Technique Original research
HellsGate @smelly__vx & @am0nsec
HalosGate @trickster012

License

MIT

About

Single-header direct syscall library for Windows x64. HellsGate + HalosGate SSN resolution, RX stub pool, zero ntdll dependency after init.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors