# Runbook Prince (Prince-Ransomware)

## Contexte du choix de cible

Recherche initiale sur Anubis (RaaS Go, décembre 2024, ECIES via
`github.com/ecies/go`) : aucun échantillon exploitable sur MalwareBazaar,
le tag/signature `Anubis` étant entièrement pollue par un cheval de Troie
bancaire Android homonyme (dizaines d'APK 2021-2026) et un unique exe de
2022 (20 Ko, trop ancien, predate le RaaS). Trend Micro note que la
bibliothèque ECIES d'Anubis est similaire à celle utilisée par
EvilByte/Prince. Prince a de vrais échantillons disponibles : bascule sur
cette cible, même piste technique (bibliothèque cryptographique publique
réutilisée), corpus réel.

## Acquisition

```bash
curl -s -X POST https://mb-api.abuse.ch/api/v1/ \
  -H "Auth-Key: $(cat ../.mb_api_key)" \
  -d "query=get_taginfo&tag=Prince&limit=100"
# 5 hashs, dont un tague explicitement golang+Ransomware :
# 84715cf1d82e5139d39d8aadb9580ba80393dfa0f63fa14974e4f74a3e69752e
# (2458624 octets, exe, first_seen 2025-02-22)

curl -s -X POST https://mb-api.abuse.ch/api/v1/ \
  -H "Auth-Key: $(cat ../.mb_api_key)" \
  -d "query=get_file&sha256_hash=84715cf1d82e5139d39d8aadb9580ba80393dfa0f63fa14974e4f74a3e69752e" \
  -o binaries/84715cf1.zip
7z x -pinfected -y -obinaries binaries/84715cf1.zip
```

## Confirmation : binaire Go non stripé, symboles complets

```bash
file binaries/*.exe
# PE32+ executable for MS Windows 6.01 (GUI), x86-64, 8 sections

strings -n 6 binaries/*.exe | grep -iE "^go1\.|golang.org|ecies|prince"
```

Chemins source complets présents en clair (le binaire n'a pas été
strippe) :
- `Encryptor/main.go`, `Encryptor/configuration/configuration.go`,
  `Encryptor/encryption/encryption.go`, `Encryptor/filewalker/filewalker.go`
- `github.com/ecies/go@v1.0.1/{ecies,privatekey,publickey}.go`
- `golang.org/x/crypto@v0.24.0/chacha20/*.go`

Clé publique de la campagne embarquée en clair dans les flags de build :
```
-ldflags="-H=windowsgui -s -w -X 'Prince-Ransomware/configuration.PublicKey=049054dc5ba95cfcb7fea790425f2ef2cf4565ffe0a6ad47913cde6112c3a47be10fd8c69bf1463d57fa794c4600dec3874ef5de853d4a3d4120360a13e9f3bd05'"
```
(point EC non compressé, préfixe `04`, 65 octets : courbe secp256k1 d'après
la documentation publique de `github.com/ecies/go`).

Go 1.23.3, module `Prince-Ransomware (devel)`.

## Localisation directe des fonctions clé (noms Go récupérés nativement par Ghidra)

```bash
python3 tools/list_functions.py binaries/84715...52e.exe /tmp/out.txt \
  "ecies" "encryption" "configuration" "filewalker" "Encryptor"
```

Pas besoin de chasse à l'adresse comme sur les binaires strippés : Ghidra
récupère directement les noms `paquet.Fonction` via son support natif Go
(pclntab). Fonctions clé :
- `Prince-Ransomware/encryption.generateKey` @ 0x5019a0
- `Prince-Ransomware/encryption.generateNonce` @ 0x501a20
- `Prince-Ransomware/encryption.EncryptFile` @ 0x501aa0
- `github.com/ecies/go.Encrypt` @ 0x4fee80
- `github.com/ecies/go.GenerateKey` @ 0x4ff260

## Génération clé/nonce par fichier : saine

```bash
python3 tools/decompile_by_name.py binaries/84715...52e.exe /tmp/out.txt \
  "Prince-Ransomware/encryption.generateKey" "Prince-Ransomware/encryption.generateNonce"
```

`generateKey` (encryption.go:30) : `crypto/rand.Read` sur un slice de 32
octets. `generateNonce` (encryption.go:38) : `crypto/rand.Read` sur un
slice de 24 octets (cohérent avec un nonce XChaCha20). Les deux passent
par le CSPRNG système de Go (`crypto/rand`, pas `math/rand`), sans graine
prédictible. **Rien d'anormal à ce niveau.**

## `EncryptFile` (encryption.go:46) : découverte du vrai problème

```bash
python3 tools/decompile_by_name.py binaries/84715...52e.exe /tmp/out.txt \
  "Prince-Ransomware/encryption.EncryptFile"
```

Flux général (confirmé par lecture du décompilé complet) :
1. `generateKey()` / `generateNonce()` : matériel frais par fichier (voir
   ci-dessus).
2. `ecies.Encrypt(cle_publique_campagne, cle_aes)` et
   `ecies.Encrypt(cle_publique_campagne, nonce)` : chaque valeur est
   enveloppée séparément via ECIES (secp256k1, clé éphémère par appel
   d'après `github.com/ecies/go.GenerateKey`, puis AES-256-GCM sur le
   secret partagé ECDH côté bibliothèque : `crypto/aes.NewCipher` suivi de
   `crypto/cipher.newGCMWithNonceAndTagSize(cipher,0x10,0x10)`). Rien
   d'evidemment cassé dans cette bibliothèque publique elle-même (courbe
   standard, GCM standard, clé éphémère par appel) : ce n'est pas la piste
   qui aboutit ici, contrairement à l'hypothèse de départ sur Anubis.
