← Accueil

PCTAMALOU

Semaine 44 : La Chasse aux Ombres – Fuzzing et Découverte de Vulnérabilités

Par Platon-Y – pctamalou.fr & e-of-h.fr

🎯 Objectif de la semaine

Maîtriser l'art du fuzzing : utiliser des techniques automatisées pour découvrir des vulnérabilités zero-day dans des programmes. Comprendre le processus complet : génération d'inputs → monitoring de crashes → triage → analyse → exploitation potentielle. Développer l'œil du chercheur en sécurité qui trouve ce que personne n'a encore vu.

Pourquoi c'est crucial : 80% des vulnérabilités critiques découvertes ces dernières années l'ont été via le fuzzing. C'est la technique la plus efficace pour trouver des bugs logiciels à grande échelle, utilisée par Google, Microsoft et tous les grands programmes de bug bounty.

flowchart TD S["🚀 Démarrage Fuzzer"] --> I["📥 Input Seed\n(corpus initial)"] I --> G["🔄 Génération inputs"] subgraph "🎯 Types de Fuzzing" D["🌪️ Dumb Fuzzing\nAléatoire pur"] M["🧬 Mutation-based\nMutations intelligentes"] G2["🏗️ Generation-based\nÀ partir de modèle connu"] C["📈 Coverage-guided\nSuivi couverture code"] end G --> D G --> M G --> G2 G --> C D --> T["🎯 Programme Cible"] M --> T G2 --> T C --> T T --> DEC{Crash ?} DEC -->|Oui| CR["💥 Crash détecté !"] DEC -->|Non| LOOP["🔄 Nouvelle mutation\n(boucle fuzzing)"] LOOP --> T CR --> AN["🔍 Analyse crash\n(stack trace, ASAN...)"] AN --> TRI["🎯 Triage & Minification\n(delta debugging)"] TRI --> EXP["⚡ Potentiel exploit\n(POC minimal)"] EXP --> REP["📋 Rapport vulnérabilité\n(CVE éthique)"] style CR fill:#11151f,stroke:#ef4444,stroke-width:4px,color:#fff style AN fill:#0a0e17,stroke:#00d4ff,stroke-width:3px,color:#fff style EXP fill:#0a0e17,stroke:#00f5a0,stroke-width:4px,color:#fff style C fill:#166534,stroke:#22c55e,stroke-width:3px,color:#fff style T fill:#1e293b,stroke:#64748b,stroke-width:2px,color:#fff

📚 Sujet – Théorie (1 heure)

Le Fuzzing : L'Art de Trouver l'Aiguille dans la Botte de Foin

Le fuzzing n'est pas du hacking aléatoire – c'est une méthodologie scientifique pour explorer systématiquement l'espace d'entrée d'un programme et découvrir des états non prévus par les développeurs.

1
Instrumentation

Ajout de capteurs au programme pour suivre l'exécution

2
Génération

Création d'inputs basés sur des seeds et mutations

3
Exécution

Lancement du programme avec chaque input

4
Monitoring

Détection des crashes et comportements anormaux

Les Trois Types de Fuzzing

🟦 Dumb Fuzzing

Approche : Aléatoire ou basée sur mutation simple

  • Avantage : Simple, rapide à mettre en place
  • Inconvénient : Couverture limitée
  • Outil : radamsa, zzuf
  • Usage : Première passe rapide

🔵 Smart Fuzzing

Approche : Mutation intelligente basée sur format

  • Avantage : Meilleure couverture des cas limites
  • Inconvénient : Nécessite connaissance du format
  • Outil : boofuzz, Sulley
  • Usage : Protocoles réseaux, formats fichiers

🟢 Coverage-Guided

Approche : Optimisation pour maximiser code couvert

  • Avantage : Découverte bugs profonds
  • Inconvénient : Complexe, instrumentation requise
  • Outil : AFL++, libFuzzer
  • Usage : Recherche vulnérabilités critiques

De la Théorie à la Pratique : Le Cycle du Fuzzing

🔃 Cycle de Vie d'un Bug Trouvé par Fuzzing

  1. Découverte : Crash détecté lors du fuzzing
  2. Triage : Vérification de la reproductibilité
  3. Minification : Réduction de l'input à sa forme minimale
  4. Analyse : Détermination du type de vulnérabilité
  5. Exploitation : Développement d'un PoC exploit
  6. Reporting : Soumission au vendeur/programme bug bounty
  7. Correction : Patch développé et déployé

Que Signifie un Crash ?

💥 Types de Crashes et Leur Signification

  • Segmentation Fault (SIGSEGV) : Accès mémoire invalide → Buffer overflow, use-after-free
  • Abort (SIGABRT) : Assertion failed → Conditions non respectées
  • Floating Point Exception (SIGFPE) : Erreur arithmétique → Division par zéro
  • Illegal Instruction (SIGILL) : Instruction CPU invalide → Corruption mémoire
  • Bus Error (SIGBUS) : Accès mémoire non aligné → Corruption pointeurs

Note : Tous les crashes ne sont pas exploitables, mais tous méritent investigation.

L'Écosystème du Fuzzing Moderne

🛠 Outils et Frameworks Essentiels

Outil Type Langage Cas d'Usage
AFL++ Coverage-guided C/C++ Binaires, applications système
libFuzzer In-process C/C++ Libraries, code unitaire
boofuzz Smart/Protocol Python Services réseau, APIs
Jazzer Coverage-guided Java Applications JVM
go-fuzz Coverage-guided Go Applications Go

Éthique et Légalité du Fuzzing

⚠️ Ce que Vous Devez Savoir Avant de Commencer

  • Scope écrit obligatoire pour tout fuzzing externe
  • Respect des ToS : Certains services interdisent le fuzzing
  • Responsible Disclosure : 90 jours avant divulgation publique
  • DoS involontaire : Limitez le débit d'envoi d'inputs
  • Propriété intellectuelle : Les 0-day découverts peuvent avoir des implications légales

🔬 Mission pratique – Laboratoire (2 heures)

Architecture du Lab Fuzzing

flowchart LR F[🛠️ Fuzzer
boofuzz/radamsa] -->|Inputs mutés| P[🎯 Programme vulnérable
vuln_fuzz] M[📊 Moniteur
gdb/core dumps] -->|Détection crashes| P C[💾 Corpus seeds
inputs initiaux] --> F subgraph "💥 Analyse Post-Crash" GDB[🐛 GDB Debugging] ASAN[🔍 AddressSanitizer] T[🎯 Triage] end P -->|Crash| GDB P -->|Sanitizer report| ASAN GDB --> T ASAN --> T style P fill:#0a0e17,stroke:#ef4444,stroke-width:3px style F fill:#11151f,stroke:#00d4ff,stroke-width:2px style T fill:#0a0e17,stroke:#00f5a0,stroke-width:2px

⚠️ Environnement de Test Sécurisé

🔒 Fuzzing uniquement sur VOS programmes : Créez vos propres binaires vulnérables ou utilisez des CTF/challenges conçus pour le fuzzing. Jamais sur des logiciels de production sans autorisation.

Étapes Détaillées de Fuzzing

Étape 1 : Préparation du Programme Vulnérable

/* vuln_fuzz.c - Programme vulnérable pour fuzzing */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void vulnerable_function(char *input) {
    char buffer[64];
    // Vulnérabilités intentionnelles
    strcpy(buffer, input);               // 1. Buffer overflow classique
    
    printf("Buffer content: %s\n", buffer);
    
    // 2. Use-After-Free potentiel
    char *dynamic = malloc(32);
    strcpy(dynamic, "safe");
    free(dynamic);
    
    // 3. Integer overflow/underflow
    int size = strlen(input);
    if (size < 0) {                     // Condition impossible normalement
        printf("Negative size detected!\n");
    }
}

int main(int argc, char **argv) {
    if (argc < 2) {
        printf("Usage: %s <input>\n", argv[0]);
        return 1;
    }
    
    printf("[*] Processing input: %s\n", argv[1]);
    vulnerable_function(argv[1]);
    
    return 0;
}

