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

Ressources

Matériel(s) :
  • PC Windows

  • Hôte réseau disponible sur adresse 192.168.7.10 (→ imprimante Brother MFC-L2710DW series Printer)

Logiciel(s) :
  • Wireshark

  • Environnement virtuel Python disposant de la librairie Scapy

Documentation :
  • <Sans objet>

Pré-requis

  • Savoir utiliser Wireshark

  • Savoir mettre en place un environnement virtuel dans le gestionnaire d’environnement Conda

  • Bases de Python

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…

— Wikipedia

💻 Travail n° 1 Découverte de la librairie Scapy

🎯 Travail à faire :

Visionner la vidéo :

💻 Travail n° 2 Installation d’un environnement virtuel Python dédié au réseau

🎯 Travail à faire :

  1. Lancer un terminal Anaconda (→ taper Anaconda Prompt (miniconda3) dans le menu Démarrer de Windows)

  2. 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
  3. 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”
  4. 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 :

  1. Saisir le code suivant dans un script Python

    ping-printer.py
    from 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")
  2. 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])

  3. Exécuter le script depuis l’environnement virtuel “cyber”

    (cyber) Z:<<répertoire travail>>\> python ping-printer.py
  4. 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 :

Consignes

Reproduire dans le compte-rendu :

  • les commandes exécutées et leurs résultats

  • les codes source produits

  1. Ouvrir la capture arp-request.pcapng dans Wireshark

  2. 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)
  3. Chercher leurs correspondances dans la capture Wireshark

  4. Toujours dans l’interpréteur Python , créer un paquet etherFrame de type Ether() et inspecter avec quelles valeurs il a été initialisé grâce la commande etherFrame.show().

    >>> etherFrame = Ether()
    >>> etherFrame.show()
  5. Expliquer ce qui différencie ls(Ether) de etherFrame.show()

  6. Modifier le champ type du paquet etherFrame pour que sa valeur corresponde au protocole ARP

    Cette valeur est récupérable dans la capture Wireshark.

  7. Créer à présent un paquet arpRequest de type ARP bâti au-dessus de etherFrame en utilisant l’opérateur / puis l’inspecter :

    >>> arpRequest = etherFrame / ARP()
    >>> arpRequest.show()
  8. 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 champs src et psrc:

    >>> 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'
  9. 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
  10. Inspecter la réponse resp avec resp.show().
    Dans quel champ de resp 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.

  1. Ouvrir la capture ping-request.pcapng dans Wireshark

  2. Dans l’interpréteur Python , créer un paquet etherFrame de type Ether() et inspecter avec quelles valeurs il a été initialisé grâce la commande etherFrame.show().

  3. Modifier le champ type du paquet etherFrame pour que sa valeur numérique corresponde au protocole ICMP

    Cette valeur est récupérable dans la capture Wireshark.

  4. Créer à présent une trame ipFrame de type IP bâtie au-dessus de etherFrame en utilisant l’opérateur / puis l’inspecter :

    >>> ipPaquet = etherFrame / IP()
    >>> ipPaquet.show()
  5. 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

  6. Créer enfin une trame icmpPaquet de type ICMP bâtie au-dessus de ipPaquet 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')
  7. Envoyer le paquet avec la fonction srp1()

    resp = srp1(icmpRequest,timeout=5,iface="Ethernet 3") (1)
    1 Nom de l’interface à adapter à votre cas
  8. Inspecter la réponse resp avec resp.show().

  9. 🔥 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.

    checksum icmp
    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 => 🤩
  10. Scapy dispose de 2 fonctions proches dans leur nom srp1() et sr1(). Parcourir la doc de ces 2 fonctions et indiquer ce qui les différencie.

    >>> help(srp1)
    [...]
    >>> help(sr1)
    [...]

🞄  🞄  🞄