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:
- Execution from a Macro Enabled Office Document
- Suspicious Macro Execution via Windows Scripts
- Suspicious File Dropped by a Macro Enabled Document
- Shortcut File Modification via Macro Enabled Document
- DLL Loaded from a Macro Enabled Document
- Process Creation via Microsoft Office Add-Ins
- Registry or File Modification from Suspicious Memory
- Access to Browser Credentials from Suspicious Memory
- Potential NTDLL Memory Unhooking
- Microsoft Common Language Runtime Loaded from Suspicious Memory
- Common Language Runtime Loaded via an Unsigned Module
- Potential Masquerading as Windows Error Manager
- Suspicious Image Load via LdrLoadDLL
- Library Loaded via a CallBack Function
- Process Creation from Modified NTDLL
- DLL Side Loading via a Copied Microsoft Executable
- Potential Injection via the Console Window Class
- Suspicious Unsigned DLL Loaded by a Trusted Process
- Process Started via Remote Thread
- Potential Injection via DotNET Debugging
- Potential Process Creation via ShellCode
- Module Stomping form a Copied Library
- Process Creation from a Stomped Module
- Parallel NTDLL Loaded from Unbacked Memory
- Potential Operation via Direct Syscall
- Potential Process Creation via Direct Syscall
- Process from Archive or Removable Media via Unbacked Code
- Network Module Loaded from Suspicious Unbacked Memory
- Rundll32 or Regsvr32 Loaded a DLL from Unbacked Memory
- Windows Console Execution from Unbacked Memory
- Process Creation from Unbacked Memory via Unsigned Parent