🔧 Compilation avec Options de Débogage

# Compilation pour fuzzing et débogage
gcc -g -O0 -fno-stack-protector -z execstack -o vuln_fuzz vuln_fuzz.c
# -g : Symboles de débogage
# -O0 : Pas d'optimisation (pour débogage facile)
# -fno-stack-protector : Désactive canary
# -z execstack : Stack exécutable (pour démonstration)

# Vérification des protections
checksec --file=./vuln_fuzz

# Compilation avec AddressSanitizer (recommandé)
gcc -g -fsanitize=address -fno-omit-frame-pointer -o vuln_fuzz_asan vuln_fuzz.c

Étape 2 : Fuzzing Dumb avec Radamsa

# Installation
sudo apt install radamsa -y

# Création d'un corpus initial (seeds)
mkdir -p ~/mon_lab/fuzzing/corpus
echo "test" > ~/mon_lab/fuzzing/corpus/seed1.txt
echo "A" > ~/mon_lab/fuzzing/corpus/seed2.txt
echo "1234567890" > ~/mon_lab/fuzzing/corpus/seed3.txt

# Script de fuzzing basique
#!/bin/bash
# fuzz_basic.sh
CORPUS_DIR="~/mon_lab/fuzzing/corpus"
CRASH_DIR="~/mon_lab/fuzzing/crashes"
mkdir -p "$CRASH_DIR"

