# Wargame (CTF)

Write-ups de challenges CTF / wargame (repris de Wiki.js le 09/11/2026). Livre public : lisible sans compte.

# Modèle de write-up CTF

# 🚩 Write-up CTF : [Nom du Challenge]

| Fiche technique | Détails |
| :--- | :--- |
| **Événement** | [Nom du CTF, ex: Root-Me / Hack The Box / PicoCTF] |
| **Catégorie** | Web / Reverse / Pwn / Crypto / Forensics / OSINT |
| **Difficulté** | 🟢 Facile / 🟡 Moyen / 🔴 Difficile |
| **Auteur** | [Nom du créateur de l'épreuve] |
| **Points** | 500 pts |

---

## 📝 Description

> [Coller ici la description officielle de l'épreuve]

**Fichiers / Liens fournis :**
- 🔗 `http://challenge.ctf:8080`
- 📁 `challenge.zip`

---

## 🔍 1. Reconnaissance & Analyse

Explication des premières étapes de scan ou d'inspection.

* **Scan Nmap :**
```bash
nmap -sC -sV -p- 10.10.10.X

---
*Page reprise de l'ancien Wiki.js (https://wikijs.htv.nasspc.fr/WARGAME, créée le 2026-09-16, modifiée le 2026-09-16) le 09/11/2026.*

# WARGAME — Trouvez la phrase top secrète

# WARGAME — Trouvez la phrase top secrète
## Writeup complet de l'investigation

**Challenge** : HESD4, par Thomas J (Nantes)
**Fichier fourni** : `trouve-la-phrase.zip` → `trouve-la-phrase.dmp` (dump RAM ELF core, 2 181 953 312 octets, VirtualBox/QEMU, machine Windows)
**Format attendu du flag** : `ESD{...}`

---

## 0. Indices de l'énoncé

- Compile des programmes C/C++ sous Windows
- Adore les raccourcis clavier, surtout **Windows+R**
- Regarde des vidéos de « Jamy et Michelle »
- Collectionne les « artéfacts de volatiles »

Deux jeux de mots identifiés dès le départ :
- **« artéfacts de volatiles »** → clin d'œil à **Volatility** (framework de forensic mémoire) + **volatile** = oiseau en français
- **« Jamy et Michelle »** → probablement **« Jacquie et Michel »** (site français bien connu), indice dont la finalité exacte n'a **jamais été confirmée** malgré une recherche exhaustive

---

## 1. Mise en place

```bash
unzip -P password trouve-la-phrase.zip   # -> trouve-la-phrase.dmp
file trouve-la-phrase.dmp                # ELF 64-bit core file (dump physique VM)
```

Analyse de l'en-tête ELF (`readelf -l`) : un unique gros segment `PT_LOAD` avec
`p_offset=0x1cb720`, `p_paddr=0x200000`, taille ~2 Go → mapping **linéaire** entre offset fichier et adresse physique invité :

```
phys = file_offset - 0x1cb720 + 0x200000
file_offset = phys - 0x200000 + 0x1cb720
```
(Vérifié : les deux constantes sont multiples de 16, donc `phys mod 16 == file_offset mod 16` — propriété réutilisée plus tard pour la recherche de PTE.)

---

## 2. Windows+R → identifiants KeePass

Recherche de la structure registre `RunMRU` dans le dump (clé `HKCU\...\Explorer\RunMRU`).
Une entrée révèle la commande tapée par le chercheur :

```
"c:\Users\passurmoi\Desktop\Package\KeePass-2.51.1\KeePass.exe" 
    c:\Users\passurmoi\Desktop\Package\OpenMe.kdbx 
    -pw:Poulet 
    -keyfile:"c:\Users\passurmoi\Documents\satz\beau_fichier"
```

→ Mot de passe KDBX : **`Poulet`** (jeu de mot avec « volatiles »)
→ Fichier-clé requis : `beau_fichier`

## 3. Récupération du fichier-clé (`beau_fichier`)

Recherche de structures NTFS `$MFT` (enregistrements `FILE`) contenant le nom `beau_fichier`, avec extraction de l'attribut `$DATA` résident (petit fichier stocké directement dans le MFT) :

```xml
<?xml version="1.0" encoding="utf-8"?>
<KeyFile>
    <Meta><Version>2.0</Version></Meta>
    <Key><Data Hash="57E83CAE">
        2B57F876 20618481 E7009C6E CB89B51A
        B5E88F9B C6BF5FFB 8698F1BD 85B22FC1
    </Data></Key>
</KeyFile>
```
Checksum vérifié (`SHA256(clé)[:4] == 57E83CAE`) ✅

## 4. Déchiffrement de `OpenMe.kdbx` — la vraie difficulté technique

### 4.1 Premier essai (échec) : lecture physique « à plat »

En-tête KDBX (KDBX 3.1, AES-256, GZip, 60 000 tours AES-KDF) localisé deux fois dans le dump.
Une lecture contiguë après l'en-tête donne un déchiffrement **partiellement correct** (les premiers Ko),
puis corrompu — cause : **fragmentation physique** du buffer applicatif (les pages virtuelles contiguës du process ne sont pas contiguës en RAM physique).

### 4.2 Percée : reconstruction par table de pages Windows

Méthode développée et réutilisée tout au long du challenge :
1. Calculer l'adresse physique de l'ancre connue (`phys = file_off - 0x1cb720 + 0x200000`)
2. Chercher dans tout le dump les **PTE** (Page Table Entries, 8 octets) dont les bits 12-51 encodent ce PFN, avec le bit *Present* (0x1) **ou** *Transition* (0x800) actif
3. Si une correspondance unique est trouvée dans une table de 512 entrées majoritairement valides → lire la table de pages complète, et reconstituer les pages virtuelles consécutives dans l'ordre logique, même si elles sont éparpillées physiquement

Appliqué à la **2ᵉ copie** du KDBX (mappée via des PTE en état *Transition*) → reconstruction complète.
**Hash SHA-256 du bloc de données vérifié conforme à la valeur attendue dans l'en-tête KDBX** → déchiffrement garanti correct à 100 %.

## 5. Contenu de la base KeePass

Après décompression Gzip du XML, puis déchiffrement des champs protégés (algorithme **Salsa20**, `nonce = E830094B97205D2A`, clé = `SHA256(ProtectedStreamKey)`) appliqué dans l'ordre du document :

- Groupe **OpenMe** → entrées `Credits ==>` (auteur), **Flag**, `valeurchamp1..5`, groupe **Corbeille** (8 objets supprimés, contenu non récupérable)
- L'entrée **Flag** explique le mécanisme :
  > Renseigner Username/Password/champ3/champ4 avec les vraies valeurs de valeurchamp1/2/3/4, puis exécuter :
  > `staz.exe valeurchamp2 valeurchamp1 valeurchamp3 VALEURCHAMP4`

### Découverte des 4 valeurs (chacune cachée par un procédé différent)

| Champ | Valeur | Méthode de découverte |
|---|---|---|
| **champ1** (username) | `kiwis` | Historique de l'entrée encodé en **T9** (clavier téléphone) : `888 2 555 33 88 777 222 44 2 6 7 3 33 88 99 0` → K-I-W-I-S |
| **champ3** | `zinzolins` | Texte stylisé caché dans le champ *Notes*, image PNG reconstituée via `{PASSWORD}{NOTES}` |
| **champ4** (MAJUSCULE) | `RACHEL` | **Geohash** `9qtwnjng4sz` → coordonnées (37.645, -115.742) → ville la plus proche d'**Area 51**, Nevada |
| **champ2** (mot de passe) | *hash NTLM du compte Windows `passurmoi`* | Voir section 6 — **jamais résolu avec certitude** |
| valeurchamp5 | — | Juste un indice texte : *« Il n'y a que 4 valeurs à renseigner »* |

---

## 6. `satz.exe` — extraction et rétro-ingénierie

### 6.1 Extraction depuis une archive ZIP cachée en RAM

Recherche de signatures `PK\x03\x04` (en-têtes locaux ZIP) : découverte du **`satz.zip` original entièrement caché en mémoire** (probablement le buffer de téléchargement/extraction), contenant `satz.exe` (deflate), `beau_fichier` (stocké), `OpenMe.kdbx` (stocké).

`satz.exe` reconstruit via la **même technique de table de pages** (CRC-32 et taille décompressée vérifiés ✅).

### 6.2 Désassemblage (`objdump`)

Le binaire contient des symboles de debug DWARF (bibliothèque MinGW uniquement, pas notre code).
Analyse du flux de `main()` :
- Charge `argv[1..4]` (aucune vérification d'`argc`)
- Concatène des `std::string` : fragments littéraux + arguments, **aucun calcul, aucun hash, aucune validation** — confirmé ligne par ligne en assembleur x86-64

**Template du flag confirmé avec certitude absolue :**
```
ESD{Les_[champ2]_cultivent_des_kiwis_zinzolins_pr3s_de_RACHEL}
```

---

## 7. Extraction du hash NTLM — la chaîne SAM/SYSTEM

### 7.1 Bootkey (ruche SYSTEM)

Recherche des cellules `nk` réelles `Lsa\{JD,Skew1,GBG,Data}`, extraction de leurs **noms de classe** (8 caractères hex chacun) via un même schéma de cellule adjacente `-24`.

```
JD    = 8ed06ec3
Skew1 = 7f4f716b
GBG   = b2f0295b
Data  = 8a7deba8
```

Permutation standard `[8,5,4,2,0xB,9,0xD,3,0,6,1,0xC,0xE,0xA,0xF,7]` appliquée à la concaténation :

```
bootkey = b24f7f6e5bf07dc38e71d08aeb29a86b
```

### 7.2 PEK (ruche SAM)

RID de `passurmoi` confirmé = **1000** (`0x3E8`, `NextRid=1001` trouvé dans le blob `F` du domaine).
Blob `Account\F` (240 octets) extrait, `Key0` parsé (`SAM_KEY_DATA`, revision=1, RC4) :

```python
tmpKey = MD5(Salt + QWERTY_CONST + bootkey + DIGITS_CONST)
hashedBootKey = RC4(tmpKey, Key + CheckSum)[:16]
```

**Checksum de contrôle vérifiée** (`MD5(PEK+DIGITS+PEK+QWERTY) == CheckSum stocké`) ✅

```
PEK = aa4f433bd3e23f9d7f524207c6eea8fa
```

### 7.3 Valeur `V` (hash NT chiffré) — introuvable

Cellule `vk "V"` du RID 1000 localisée (`DataLength=0x110=272`), mais son contenu (offset hive-relatif `0x1730`) s'est révélé **physiquement absent** du dump : recherche exhaustive (par signature de structure, par empreinte RC4 calculée à partir de la PEK, sur les 2 Go entiers) → **0 résultat**. Abandon de cette voie.

---

## 8. Les secrets LSA — percée majeure puis mur technique

### 8.1 Découverte via journal de transaction (TxR/CLFS)

Recherche de `Policy\Secrets\DefaultPassword\CurrVal` **encodé en UTF-16** (les journaux de transaction du registre stockent les chemins en Unicode, contrairement aux cellules brutes en ASCII — angle mort de nos recherches précédentes).

→ Découverte d'un **enregistrement de log auto-contenu** avec la structure complète `LSA_SECRET` (Version+GUID+Algo+Flags+Data).

### 8.2 Déchiffrement de `PolEKList` → clé LSA

Algorithme AES vérifié contre le **code source officiel de Volatility 3** (`lsadump.py`, fonction `decrypt_aes`) :
```python
sha = SHA256(); sha.update(bootkey)
for _ in range(1000): sha.update(secret[28:60])   # salt
aeskey = sha.digest()
for chaque bloc de 16 octets à partir de l'offset 60:
    AES-CBC(aeskey, IV=0) déchiffre indépendamment chaque bloc
```
→ Structure de clé LSA décodée, checksum interne cohérent.

```
LSAKey = 4c12960aa7a1117b3a999e9a90481282e1929e5af6cbc8d5f1c6413d78cff34
```

### 8.3 `OldVal` déchiffré avec succès → **`extraterrestres`** (PIÈGE)

Même algorithme appliqué à `Policy\Secrets\DefaultPassword\OldVal` → déchiffrement **intégral et propre** :

```
"extraterrestres"  (15 caractères, 30 octets UTF-16, Length=30 confirmé)
```

Thématiquement parfait (Area 51 / Rachel) mais **soumis et rejeté par le wargame**.

### 8.4 Le vrai `CurrVal` (22 caractères) — mystère non résolu

`CurrVal` (mot de passe **actuel**) a `Length=44` (22 caractères) — **différent** d'`OldVal`, donc `extraterrestres` est bien la mauvaise valeur.

**3 copies indépendantes** de `CurrVal` localisées en mémoire (sels différents : `bbabe5b8...`, `4a0a673d...`, `8a58b233...`), résolution d'adresse **non-ambiguë** vérifiée via la vraie liste de valeurs du registre (grâce à des pages `hbin` à offset hive-relatif connu).

**Phénomène reproductible et inexpliqué** sur les 3 copies :
- Le **premier bloc AES (16 octets)** se déchiffre **parfaitement** à chaque fois (`Length=44` + 12 octets nuls — motif si spécifique qu'un test de calibration sur 2000 clés aléatoires n'a donné **aucun** faux positif)
- **Tous les blocs suivants** (contenant le mot de passe lui-même) restent illisibles, quel que soit le mode testé : ECB, CBC (IV=0 / IV=sel), CFB, OFB, CTR, RC4, AES-128 (clé tronquée), ordres de hachage alternatifs, sel décalé, etc.

L'algorithme a été **validé ligne par ligne contre le code source réel de Volatility 3** — aucune divergence trouvée. Conclusion : la clé et la méthode sont prouvées correctes ; **les octets du mot de passe au-delà du premier bloc sont absents du dump**, probablement une perte de données réelle à la capture, pas un bug de déchiffrement.

---

## 9. Pistes explorées sans succès pour trouver/deviner le `CurrVal`

- Recherche exhaustive (2 Go) de tout bloc déchiffrable en texte UTF-16 lisible avec la clé confirmée (ECB **et** CBC chaîné) → 0 résultat
- ~13 000 combinaisons thématiques testées par **vérification cryptographique** (chiffrement du candidat + comparaison aux octets réels, pas du bruteforce aveugle) → 0 correspondance
- Historique de frappe URL (`TypedURLs`), dossiers Vidéos/Téléchargements, Corbeille (`$R...`), cache DNS, navigateurs installés → aucune trace de fichier vidéo ou de site visité
- Indice « Jacquie et Michel » → aucune trace littérale dans le dump ; probablement juste une inspiration thématique pour les autres champs (kiwis, zinzolins, Rachel) sans artefact direct à trouver
- Blob DPAPI (`Credentials\`) → uniquement un artefact par défaut du modèle de ruche Windows, pas de vraies données
- Autres secrets LSA (`$MACHINE.ACC`, `NL$KM`, `DPAPI_SYSTEM`) pour valider la méthode sur une référence → non trouvés en mémoire
- Commandes `reg add` / `netplwiz` → uniquement des signatures antivirus et enregistrements COM système

---

## 10. État final

**Flag quasi complet, un seul élément manquant :**
```
ESD{Les_[HASH_NTLM_DE_22_CARACTERES]_cultivent_des_kiwis_zinzolins_pr3s_de_RACHEL}
```

**Confirmé avec certitude** : `champ1=kiwis`, `champ3=zinzolins`, `champ4=RACHEL`, template exact, mécanisme complet.
**Non résolu** : la valeur du mot de passe Windows actuel de `passurmoi` (22 caractères), et donc son hash NTLM (`champ2`), à cause d'une donnée physiquement absente de ce dump mémoire précis — malgré une chaîne cryptographique intégralement reconstruite et vérifiée (bootkey → PEK → clé LSA → structure du secret).

### Pistes pour la suite
- Un dump mémoire plus complet ou plus récent de la même VM
- Accès à un outil Volatility 3 complet (`windows.hashdump`, `windows.lsadump`) sur une machine dédiée, au cas où son parcours d'adressage virtuel (VAD) résout ce que notre reconstruction manuelle de tables de pages n'a pas pu résoudre
- Contact direct avec le créateur du challenge pour un indice complémentaire sur `champ2`


---
*Page reprise de l'ancien Wiki.js (https://wikijs.htv.nasspc.fr/WARGAME_Trouvez_la_phrase_top_secrete, créée le 2026-09-16, modifiée le 2026-09-16) le 09/11/2026.*