HybridPetya : runbook
Analyse statique et émulation uniquement (Ghidra/pyghidra, Unicorn, capstone). Aucun échantillon exécuté, aucune infrastructure contactée. Les images de disque de test sont créées localement ou proviennent d'un corpus forensique public (Digital Corpora).
Échantillons#
Source : MalwareBazaar, tag HybridPetya (8 fichiers, tous soumis le 2025-09-13), plus le chargeur NotPetya de référence pour comparaison.
| SHA-256 (préfixe) | Type | Rôle établi |
|---|---|---|
| 65f77a21 | PE32 exe | Installeur, build du 2025-01-30, AES clé/IV en clair dans .data, DLL sans bootkit EFI (MBR seul) |
| f3cc2284 | PE32 exe | Installeur, build du 2025-02-08 (DLL du 2025-02-03), clé préfixée au blob, MBR seul |
| 01b57ae9 | PE32 exe | Installeur, DLL du 2025-02-11, bootkit EFI build "0211" |
| ccdad8f0 | PE32 exe | Installeur, DLL du 2025-02-13, bootkit EFI build "0214" |
| c75a0c76 | PE32 exe | Installeur, DLL du 2025-02-14, bootkit EFI build "0214" |
| b949e951 | PE32 exe | Dump mémoire de c75a0c76 après déchiffrement (base 0xf20000, DLL identique, IV en mémoire = dernier bloc chiffré de c75a0c76) |
| c25e5f72 | DLL | Dump mémoire de la DLL de c75a0c76, pris juste après le décodage de la config (aucune clé présente) |
| 20fbf95c | EFI x64 signé | reloader.efi vulnérable (CVE-2024-7344), déposé comme bootmgfw.efi par la variante Secure Boot |
027cc450 (related/) |
DLL | NotPetya perfc.dat, référence pour le chargeur MBR |
Non disponibles publiquement : cloak.dat (SHA-1 D0BD2831...) et le bootkit qu'il contient (SHA-1 9DF922D0...). La variante contournant Secure Boot n'est donc pas analysable ici.
Concordance avec les IoC ESET : le bootkit extrait du build "0214" a le SHA-1 BD35908D..., identique à celui publié par ESET. Le bootkit "0211" (SHA-256 a9d81bbe...) n'apparaît pas dans leurs IoC.
Contenu publié#
Les échantillons (binaries/) et les composants malveillants extraits des DLL (work/ : DLL, bootkits EFI, MBR, chargeur 16 bits, décompilations) restent hors paquet : leurs hashs sont dans iocs.csv et les échantillons se téléchargent sur MalwareBazaar (étiquette HybridPetya). Sont publiés : ce runbook, remediation.md, hybridpetya.yar, iocs.csv, tools/ et les relevés passifs de osint/.
Chaîne d'extraction#
tools/extract_installer_payload.py: lit dans l'amorce de l'installeur (0x401000) les opérandes deaes_setkey_dec/aes_crypt_cbcet déchiffre la DLL (AES-256-CBC). Deux formes : clé et IV en clair dans.data(65f77a21), ou 32 + 16 octets préfixés au blob chiffré et recopiés en 0x4012b0 (les autres).tools/extract_dll_blobs.py: repère dans la DLL toutes les bouclesoctet[i] ^= 0x6b81 % (i+1)et décode les blocs : config de 0x328 octets (note de rançon, portefeuille), MBR (0x200), chargeur 16 bits (0x2400), bootkit EFI (0x5aa0, absent des deux builds les plus anciens).tools/decode_xxxx_mbr.py: décodage isolé de la section.xxxx(MBR + chargeur 16 bits).tools/decompile_all.py: décompilation Ghidra complète (installeur, DLL, bootkits). Sorties dans un dossier de travail local.
Constats établis#
DLL (Windows)#
- Config XORée (0x6b81) : note "Ooops", 1000 dollars, portefeuille
34UNkKSGZZvf5AYbjkUa2yYYzw89ZLWxu2, contactwowsmith123457@proton.me(identique dans tous les builds ; ESET écritwowsmith1234567). - Graine aléatoire R de 16 octets (CryptGenRandom). Clé de fichiers = SHA-512(R)[:32]. Clé disque = hex(R), 32 caractères ASCII (
FUN_10001380). - Protection de R : ECDH secp192k1 (NID 0x2c7) avec la clé publique opérateur
041acd5c...(49 octets, 0x1005b18c), SHA-512 du secret, AES-256-ECB de R. Clé d'installation = Base58(clé éphémère 49 o + R chiffré 16 o + 4 o nuls + 2 o de somme SHA-512), 96 caractères. - Chiffrement de fichiers (toujours exécuté, même en administrateur) : ~235 extensions, AES-256-CBC par blocs de 0x400, IV aléatoire par fichier, fichiers > 50 Mio limités aux 5 premiers Mio, pied de 0x76 octets (indicateur partiel, taille, IV, clé d'installation), extension
.+ 8 premiers caractères de la clé d'installation, noteYOUR_FILES_ARE_ENCRYPTED.TXT. Construction saine : seule la clé privée opérateur permet le déchiffrement. - En administrateur, nouvelle graine R2 pour le disque. GPT : suppression de
\EFI\Boot\bootx64.efi,bootmgfw.efirenommé.old, dépôt du bootkit, deconfig(statut, hex(R2), nonce 8 o, portefeuille, clé d'installation) et deverify(512 x 0x07), puis BSOD parNtRaiseHardError(0xC0000350). MBR : écriture du MBR et du chargeur 16 bits, config en secteur 0x20 avec SPONGENT-256 appliqué 129 fois à hex(R2), secteur 0x21 de vérification, MBR d'origine XORé 7 en secteur 0x22.
Builds de la DLL#
- 30 janvier et 3 février : section de code identique, pas de branche GPT/UEFI.
- 11 février : routine d'installation disque de 1504 à 2510 octets (branche GPT/UEFI).
- 13 février : liste de dossiers exclus du chiffrement de fichiers (
Windows,$Recycle.Bin,Microsoft,Mozilla Firefox,Opera,Internet Explorer,Temp,Local,LocalLow,Chrome). - 14 février : même logique, structure compilée de la comparaison d'extensions différente.
- Clé d'installation vérifiée de bout en bout par émulation de
FUN_10001470avec une clé opérateur de test : graine retrouvée 8 fois sur 8 (tools/emulate_install_key.py).
Bootkit UEFI (builds 0211 et 0214, fonctionnellement identiques)#
- Salsa20/20 standard, confirmé par émulation de
FUN_000006a8contre pycryptodome (tools/emulate_bootkit_salsa.py). - Réutilisation du flux de clé : chaque cluster du
$MFTest chiffré par un appel séparé avec la même clé, le même nonce et un compteur remis à 0. Tous les clusters partagent donc le même flux. Le fichierverify(512 x 0x07) est chiffré avec ce même flux et livre ses 512 premiers octets. - Parcours du
$MFT: dernier attribut 0x80 de l'enregistrement 0, 0x20 premiers secteurs sautés à chaque extent, décalage de chaque extent lu en absolu (pas de cumul), confirmé au désassemblage (0xd3fxor edx, edx, 0xe34imul r8, rdx, 0xe3cadd r8, 0x20). Conséquence attendue sur un$MFTfragmenté : les fragments suivants restent en clair et d'autres clusters sont chiffrés à leur place (pas encore démontré sur image). - Clé effacée de
configdès le début du chiffrement (conforme à ESET).counter= nombre d'itérations de la boucle, incrémenté même quand la lecture échoue. - Enregistrement 0 lu sans fixups NTFS (ReadBlocks en 0xcbe, parcours en 0xcc2) ; bornes comparées en non signé (
ja0xe51,jbe0xf8d). Sur un$MFTde 73 extents (tools/build_fragmented_volume.sh) : 67 extents lus, boucle sans fin jusqu'à la fin du volume, 16 225 clusters chiffrés dont 15 585 hors$MFT, 155 traités deux fois (revenus en clair). - Bootkits 0211 et 0214 : seule différence, l'emplacement d'un saut de ligne autour de la barre
█de l'écran de rançon. - Bootkits sans aucune signature (répertoire de sécurité vide).
Chargeur MBR (BIOS)#
- Chargeur 16 bits de NotPetya patché : constante
-1nvalid s3ct-idremplacée parexpand 32-byte k, 300 vers 1000 dollars, nouveau contact, couleur 0x0e vers 0x0c, recopie de clé 16 vers 32 octets, constante LFSR de l'éponge 0xa3 vers 0x9e. - Défaut hérité tel quel :
s20_littleendian(0x15ec) perd les octets 2 et 3 de chaque mot en 16 bits. Flux de période 4 Mio. Confirmé par émulation et par un modèle Python exact sur 20 tirages, pour HybridPetya et NotPetya (tools/emulate_legacy_salsa.py). C'est la faille déjà exploitée par CrowdStrike sur NotPetya. - Clé saisie filtrée sur
0123456789abcdef(minuscules), éponge appliquée 129 fois, vérification sur le secteur 0x21. État de l'éponge (0xa2b2, 0x22 octets) jamais remis à zéro, et retour direct à l'invite après erreur (jmp 0x49b) : après une saisie erronée, la bonne clé est refusée jusqu'au redémarrage. Confirmé partools/emulate_legacy_keycheck.py(DLL et chargeur émulés, disque simulé par l'interruption 13h).
Variante Secure Boot, portefeuille#
reloader.efi: signé « Microsoft Windows UEFI Driver Publisher » / « Microsoft Corporation UEFI CA 2011 », empreinte Authenticodecdb7c90d...présente dansdbx_info_msft_latest.json(ReLoader64_.efi, ajouté le 2024-11-01).cloak.datindisponible.- Portefeuille
34UNkKSG...: 4 dépôts de 11,6 à 49,2 dollars entre le 2025-02-11 et le 2025-05-22, trois balayés vers une adresse de consolidation d'environ 278 000 transactions (service de garde à fort volume). Aucun montant de rançon.
Récupération du $MFT sans la clé (variante UEFI)#
Outils : tools/hp_ntfs.py (ordonnancement exact du bootkit), tools/simulate_bootkit_encrypt.py, tools/recover_mft_without_key.py, tools/build_test_volume.sh, tools/build_fragmented_volume.sh.
Méthodes : exacte (un cluster nul chiffré égale le flux, candidat validé par la cohérence d'au moins 90 % des enregistrements), puis statistique en repli (votes par colonne, modèle local, clairs connus, crib des index $I30, validation structurelle, passe ciblée sur les champs qui échouent). Amorçage si l'enregistrement 0 a été chiffré.
Images de test régénérables : victime 256 Mo / 4000 fichiers / graine 1337, victime2 384 Mo / 6000 / 99, référence 256 Mo / 3000 / 4242, volume fragmenté (build_fragmented_volume.sh), image Windows ntfs1-gen2.E01 (Digital Corpora) convertie par ewfexport.
| Scénario | Clusters chiffrés | Méthode | Résultat | Durée |
|---|---|---|---|---|
| victime, complet | 1015 | exacte | image identique | 1 s |
| victime2, complet | 1515 | exacte | identique | 2 s |
$MFT de 73 extents, balayage |
16 225 | exacte | identique | 25 s |
| Windows XP, clusters de 512 octets | 160 | exacte | identique | 1 s |
| victime, interrompu | 300 | statistique | identique | 48 s |
| victime2, interrompu | 800 | statistique | identique | 77 s |
Contrôle voie statistique seule (HP_NO_ZERO=1) : victime et victime2 complets à 100 %, volume balayé à 91 % (clusters hors $MFT aléatoires, index $I30 eux-mêmes chiffrés). Limite consignée dans le writeup.
Corrections apportées à l'outil au fil des tests : repli exhaustif trop tardif (seuil à 50 %) remplacé par une passe ciblée sur les champs qui échouent ; taille de cluster lue dans le secteur de boot au lieu de 4096 ; contrainte de longueur des attributs résidents et de $FILE_NAME ; extents bornés au volume ; crib d'index sans les champs de taille (périmés) et à majorité nette.
Plan#
Tout est fait, sauf la divulgation, qui attend un accord explicite.
Fiabiliser l'outil de récupération#
- [x] Corriger le cas victime2 complet (position 1441).
- [x] Rejouer les scénarios et figer le tableau.
- [x] Tester un
$MFTfragmenté (fait sur image réelle : défauts d'extents et de fixups démontrés, volume restauré). - [ ] Valider sur un
$MFTWindows 10/11 de grande taille : aucune image publique adaptée trouvée, consigné comme limite. - [x] Nettoyer l'outil (documentation réécrite, interrupteurs de test documentés).
Compléter l'analyse#
- [x] Écarts entre builds (DLL et bootkits).
- [x] Clé d'installation vérifiée par émulation.
- [x] Chemin MBR : clé = éponge^129(hex(R)) confirmée côté DLL et côté chargeur, période de 4 Mio, défaut de l'état d'éponge.
- [x] Variante Secure Boot documentée dans ses limites (DBX).
- [x] OSINT passive du portefeuille.
Livrables#
- [x] Billet.
- [x]
remediation.md. - [x]
hybridpetya.yar(6 règles, 19 détections positives, aucune sur le corpus négatif). - [x]
iocs.csv(29 entrées).
Divulgation#
- [x] Message à ESET Research préparé (non publié).
- [ ] Envoi à ESET : en attente.
- [ ] No More Ransom : à décider ; HybridPetya n'est pas observé en circulation.