Upping the Ante: Detecting In- Memory Threats with Kernel Call Stacks

We aim to out-innovate adversaries and maintain protections against the cutting edge of attacker tradecraft. With Elastic Security 8.8, we added new kernel call stack based detections which provide us with improved efficacy against in-memory threats.

Upping the Ante: Detecting In-Memory Threats with Kernel Call Stacks

Intro

Elastic Security for endpoint, with its roots in Endgame, has long led the industry for in-memory threat detection. We pioneered and patented many detection technologies such as kernel thread start preventions, call stack anomaly hunting, and module stomping discovery. However, adversaries continue to innovate and evade detections. For example, in response to our improved memory signature protection, adversaries developed a flurry of new sleep based evasions. We aim to out-innovate adversaries and maintain protections against the cutting edge of attacker tradecraft. With Elastic Security 8.8, we added new kernel call stack based detections which provide us with improved efficacy against in-memory threats.

Before we get started, it's important to know what call stacks are and why they’re valuable for detection engineering. A call stack is the ordered sequence of functions that are executed to achieve a behavior of a program. It shows in detail which functions (and their associated modules) were executed to lead to a behavior like a new file or process being created. Knowing a behavior’s call stack, we can build detections with detailed contextual information about what a program is doing and how it’s doing it.

Deep Visibility

The new call stack based detection capability leverages our existing deep in-line kernel visibility for the most common system behaviors (process, file, registry, library, etc). With each event, we capture the call stack for the activity. This is later enriched with module information, symbols, and evidence of suspicious activity. This gives us procmon-like visibility in real-time, powering advanced preventions for in-memory tradecraft.

Process creation call stack fields :

File, registry and library call stack fields:

New Rules

Additional visibility wouldn’t raise the bar unless we could pair it with tuned, high confidence preventions. In 8.8, behavior protection comes out of the box with 30+ rules to provide us with high efficacy against cutting edge attacker techniques such as: - Direct syscalls - Callback-based evasion - Module Stomping - Library loading from unbacked region - Process created from unbacked region - Many more

Call stacks are a powerful data source that can be used to improve protection against non-memory-based threats as well. For example, the following EQL queries look for the creation of a child process or an executable file extension from an Office process with a call stack containing VBE7.dll (a strong sign of the presence of a macro-enabled document). This increases the signal and coverage of the rule logic while reducing the necessary tuning efforts compared to just process or file creation events with no call stack information:

Below are some examples of matches where Macro-enabled malicious Excel and Word documents spawning a child process where the call stack refers to vbe7.dll :

Here, we can see a malicious XLL file opened via Excel spawning a legitimate browser\_broker.exe to inject into. The parent call stack indicates that the process creation call is coming from the [xlAutoOpen](https://learn.microsoft.com/en-us/office/client-developer/excel/xlautoopen) function:

The same enrichment is also valuable in library load and registry events. Below is an example of loading the Microsoft Common Language Runtime CLR.DLL module from a suspicious call stack (unbacked memory region with RWX permissions) using the Sliver execute-assembly command to load external .NET assemblies:

library where dll.name : "clr.dll" and
process.thread.Ext.call_stack_summary : "*mscoreei.dll|Unbacked*"

Hunting for suspicious modification of certain registry keys such as the Run key for persistence tends to be noisy and very common in legit software but if we add the call stack signal to the logic, the suspicion level is significantly increased :

registry where 
 registry.path : "H*\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\*"
// the creating thread's stack contains frames pointing outside any known executable image
 and process.thread.Ext.call_stack_contains_unbacked == true

Another “fun” example is the use of the call stack information to detect rogue instances of core system processes that normally have very specific functionality. By signaturing their normal call stacks, we can easily identify outliers. For example, WerFault.exe and wermgr.exe are among the most attractive targets for masquerading:

Examples of matches:

Apart from the use of call stack data for finding suspicious behaviors, it’s also useful when it comes to excluding false positives from behavior detections in a more granular way. This also helps reduce evasion opportunities.

A good example is a detection rule looking for unusual Microsoft Office child processes. This rule is used to exclude splwow64.exe , which can be legitimately spawned by printing activity. Excluding it by process.executable creates an evasion opportunity via process hollowing or injection, which can make the process tree look normal. We can now mitigate this evasion by requiring such process creations to come from winspool.drv!OpenPrinter :

process where event.action == "start" and
  process.parent.name : ("WINWORD.EXE", "EXCEL.EXE", "POWERPNT.EXE", "MSACCESS.EXE", "mspub.exe", "fltldr.exe", "visio.exe") and
// excluding splwow64.exe only if it’s parent callstack is coming from winspool.drv module  
not (process.executable : "?:\\Windows\\splwow64.exe" and``_arraysearch(process.parent.thread.Ext.call_stack, $entry, $entry.symbol_info: ("?:\\Windows\\System32\\winspool.drv!OpenPrinter*", "?:\\Windows\\SysWOW64\\winspool.drv!OpenPrinter*")))

To reduce event volumes, call stack information is collected on the endpoint and processed for detections but not always streamed in events. To always include call stacks in streamed events an advanced option is available in Endpoint policy:

C2 Coverage

Elastic Endpoint makes quick work detecting some of the top C2 frameworks active today. See below for a screenshot detecting Nighthawk, BruteRatel, CobaltStrike, and ATP41’s StealthVector.

Conclusion

While this capability gives us a lead over the cutting edge of in-memory tradecraft today, attackers will no doubt develop new innovations in attempts to evade it. That’s why we are already hard at work to deliver the next set of leading in-memory detections. Stay tuned!

Resources

Rules released with 8.8: