Unsere Malware-Erkennungspipelines schlugen kürzlich bei einem kleinen Cluster von npm-Paketen an, die... bekannt vorkamen.
Pakete wie json-bigint-extend, jsonfx, und jsonfb imitierten die beliebte json-bigint Bibliothek: gleiche Funktionalität, eine identische README-Datei und sogar ein Autorenname, der dem des ursprünglichen Maintainers unangenehm ähnlich war.
Meistens deutet dieses Muster auf gängige Supply-Chain-Angriffe wie Typosquatting und Dependency Confusion hin, die darauf abzielen, Systeme zu kompromittieren und Secrets zu exfiltrieren. Doch dieser hier fühlte sich fast sofort anders an.
Er versuchte nicht, jeden zu treffen. Er versuchte, etwas zu treffen.
Die Kaperung
Auf den ersten Blick, json-bigint-extend verhält sich genau wie die legitime json-bigint Bibliothek: Sie exportiert die bekannten parse-/stringify-Funktionen, die zur Unterstützung großer Ganzzahlen in JSON verwendet werden. Tatsächlich würden die meisten Entwickelnden und Organisationen nichts Ungewöhnliches bemerken. Diese Payload ist speziell darauf ausgelegt, unauffällig zu bleiben und nur dann auszulösen, wenn sie erkennt, dass sie in einer spezifischen Zielumgebung läuft, indem sie den Wert einer bestimmten Umgebungsvariablen namens SERVICE_NAME.
Sobald sie erkennt, dass sie sich in der richtigen Umgebung befindet, installiert sie zwei Backdoors:
Zuerst installiert sie eine gezielte Express-Middleware, die speziell in eine Zahlungsroute (/v1/pay/purchase-goods). Diese Middleware ist darauf ausgelegt, zusätzlichen Code, der von einem Endpunkt abgerufen wird, dynamisch auszuführen. Bei genauerer Untersuchung des abgerufenen Codes scheint es sich um ein komplexes Cashflow-Umschreibungssystem zu handeln, das zur Manipulation eines Glücksspiels verwendet wird.
const routeInjectionRules = {
'/v1/pay/purchase-goods': {
identify: function (handlers, fn, index) {
...
},
position: 'after',
extraMiddlewares: [function (req, res, next) {
// Translation: [Plugin] Mount risk middleware as post-payment success logic.
log('[插件] 支付成功后的后置逻辑挂载risk');
riskCode(req, res, next); // Executes dynamically fetched code
}]
}
};
Zweitens eine Middleware auf Prototyp-Ebene, die Express.js unauffällig per Monkey-Patching erweitert und globale Middleware zu jeder POST-Route hinzufügt. Diese Middleware lauscht auf einen geheimen x-operation Header und schaltet dem Operator vier Arten von Befehlen frei:
- RunSQL: beliebiges SQL gegen die Produktionsdatenbank ausführen.
- RunFileList: serverseitige Dateien und Verzeichnisse auflisten.
- RunFileContent: den Inhalt einer ausgewählten Datei herunterladen.
- CompressDownload: ein Verzeichnis als Zip-Datei herunterladen.
Das Operator-Dashboard
Innerhalb des Pakets befindet sich auch eine eingebettete HTML-Seite für einen „Verzeichnis-Komprimierungs-Download-Dienst“ (Chinesischer Titel: 目录压缩下载服务).

