Le 28 juin 2026, le monitor MUAD'DIB a détecté le package npm longzy-basic-ui@2.0.3 (score 100/100, sévérité CRITICAL). Son script postinstall (.prepare.cjs) implémente une vingtaine de gardes anti-analyse. L'une d'elles teste la variable d'environnement MUADDIB_GVISOR, le marqueur injecté par la sandbox de MUAD'DIB, dont le nom est reconstruit par String.fromCharCode(...) pour ne pas apparaître en clair dans le source.
Le package appartient à une campagne attribuée au mode opératoire nord-coréen « Contagious Interview », comprenant 5 packages répartis sur 3 scopes npm. Ce rapport documente la détection, l'analyse du payload, une perte de couverture liée au délestage mémoire du monitor, et le traitement retenu pour le marqueur MUADDIB_GVISOR.
OpenSourceMalware (OSM)
OpenSourceMalware (OSM) est une base communautaire de menaces de la supply chain logicielle : packages (npm, PyPI…), dépôts, extensions, domaines et IP malveillants confirmés, documentés avec leurs indicateurs de compromission et vérifiés par la communauté avant publication. Elle est maintenue par Paul McCarty (Head of Research chez Safety, fondateur de SecureStack de 2017 à 2023). MUAD'DIB l'utilise comme source d'IOC, avec Aikido et le feed OSV osv-malicious.
L'API publique d'OSM expose un unique endpoint query-latest renvoyant les ~100 dernières menaces d'un écosystème, soit une fenêtre glissante d'environ deux jours, sans recherche par package ni backfill. L'ingestion se limite au contenu de cette fenêtre au moment de la collecte ; un package catalogué hors fenêtre n'est pas récupéré par ce canal. Ce point intervient dans la section sur le délestage.
La liste complète des scopes de l'opérateur a été communiquée par Paul McCarty, ce qui a permis de rattacher les packages à une même campagne.
La campagne et son opérateur
Le package.json de longzy-basic-ui@2.0.3 contient les métadonnées suivantes :
"author": "yangguisheng <yangguisheng@szlongzy.com>",
"homepage": "http://192.168.100.4:9088/app/lzy-basic-module.git",
"repository": { "url": "http://192.168.100.4:9088/app/lzy-basic-module.git" }
Elles indiquent un host de build interne (192.168.100.4:9088, instance Git privée) et un domaine d'auteur szlongzy.com. Selon l'intel communiquée par Paul McCarty (OSM), la même adresse 192.168.100.4 figure dans les indicateurs de @longzy/react-native-polyfill, ce qui rattache les scopes à un même opérateur.
L'opérateur répartit les packages sur plusieurs namespaces afin qu'un takedown d'un scope n'interrompe pas l'ensemble. Les 5 packages (3 scopes), tous catalogués par OSM, OSV ou Aikido :
| Package | Version | Catalogué par |
|---|---|---|
longzy-basic-ui | 2.0.3 | Aikido + OSM |
@longzy/react-native-polyfill | 1.0.1 | OSV (osv-malicious) |
@digitalcnzz/embedded-sdk | 1.0.7 | OSM (infostealer, install-hook) |
@digitalcnzz/commonmodule | 1.0.3 | OSM + Aikido |
@sailing-package/core | 10.0.18 | OSM + Aikido |
Les noms imitent des packages react-native courants, ce qui correspond au mode opératoire « Contagious Interview » (dépendances piégées présentées lors de faux entretiens techniques).
Détection de longzy-basic-ui
Le 28 juin à 19:42 UTC, MUAD'DIB attribue au package un score de 100/100 (CRITICAL). Règles déclenchées :
| Règle | Sévérité | Objet |
|---|---|---|
MUADDIB-FLOW-001 | CRITICAL | Flux credential→réseau dans .prepare.cjs |
MUADDIB-COMPOUND-007 | CRITICAL | Hook de cycle de vie exécutant un fichier malveillant (lifecycle_file_exec) |
MUADDIB-COMPOUND-009 | HIGH | Hook de cycle de vie + flux de données suspect (lifecycle_dataflow) |
MUADDIB-PKG-001 | MEDIUM | Script de cycle de vie suspect (lifecycle_script) |
FLOW-001 détecte un flux reliant une lecture de données sensibles (process.env, os.hostname, os.userInfo, os.networkInterfaces) à un envoi réseau (https.request). La détection est comportementale et ne dépend d'aucune signature. Le ledger du scan indique sandbox: none : la sandbox dynamique n'a pas été exécutée, le score provient uniquement de l'analyse statique.
Les quatre règles sont comportementales (flux de données, hooks de cycle de vie) et ne dépendent d'aucune base d'IOC. Le package n'est entré dans notre base d'IOC que le 29 juin (05:32, via OSM), soit après ce scan : la détection du 28 juin repose entièrement sur le comportement.
Analyse du payload (.prepare.cjs)
Le fichier est un script prepare/postinstall obfusqué par String.fromCharCode. Déobfusqué, il présente la structure d'un infostealer à exécution différée.
1. Gardes anti-analyse
Le script exécute une vingtaine de process.exit(0) conditionnels destinés à interrompre l'exécution en environnement d'analyse. Conditions observées : credentials AWS égaux aux exemples de la documentation AWS (AKIAIOSFODNN7EXAMPLE) ; token correspondant à /honey|dummy|F4k3/i ; NODE_OPTIONS contenant --require ; hostname de type detonat|cuckoo|sandbox ; présence de variables d'environnement propres à divers outils d'analyse.
MUADDIB_GVISOR, avec les outils auxquels ils sont attribués. Table de déobfuscation communiquée par Paul McCarty (OpenSourceMalware).
La ligne 60 teste env['MUADDIB_GVISOR'], nom reconstruit par charcode :
// .prepare.cjs, ligne 60 (déobfusquée)
if (env['MUADDIB_GVISOR']) process.exit(0);
L'obfuscation par String.fromCharCode vise une recherche textuelle (grep) et reste sans effet sur une détection comportementale : c'est le flux credential→réseau (FLOW-001) qui déclenche l'alerte, indépendamment de la forme du marqueur.
2. Exécution différée
Le payload est armé par setTimeout(..., 15000 + random*30000), soit un délai de 15 à 45 secondes destiné à dépasser les timeouts des sandbox d'analyse.
3. Reconnaissance et exfiltration
Après les gardes et le délai, le script collecte les interfaces réseau non internes (os.networkInterfaces()), le hostname, l'utilisateur, la plateforme, la version Node et le registry, puis sérialise l'intégralité de process.env. Le rapport utilise des libellés en chinois (包名 / 主机 / 用户 / 平台 / 时间).
4. Command & control
Le host et le path sont encodés séparément (host : tableau inversé puis soustraction de 7 sur chaque code de caractère ; path : XOR avec la clé Zk9x). Valeur décodée :
https://open.larksuite.com/open-apis/bot/v2/hook/813ab7c8-27fc-4df5-8704-74bd504c47c3
Il s'agit d'un webhook de bot Lark/Feishu : infrastructure légitime utilisée comme canal d'exfiltration (pas de domaine dédié à bloquer, TLS valide).
Perte de couverture : délestage mémoire
Le 28 juin entre 22:06:34 et 22:06:37 UTC, deux packages de la campagne (@longzy/react-native-polyfill et @digitalcnzz/embedded-sdk) sont entrés dans la file de scan du monitor et en ont été retirés par le mécanisme de délestage mémoire (mem_emergency_spill), sans être scannés. Un troisième nom présent dans la même rafale, @longzy/react-native-audio-recorder-player, est un artefact de file (détaillé plus bas), non un package publié.
La cause n'est pas un délestage insensible aux IOC. Au moment du délestage, aucun de ces noms n'était référencé dans un feed : react-native-polyfill apparaît dans les feeds le 29 juin, embedded-sdk est catalogué par OSM le 29 juin à 07:20, et l'ensemble n'entre dans notre base d'IOC que le 1er juillet, soit 9 heures à 3 jours après le délestage. Les packages retirés étaient alors inconnus. Il s'agit d'une limite de débit, non de détection.
Ce troisième nom,@longzy/react-native-audio-recorder-player, n'apparaît que dans la file du monitor (champversionvide, sans tarball) et dans aucun catalogue (OSM ne référence que 2 packages souslongzy*). Il correspond à un artefact de file, non à un package publié. La campagne compte 5 packages.
En l'absence des archives (aujourd'hui en 404), les packages retirés ne peuvent pas être scannés a posteriori. Ils relèvent du même opérateur et du même scope, et ont été confirmés par les feeds après coup (OSM classe embedded-sdk en infostealer / install-hook). Aucun score par-package ne peut toutefois être produit sans les fichiers.
Correctif : anti-spill IOC-aware
Le délestage protège déjà les items marqués isIOCMatch. Ce marquage est effectué à l'enqueue et devient obsolète si le package n'était pas connu à ce moment, ou si la base d'IOC est rafraîchie pendant l'attente en file.
Le correctif ajoute une vérification au moment du délestage : le nom de l'item est comparé à la base d'IOC courante (lookup de nom, sans chargement de l'AST ni de la sandbox). Un nom présent est protégé et conservé dans la file.
Portée : ce correctif n'aurait pas protégé les packages du 28 juin, alors inconnus. Il couvre les cas où le package est déjà référencé (republication, campagne active, IOC wildcard, rafraîchissement de la base pendant l'attente). La couverture des 0-day relève du débit (scan avant délestage, prefetch), traitée séparément.
Traitement du marqueur MUADDIB_GVISOR
Le renommage, la rotation ou le masquage de cette variable ont été envisagés puis écartés.
Rôle. MUADDIB_GVISOR est un flag fonctionnel. Il est injecté par la sandbox (src/sandbox/index.js:279) et lu par l'entrypoint du conteneur (docker/sandbox-runner.sh) pour sélectionner l'instrumentation : sous gVisor, strace et tcpdump sont désactivés, le runtime capturant syscalls et réseau au niveau kernel. La variable ne peut donc pas être simplement supprimée.
Limites du masquage.
- Le script est copié dans l'image Docker ; une rotation implique un rebuild périodique, et le nom doit rester connu des deux côtés.
- La présence de gVisor est détectable par d'autres moyens (
/proc, comportement de syscalls, timing). Le marqueur n'est qu'un test parmi une vingtaine. - La sandbox dynamique est secondaire : en CLI, son déclenchement automatique est borné à une fenêtre de score intermédiaire [15-35] (au-delà, le verdict statique est déjà définitif) ; dans le monitor, elle alimente l'alerte et la classification « dormant » sans modifier le score statique.
longzy-basic-uia été traité avecsandbox: none. - Les credentials-canaris injectés par la sandbox, s'ils correspondent aux motifs
AKIAIOSFODNN7EXAMPLE/honey/dummy, provoquent une sortie du payload en amont du testMUADDIB_GVISOR.
Approche retenue. La variable est conservée et traitée comme honeytoken : publique et stable, elle n'est significative que sous forme obfusquée. L'effort porte sur la détection de l'évasion elle-même, en analyse statique. Deux règles ont été ajoutées :
MUADDIB-AST-096,analyzer_honeytoken_reference(CRITICAL) : référence dissimulée en charcode à un marqueur d'environnement d'analyse (dontMUADDIB_GVISOR). Seule la forme obfusquée est détectée ; le code de sandbox de MUAD'DIB et l'outillage de sécurité légitime, qui référencent ces marqueurs en clair, ne déclenchent pas la règle. Le re-scan delongzy-basic-uila déclenche.MUADDIB-AST-095,anti_analysis_evasion(HIGH) : détection structurelle du « mur de gardes ». Au moins cinqprocess.exit()silencieux couplés à une lecture d'environnement et à de l'obfuscation charcode ou de la reconnaissance hôte. Elle repose sur la structure (comptage des gardes et signaux couplés), non sur un suivi de flux ; agnostique au marqueur testé, donc robuste aux marqueurs inédits.
La récupération de payload distant à l'exécution (dead-drop) est en grande partie déjà couverte : la variante fetch→eval/Function déclenche remote_code_load et staged_payload (CRITICAL). Le résidu identifié, le staging via disque (fetch→writeFileSync→require, actuellement sous-scoré), n'est pas encore traité.
Indicateurs de compromission (IOC)
| Type | Valeur |
|---|---|
| Package | longzy-basic-ui@2.0.3 |
| Package | @longzy/react-native-polyfill@1.0.1 |
| Package | @digitalcnzz/embedded-sdk@1.0.7 |
| Package | @digitalcnzz/commonmodule@1.0.3 |
| Package | @sailing-package/core@10.0.18 |
| C2 | open.larksuite.com/open-apis/bot/v2/hook/813ab7c8-27fc-4df5-8704-74bd504c47c3 |
| Build host | 192.168.100.4:9088 (/app/lzy-basic-module.git) |
| Auteur | yangguisheng@szlongzy.com |
| ATT&CK | T1195.002 (supply chain), T1041 (exfiltration C2), T1552 (credentials) |
| Signal MUAD'DIB | MUADDIB-FLOW-001 (credential→réseau, CRITICAL, comportemental) |
Synthèse
- La détection de
longzy-basic-uirepose sur l'analyse comportementale (FLOW-001) et a fonctionné sur un package 0-day, inconnu des feeds au moment du scan. - La perte de couverture (deux packages de campagne non scannés) provient du délestage mémoire, non de la détection. Le correctif IOC-aware couvre les packages déjà référencés ; les 0-day relèvent du débit.
- Le marqueur
MUADDIB_GVISORest conservé et traité comme honeytoken. Le masquage n'apporte pas de gain défensif ; deux règles de détection statique de l'évasion ont été ajoutées (AST-095,AST-096). La récupération de payload distant est déjà couverte, hors un résidu (staging via disque).
Sources : liste des scopes et table des marqueurs anti-analyse communiquées par Paul McCarty (OpenSourceMalware). IOC : OpenSourceMalware, OSV (osv-malicious), Aikido.