3. `chacha20.newUnauthenticatedCipher(etat, cle, nonce)` initialise le
   flux ChaCha20 avec le matériel EN CLAIR (avant enveloppe ECIES).
4. Lecture intégrale du fichier en mémoire (boucle `os.File.Read` par
   blocs de 0x10000 octets, accumulés dans un buffer).
5. **Boucle de "chiffrement" du contenu, le vrai problème** :

```c
for (uVar3 = 0; (int)uVar3 < (int)local_188; uVar3 = uVar3 + 3) {
    // ... XORKeyStream() applique UNIQUEMENT sur 1 octet ...
    if (uVar3 + 1 < local_188) { local_18[uVar3+1] = local_90[uVar3+1]; } // copie brute
    if (uVar3 + 2 < local_188) { local_18[uVar3+2] = local_90[uVar3+2]; } // copie brute
}
```

Vérification par désassemblage précis (le décompilateur élide les
arguments de registre du call sur cet ABI Go interne, ne pas se fier
uniquement au pseudo-C) :

```bash
objdump -d -M intel --start-address=0x501aa0 --stop-address=0x5022e0 binaries/*.exe \
  | grep -B25 "call.*5007e0"   # 0x5007e0 = XORKeyStream
```

```asm
501f00: mov ecx,0x1     ; dst.len = 1
501f05: mov r8,rcx      ; src.len = 1 (copie depuis ecx)
501f08: mov rax,rsi     ; receiver (cipher) -> AX
501f0b: mov rbx,r12     ; dst.ptr -> BX
501f0e: mov rdi,r11     ; dst.cap -> DI
501f11: mov rsi,r13     ; src.ptr -> SI
501f14: call 0x5007e0
```

Mapping ABIInternal Go (amd64) pour `func (s *Cipher) XORKeyStream(dst, src []byte)` :
receiver=AX, dst.ptr=BX, dst.len=CX, dst.cap=DI, src.ptr=SI, src.len=R8,
src.cap=R9. **`CX` et `R8` (dst.len et src.len) valent explicitement `1`** :
chaque appel ne traite qu'un seul octet. Confirmé indépendamment par la
structure du pseudo-C Ghidra (incrément de boucle par 3, deux copies
brutes explicites des octets suivants).