Obwohl diese Seite im von uns beobachteten Backdoor-Code nirgendwo angebunden war, scheint es sich um eine Operator-orientierte Benutzeroberfläche zum Durchsuchen und Exfiltrieren von Verzeichnissen als Zip-Dateien zu handeln.
Manipulation von Glücksspielergebnissen
Der beängstigende Teil: dass die riskCode(...) im Middleware aufgerufene Funktion fernsteuerbar ist und alle 30 Sekunden aktualisiert wird.
Obwohl die Payload (noch) nicht aktiv aufgerufen wird, haben wir eine Logik beobachtet, die in der Lage ist, die jüngste Spielhistorie eines Benutzers rückwirkend anzupassen. Die raffinierteste Komponente dieser Backdoor ist die fixFlow Funktion, eine Engine zur Saldenmanipulation, die die Glücksspielhistorie eines Benutzers rückwirkend umschreibt, um eine gewünschte Saldenänderung zu erreichen, während der Anschein eines legitimen Gameplays gewahrt bleibt.
Die Hauptorchestrierung findet in diesem fixFlow, der eine vierphasige Pipeline ausführt:
// Takes a desired amount as argument
async fixFlow(backupAmount) {
// Phase 1: Load recent cashflow records
const original = await this.getCashFlow();
// Phase 2: Compute required adjustments to betting history
const adjuster = new GameResultAdjuster({ debug: false });
const adjustResult = adjuster.adjustDBData(original, backupAmount);
const { backupRecords, adjustedResult } = adjustResult;
const rewritten = this.writeBackFlowData(backupRecords);
// Phase 3: Validate consistency
const validation = this.validateCashFlowChain(...);
if (!validation.isValid) {
...
}
// Phase 4: Persist to database
await this.updateUserCashFlow(rewritten);
await sendToUser(userId, { pop: false });
await this.updateUserGameRoundFlow(gameTasks);
}
Die Funktion lädt aktuelle Cashflow-Datensätze und wandelt sie in strukturierte Spielprotokolle um. Anschließend generiert die adjustDBData Methode Ersatz-Wettergebnisse, die darauf ausgelegt sind, eine gefälschte Saldenänderung zu erzeugen. Interessant ist, dass sie sich nicht auf einen Algorithmus verlässt, sondern zwei konkurrierende Strategien (greedy vs backtracking), und wählt den Ansatz mit der höchsten „Realismus“-Bewertung aus. Nach der Überprüfung der umgeschriebenen Saldenkette auf Konsistenz, updateUserCashFlow und updateUserGameRoundFlow werden die geänderten Datensätze über Prisma zurück in die Live-Produktionsdatenbank geschrieben, während sendToUser das aktualisierte Saldenereignis an das Gerät des Benutzers gesendet wird.
Greedy-Betrugsstrategie
Der Greedy-Ansatz teilt den gewünschten Zielbetrag gleichmäßig auf die verfügbaren Runden auf. Er ist schnell, erzeugt aber verdächtige Muster. Stellen Sie sich vor, Sie müssten 300 gefälschte Münzen auf 3 Spielrunden verteilen. Der Greedy-Ansatz teilt einfach gleichmäßig auf: 100 pro Runde. Für jede Runde wird die Auszahlung so festgelegt, dass das gewünschte Ergebnis dieser Runde erreicht wird. Dies hat jedoch die Tendenz, unecht auszusehen. Echte Spiele erzielen nicht in jeder einzelnen Runde konsistente Ergebnisse.
Backtracking-Suche
Der Backtracking-Ansatz erkundet den gesamten Lösungsraum und probiert verschiedene Wett-/Auszahlungskombinationen aus, bis er eine findet, die das gewünschte Ziel innerhalb einer Fehlermarge von 0,01 % erreicht. Anstatt sich sofort auf Entscheidungen festzulegen, erkundet er den gesamten Baum der Möglichkeiten. Man probiert einen Wettbetrag aus, sieht, wohin er führt, und wenn es nicht funktioniert, macht man es rückgängig und versucht einen anderen Wettbetrag. Dies ist vergleichbar mit dem Lösen eines Labyrinths, indem man jeden Pfad ausprobiert, bis man den Ausgang findet. Dieser Ansatz findet eine realistische Kette von Ergebnissen, die Einschränkungen berücksichtigt, wie z. B. das verfügbare Guthaben des Benutzers zum Zeitpunkt der Wette.
Es funktioniert ungefähr so:
- Eine Liste möglicher Wettbeträge generieren
- Wetten herausfiltern, die der Benutzer sich nicht leisten kann
- Jede nach „Erreichbarkeit“ zum gewünschten Ziel bewerten
- Die Option mit der höchsten Punktzahl zuerst versuchen
- Führt dies zum Erfolg, ist der Vorgang abgeschlossen.
- Führt dies zu einem Misserfolg, wird der Schritt rückgängig gemacht und der nächste Versuch gestartet.
Der Algorithmus nutzt verschiedene Optimierungen, darunter Memoization, um die erneute Untersuchung fehlgeschlagener Versuche zu vermeiden, und Reachability Pruning, um Zweige zu überspringen, die das gewünschte Ziel mathematisch nicht erreichen können.
Qualitätsbewertung: Betrug natürlich erscheinen lassen
Nachdem beide Strategien ausgeführt wurden, wendet das System einen ausgeklügelten Mechanismus zur Qualitätsbewertung an, der beurteilt, wie „realistisch“ eine gefälschte Glücksspielhistorie erscheint. Dieser Score bestimmt, welche Ausgabe der Betrugsstrategie verwendet wird, und dient als Metrik für den Erfolg des Angriffs.
// Qualitätsbewertung generieren
const overallQuality = this.evaluateLogsQuality(adjustedGameLogs, completeness.actualNetGain);Der evaluateLogsQuality Die Funktion startet mit 100 Punkten und zieht Strafen für verdächtige Muster ab. Einige dieser Strafen sind:
- Unmögliche Wetten (Strafe: -100): Eine Wette, die das verfügbare Guthaben übersteigt, ist im echten Spielverlauf unmöglich. Sie würde vom Spielserver abgelehnt werden.
- Verwaiste Auszahlungen (Strafe: -2): Eine Auszahlung ohne entsprechende Wette in derselben Runde ist strukturell ungültig. Im legitimen Spielverlauf ist jede Auszahlung das Ergebnis einer Wette. Eine Auszahlung, die aus dem Nichts erscheint, deutet auf Manipulation der Aufzeichnungen hin.
- Verschachtelte Runden (Strafe: -1 bis -40 je nach Schweregrad): Echte Spieler schließen typischerweise eine Runde ab, bevor sie eine andere beginnen. Übermäßiges Verschachteln, d. h. das gleichzeitige Starten mehrerer Runden, deutet auf Bot-Verhalten oder Manipulation hin.
- Unrealistische Multiplikatoren (Strafe: -15): Gewinne mit dem 100-fachen oder höher sind selten. Wenn mehr als 10 % der Runden extreme Multiplikatoren aufweisen, wirkt das Muster fabriziert und eine Strafe wird angewendet. Der einfache Greedy-Algorithmus neigt dazu, dieses Muster zu erzeugen.
- Monotones Gameplay (Strafe: -10): Ein Spieler, der in vielen Spielen jede einzelne Runde verliert, ist verdächtig; selbst Pechvögel gewinnen gelegentlich.
Zusammenfassend bedeutet dies, dass das Ziel nicht nur Betrug ist. Es handelt sich um Betrug, der interne Konsistenzprüfungen übersteht, Gewinne und Verluste fälscht und gleichzeitig die Buchhaltung mithilfe eines cleveren Anti-Erkennungsmechanismus konsistent hält.
Bappa Rummy
Wir können nicht mit Sicherheit sagen, welches Spiel angegriffen wird, aber die umgebenden Referenzen deuten darauf hin, dass es sich um eine Glücksspiel-App namens Bappa Rummy handeln könnte. Einer der in der Datei referenzierten Endpunkte ist gameland.myapptest.top/v1, und eine schnelle SSL-Zertifikatstransparenzsuche für diese Domain zeigt verwandte Hosts wie gali.web.test.myapptest.top. Eine Inspektion zeigt eine scheinbar defekte Landingpage, die mit Bappa Rummy verbunden ist, was es zu einem plausiblen Ziel der Backdoor macht. Die App scheint online über Empfehlungsprogramme und alternative App Stores weit verbreitet zu sein, ist aber nicht mehr im offiziellen Google Play Store gelistet.