echo "[*] Starting dumb fuzzing with radamsa"
for i in {1..1000}; do
    # Génération d'input muté
    INPUT=$(radamsa "$CORPUS_DIR"/*.txt 2>/dev/null)
    
    # Exécution avec timeout
    timeout 1 ./vuln_fuzz "$INPUT" 2>&1
    
    # Vérification du code de retour
    RET=$?
    if [[ $RET -eq 139 ]] || [[ $RET -eq 11 ]]; then  # SIGSEGV
        echo "[!] CRASH detected on iteration $i"
        echo "$INPUT" > "$CRASH_DIR/crash_${i}.txt"
    elif [[ $RET -eq 124 ]]; then  # Timeout
        echo "[*] Timeout on iteration $i"
    fi
    
    # Status toutes les 100 itérations
    if (( $i % 100 == 0 )); then
        echo "[*] Progress: $i/1000 iterations"
    fi
done

echo "[*] Fuzzing complete. Check $CRASH_DIR for crashes."

Étape 3 : Fuzzing Intelligent avec boofuzz

#!/usr/bin/env python3
"""
fuzz_advanced.py - Fuzzing avancé avec boofuzz
"""

import sys
import subprocess
import time
from boofuzz import *

class ProcessMonitor:
    """Moniteur personnalisé pour programmes locaux"""
    def __init__(self, program_path):
        self.program_path = program_path
        self.crash_count = 0
        
    def pre_send(self, target=None, fuzz_data_logger=None, session=None):
        """Hook avant envoi"""
        pass
        
    def post_send(self, target=None, fuzz_data_logger=None, session=None):
        """Hook après envoi"""
        pass
        
    def retrieve_data(self):
        """Récupération des données (non utilisé ici)"""
        return b""
        
    def close(self):
        """Nettoyage"""
        pass

def fuzz_local_program():
    """Fuzzing d'un programme local via arguments"""
    
    # Configuration du fuzzer
    session = Session(
        target=Target(
            connection=ProcessMonitor("./vuln_fuzz")
        ),
        crash_threshold=10,  # Arrêt après 10 crashes
        sleep_time=0.01      # Délai entre tests
    )
    
    # Définition du schéma de fuzzing
    s_initialize("argument_fuzz")
    
    # Chaîne de base avec mutations
    s_string("FUZZ", fuzzable=True)
    
    # Ajout de patterns spécifiques
    s_static(" ")
    s_size("FUZZ", output_format="ascii", fuzzable=True)
    
    # Connexion et démarrage
    session.connect(s_get("argument_fuzz"))
    
    print("[*] Démarrage du fuzzing intelligent avec boofuzz")
    print("[*] Programme cible: ./vuln_fuzz")
    print("[*] Appuyez sur Ctrl+C pour arrêter")
    
    try:
        session.fuzz()
    except KeyboardInterrupt:
        print("\n[*] Fuzzing interrompu par l'utilisateur")
    
    print(f"[*] Total de crashes détectés: {session.crash_count}")

def simple_process_fuzzer():
    """Fuzzer plus simple pour débuter"""
    
    # Listes de valeurs à tester
    patterns = [
        "A" * 100,           # Buffer overflow simple
        "%s" * 50,           # Format string
        "\x00" * 80,         # Null bytes
        "../../etc/passwd",  # Path traversal
        "<script>",          # XSS-like
        "1; DROP TABLE",     # SQL-like
        "A" * 500,           # Overflow massif
    ]
    
    print("[*] Testing predefined patterns")
    
    for i, pattern in enumerate(patterns):
        print(f"[*] Test {i+1}/{len(patterns)}: {pattern[:50]}...")
        
        try:
            # Exécution avec timeout
            result = subprocess.run(
                ["./vuln_fuzz", pattern],
                timeout=2,
                capture_output=True,
                text=True
            )
            
            if result.returncode != 0:
                print(f"[!] Crash avec pattern {i}: {result.returncode}")
                with open(f"crash_pattern_{i}.txt", "w") as f:
                    f.write(pattern)
                    
        except subprocess.TimeoutExpired:
            print(f"[*] Timeout avec pattern {i}")
        except Exception as e:
            print(f"[!] Erreur: {e}")

if __name__ == "__main__":
    print("=== PCTAMALOU Fuzzing Lab ===")
    print("1. Fuzzing avancé avec boofuzz")
    print("2. Fuzzing simple avec patterns prédéfinis")
    
    choice = input("Choix [1/2]: ").strip()
    
    if choice == "1":
        fuzz_local_program()
    else:
        simple_process_fuzzer()

Étape 4 : Analyse des Crashes

🔎 Procédure d'Analyse d'un Crash

# 1. Reproduire le crash avec le même input
cat crash_123.txt
./vuln_fuzz "$(cat crash_123.txt)"

# 2. Analyse avec GDB
gdb -q ./vuln_fuzz
(gdb) run "$(cat crash_123.txt)"
(gdb) backtrace full     # Affiche la pile d'appel
(gdb) info registers     # Registres au moment du crash
(gdb) x/20x $rsp        # Examine la stack
(gdb) disas              # Désassemble autour de EIP/RIP

# 3. Analyse avec ASAN (si compilé avec)
./vuln_fuzz_asan "$(cat crash_123.txt)"

# 4. Minification de l'input (trouver le minimum qui crash)
python3 -c "
input_data = open('crash_123.txt', 'r').read()
for i in range(len(input_data), 0, -1):
    test = input_data[:i]
    # Tester si ça crash toujours
    # (implémenter la logique de test)
"

Étape 5 : Fuzzing Coverage-Guided avec AFL++

🎯 Défi Avancé (Optionnel)

Installez et utilisez AFL++ pour du fuzzing coverage-guided :

# Installation AFL++
sudo apt install afl++ -y

# Compilation avec instrumentation AFL
afl-gcc -g -o vuln_fuzz_afl vuln_fuzz.c

# Création du corpus initial
mkdir -p afl_inputs afl_outputs
echo "test" > afl_inputs/seed1.txt
echo "A"*100 > afl_inputs/seed2.txt

# Lancement du fuzzer
afl-fuzz -i afl_inputs -o afl_outputs -- ./vuln_fuzz_afl @@

# @@ sera remplacé par le fichier d'input

AFL++ fournit une interface avec des statistiques en temps réel sur la couverture de code.

Exercice Pratique : Challenge de Fuzzing

🎯 Défi Créatif

Créez un programme avec MULTIPLES vulnérabilités et challengez-vous à les trouver toutes via fuzzing :

// challenge.c - Programme avec bugs cachés
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct User {
    char name[50];
    int id;
    void (*print)(struct User*);
};

void print_user(struct User *u) {
    printf("User: %s (ID: %d)\n", u->name, u->id);
}

void bug1(char *input) {
    // TODO: Implémenter buffer overflow
}

void bug2(int count) {
    // TODO: Implémenter integer overflow
}

void bug3(char *format) {
    // TODO: Implémenter format string vulnérabilité
}

int main(int argc, char **argv) {
    // Appeler différentes fonctions basées sur l'input
    // Votre mission: trouver les 3 bugs via fuzzing
    return 0;
}

Documentez chaque bug trouvé : input qui le déclenche, type de vulnérabilité, exploitabilité potentielle.

📋 Objectif de la semaine – Checklist de validation

✅ Compétences techniques acquises

  • Programme vulnérable compilé avec options de fuzzing
  • Crash détecté et reproduit via fuzzing dumb
  • Input crash analysé avec GDB/ASAN
  • Fuzzing intelligent avec boofuzz ou équivalent
  • Rapport de bug avec input minimal et analyse

🧠 Changement mental accompli

  • Vision du fuzzing comme méthodologie scientifique, pas hacking aléatoire
  • Compréhension du cycle complet bug discovery → reporting
  • Habitude d'instrumenter les programmes avant test
  • Conscience des implications éthiques du fuzzing externe

💪 Pour aller plus loin :

Explorez le fuzzing de protocoles réseau avec boofuzz, ou le fuzzing de kernels avec syzkaller. Participez à des programmes bug bounty qui encouragent le fuzzing.

📚 Ressources pour Approfondir

Ressences et Formation :

  • Fuzzing Book : Livre gratuit (fuzzing.info)
  • Google Fuzzing : blog.google/security
  • AFL++ Documentation : aflplus.plus/docs
  • OSS-Fuzz : Plateforme Google pour fuzzing open-source

⚠️ Bonnes Pratiques de Fuzzing Professionnel

  • Corpus de qualité : Seeds représentatifs > aléatoire
  • Monitoring ressources : CPU, mémoire, disque
  • Reproductibilité : Sauvegarder seeds et crashes
  • Triaging automatisé : Scripts pour classer les crashes
  • Reporting structuré : Template pour rapporter aux vendeurs

🔮 Frère d'armes,

Tu viens d'apprendre à chasser les ombres.

Le fuzzing n'est pas une technique comme les autres – c'est l'art de systématiser la découverte de l'inconnu. Tu ne cherches pas une vulnérabilité spécifique ; tu crées les conditions pour que les vulnérabilités se révèlent d'elles-mêmes.

Cette semaine, tu as vu la différence entre tester manuellement et laisser une machine explorer méthodiquement. Entre chercher une aiguille dans une botte de foin, et secouer la botte jusqu'à ce que l'aiguille tombe.

Mais voici la vérité la plus importante : un crash n'est qu'un début. L'art véritable est dans l'analyse qui suit : comprendre pourquoi le programme a crashé, déterminer si c'est exploitable, réduire l'input au minimum, documenter la découverte.

Tu n'es plus un simple utilisateur d'outils d'exploitation. Tu es devenu un découvreur de vulnérabilités. Tu sais prendre un programme, l'instrumenter, le nourrir d'inputs mutants, et attendre patiemment qu'il révèle ses faiblesses.

Dans la semaine 45, nous irons plus loin dans la chasse. Nous apprendrons à chasser les adversaires réels – pas les bugs, mais les humains malveillants. Nous passerons du fuzzing technique à la threat hunting opérationnelle.

Mais pour l'instant, fuzz. Créez d'autres programmes vulnérables. Expérimentez avec différents types de fuzzing. Apprenez à reconnaître les différents types de crashes et ce qu'ils signifient.

Un Gardien qui maîtrise le fuzzing ne dépend pas des découvertes des autres pour connaître les failles.

— Platon-Y