**Conclusion : Prince ne chiffre réellement qu'un octet sur trois du
contenu de chaque fichier. Les deux autres octets sur trois sont copiés
tels quels (en clair) à leur position d'origine dans le fichier "chiffré".**
Ce n'est pas du chiffrement intermittent par blocs (technique légitime
utilisée par plusieurs familles réelles pour accélérer le chiffrement de
gros volumes tout en rendant les fichiers inutilisables), c'est un
entrelacement octet par octet qui laisse les deux tiers du contenu
directement lisibles sans aucune clé.

## Format du fichier produit

Après la boucle, le programme réécrit le fichier en place (`Truncate` +
`Seek` + `Write`) avec la structure suivante :
```
[enveloppe ECIES de la cle AES][0x7c 0x7c]["||"][enveloppe ECIES du nonce][0x7c 0x7c][contenu "chiffre" par octet]
```
(`0x7c 0x7c` = `"||"` ASCII, marqueur de séparation en clair, facilement
repérable pour localiser le début exact du contenu et donc l'alignement
du motif 1-octet-chiffre/2-octets-clairs).

## Environnement

Ghidra headless via PyGhidra (`pyghidra.start(install_dir="/opt/ghidra")`),
venv `~/.venvs/ctf-reverse`. Outils génériques copiés depuis
`gunra-time-seed-rng/tools/` (`list_functions.py`, `decompile_by_name.py`,
`find_api_callers.py`, `decompile_addr.py`, `find_string_refs.py`), tous
directement utilisables sans adaptation : le support natif Go de Ghidra
(pclntab) récupère les noms de fonctions complets `paquet.Fonction`, comme
déjà observé sur l'analyse Hive de ce dépôt.

## Extraction de la configuration (extension, note de rançon, listes d'exclusion)

`filewalker.EncryptDirectory.func1` (filewalker.go:14) révèle deux choses
en plus du chiffrement lui-même : l'écriture de la note de rançon
(`os.WriteFile`, chemin construit via `path/filepath.Join`) et trois
listes de contrôle (fichiers/extensions à exclure). `func1.1`
(filewalker.go:40) révèle le renommage post-chiffrement
(`os.rename(ancien_nom, ancien_nom + extension)`).

```bash
python3 tools/decompile_by_name.py binaries/84715...52e.exe /tmp/out.txt \
  "main.main" "Prince-Ransomware/filewalker.EncryptDirectory.func1" \
  "Prince-Ransomware/filewalker.EncryptDirectory.func1.1"
# reperage des adresses de donnees (pointeur+longueur) des chaines et
# tableaux impliques, puis resolution directe via decompile_addr.py
python3 tools/decompile_addr.py binaries/84715...52e.exe /tmp/out.txt \
  00645d30 00645d38 0053bb35 00645d50 00645d58 \
  006462b0 006462b8 006462d0 006462d8 006462f0 006462f8
```

Extraction finale des octets bruts (pefile, lecture directe par RVA) :
- extension : `.MOONMAN` (8 octets @ 0x53a43c)
- nom de la note de rançon : `READTHISNOW.txt` (15 octets @ 0x53bb35)
- contenu de la note : 979 octets @ 0x545e92, signée "MoonMan/UwU/Sprunki/LockBitch",
  rançon 1488$ en crypto, contacts `sn33ds3curity@tutanota.com` /
  `vitollebonzi@gmail.com`, adresse `357a3So9CbsNfBBgFYACGvxxS6tMaDoa1P`
- liste d'exclusion (fichiers propres au malware, à ne jamais chiffrer) :
  `TOTAL_TRANNY_DEATH.png`, `READTHISNOW.txt`
- extensions systématiquement exclues : `.exe`, `.dll`, `.sys`, `.lnk`

## Contexte : builder open-source, deuxième échantillon vérifié