Erkennung und Prävention
Obwohl wir nicht wissen, wer hinter der Backdoor steckt, ist der beängstigende Teil, was sie tut, sobald sie in der richtigen Umgebung landet. Dies ist nicht „nur“ ein typisches Dependency-Implantat, das Quellcode, Secrets oder Kundendaten exfiltriert.
Stattdessen klinkt sie sich direkt in die Geschäftslogik ein, führt ferngesteuerten Code auf echtem Traffic aus und kann die datenbankgestützte Finanzhistorie umschreiben. Wenn Ihr Monitoring davon ausgeht, dass Datenbankprotokolle vertrauenswürdig sind, kann diese Art der Manipulation lange Zeit unsichtbar bleiben.
Wenn Sie bereits Aikido nutzen, würde dieses Paket in Ihrem Feed als 100/100 kritisches Ergebnis markiert werden.
Noch nicht auf Aikido? Erstellen Sie ein kostenloses Konto und verknüpfen Sie Ihre Repositories. Der kostenlose Plan beinhaltet unsere Malware-Erkennungsabdeckung (keine Kreditkarte erforderlich).
Schließlich kann ein Tool, das Malware in Echtzeit beim Auftreten stoppen kann, eine ernsthafte Infektion verhindern. Dies ist die Idee hinter Aikido Safe Chain, einem kostenlosen Open-Source-Tool, das npm, npx, yarn, pnpm und pnpx umschließt und sowohl KI als auch menschliche Malware-Forscher einsetzt, um die neuesten Supply-Chain-Risiken zu erkennen und zu blockieren, bevor sie in Ihre Umgebung gelangen.
Indikatoren für Kompromittierung
Pakete und Autoren:
- jsonfb (von sidoraress)
- jsonfx (von sidoraress)
- json-bigint-extend (von sidoraress & infinitynodestudio)
Die Backdoor kommuniziert mit einem Remote-Host sowohl für Payload-Updates als auch für die Protokollierung.
Beobachtete Endpunkte:
https://payment[.]y1pay[.]vip/v1/risk/get-risk-codehttps://payment[.]y1pay[.]vip/v1/risk/loghttps://payment[.]snip-site[.]cchttps://gameland[.]21game[.]livehttps://gameland[.]myapptest[.]top/v1https://gameland[.]nbzysp1[.]com/v1https://gameland[.]21game[.]live/v1
Weitere IOCs und jagdbare Verhaltensweisen:
- Anfragen, die
x-operation headermit einem der vier Operationstoken:- RunSQL (Token: cfh2DNITa84qpYQ0tdCz)
- RunFileList (Token: m3QiEkg8Y1r9LFTI5e4f)
- RunFileContent (Token: Y3SrZjVqWOvKsBdpTCh7)
- CompressDownload (Token: SJQf31UJkZ1f88q9m361)
- Laufzeitmodifikationen an
express.Route.prototype.post

