# Runbook : reproduction pas à pas (INC Ransom, chaîne Ghost, Phantom et RevSocks)

## Point de départ et contraintes

Tout part de l'échantillon `f6fbe168...0c60`, signalé sur MalwareBazaar le 2026-10-20 par SOCRadarSTRU avec un renvoi vers l'article de SOCRadar sur une infrastructure d'affiliés INC et Gentlemen. L'objectif était de remonter toute la chaîne en statique, en émulation et en OSINT passif. Trois règles tenues de bout en bout : aucun échantillon exécuté, aucune connexion vers l'infrastructure de l'opérateur (ni adresse, ni domaine, ni API Telegram), aucun usage d'un identifiant récupéré dans un binaire.

## Acquisition

Pivots sur MalwareBazaar (clé dans `.mb_api_key`) à partir des étiquettes de l'échantillon :

```bash
for t in "INC Ransomware" "The Gentlemen Ransomware" STRU; do
  curl -s -X POST https://mb-api.abuse.ch/api/v1/ -H "Auth-Key: $(cat .mb_api_key)" \
       --data-urlencode "query=get_taginfo" --data-urlencode "tag=$t" -d "limit=1000"
done
```

141 échantillons uniques, dont 114 déposés le 2026-10-20 entre 12:11 et 12:24 UTC. Les 27 autres (`PivotC2`, `ICMacOS`, `Operation_Conflict_Compass`) appartiennent à d'autres campagnes du même rapporteur, je les ai écartés. Les pivots par imphash et par TLSH ne ramènent qu'un seul résultat chacun : l'échantillon lui-même.

Téléchargement par `query=get_file`, mot de passe d'archive `infected`, un dossier par SHA-256, vérification de chaque hash. Piège rencontré au passage : la dernière ligne de mon fichier de hashs n'avait pas de saut de ligne final, donc `while read` l'ignorait. J'ai eu 113 fichiers au lieu de 114 jusqu'à ce que je comprenne pourquoi.

## Triage en masse

```bash
python3 tools/triage_cluster.py binaries/cluster osint/triage
```

Le script produit un CSV et un JSON par fichier : type, taille, imphash, horodatage PE, signature, langage probable (Go, Rust, MinGW, MSVC), adresses, URL, domaines et mutex en clair. Lecture : 34 binaires MinGW, 17 Go, 8 Rust, 4 MSVC, 45 ELF, plus 2 documents Word, un script Python, un script PowerShell, une archive zip et une DLL non classée. L'inventaire consolidé est `cluster_inventory.csv`.

## Métadonnées de build des binaires Go

Le format `buildinf` de Go (marqueur `\xff Go buildinf:`) contient en clair, pour les versions récentes, la version du compilateur, le chemin du module et les paramètres de build (`-ldflags`, `GOOS`, `CGO_ENABLED`). Lecture directe des octets, sans exécuter le binaire. C'est de là que viennent les noms de modules (`mcp-agent`, `lsass-clean`, `hostprobe`, `rks3`, `fileseeker2`) et les versions de Go (de 1.22.4 à 1.26.5) du tableau du §7 du billet. Les versions de l'éditeur de liens, les en-têtes Rich et les langues de ressources des 64 exécutables Windows sont dans `osint/attribution_metadata.csv`.

