Manipulez les paquets réseau avec Scapy

Formation

En Ligne

Prix sur demande

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Description

  • Typologie

    Formation

  • Méthodologie

    En ligne

Grâce à cette formation vous pourrez acquérir les connaissances nécessaires qui vous permettrons d’ajouter des compétences à votre profil et obtenir de solides aptitude qui vous offriront de nombreuses opportunités professionnelles.

Questions / Réponses

Ajoutez votre question

Nos conseillers et autres utilisateurs pourront vous répondre

À qui souhaitez-vous addresser votre question?

Saisissez vos coordonnées pour recevoir une réponse

Nous ne publierons que votre nom et votre question

Les Avis

Les matières

  • Réseau
  • Mise en réseau

Le programme

Introduction du cours

Scapy est un module pour Python permettant de forger, envoyer, réceptionner et manipuler des paquets réseau.
Si le réseau vous intéresse et que vous aimeriez mieux comprendre le fonctionnement des outils traditionnels (wireshark, dig, ping, traceroute, nmap...), alors ce tutoriel est fait pour vous ! :)
La compréhension de ce tutoriel nécessite quelques prérequis :

  • Des connaissances en Python (grosso modo les parties 1 et 2 du tutoriel officiel)

  • Des connaissances de base en réseau (je vous conseille la lecture du tutoriel de elalitte)

A l'issue de ce tutoriel, vous devriez être en mesure de programmer par vous même des utilitaires simples, et surtout de comprendre leur fonctionnement.

Allez, à l'assaut !

Installation et utilisation

Profitant de la portabilité du langage Python, Scapy est multi-plateforme. Cela dit, je ne détaillerai ici son installation et son utilisation que sous Linux, ne possédant que cet OS lors de l'écriture de ce tutoriel. La procédure d'installation est décrite ici pour Mac OS X et ici pour Windows. Par facilité, vous pouvez également préférer installer GNU/Linux en machine virtuelle.

InstallationDepuis les dépôts de votre distribution

Pour les pressés, l'installation minimale se fait avec la commande suivante :

$ sudo apt-get install python-scapy

Scapy ne fonctionne qu'avec la branche 2.x de Python (>= 2.5), la dernière version de cette branche étant Python 2.7. De plus, vous pouvez étendre les possibilités de Scapy (rapports pdf, traceroute 3D...) grâce à d'autres paquets.
Pour une distribution à base de Debian, l'installation complète se fait donc par cette ligne de commande :

$ sudo apt-get install python2.7 tcpdump graphviz imagemagick python-gnuplot python-crypto python-pyx nmap python-scapyDepuis Mercurial

Si vous souhaitez disposer de la toute dernière version de Scapy, vous pouvez l'installer depuis les sources en les récupérant depuis le dépôt mercurial.
Si ce n'est pas déjà fait, installez mercurial :

$ sudo apt-get install mercurial

Récupérez les sources et installez Scapy :

$ hg clone http://hg.secdev.org/scapy $ cd scapy $ sudo python setup.py install

Scapy est maintenant installé, c'est bien beau, mais passons à son utilisation. :)

UtilisationDepuis l'interpréteur

Rien de plus simple, lancez la commande suivante :

$ sudo scapy

Scapy manipule des paquets réseaux, ce qui nécessite d'être en root pour une majorité de tâches

Si tout se passe bien, vous vous retrouvez devant l'interpréteur python :

WARNING: No route found for IPv6 destination :: (no default route?) Welcome to Scapy (2.1.0) >>>Dans un script Python (.py)

Pour disposer des fonctionnalités de Scapy dans vos programmes Python, ajoutez simplement cette ligne à vos fichiers :

from scapy.all import *

Nous voilà maintenant prêts à utiliser Scapy !

Manipulation de paquetsForgeons !

Euh, quel rapport entre la métallurgie et les paquets ? o_O

Aucun. ^^
Forger un paquet désigne le fait de le construire en mettant les "mains dans le cambouis".
Je m'explique. D'ordinaire, quand vous utilisez un logiciel orienté réseau tel qu'un navigateur web, un logiciel de messagerie, etc, celui-ci échange des paquets. Par exemple, votre navigateur, quand vous vous rendez sur http://www.siteduzero.com, échange des paquets avec le serveur web du Site du Zéro. Pour simplifier cet échange, on peut dire que votre navigateur envoie un paquet "envoie moi cette page web s'il te plaît", et que le serveur du Site du Zéro lui renvoie le paquet "tiens, la voici : <html>Coucou</html>" (ne m'en veuillez pas pour cet exemple :D ).
Si vous décidez de programmer un tel logiciel, en pratique, vous n'aurez pas à vous soucier du détail de cette conversation par paquets. Par exemple, en C++, à l'aide de la bibliothèque Qt, afficher une page web se fait ainsi :

QWebView *pageWeb = new QWebView; pageWeb->load(QUrl("http://www.siteduzero.com/"));

