Tsundere Botnet — Node.js Binary
The Tsundere botnet, identified by Kaspersky Global Research and Analysis Team (GReAT) in July 2025, represents a sophisticated evolution in cross-platform malware campaigns orchestrated by a Russian-speaking threat actor known as “koneko.” Initially surfacing through a 2024 npm supply-chain attack involving 287 typosquatted Node.js packages, Tsundere has matured into an actively expanding botnet primarily targeting Windows systems. Leveraging blockchain technology for command-and-control (C2) resilience, it enables dynamic execution of arbitrary JavaScript payloads, posing risks of data exfiltration, cryptocurrency theft, and further compromise.
Note: I analyzed this sample on my own, after my analysis I identified the malware as being Tsundre Botnet, based on the activity Kaspersky reported on here: The Tsundere botnet uses the Ethereum blockchain to infect its targets | Securelist. I will be reviewing IOC’s and if the ones I observed are still consistent with what was previously reported.
SHA256: 16e0dbcc6670e7722f68620b6f305e2c4433ed6f7b25174a75480ed5c4b4fe42
This hash was initially reported yesterday, December 3, 2025, and there was no real indicators that this was malicious. Comments also reflected one person believing that this was a benign binary based on analysis from an automated sandbox.
Press enter or click to view image in full size
Sample.js
This JavaScript (JS) code is pretty heavily obfuscated. The obfuscation combines string encryption, control flow flattening, identifier mangling, and runtime decryption, making it challenging to analyze without deobfuscation tools or manual unpacking.
String Obfuscation via Custom Base64 + RC4 Decryption:
Nearly all strings (e.g., URLs, function names, console messages) are stored in encrypted arrays and decrypted at runtime using a hybrid Base64 decoder followed by an RC4 (ARC4) stream cipher. This prevents static string-based signatures (e.g., YARA rules) from triggering.
Implementation Details:
The core decoder is defined in the _0x2905 function (lines ~1–50). It initializes an RC4 key schedule using a user-provided key (_0x3495d6).
Step 1: Base64-like decoding. A mangled Base64 alphabet (‘abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=’) decodes input into a URI-encoded string (e.g., %XX format), then decodeURIComponent expands it.
Step 2: RC4 decryption. The decoded string is fed into an RC4 keystream generator
// Simplified pseudocode from _0x2905['SbIfNX']
for (let i = 0; i < 256; i++) sbox[i] = i; // Initialize S-box
j = 0;
for (let i = 0; i < 256; i++) {
j = (j + sbox[i] + key.charCodeAt(i % key.length)) % 256;
swap sbox[i] and sbox[j];
}
// Keystream XOR with plaintext
for (let i = 0; i < plaintext.length; i++) {
i_idx = (i + 1) % 256;
j = (j + sbox[i_idx]) % 256;
swap sbox[i_idx] and sbox[j];
k = sbox[(sbox[i_idx] + sbox[j]) % 256];
output += String.fromCharCode(plaintext.charCodeAt(i) ^ k);
}Identifier Mangling with Hexadecimal Names
All variables, functions, and properties use randomized hexadecimal prefixes (e.g., _0x2905, _0x381842, _0x4f8a99). This is generated by tools like javascript-obfuscator, renaming ~90% of identifiers to meaningless shorts.
Implementation Details:
Functions like _0x4f8a() return the string array.
Nested helpers (e.g., _0x56f881(_0x575caf — -0xf5, _0x3c7903) ) compute indices via arithmetic offsets (e.g., arg — 0x17b).
Multiple layers: Outer _0x2905, inner _0x232dae, _0x5cb090, each with its own array and offset math.
Control Flow Flattening with While-Try-Catch Loops
Linear code is “flattened” into opaque predicates — while loops that shift/rotate an array until a computed checksum matches a magic value. This disrupts disassemblers and adds anti-debugging (e.g., infinite loops if tampered).
while(!![]){ // Infinite loop
try {
const _0x1b1805 = parseInt(_0x56f881(0x238, ...)) / 1 + ...; // Compute sum of obfuscated ints
if (_0x1b1805 === _0xad14cc) break; // Magic: 0x704f6 (~458086)
else _0x596a8d['push'](_0x596a8d['shift']()); // Rotate array
} catch { _0x596a8d['push'](_0x596a8d['shift']()); }
}Multi-Layered Obfuscation and Runtime Code Execution
Obfuscation is nested (e.g., _0x2905 calls _0x232dae, which calls _0x5cb090). Dynamic new Function() executes decrypted payloads, enabling further evasion.
Implementation Details:
Layers: 4+ decoders (_0x2905, _0x232dae, _0x5cb090, _0x31de0e). Each has its own array (e.g., _0x41cd62 with 70+ hex keys).
Runtime Eval: In onMessage (lines ~600+), decrypts incoming WebSocket data, then
const _0x33324b = new Function('require', 'global', ..., decrypted_code);
_0x33324b(require, global, ...); // Executes arbitrary JSPayloads include overrides like global.serverSend for C2 callbacks.
AES Encryption: Outbound/inbound messages use AES-256-CBC (crypto module) with runtime keys/IVs (16-byte IV check).
Anti-Analysis and Evasion Techniques
Dynamic Imports: require(‘ws’), require(‘crypto’), require(‘os’), require(‘ethers’) — delays footprint.
Error Handling: Broad try-catch swallows exceptions, logging minimally
Timing/Polling: Ping-pong over WebSocket (30s interval), reconnects on failure (15s timeout).
Platform-Specific: Windows-focused (e.g., wmic queries for UUID, GPU via PowerShell). Collects sysinfo (MAC, BIOS, volume serial) hashed into UUID.
System Fingerprinting
The malware collects victim data:
Username (os.userInfo())
Hostname (os.hostname())
Platform/Architecture
CPU information
GPU information (WMI: Win32_VideoController)
MAC Address (first non-internal interface)
Total Memory
Node.js Version
Windows Edition (WMI: Win32_OperatingSystem)
Volume Serial Number (vol command)
BIOS Information (WMI: SystemBIOS)
System UUID (Registry: MachineGuid)
All data is hashed (SHA256) to create a unique userId in UUID format.
Press enter or click to view image in full size
Connections and Host Enumeration
CIS Country Kill Switch (Ukraine being an exception)
hy (Armenian)
hy-AM (Armenian — Armenia)
az (Azerbaijani)
be (Belarusian)
be-BY (Belarusian — Belarus)
kk (Kazakh)
ky (Kyrgyz)
ky-KG (Kyrgyz — Kyrgyzstan)
ru (Russian)
ru-RU (Russian — Russia)
ru-BY (Russian — Belarus)
ru-KG (Russian — Kyrgyzstan)
ru-MD (Russian — Moldova)
ru-UA (Russian — Ukraine)
tg (Tajik)
uk (Ukrainian)
uk-UA (Ukrainian — Ukraine)
uz (Uzbek)
Remote Code Exection (RCE)
When receiving a message with id=1, the malware:
1. Creates a new Function() with the received code
2. Provides access to: require, global, console
3. Executes the code in a try-catch wrapper
4. Sends results back via serverSend() callback
This allows the C2 server to push arbitrary Node.js code for execution.
Registry Queries:
HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid (System UUID)
HKLM\SYSTEM\CurrentControlSet\Control\SystemInformation\SystemBIOSVersion
When I execute the JS binary in cmd.exe:
Press enter or click to view image in full size
We see it making connections to RPC at multiple different Ethereum Wallet Endpoints. It also gathers host enumeration details as defined earlier in the code review.
The Process Tree:
Press enter or click to view image in full size
Process Tree Breakdown
node.exe (8072)
├── “C:\Program Files\nodejs\node.exe” …16e0dbcc6670e7722f68620b6f305e2c4433ed6f7b25174a75480ed5c4b4fe42.js
│
├── cmd.exe (3812) → powershell.exe “[System.Globalization.CultureInfo]::InstalledUICulture.Name”
│ └── [CIS LOCALE CHECK — Kill Switch]
│
├── cmd.exe (7164) → powershell.exe “Get-WmiObject Win32_VideoController | Select-Object -ExpandProperty Name”
│ └── [GPU FINGERPRINTING]
│
├── cmd.exe (7252) → reg query “HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion” /v ProductName
│ └── [WINDOWS EDITION]
│
├── reg.exe (4356) → reg query “HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion” /v ProductName
│ └── [WINDOWS EDITION — direct call]
│
├── cmd.exe (4440) → cmd.exe /d /s /c “vol”
│ └── [VOLUME SERIAL NUMBER]
│
├── cmd.exe (7008) → reg query “HKLM\HARDWARE\DESCRIPTION\System\BIOS”
│ └── [BIOS FINGERPRINTING]
│
├── reg.exe (8068) → reg query “HKLM\HARDWARE\DESCRIPTION\System\BIOS”
│ └── [BIOS — direct call]
│
├── cmd.exe (832) → reg query “HKLM\SOFTWARE\Microsoft\Cryptography” /v MachineGuid
│ └── [SYSTEM UUID — Unique identifier]
│
├── reg.exe (7432) → reg query “HKLM\SOFTWARE\Microsoft\Cryptography” /v MachineGuid
│ └── [SYSTEM UUID — direct call]
│
├── cmd.exe (1496) → powershell.exe “[System.Globalization.CultureInfo]::InstalledUICulture.Name”
│ └── [SECOND LOCALE CHECK — possibly in reconnect loop]
│
└── powershell.exe (4) → “[System.Globalization.CultureInfo]::InstalledUICulture.Name”
└── [THIRD LOCALE CHECK]
DNS Query to one of the Ethereum Wallet Endpoints
DNS Query to a second Ethereum Wallet Endpoint
Shodan
Example of the Websocket Handshake Get Request:
CLIENT REQUEST:
— — — — — — — -
GET / HTTP/1.1
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: gcG7wVAmx5B2IhtwTdv9WQ==
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Host: 193.24.123.68:3011
Traffic Flow Summary:
13808 551.95 →C2 227 WebSocket handshake request
13815 552.12 ←C2 129 101 Switching Protocols
13816 552.12 ←C2 34 AES-256 Key (binary)
13818 552.13 →C2 6 ACK
13822 552.29 ←C2 18 AES IV (binary)
13923 553.41 →C2 520 Encrypted victim fingerprint
13997 553.63 ←C2 50 Encrypted ack: “Connected”
IOCs
Network:
193.24.123.68:3011 (WebSocket C2)
rpc.flashbots.net (Ethereum RPC)
rpc.mevblocker.io (Ethereum RPC)
eth.llamarpc.com (Ethereum RPC)
eth.merkle.io (Ethereum RPC)
eth.drpc.org (Ethereum RPC)
Also, these are all new IOC’s compared to the Kaspersky report:
Encryption
AES Key: e5f4e1b5d1065b0ecd6b3ef972d451e1c63ecd8da4d73b82cf429d70d13d166f
AES IV: 4e3d21a0941bb92632c4997fd4a582b1
Thank you! Critque welcome and appreciated!
August Vansickle
Twitter: @LunchM0n3ey9090
Linkedin: August Vansickle | LinkedIn
References:
The Tsundere botnet uses the Ethereum blockchain to infect its targets | Securelist