# 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.
