Listen to this Post

Introduction:
As security vendors relentlessly monitor Win32 APIs for malicious activity, adversaries are forced to venture deeper into undocumented Windows territory. This article explores an experimental yet potent technique that weaponizes non‑exported, internal NTDLL functions as execution primitives, effectively bypassing user‑land hooks and traditional detection. By scanning NTDLL for specific byte patterns and hijacking a function that calls a pointer from a controlled structure, attackers can execute shellcode without ever invoking a monitored API like `CreateThread` or `QueueUserAPC` – a stealthy evolution in the ongoing cat‑and‑mouse game of endpoint evasion.
Learning Objectives:
- Understand how internal NTDLL functions can be located and abused to execute shellcode without relying on exported Win32 APIs.
- Learn to implement a custom, EDR‑aware shellcode loader using function‑pointer trampolines and indirect syscalls.
- Develop detection strategies and hardening measures to identify and mitigate such advanced execution primitives.
You Should Know:
1. Locating and Invoking a Non‑Exported NTDLL Gadget
Traditional shellcode execution often relies on a simple function pointer cast:
void exec = VirtualAlloc(NULL, shellcodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); memcpy(exec, shellcode, shellcodeSize); ((void()())exec)();
While effective, the call to `VirtualAlloc` and the casted call itself are easily hooked by EDRs. A more covert approach uses an internal NTDLL function that acts as a trampoline. By analyzing NTDLL with a disassembler like Ghidra, one may encounter a function whose first two instructions are:
MOV RAX, QWORD PTR [RCX + 0x20] CALL RAX
This gadget loads a pointer from offset `0x20` in the structure pointed to by `RCX` (the first argument) and then calls it. Because the function is not exported (i.e., not listed in NTDLL’s export table), it cannot be retrieved via GetProcAddress. Instead, the attacker must locate it by scanning NTDLL’s `.text` section for the unique byte pattern of those instructions.
Step‑by‑Step Guide:
- Obtain the base address of NTDLL in the current process (e.g., by walking the PEB or using
GetModuleHandle("ntdll.dll")). - Parse the PE headers to locate the `.text` section and its size.
- Scan the `.text` section for the byte signature corresponding to
MOV RAX, [RCX+0x20]; CALL RAX. In x64, this might be0x48 0x8B 0x41 0x20 0xFF 0xD0. - Store the found address in a function pointer.
- Define a custom structure that mimics the expected layout:
typedef struct _MY_STRUCT { BYTE padding[bash]; // 32 bytes of filler PVOID pvShellcodeAddr; // pointer to shellcode } MY_STRUCT, PMY_STRUCT; - Allocate executable memory for the shellcode (still using `VirtualAlloc` or a direct syscall), copy the shellcode, and fill an instance of `MY_STRUCT` with the pointer to that memory.
- Invoke the internal function with the address of the structure:
pInternalFunction(&myStruct);
Why this evades many EDRs:
The call chain no longer includes a well‑known API like `CreateThread` or a direct call to an executable memory region. Instead, control flow passes through a legitimate, Microsoft‑signed NTDLL function that appears as a normal internal call. However, as the original research notes, this technique is not bulletproof: sophisticated EDRs that perform call‑stack unwinding may still flag the fact that the final target address points outside of NTDLL’s own memory.
2. Indirect Syscalls and Return Address Spoofing
A more mature evasion method is the use of indirect syscalls combined with return address spoofing. Instead of calling an NTDLL function that may be hooked, the attacker constructs a small stub that invokes the `syscall` instruction directly, while also ensuring that the return address on the stack points to a location inside NTDLL (or another trusted module). This defeats EDRs that verify whether the return address lies in trusted memory.
Step‑by‑Step Guide:
- Locate export functions in NTDLL by parsing the export table. For each desired syscall (e.g.,
NtAllocateVirtualMemory), extract its System Service Number (SSN) and the address of the `syscall` instruction inside the function. - Find a suitable return gadget – a short sequence like `ADD RSP, 0x58; RET` inside NTDLL. This gadget will be used as the spoofed return address.
3. Create a custom stub that:
- Saves the current stack pointer.
- Adjusts the stack to accommodate the gadget.
- Copies the syscall arguments to the new stack location.
- Pushes the gadget’s address as the return address.
- Jumps to the saved `syscall` instruction address.
- Execute the stub instead of calling the original NTDLL function.
Example of an indirect syscall stub (NASM syntax):
indirect_syscall: push rbp mov rbp, rsp ; save original stack mov [rsp - 8], rsp ; adjust stack for gadget sub rsp, 0x60 ; copy arguments to new stack... ; push return gadget mov rax, [bash] push rax ; jump to syscall instruction mov rax, [bash] jmp rax
Projects like DoomSyscalls automate this process, dynamically resolving SSNs and `syscall` addresses while also locating suitable return gadgets. By ensuring that every syscall returns from a location inside NTDLL, the call stack appears legitimate to kernel‑mode EDR components.
3. APC Injection via NtTestAlert
Another stealthy execution primitive leverages the Asynchronous Procedure Call (APC) mechanism. The `NtTestAlert` function, which is exported from NTDLL, forces the current thread to process its APC queue. Attackers can queue a shellcode‑executing APC to a target thread and then invoke NtTestAlert, causing the shellcode to run in the context of that thread.
Step‑by‑Step Guide:
- Identify a target thread within the desired process (e.g., using
CreateToolhelp32Snapshot). - Open the thread with `THREAD_SET_CONTEXT | THREAD_QUERY_INFORMATION` privileges.
- Allocate memory in the target process for the shellcode (using `NtAllocateVirtualMemory` directly to avoid hooks).
- Write the shellcode into the allocated region (
NtWriteVirtualMemory). - Queue an APC to the target thread using
QueueUserAPC, pointing to the shellcode address. - Trigger execution by calling `NtTestAlert` in the target thread. If the thread is in an alertable state, the APC will fire and the shellcode will run.
This method avoids creating new threads and does not modify existing thread code, making it harder for EDRs to detect. The `NtTestAlert` based trigger is particularly effective when combined with Early Bird injection, where the APC is queued to the main thread of a suspended process before it resumes.
4. Bypassing Call‑Stack Signatures
Modern EDRs, such as Elastic’s platform, rely heavily on call‑stack telemetry to detect anomalies. For example, loading a network library (like ws2_32.dll) from unbacked memory may trigger a rule that examines the call stack for specific patterns. Researchers have demonstrated a bypass that inserts a “call gadget” from an innocuous DLL (e.g., dsdmo.dll) into the stack, disrupting the signature.
Step‑by‑Step Guide:
- Pre‑load a benign DLL that is not on the EDR’s watch list (e.g., `dsdmo.dll` from System32).
- Locate a gadget inside that DLL – a sequence of instructions that calls a register (e.g., `call rax` or
call qword ptr [bash]) followed by aret. - When performing a sensitive operation (e.g.,
LoadLibraryA), manipulate the stack so that the return address points into this gadget. - The gadget then calls the actual API, and the stack frame now shows the benign DLL between `ntdll.dll` and
kernelbase.dll, bypassing the EDR’s signature.
This technique is highly version‑dependent, as gadget addresses may change with Windows updates. Tools like Winbindex help in hunting for stable gadgets across builds.
5. Hardening and Detection Strategies
What Undercode Say:
- Call‑stack integrity monitoring is essential – use kernel‑mode call‑stack profiling to detect return addresses that lie outside of trusted modules, even when they are spoofed.
- Harden NTDLL by enabling Control Flow Guard (CFG) and Arbitrary Code Guard (ACG) to block execution of non‑image memory and enforce valid call targets.
- Implement behavior‑based detection rules that look for sequences of low‑level operations (e.g., `NtAllocateVirtualMemory` followed by `NtWriteVirtualMemory` and an APC queue) rather than single API calls.
From a defensive perspective, isolating critical processes with Protected Process Light (PPL) and enabling Credential Guard can mitigate many injection techniques. Moreover, leveraging Event Tracing for Windows (ETW) with call‑stack collection provides rich telemetry that, when combined with machine learning, can flag anomalous call patterns. Regularly updating EDR rules to include newly discovered internal function gadgets and return‑address spoofing indicators is also crucial.
Prediction:
As EDRs become more adept at call‑stack analysis, attackers will shift toward hardware‑assisted execution primitives (e.g., Intel PT‑based bluepill techniques) and kernel‑only payloads that never touch user‑mode NTDLL. The future will see a renewed focus on virtualization‑based security (VBS) and hypervisor‑protected code integrity (HVCI) as the last line of defense, while attackers will increasingly abuse legitimate signed drivers and firmware interfaces. The cat‑and‑mouse game is far from over – it is merely moving to deeper layers of the system stack.
▶️ Related Video (76% Match):
🎯Let’s Practice For Free:
IT/Security Reporter URL:
Reported By: Abelousova Internal – Hackers Feeds
Extra Hub: Undercode MoN
Basic Verification: Pass ✅


