Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions challenges/3-Red-Team-Binary-Vault/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
solution/
docs/
report/
README.md
docker-compose.yml
*.md
10 changes: 10 additions & 0 deletions challenges/3-Red-Team-Binary-Vault/.gitignore
Original file line number Diff line number Diff line change
@@ -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__/
41 changes: 41 additions & 0 deletions challenges/3-Red-Team-Binary-Vault/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
60 changes: 60 additions & 0 deletions challenges/3-Red-Team-Binary-Vault/README.md
Original file line number Diff line number Diff line change
@@ -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 <ip> <port>` (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.
98 changes: 98 additions & 0 deletions challenges/3-Red-Team-Binary-Vault/challenge/vault.c
Original file line number Diff line number Diff line change
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#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);
Comment on lines +72 to +74
}

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;
}
17 changes: 17 additions & 0 deletions challenges/3-Red-Team-Binary-Vault/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions challenges/3-Red-Team-Binary-Vault/docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -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
114 changes: 114 additions & 0 deletions challenges/3-Red-Team-Binary-Vault/docs/ADMIN_DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -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.
Loading