Comme vous le voyez, aucune connaissance en réseau n'est nécessaire pour réaliser une telle chose, car on n'a pas réellement mis les mains dans le cambouis.
Or, nous, ce qui nous intéresse, avec Scapy, c'est de comprendre le détail de ces mystérieuses conversations...

Création d'une trame Éthernet

Les mots "trame", "paquet", mais aussi "datagramme", "segment", ont des sens bien précis, mais par abus de langage j'utiliserai surtout le mot "paquet" pour désigner les informations échangées sur le réseau.

L'échange de paquets avec un serveur web est loin d'être simple, elle fait intervenir le protocole HTTP, le handshake TCP, l'entête IP, bref, nous allons rester plus basique.
Commençons donc par créer et afficher une trame Éthernet dans l'interpréteur Scapy :

>>> ma_trame = Ether() >>> ma_trame.show() ###[ Ethernet ]### WARNING: Mac address to reach destination not found. Using broadcast. dst= ff:ff:ff:ff:ff:ff src= 00:00:00:00:00:00 type= 0x0 >>>

Comme on le voit, la création d'une trame éthernet se fait en instanciant la classe Ether(). Bien qu'on ne lui ai fournit aucun paramètre, on constate a l'appel de la méthode show() que les attributs dst, src et type ont des valeurs par défaut.

Que représentent ces différents attributs ?

Pour ceux qui ne connaitraient pas le protocole éthernet, voici la structure qu'une trame éthernet doit présenter :

Nous venons de créer une trame éthernet "pure", c'est à dire qu'on a rien dans data. Le CRC permet le contrôle d'intégrité de notre trame : si on le modifiait, notre trame deviendrait invalide et inutile. Il ne nous reste donc que 3 champs modifiables :

  • dst : représente l'adresse mac du destinataire

  • src : représente l'adresse mac de l'émetteur

  • type : représente le type de protocole (dépend du contenu de la partie "data" pour l'instant vide)

Pour les modifier, c'est très simple :

>>> ma_trame.dst = '00:19:4b:10:38:79' >>> ma_trame.show() ###[ Ethernet ]### dst= 00:19:4b:10:38:79 src= 00:00:00:00:00:00 type= 0x0 >>>

On aurait pu préciser l'adresse mac du destinataire lors de la création de la trame :

>>> ma_trame = Ether(dst='00:19:4b:10:38:79')

Les attributs dst, src et type sont modifiables à votre guise. Cela veut donc dire que vous pouvez facilement envoyer des trames en faisant croire que l'émetteur est quelqu'un d'autre !

Les envoyer ? Non, je ne sais pas faire ... :'(

Envoi de la trame

Pour envoyer une trame Éthernet, il existe la fonction sendp() :

>>> sendp(ma_trame) . Sent 1 packets. >>>

Dans le jargon Scapy, un point "." représente un envoi.

Voilà, mon paquet a bien été envoyé à la machine dont j'avais précisé l'adresse mac.

Génial ! .. C'est tout ? :-°

Je vous vois bien déçu. :p Ce que nous venons de faire ne présentait guère d'intérêt, je vous l'accorde. En effet, une trame Éthernet pure ne sert pratiquement à rien ; pour pouvoir faire quelque chose d'intéressant, il faudrait donc mettre quelque chose dans le "data" vu plus haut...
Nous allons donc faire de l'encapsulation.

Encapsulons !

Citation : Wikipédia

L'encapsulation, en informatique et spécifiquement pour les réseaux informatiques, est un procédé consistant à inclure les données d'un protocole dans un autre protocole.

Encapsuler les protocoles : l'exemple du ping

La commande ping permet de savoir si un hôte, désigné par son adresse IP, existe. En version cambouis, la commande ping consiste à envoyer un paquet ICMP "echo-request" à l'hôte et à dire si un paquet ICMP "echo-reply" a été renvoyé.
Forgeons donc un paquet ICMP echo-request !

>>> mon_ping = ICMP() >>> mon_ping.show() ###[ ICMP ]### type= echo-request code= 0 chksum= None id= 0x0 seq= 0x0 >>>

On voit que par défaut, l'instanciation de la classe ICMP() met le type du ping à echo-request. On pourrait tout à fait le modifier, tout comme les autres champs. Pour savoir ce qu'ils représentent, je vous renvoie à l'article ICMP sur Wikipédia. Dans cet article, on peut notamment lire quelque chose d'intéressant : un paquet ICMP est encapsulé dans un datagramme IP. En effet, c'est dans le datagramme IP qu'on va pouvoir renseigner l'adresse IP du destinataire.

L'encapsulation entre protocoles, dans Scapy, est réalisée par l'opérateur / (slash). Rien à voir avec une division, donc ;) .

>>> mon_ping = Ether() / IP(dst='192.168.1.1') / ICMP() >>> mon_ping.show() ###[ Ethernet ]### dst= 00:19:4b:10:38:79 src= 00:26:5e:17:00:6e type= 0x800 ###[ IP ]### version= 4 ihl= None tos= 0x0 len= None id= 1 flags= frag= 0 ttl= 64 proto= icmp chksum= None src= 192.168.1.14 dst= 192.168.1.1 \options\ ###[ ICMP ]### type= echo-request code= 0 chksum= None id= 0x0 seq= 0x0 >>>