Chemin de build `C:/MoonMan/Prince-Ransomware-main/...` (suffixe `-main`
caractéristique d'un ZIP GitHub) : recherche web confirme que
Prince-Ransomware est un générateur de ransomware open-source publie sur
GitHub (sources : halcyon.ai, cyberpress.org, cybersecuritynews.com,
gbhackers.com). Plusieurs variantes connues : "Black (Prince)", "Wenda",
"UwU" (celle analysée ici).

Vérification croisée sur un deuxième échantillon du même tag MalwareBazaar,
tagué `crazyhunter` :

```bash
curl -s -X POST https://mb-api.abuse.ch/api/v1/ \
  -H "Auth-Key: $(cat ../.mb_api_key)" \
  -d "query=get_file&sha256_hash=f72c03d37db77e8c6959b293ce81d009bf1c85f7d3bdaa4f873d3241833c146b" \
  -o binaries/f72c03d3.zip
7z x -pinfected -y -obinaries binaries/f72c03d3.zip

strings -n 6 binaries/f72c03d3*.exe | grep -iE "prince|hunter"
# chemin de build : C:/Users/fake/Downloads/Prince/Prince v1.0.0/Encryptor/...
# fond d'ecran : "---------- Hunter Ransomware ----------"
# meme dependance github.com/ecies/go@v1.0.1, meme structure de packages

python3 tools/list_functions.py binaries/f72c03d3...46b.exe /tmp/out.txt \
  "EncryptFile" "XORKeyStream"
objdump -d -M intel --start-address=0x4fb6e0 --stop-address=0x4fbfa0 binaries/f72c03d3...46b.exe \
  | grep -B8 "call.*4f88e0"
# meme motif : mov ecx,0x1 ; mov r8,rcx juste avant l'appel XORKeyStream
```

**Le même défaut de chiffrement partiel est confirmé à l'identique sur ce
second échantillon.** Des sources publiques (recherche web) rapportent
qu'un ransomware nomme CrazyHunter a touché l'hôpital Mackay Memorial à
Taïwan en février 2025 (600+ appareils chiffrés, infection initiale par
clé USB). Le rattachement précis de ce hash à cet incident repose sur un
recoupement (nom "Hunter" + tag MalwareBazaar "crazyhunter"), pas sur une
source qui le confirmerait explicitement.

## Preuve de concept

`poc/simulate_prince_format.py` reproduit le format de fichier (sans
réimplémenter le vrai chiffrement, un flux pseudo-aléatoire quelconque
suffit à démontrer que la faille est structurelle) ;
`poc/poc_recover_plaintext.py` démontré la récupération. Test sur un
fichier texte de 256 octets : 170 octets récupérés sans aucune clé
(66,4%), texte reconstitué largement lisible malgré l'absence d'un
caractère sur trois.

```bash
python3 poc/simulate_prince_format.py test_plain.txt test_encrypted.moonman
python3 poc/poc_recover_plaintext.py test_encrypted.moonman test_recovered.txt
```

`poc/poc_infer_gaps.py` explore en complément une inférence heuristique
(pas cryptographique) du tiers restant, sur du contenu textuel
uniquement : le motif laisse toujours les deux voisins immédiats de
chaque octet manquant en clair, ce qui permet parfois de deviner une
lettre manquante par recherche dans un dictionnaire. Dictionnaires
hunspell locaux trouvés via les paquets OnlyOffice déjà installés
(`/opt/onlyoffice/desktopeditors/dictionaries/{fr_FR,en_US}/`), pas
besoin d'installation supplémentaire. Résultat sur le même fichier de
test : 9 mots à une seule lacune, 2 à solution unique, gain de
66,4% à 67,2%. Rappel important : ceci ne casse pas XChaCha20, c'est de
la devinette contextuelle qui échoue silencieusement sur du contenu à
haute entropie (voir avertissement en tête du script).

```bash
python3 poc/poc_infer_gaps.py test_recovered.txt test_inferred.txt
```

## Confirmation par le code source réel (GitHub public)

Le dépôt source du builder est public (`github.com/oakkaya/Prince-Ransomware`,
également forke sur `github.com/Kmaahan/Prince-Ransomware`). Recupere via
l'API GitHub brute (pas de `gh auth login` configure ici) :

```bash
curl -s "https://api.github.com/repos/oakkaya/Prince-Ransomware/contents"
curl -s "https://raw.githubusercontent.com/oakkaya/Prince-Ransomware/main/Encryptor/encryption/encryption.go"
curl -s "https://raw.githubusercontent.com/oakkaya/Prince-Ransomware/main/Builder/main.go"
curl -s "https://raw.githubusercontent.com/oakkaya/Prince-Ransomware/main/Decryptor/decryption/decryption.go"
```

`Encryptor/encryption/encryption.go` correspond **exactement**, instruction
pour instruction, à la boucle de "chiffrement" retrouvée par décompilation
et désassemblage (voir "EncryptFile" plus haut). Levée de toute ambiguïté
résiduelle : ce n'est pas une erreur de lecture du binaire.

`Builder/main.go` montre que la paire de clés ECIES est générée une seule
fois par build via `eciesgo.GenerateKey()` (donc `crypto/rand`, sain), puis
**compile deux exécutables à partir de cette même paire** : l'Encryptor
(clé publique embarquée via ldflags) et un Decryptor distinct, ou la clé
privée est embarquée de la même manière
(`-X 'Prince-Decryptor/configuration.PrivateKey=%s'`). Si un Decryptor est
un jour obtenu (partagé par une victime, saisie, fuite), la clé privée
complète s'en extrait aussi trivialement que la clé publique de
l'Encryptor : déchiffrement intégral possible, y compris le tiers de
contenu actuellement inaccessible. Aucun Decryptor trouvé sur
MalwareBazaar au moment de cette analyse (attendu, ce n'est pas un
exécutable malveillant, rarement soumis à ces plateformes).

`Decryptor/decryption/decryption.go` confirme aussi que le décrypteur
officiel localise le contenu par `strings.SplitN(contenu, "||", 3)`,
exactement la même méthode (et la même fragilité théorique face à une
coïncidence dans les enveloppés ECIES à haute entropie) que celle utilisée
dans `poc/poc_recover_plaintext.py` : ce n'est donc pas une simplification
de notre part, c'est la méthode réelle du projet.

## Vérification des 3 échantillons restants du tag `Prince`

```bash
for h in a205f794058c59a19322debb1e96a6133ebbab01cb57ae159c2ddb3c7e97a922 \
         4d0be6135596f7ec27f4b071f8225b6423132d51e44320f251cbe7036f447165 \
         b44af07de17ed16af4d6699b9462bee6855a0a0207ec787d41f00fbed81e1c13; do
  curl -s -X POST https://mb-api.abuse.ch/api/v1/ \
    -H "Auth-Key: $(cat ../.mb_api_key)" \
    -d "query=get_file&sha256_hash=$h" -o "binaries/$h.zip"
  7z x -pinfected -y -obinaries "binaries/$h.zip"
done

strings -n 6 binaries/*.exe | grep -oE "[A-Za-z0-9_/. :-]*Encryptor/[a-z]*\.go"
```

Résultat par chemin de build source embarqué :
- `a205f794...` : `EByte-Ransomware-main/Encryptor/main.go`, fond d'écran
  "Zéro Day Locker" -> **projet différent** (EByte-Ransomware, pas
  Prince-Ransomware), `list_functions.py` filtre "EncryptFile" ->
  0/5620 match, code de chiffrement différent, non vérifié plus avant.
- `4d0be613...` : `/home/lucy/Desktop/GoConti/Encryptor/main.go` ->
  **encore un projet différent** ("GoConti"), décompilation complète non
  tentée (le fichier a 5620+ fonctions, coût disproportionné pour ce
  degré de confirmation).
- `b44af07d...` : `path Prince-Ransomware`, wallpaper "Hello, we are
  AnonWorld.", clé publique différente -> **code Prince-Ransomware non
  modifié**, vérifié :

```bash
python3 tools/list_functions.py binaries/b44af07d...c13.exe /tmp/out.txt \
  "EncryptFile" "XORKeyStream"
# -> memes adresses exactes que sur l'echantillon UwU (0x501aa0, 0x5007e0) :
#    module de chiffrement compile a l'identique, seule la cle publique differe
objdump -d -M intel --start-address=0x501aa0 --stop-address=0x5022e0 binaries/b44af07d...c13.exe \
  | grep -B8 "call.*5007e0"
# -> mov ecx,0x1 ; mov r8,rcx identique, meme defaut confirme
```

**Bilan : 3 des 5 échantillons du tag `Prince` sont du code non modifié du
générateur (tous confirmés vulnérables au même défaut), les 2 restants
sont des forks a codé différent, non vérifiés.**

## Contexte additionnel (recherche web)

- Prince première observation publique rapportée : 2024-12-01, cohérent
  avec le corpus MalwareBazaar (dates first_seen de nov. 2024 à fev. 2025).
- PCRisk note que le générateur Prince ne met en place aucun identifiant
  unique par victime : les opérateurs ne peuvent pas savoir qui a payé,
  ce qui suggère que certains déploiements pourraient ne jamais restituer
  l'accès même en cas de paiement.
- Aucun décrypteur public trouvé (No More Ransom vérifié explicitement,
  aucune entrée pour Prince/Hunter/Black Prince/CrazyHunter/UwU).
- Chiffrés CrazyHunter corrigés par rapport à la première recherche web
  (sources : WithSecure Labs, TeamT5, presse taïwanaise) : plus de 500
  postes touchés (pas 600), jusqu'à 32,5 Go de données patients exfiltrées,
  vecteur USB puis BYOVD (pilote Zemana AntiMalware vulnérable pour
  désactiver l'EDR). Opérateur identifié par les autorités taïwanaises
  (ressortissant chinois, province du Zhejiang), au moins 11 attaques
  similaires (hôpitaux, écoles, entreprises cotées) en février-mars 2025.

## Pistes ouvertes

1. ~~Valider empiriquement le motif 1/3 sur un vrai fichier chiffré~~ :
   fait par PoC reproduisant le format (voir "Preuve de concept"
   ci-dessus), pas encore sur un vrai fichier de victime. Reste ouvert
   si un tel fichier est retrouvé publiquement.
2. ~~Écrire un script de récupération partielle~~ : fait
   (`poc/poc_recover_plaintext.py`).
3. ~~Déterminer l'extension et le contenu de la note de rançon~~ : fait
   (voir "Extraction de la configuration" ci-dessus).
4. ~~Confirmer si le même défaut existe dans les 3 échantillons restants
   du tag `Prince`~~ : fait, voir "Vérification des 3 échantillons
   restants" ci-dessus (3/5 confirmés vulnérables, 2/5 forks a codé
   différent non vérifiés). Reste ouvert seulement pour Anubis (toujours
   aucun échantillon exploitable) et pour les 2 forks (EByte-Ransomware,
   GoConti) si un intérêt spécifique s'y porte un jour.
5. Rediger un prince-ecies-go.md final une fois ces points tranches : fait,
   intégré l'ensemble des points ci-dessus.
6. **Chercher un Decryptor.exe** (voir "Confirmation par le code source
   réel" ci-dessus) : source de récupération bien plus directe que le
   défaut de chiffrement partiel si trouve, clé privée extractible en
   clair. Vérifié sur No More Ransom (aucune entrée Prince/variantes) et
   MalwareBazaar (aucun résultat) : pas trouvé. Pistes non essayées :
   forums de victimes, rapports d'incident response payants/privés sur
   CrazyHunter, VirusTotal (nécessite une clé API non configurée ici).
