Prise en main de Scapy
Fiche d’activité
Objectifs
À l’issue de cette activité, vous devez être capable de … :
-
forger des trames ARP et ICMP avec la librairie Python Scapy
-
Analyser une capture Wireshark avec Scapy
Mise en situation
Dans le cadre de votre travail, on vous demande de vous former sur la librairie Python Scapy afin d’interagir avec le réseau par programmation plutôt qu’à partir d’applications prêtes à l’emploi (Nmap, Wireshark…).
Scapy est un logiciel libre de manipulation de paquets réseau écrit en python.
Il est capable, entre autres, d’intercepter le trafic sur un segment réseau, de générer des paquets dans un nombre important de protocoles, de réaliser une prise d’empreinte de la pile TCP/IP, de faire un traceroute, d’analyser le réseau informatique…
💻 Travail n° 2 Installation d’un environnement virtuel Python dédié au réseau
🎯 Travail à faire :
-
Lancer un terminal Anaconda (→ taper Anaconda Prompt (miniconda3) dans le menu Démarrer de Windows)
-
saisir la commande suivante pour créer un nouvel environnement virtuel nommé “cyber” avec la dernière version de Python
conda create --name cyber python
-
Basculer dans ce nouvel environnement :
activate cyber
Exemple :(base) C:\Users\claud>activate cyber (1) (cyber) C:\Users\claud> (2)
1 on est dans l’environnement de base 2 l’invite de commande indique que l’on est désormais dans l’environnement “cyber” -
Installer la librairie Scapy :
conda install conda-forge::scapy
💻 Travail n° 3 Prise en main de Scapy
On vous propose ici de mettre en œuvre Scapy pour faire un echo request ICMP (→ ping) vers l’imprimante du local profs (→ 192.168.7.10) avec un autre payload que celui utilisé par défaut par la commande ping
(→ lettres de l’alphabet) et l’inspecter depuis une capture Wireshark:
🎯 Travail à faire :
-
Saisir le code suivant dans un script Python
ping-printer.pyfrom scapy.all import * echoRequest = IP(dst='192.168.7.10')/ICMP()/Raw(load="Hello from Lycee Benoit") resp = sr1(echoRequest,timeout=10, iface="Wi-Fi") if resp != None: print("L'imprimante est ON") print("Réponse reçue :") resp.show() else: print("L'imprimante est OFF")
-
Lancer une capture depuis Wireshark de façon à acquérir les échanges ARP et ICMP avec l’hôte 192.168.7.10 (→ même filtre que dans le travail [analyse])
-
Exécuter le script depuis l’environnement virtuel “cyber”
(cyber) Z:<<répertoire travail>>\> python ping-printer.py
-
Vérifier dans la capture Wireshark que la payload de l’echo request est bien “Hello from Lycee Benoit”
🕮 Sources :
💻 Travail n° 4 “Forgeage” d’un paquet ARP
On vous demande de construire étape par étape dans Scapy une requête ARP.
🎯 Travail à faire :
-
Ouvrir la capture arp-request.pcapng dans Wireshark
-
Dans l’interpréteur Python ouvert dans l’environnement virtuel “cyber”, taper
ls(Ether)
pour lister tous les champs d’une trame Ethernet.>>> ls(Ether)
-
Chercher leurs correspondances dans la capture Wireshark
-
Toujours dans l’interpréteur Python , créer un paquet
etherFrame
de typeEther()
et inspecter avec quelles valeurs il a été initialisé grâce la commandeetherFrame.show()
.>>> etherFrame = Ether() >>> etherFrame.show()
-
Expliquer ce qui différencie
ls(Ether)
deetherFrame.show()
-
Modifier le champ
type
du paquetetherFrame
pour que sa valeur corresponde au protocole ARPCette valeur est récupérable dans la capture Wireshark.
-
Créer à présent un paquet
arpRequest
de type ARP bâti au-dessus deetherFrame
en utilisant l’opérateur/
puis l’inspecter :>>> arpRequest = etherFrame / ARP() >>> arpRequest.show()
-
Modifier au besoin les champs de ce paquet de façon à ce qu’il corresponde à un ARP request en broadcast pour déterminer qui possède l’adresse IP 192.168.7.10.
Exemples pour modifier les champssrc
etpsrc
:>>> arpRequest[Ether].src = 'b4:69:21:15:9b:db' # OU # >>> arpRequest.src = 'b4:69:21:15:9b:db' >>> arpRequest[ARP].psrc = '192.168.5.99' # OU # >>> arpRequest.psrc = '192.168.5.99'
-
Envoyer le paquet avec la fonction
srp1()
(le ‘p’ dans le nom de la fonction n’est pas une coquille !)resp = srp1(arpRequest,timeout=5,iface="Ethernet 3") (1)
1 Nom de l’interface à adapter à votre cas -
Inspecter la réponse
resp
avecresp.show()
.
Dans quel champ deresp
apparait l’adresse MAC de l’hôte 192.168.7.10 ? Quelle est sa valeur ?
Vérifier qu’elle est identique à celle qui apparait dans la capture Wireshark.
💻 Travail n° 5 “Forgeage” d’un paquet ICMP
On vous demande de construire étape par étape dans Scapy une requête ICMP.
-
Ouvrir la capture ping-request.pcapng dans Wireshark
-
Dans l’interpréteur Python , créer un paquet
etherFrame
de typeEther()
et inspecter avec quelles valeurs il a été initialisé grâce la commandeetherFrame.show()
. -
Modifier le champ
type
du paquetetherFrame
pour que sa valeur numérique corresponde au protocole ICMPCette valeur est récupérable dans la capture Wireshark.
-
Créer à présent une trame
ipFrame
de type IP bâtie au-dessus deetherFrame
en utilisant l’opérateur/
puis l’inspecter :>>> ipPaquet = etherFrame / IP() >>> ipPaquet.show()
-
Modifier au besoin les champs de ce paquet IP de façon à ce qu’il corresponde à celui qui apparait dans la partie IP d’un des échanges ping request de la capture Wireshark
-
Créer enfin une trame
icmpPaquet
de type ICMP bâtie au-dessus deipPaquet
dont les champs seront renseignés de façon à réaliser un ping sur l’hôte 192.168.7.10 (→ tirer les valeurs de la capture Wireshark)Le payload du paquet ICMP peut être fourni de la manière suivante :
>>> icmpPaquet = icmpPaquet / Raw(load='ceci est le payload de mon echo request')
-
Envoyer le paquet avec la fonction
srp1()
resp = srp1(icmpRequest,timeout=5,iface="Ethernet 3") (1)
1 Nom de l’interface à adapter à votre cas -
Inspecter la réponse
resp
avecresp.show()
. -
🔥 Compléter le script Python ci-dessous pour qu’il calcule le checksum d’un paquet ICMP et vérifier qu’il est identique à celui indiqué dans le paquet.
Le checksum est obtenu en calculant le complément à un sur 16 bits de la somme en complément à un de tous les octets du paquet ICMP après avoir mis à 0 les 2 octets destinés à recevoir ce checksum.def calculer_icmp_checksum(data): # <À COMPLÉTER> icmpPackets = [ b"\x08\x00\x4c\xc5\x00\x01\x00\x96\x61\x62\x63\x64\x65\x66\x67\x68" \ b"\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x61" \ b"\x62\x63\x64\x65\x66\x67\x68\x69" , b"\x00\x00\x54\xc5\x00\x01\x00\x96\x61\x62\x63\x64\x65\x66\x67\x68" \ b"\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x61" \ b"\x62\x63\x64\x65\x66\x67\x68\x69" , b"\x08\x00\x95\xfe\x00\x00\x00\x00\x48\x65\x6c\x6c\x6f\x20\x66\x72" \ b"\x6f\x6d\x20\x4c\x79\x63\x65\x65\x20\x42\x65\x6e\x6f\x69\x74" , b"\x00\x00\x9d\xfe\x00\x00\x00\x00\x48\x65\x6c\x6c\x6f\x20\x66\x72" \ b"\x6f\x6d\x20\x4c\x79\x63\x65\x65\x20\x42\x65\x6e\x6f\x69\x74" ] icmpTypes = { 0x08 : "echo request" , 0x00 : "echo reply" } for packet in icmpPackets : chks = calculate_icmp_checksum(packet) print(f". Paquet ICMP : {packet.hex()}") print(f"\tLongueur : {len(packet)}") print(f"\tType : {icmpTypes[packet[0]]}") print(f"\tPayload : {packet[8:]}") print(f"\tChecksum paquet : 0x{packet[2:4].hex()}") chksPacket = int.from_bytes(packet[2:4], 'big') smiley = '🤩' if chks == chksPacket else '😭' print(f"\tChecksum calculé : {chks:#04x} => {smiley}")
Résultat attendu :. Paquet ICMP : 08004cc5000100966162636465666768696a6b6c6d6e6f7071727374757677616263646566676869 Longueur : 40 Type : echo request Payload : b'abcdefghijklmnopqrstuvwabcdefghi' Checksum paquet : 0x4cc5 Checksum calculé : 0x4cc5 => 🤩 . Paquet ICMP : 000054c5000100966162636465666768696a6b6c6d6e6f7071727374757677616263646566676869 Longueur : 40 Type : echo reply Payload : b'abcdefghijklmnopqrstuvwabcdefghi' Checksum paquet : 0x54c5 Checksum calculé : 0x54c5 => 🤩 . Paquet ICMP : 080095fe0000000048656c6c6f2066726f6d204c796365652042656e6f6974 Longueur : 31 Type : echo request Payload : b'Hello from Lycee Benoit' Checksum paquet : 0x95fe Checksum calculé : 0x95fe => 🤩 . Paquet ICMP : 00009dfe0000000048656c6c6f2066726f6d204c796365652042656e6f6974 Longueur : 31 Type : echo reply Payload : b'Hello from Lycee Benoit' Checksum paquet : 0x9dfe Checksum calculé : 0x9dfe => 🤩
-
Scapy dispose de 2 fonctions proches dans leur nom
srp1()
etsr1()
. Parcourir la doc de ces 2 fonctions et indiquer ce qui les différencie.>>> help(srp1) [...] >>> help(sr1) [...]
🞄 🞄 🞄