Pour les binaires Go dont les chaînes sont masquées (garble), les outils `tools/decompile_go_main.py`, `tools/decompile_tail_funcs.py`, `tools/decompile_at.py` et `tools/go_pclntab_funcs.py` ciblent la décompilation sur `main` et sur les fonctions de fin de binaire, et `tools/decode_garble_literals.py` et `tools/decode_garble_shuffle.py` recalculent les chaînes à partir du texte décompilé, sans rien exécuter. C'est ainsi que sortent les chemins de compilation `/opt/c2/ghost-api-tg/...` et `/root/chat/...`, le mode `ghostSend` de BackupConsole, les douze messages de log de `Ghost_dump_ppl` et l'identité de son pilote BYOVD (Dell DBUtil_2_3.sys, embarqué à l'offset `0xa358c0`).

## Certificats de signature

Extraction de la structure Authenticode de chaque PE signé (répertoire de sécurité, indice 4) puis `openssl pkcs7 -print_certs`. Les cinq binaires « Microsoft Corporation » ont un sujet égal à l'émetteur et une validité d'un an à la seconde près à partir de l'horodatage PE. Les trois outils de sauvegarde portent un « SysTool Software Ltd. » auto-signé, valable du 2026-06-25 au 2036-06-22, avec un contre-horodatage DigiCert.

## Livraison

Modèles Word : `unzip`, lecture de `docProps/core.xml` et `app.xml`, extraction du projet VBA avec `olevba` (oletools). Le projet VBA contient une seule procédure, `Document_Open`, qui crée un processus par WMI. Les métadonnées fines (racines d'identifiants de révision, version d'Office, langue, format de page) se lisent dans les mêmes fichiers XML.

Cradle PowerShell et beacon Python : lecture directe. Pour le cradle, la vérification du contournement AMSI consiste à comparer les décalages écrits dans le tampon de contexte (`0x350`, `0x378`) à ceux de `Dr0` et `Dr7` dans une structure `CONTEXT` x64 (`0x48`, `0x70`).

## Chargeur Ghost_loader et Meterpreter

Clé et IV sont en clair dans `.data`. Le déchiffrement du blob de 250 016 octets se rejoue avec OpenSSL en AES-256-CBC, et l'émulation du `WinMain` sous Unicorn jusqu'à `CryptDecrypt` donne le même résultat. La DLL obtenue se compare au `metsrv.x64.dll` du gem `metasploit-payloads` 2.0.245 téléchargé sur rubygems.org : identique à 37 octets d'amorce près, suivie du bloc de configuration de 2 202 octets (URL, en-tête `Host`, User-Agent, expiration).

## Implants Ghost : décompilation et émulation des chaînes

```bash
python3 tools/decompile_all.py <echantillon> cluster_decomp/<pre>.c.txt
```

Les chaînes de configuration sont chiffrées par une routine vectorisée SSE. Plutôt que de la réécrire, j'appelle la fonction du binaire sous Unicorn avec le tampon chiffré en entrée :

```bash
# G6B77FE12 : decodeur a 2 arguments (tampon, longueur)
python3 tools/emulate_string_decoder.py <G6B77FE12.exe> 140001bf0 \
   "qw:970605a333b1ce61,a6c7b0e69d9c2d26,84c8caba9e764a52:24" \
   "qw:d02910b17fb7d247,eb96f6f7d6d42172,84ca8bb6833f4f07:24" \
   "rva:b010:21"
# -> Global\31ae8e4090b03c3e ; api.system-strategy.org ; /api/v3/be13a10b476f
```

Pour les générations Telegram, le décodeur prend un contexte en premier argument (`ctx`, `tampon`, `longueur`). `extract_encoded_strings.py` relit la décompilation, reconstitue les tampons posés sur la pile, résout les alias de pointeurs puis émule le décodeur à chaque appel :

```bash
python3 tools/extract_encoded_strings.py <G00F9FCB5.exe> cluster_decomp/118c38f1.c.txt FUN_1400048f0 FUN_1400040c0 14000f040
```

Le build `stealth` utilise deux contextes (`0x140013000` et `0x140013010`) et des écritures d'octets isolés sur la pile, que l'outil gère. Limite connue : quand un pointeur n'est pas un alias direct d'un tampon de pile, l'outil retombe sur une heuristique qui produit parfois de faux décodages (octets illisibles). Ces lignes sont à écarter à la main.

Le jeton Telegram complet apparaît à l'écran lors de ce passage. Il n'est reporté dans aucun fichier, seul l'identifiant numérique du bot l'est. Le PE extrait de `Ghost_OneDriveSync` (voir plus bas) a été traité de la même façon, par émulation de `FUN_1400043b0` : même mutex, même bot `8986910514`, même conversation `8809711703` que l'implant `G7CDD6836` du lot.

## Ghost_OneDriveSync et blob Donut

```bash
python3 -I tools/decrypt_onedrivesync.py <Ghost_OneDriveSync.exe> payload.bin   # AES-256 CTR, cle derivee d'une chaine du binaire
python3 -I tools/extract_donut_module.py payload.bin donut_out/                   # instance Chaskey CTR, puis module embarque
```

`payload.bin` (114 388 octets) est un blob Donut, ce que Triage et VMRay avaient aussi identifié. Le module extrait est un PE x64 de 94 824 octets, implant Ghost de troisième génération, absent de MalwareBazaar. La règle YARA `Ghost_OneDriveSync_DonutShellcode` vise le blob déchiffré.

## RevSocks

Les 16 builds ont été décompilés un par un. C'est ce passage qui a fait sortir le second serveur de livraison, `141.98.83.180`, dont l'URL est masquée par un XOR `0xcb` et n'apparaît dans aucune extraction de chaînes. La règle structurelle du fichier YARA reconnaît les 16 builds.

Pour les agents v4, la dérivation de clé lue dans la décompilation a été confirmée par émulation, et le format des trames est implémenté dans un codec :

```bash
python3 tools/emulate_v4_kdf.py .          # émule FUN_140001910 et FUN_140001990 sur le préfixe XOR 0x5a et le secret, compare à hashlib
python3 tools/revsocks_codec.py selftest   # clé, poignée de main, scellement et ouverture des trames, détection d'une étiquette altérée
python3 tools/revsocks_codec.py decrypt --secret <secret> flux.bin   # déchiffre une session capturée
```

Le tunnel de l'agent ELF `RevSocks_vmware-usbarbitrator` se lit avec `tools/decompile_refs.py` en ciblant la chaîne `chacha20_tunnel_v3_` : la constante de 32 caractères est présente deux fois dans le binaire.

## Encrypteurs INC

### Builds Windows

Les six builds de 2 206 208 octets se comparent octet par octet (53 à 62 octets d'écart par paire). Dump des zones qui diffèrent, décodage de l'horodatage Unix des 8 premiers caractères du premier identifiant, extraction des crates Rust depuis les chemins `registry/src/` du binaire.

### Builds Linux et ESXi

Sans exécuter aucun binaire :

```bash
# champs patchés par lot : clé base64 de 44 caractères et identifiant de 24 hex (voir osint/inc_victim_ids.csv)
# note de rançon : base64 stocké dans le binaire, décalage de 2 caractères
# rôle de la clé : décompiler seulement les fonctions qui référencent la chaîne
python3 -I tools/decompile_refs.py <elf_x86_64_gnu> cluster_decomp/inc_elf_x86_64_gnu_refs.txt 'rCdf4MoDNZi3/S'
```

La fonction trouvée tire 32 octets aléatoires, applique le masquage de scalaire Curve25519, calcule la clé publique éphémère, décode la chaîne base64 et exige 32 octets : échange X25519 avec une clé publique d'opérateur propre à chaque victime. Les trois lots (kit1, kit2, kit3) correspondent à trois des six builds Windows personnalisés. Les 6 builds PowerPC ne contiennent pas l'identifiant en clair.

Le mode de chiffrement est un paramètre d'exécution, pas un champ patché : les chaînes d'aide d'un ELF `gnu` et du build ESXi donnent les options `directory`, `file`, `mode`, `motd`, `esxi`, `skip`, `daemon` et `delay`. L'extension `INC` se trouve juste avant la clé base64 dans les 39 ELF (recherche de la clé dans chaque fichier, puis test des octets qui la précèdent).

RDAP ne couvre pas `.su`, et les journaux de transparence n'ont aucun certificat pour `incblog.su`.

### Kits historiques, Lynx et Sinobi

Les 54 autres hachages étiquetés INC sur MalwareBazaar ont été téléchargés (`get_file`, mot de passe `infected`) et lus de la même manière. Le détail est dans `osint/inc_candidates_analysis.md`. L'ESXi antérieur au lot (`86d04474...`) a été comparé par `diff` binaire à un build ESXi du lot : 67 octets d'écart.

Parenté de code : extraction des chaînes imprimables de huit caractères et plus de cinq échantillons (INC C++ ELF 2024, INC C++ PE32 2024, Lynx PE32 2024, INC Rust Windows 2026, INC Rust ELF 2026), puis indice de Jaccard par paire. INC C++ PE32 et Lynx : 0,467 (274 chaînes communes). Les paires avec les builds Rust restent à 0,05 ou moins. C'est une mesure sur des chaînes, pas sur des fonctions.

### Identifiants de victime et blog de fuite

Relevé des publications des groupes `inc ransom` (967), `lynx` et `sinobi` par l'API publique de l'agrégateur ransomlook, sans aucun contact avec les blogs eux-mêmes. Extraction de l'identifiant de 24 caractères hexadécimaux de chaque lien de publication, découpage en horodatage (4 octets), valeur aléatoire (5 octets) et compteur (3 octets), regroupement par valeur aléatoire (`osint/inc_objectid_process_values.csv`), puis comparaison avec les identifiants des kits historiques et des trois lots de 2026.

## Ghost_AetherPulse

```bash
python3 tools/ghidra_api_callgraph.py <Ghost_AetherPulse.dll> sortie.txt <dossier_projet_ghidra>
```

Le script calcule les chemins d'appel depuis les trois exports vers les API Windows. Il atteint une routine de copie puis d'écrasement de fichier (`FILE_FLAG_DELETE_ON_CLOSE`) et une routine de création de fichier dans le dossier temporaire. Les deux blocs d'entropie élevée de `.rdata` sont des tables zlib et CRC-32 et un pool de constantes, pas des charges. `tools/ghidra_function_fingerprint.py` et `tools/ghidra_refs_bytes.py` ont servi aux comparaisons fonction par fonction (implants Ghost) et aux recherches d'octets référencés.

## OSINT passif

| Source | Requête |
|---|---|
| Certificats | `https://crt.sh/?q=%25.<domaine>&output=json` |
| Domaines | `https://rdap.org/domain/<domaine>` |
| Blocs IP | `https://rdap.org/ip/<adresse>` |
| Flux abuse.ch | ThreatFox `search_ioc` et URLhaus `host` pour chaque adresse et domaine |
| Historique web | Wayback Machine (API CDX), urlscan.io (recherche publique) |
| Exposition | Shodan InternetDB, RIPEstat, AlienVault OTX, GreyNoise communautaire |

Aucune de ces requêtes ne contacte l'infrastructure de l'opérateur. Chaque requête et chaque résultat, y compris les négatifs, est consigné dans `osint/pivots_log.md`. Les réponses brutes (RDAP, urlscan, Wayback) contiennent des contacts de personnes physiques et ne sont pas publiées.

### GitHub

Recherche de code en lecture seule avec ma session `gh` (l'API `search/code` exige une session authentifiée) sur les chemins de compilation, les noms d'utilisateur et les chaînes uniques relevés dans les binaires, dont le runner `runneradmin`. Journal complet dans `osint/github_search.md` : aucun dépôt de l'opérateur, seulement des listes de blocage de défenseurs.

### VirusTotal

Clé dans `../.vt_api_key`, hors du dépôt et jamais écrite dans les sorties. Passes successives, toutes en lecture seule, à 4 requêtes par minute :

```bash
python3 tools/vt_pull.py          # résolutions, fichiers qui communiquent, sous-domaines, objets
python3 tools/vt_pull_pivots.py   # contacts des fichiers hors lot et adresses additionnelles de mondeikin.com
python3 tools/vt_pull_leads.py    # pistes de domaine issues des résolutions
```

Rien n'est soumis. Le tableau des fichiers vus hors du lot est `osint/vt_hors_lot.csv`, les réponses brutes restent hors paquet.

### Triage

Clé dans `../.env` (`TRIAGE_API_KEY`), jamais écrite dans les sorties. Le service exige un User-Agent explicite. L'outil masque les jetons de bots Telegram à l'écriture.

```bash
python3 tools/triage_pull.py --no-pcap <id_echantillon> ...   # rapports comportementaux
python3 tools/triage_pull.py <id_echantillon>                  # avec capture réseau
```

Les captures sont analysées hors ligne avec un lecteur de paquets Python : flux TCP vers les serveurs de la campagne, octets échangés, messages de poignée de main TLS et certificats lus en clair (TLS 1.2). Rapports et captures restent hors paquet. Les rapports publics consultés pour `Ghost_loader` : Triage `261020-pw4hkshv61` (score 3, tag `discovery`), CAPE `93070` (Meterpreter), Intezer `49a39783-487a-4c27-b62d-302147486844`, VMRay `_mb/f6fbe1681165`. Les API d'ANY.RUN, OpenTIP, FileScan et mwdb.cert.pl refusent l'accès sans compte, je ne les ai pas utilisées.

### Article du rapporteur

Lu le 2026-10-24 dans sa version mise à jour du 2026-10-23. Les neuf hashs INC qu'il cite sont tous dans le lot. Les domaines qu'il ajoute via ThreatFox (`mondeikin.com`, `ghost-relay.stk-cdn-07.workers.dev`, `karabas.corp`) ont été examinés passivement : journaux de transparence, RDAP, DNS par résolveur public, urlscan.io, Wayback Machine, ThreatFox.

## Règles YARA et tests négatifs

`ghost_phantom_toolchain.yar` compte 18 règles. Contrôle fichier par fichier sur les 114 échantillons (`yara -w` sur chaque fichier) : 90 fichiers touchés par 17 règles, la dix-huitième visant le blob Donut déchiffré, hors lot. Les deux implants Telegram étaient déjà couverts avant l'ajout de la règle `Ghost_Telegram_Implant_Commands`.

Tests négatifs : `yara -r -f` sur `/usr/bin`, les paquets Python, les bibliothèques .NET, Ghidra, le JDK et le SDK Android, puis `yara -f` sur chacun des 6 579 fichiers `.exe` et `.dll` du disque hors du dépôt. Aucune correspondance, hormis une copie de `Ghost_loader` qui traînait dans le répertoire de travail.

```bash
yara -r ghost_phantom_toolchain.yar binaries/cluster
```

## Export STIX

```bash
python3 tools/build_stix.py       # bundle STIX 2.1 a partir de iocs.csv et cluster_inventory.csv
```

Le script écrit le bundle sous `disclosure/` dans mon arborescence de travail ; c'est ce fichier qui est publié ici sous le nom `stix_bundle.json`. Il s'importe directement dans MISP ou OpenCTI.

## Versions des outils

Python 3.14.8, Ghidra 12.1.2 (pyghidra), Unicorn 2.1.4, pefile 2024.8.26, pycryptodome 3.23.0, Capstone 5.0.7, oletools, OpenSSL, YARA.

## Contrôles avant publication

Un contrôle complet du dossier a recalculé les chiffres du billet, relancé les règles YARA et corrigé les affirmations trop fortes ou périmées. Trois corrections de fond en sont sorties : le contournement AMSI par point d'arrêt matériel du cradle PowerShell n'arme rien (décalages de registres de débogage faux), `DelegateExecute` appartient à la commande `uac` de l'implant Ghost et non à `persist`, et quatre domaines sur sept seulement résolvent vers Cloudflare. Une seconde relecture des sections OSINT a trouvé huit écarts de dates, tous corrigés (le site légitime de `system-strategy.org` est archivé jusqu'au 2026-01-17 et non 2025-09-13, 39 certificats distincts pour `13rzip.ru`, bloc de `91.199.163.67` créé le 2026-02-19, indice Shodan sur l'origine de `89.185.83.8`).

Avant publication : `grep` sur l'ensemble du dossier pour vérifier l'absence de tiret cadratin, de jeton et de clé d'accès complète.

## Ce qui reste ouvert

Le rôle fonctionnel de `Ghost_AetherPulse.dll` (copie et écrasement de fichiers, aucune charge ni configuration, rattachement à la campagne non établi), le contenu de `stage1.cmd` (absent du lot, hub injoignable pendant les exécutions publiques), la destination exacte de l'envoi HTTP de `Ghost_dump_ppl` (passée en argument), la primitive noyau de l'« exploit » `CVE-2025-62215`, la disposition exacte du trailer du chiffreur Lynx (déduite de l'ordre des variables locales, à confirmer au désassemblage), le côté serveur du protocole RevSocks v4 et le canal DNS des implants Ghost annoncé par l'article, que les échantillons ne montrent pas.

## Ce qui n'est pas publié

La liste des victimes relevée sur les blogs de fuite, les noms des organisations tierces dont la marque est imitée ou dont le nom apparaît dans les métadonnées, les réponses OSINT brutes, les rapports et captures Triage, les sorties brutes des outils d'émulation (qui contiennent les jetons Telegram), le dossier de divulgation et les notes de travail intermédiaires. Les jetons des bots Telegram et la clé AES de l'agent Athena ne sont écrits nulle part, ni en public ni en restreint : ils se recouvrent au besoin avec les outils fournis, à partir des échantillons publics.