On constate que, en précisant simplement l'adresse IP du destinataire, Scapy a compris tout seul qu'il devait modifier les attributs dst, src et type de Ether() ainsi que l'adresse IP de l'émetteur (src dans IP()) ! C'est très pratique, mais évidemment nous aurions pu forcer Scapy à mettre les valeurs que l'on voulait.

Voyons maintenant si 192.168.1.1 (ma Livebox) va répondre à cela par un paquet ICMP echo-reply.

Envoi du paquet

L'envoi s'effectue comme auparavant :

>>> sendp(mon_ping) . Sent 1 packets. >>>

Hé, on a toujours rien ! Tu nous aurais menti ?

Oui et non !
Oui, car la fonction sendp() ne fait qu'envoyer, c'est vrai. Pour envoyer et recevoir, il faut utiliser les fonctions srp() et srp1().
Non, car dans le cas d'une trame Éthernet pure, srp() et srp1() n'auraient de toute façon rien reçu !
srp() renvoie deux objets : le premier contient les paquets émis et leurs réponses associées, l'autre contient les paquets sans réponse.

>>> rep,non_rep = srp(mon_ping) Begin emission: Finished to send 1 packets. * Received 1 packets, got 1 answers, remaining 0 packets >>> rep <Results: TCP:0 UDP:0 ICMP:1 Other:0> >>> non_rep <Unanswered: TCP:0 UDP:0 ICMP:0 Other:0> >>>

Dans le jargon scapy, une étoile représente une réponse.

On voit qu'on a eu une réponse, zéro échecs, et que notre réponse est un paquet ICMP ! Examinons-le :

>>> rep.show() 0000 Ether / IP / ICMP 192.168.1.14 > 192.168.1.1 echo-request 0 ==> Ether / IP / ICMP 192.168.1.1 > 192.168.1.14 echo-reply 0 >>>

Bingo, on a bien reçu un ICMP echo-reply ! :)
rep contient en réalité une liste de couples de paquets. En l'occurence, la liste ne contient qu'un seul couple de paquets, qu'on peut afficher ainsi comme on afficherai n'importe quel élément d'une liste en Python :

>>> rep[0] (<Ether type=0x800 |<IP frag=0 proto=icmp dst=192.168.1.1 |<ICMP |>>>, <Ether dst=00:26:5e:17:00:6e src=00:19:4b:10:38:79 type=0x800 |<IP version=4L ihl=5L tos=0x0 len=28 id=58681 flags= frag=0L ttl=64 proto=icmp chksum=0x1248 src=192.168.1.1 dst=192.168.1.14 options=[] |<ICMP type=echo-reply code=0 chksum=0xffff id=0x0 seq=0x0 |>>>) >>>

Le résultat est un couple (tuple à deux valeurs). Pour afficher le paquet émis (notre ICMP echo-request), on fera donc rep[0][0].show(), et pour le paquet reçu en réponse, rep[0][1].show() :

>>> rep[0][0].show() ###[ Ethernet ]### dst= 00:19:4b:10:38:79 src= 00:26:5e:17:00:6e type= 0x800 ###[ IP ]### version= 4 ihl= None tos= 0x0 len= None id= 1 flags= frag= 0 ttl= 64 proto= icmp chksum= None src= 192.168.1.14 dst= 192.168.1.1 \options\ ###[ ICMP ]### type= echo-request code= 0 chksum= None id= 0x0 seq= 0x0 >>> rep[0][1].show() ###[ Ethernet ]### dst= 00:26:5e:17:00:6e src= 00:19:4b:10:38:79 type= 0x800 ###[ IP ]### version= 4L ihl= 5L tos= 0x0 len= 28 id= 58681 flags= frag= 0L ttl= 64 proto= icmp chksum= 0x1248 src= 192.168.1.1 dst= 192.168.1.14 \options\ ###[ ICMP ]### type= echo-reply code= 0 chksum= 0xffff id= 0x0 seq= 0x0 >>>

Pour simplifier tout cela, on peut préférer ici la fonction srp1(). Cette fonction renvoie renvoie un seul objet : la première réponse.

>>> rep = srp1(mon_ping) Begin emission: Finished to send 1 packets. * Received 1 packets, got 1 answers, remaining 0 packets >>> rep.show() ###[ Ethernet ]### dst= 00:26:5e:17:00:6e src= 00:19:4b:10:38:79 type= 0x800 ###[ IP ]### version= 4L ihl= 5L tos= 0x0 len= 28 id= 3386 flags= frag= 0L ttl= 64 proto= icmp chksum= 0xea47 src= 192.168.1.1 dst= 192.168.1.14 \options\ ###[ ICMP ]### type= echo-reply code=...

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Manipulez les paquets réseau avec Scapy

Prix sur demande