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#

  1. tools/extract_installer_payload.py : lit dans l'amorce de l'installeur (0x401000) les opérandes de aes_setkey_dec / aes_crypt_cbc et 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).
  2. tools/extract_dll_blobs.py : repère dans la DLL toutes les boucles octet[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).
  3. tools/decode_xxxx_mbr.py : décodage isolé de la section .xxxx (MBR + chargeur 16 bits).
  4. 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, contact wowsmith123457@proton.me (identique dans tous les builds ; ESET écrit wowsmith1234567).
  • 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, note YOUR_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.efi renommé .old, dépôt du bootkit, de config (statut, hex(R2), nonce 8 o, portefeuille, clé d'installation) et de verify (512 x 0x07), puis BSOD par NtRaiseHardError(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_10001470 avec 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_000006a8 contre pycryptodome (tools/emulate_bootkit_salsa.py).
  • Réutilisation du flux de clé : chaque cluster du $MFT est 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 fichier verify (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 (0xd3f xor edx, edx, 0xe34 imul r8, rdx, 0xe3c add r8, 0x20). Conséquence attendue sur un $MFT fragmenté : 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 config dè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é (ja 0xe51, jbe 0xf8d). Sur un $MFT de 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-id remplacée par expand 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é par tools/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 Authenticode cdb7c90d... présente dans dbx_info_msft_latest.json (ReLoader64_.efi, ajouté le 2024-11-01). cloak.dat indisponible.
  • 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 $MFT fragmenté (fait sur image réelle : défauts d'extents et de fixups démontrés, volume restauré).
  • [ ] Valider sur un $MFT Windows 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.