Stack Spoofing on Windows 11 24H2: What EDR Telemetry Misses
Stack spoofing is one of those techniques that sounds more exotic than it is. At a high level, the attacker tries to make a sensitive action appear to come from a normal-looking caller chain. When an EDR records a call to a high-signal API, it often walks the user-mode stack and asks: "Does this look like a normal program path, or does it look like injected code?"
That question is useful, but it is not enough by itself.
During assessments, we still see detection logic that treats a module-attributed stack as trustworthy. If the frames appear to belong to signed DLLs, the event is scored lower. If the frames include unknown memory, the event is scored higher. That model misses the uncomfortable middle ground: a stack can look ordinary while the surrounding behavior is not.
This post is written from a defensive perspective. It explains why stack inspection breaks down, what Windows 11 24H2 changed and did not change, and how teams can build better detection around behavior, memory provenance, and unwind consistency.
The Core Problem
Most endpoint controls observe sensitive activity at a few choke points:
- memory allocation with executable permissions
- remote thread creation
- process injection paths
- handle access to protected processes
- unusual script, LOLBin, or child-process behavior
- network callbacks from unexpected processes
When one of those events fires, the product often collects a call stack. The stack provides context. A browser allocating memory through a normal JIT path should not be treated the same as an unsigned region calling into ntdll.
The weakness is that a call stack is not a complete explanation of intent. It is a runtime artifact, and runtime artifacts can be shaped.
Defenders should treat the stack as one signal among many, not as the final answer.
Why Stack-Based Trust Fails
A shallow stack inspection usually answers three questions:
- Do the return addresses resolve to loaded modules?
- Are those modules signed or expected?
- Does the chain include suspicious anonymous memory?
Those questions catch a lot of noisy tooling. They fail when the suspicious action is surrounded by a plausible-looking frame sequence.
The more useful questions are harder:
- Are the frames consistent with unwind metadata?
- Do the return addresses land in executable sections that match the module's image?
- Does the thread's recent behavior match the apparent caller chain?
- Did this thread previously execute from private memory, then suddenly produce a clean stack?
- Does the process normally call this API with this access mask, region size, or target process?
- Did the same actor perform staging activity through another process, handle, or named object?
That is where strong detection engineering starts.
Windows 11 24H2: What Changed
Windows 11 24H2 changed several internals around user-mode unwinding and system behavior, but the basic defender challenge remains the same: call stacks are contextual evidence, not proof.
In practical testing, module-attributed frames still help an event appear less suspicious to simplistic logic. But modern Windows also gives defenders several ways to ask deeper questions:
- ETW providers can correlate thread, image load, process, and memory events.
- VAD inspection can distinguish private executable memory from image-backed regions.
- Unwind metadata can be checked for consistency instead of only resolving symbols.
- Code integrity, signer, and section characteristics can be included in scoring.
- Cross-event correlation can catch staging that the final call stack hides.
The lesson is not "stack telemetry is useless." The lesson is that stack telemetry becomes useful when it is correlated.
What Good Telemetry Looks Like
A mature detection should preserve enough detail to answer these questions after the fact:
| Signal | Why it matters |
|---|---|
| Thread start address | Helps identify threads that began in private or unusual memory. |
| Recent execution region | Catches threads that briefly executed from private memory before a sensitive API call. |
| Return address provenance | Distinguishes image-backed .text from writable or remapped regions. |
| Unwind validity | Identifies frames that resolve but do not unwind cleanly. |
| Allocation history | Links memory permissions, region size, and caller process. |
| Handle lineage | Shows who opened what, when, and with which rights. |
| Parent and sibling activity | Catches staging spread across processes. |
The best detections we have seen do not rely on a single dramatic IOC. They combine small inconsistencies until the story no longer looks normal.
Detection Ideas
Here are defensive analytics that tend to age better than signature-only logic:
1. Score private executable memory by behavior
Private executable memory is not automatically malicious. Browsers, runtimes, security tools, and game engines can all create it. But private executable memory followed by sensitive process access, unusual thread behavior, or direct system-call-heavy activity deserves attention.
Useful fields:
- allocation protection transitions
- region size
- creating thread
- process lineage
- whether the region was later executed
- whether the region has a mapped-file relationship
2. Validate return address sections
Do not stop at "the frame belongs to a signed module." Ask whether the return address is in an executable section that matches the loaded image and whether the section characteristics make sense.
High-signal cases include:
- return address outside
.text - return address inside writable/executable memory
- return address in a manually mapped module
- return address in a module with unusual load path or signer mismatch
3. Compare stack story to thread story
A clean-looking stack at the final event can hide suspicious activity earlier in the same thread. Track whether the thread recently:
- started at an unusual address
- executed from private memory
- changed memory protections
- touched another process with high access rights
- loaded unusual modules shortly before the event
The timeline is often more honest than the final stack.
4. Hunt impossible or rare unwind patterns
Some spoofed stacks are convincing only at the surface level. Deeper unwind validation can reveal frames that do not behave like normal compiler-generated call paths.
Defenders can look for:
- abnormal frame pointer behavior
- inconsistent unwind metadata
- return addresses that do not line up with expected call sites
- repeated synthetic-looking frame chains across unrelated processes
5. Correlate API intent with process role
The same API call means different things in different processes. NtAllocateVirtualMemory in a browser renderer is not the same signal as the same call inside an office process followed by a suspicious child process.
A useful model includes:
- process role
- command line
- parent process
- signer
- user context
- target process
- access mask
- historical baseline
What We Recommend to Clients
When we test EDR resilience, we do not grade success by whether one event fired. We look at whether the defender can reconstruct the chain.
For blue teams, the practical checklist is:
- keep raw telemetry long enough to investigate
- collect stack traces, but do not over-trust them
- enrich return addresses with section and memory provenance
- correlate memory, thread, process, handle, and image-load events
- alert on suspicious sequences, not just suspicious calls
- test detections against realistic tradecraft regularly
For engineering leaders, the takeaway is simpler: a prevention product is not a security program. You still need tuned detections, response playbooks, and periodic adversary simulation.
Closing
Stack spoofing is not magic. It is a reminder that attackers operate in the gaps between signals. A stack trace can be useful evidence, but it should not be treated as identity, intent, or innocence.
Good detection engineering asks a better question:
Does the whole execution story make sense?
When the stack says one thing and the process history says another, investigate the history.