wiki/HowtoScapy.md
2024-05-21 17:35:25 +02:00

12 KiB
Raw Blame History

categories title
network Howto Scapy

Scapy est un programme et une librairie de manipulation de paquets écrite en Python. L'outil permet de forger, décoder des paquets reçus, émettre des paquets et capturer un segment de communication (format PCAP) ainsi que des procédures de prototypage de paquets simplifiées et naturelles grâce à lauto-complétion intelligente mise à disposition.

C'est avant tout une librairie Python offrant aux utilisateur une interface accessible directement via linterpréteur.

Principe : Forger, manipuler, écouter les flux de communication réseau.

Principe de fonctionnement et Concepts

Scapy se sert du mode promiscuous des interfaces et réintroduit intégralement une stack TCP/IP sur laquelle il possède les pleins pouvoirs. Cela simplifie notamment la traversée du niveau 2 (Modèle OSI) dans la stack TCP/IP noyau. L'interface étant configurée en promiscuous le kernel est alors supplanté par Scapy et le matériel à disposition.

Cette structure nous permets de définir entièrement un environnement réseau spécifique. Scapy nous offre la possibilité de créer/renommer/ajouter des interfaces réseau, créer une table routage dédiée dépendante de celle du système "hôte", la gestion de certificats d'encryption et bien d'autres à travers l'objet conf.

L'outil se base sur les nivaux du modèle OSI afin de structurer les couches composant les objets descripteurs de paquets. Ainsi un objet de type PKT dans Scapy se compose généralement de plusieurs couches en fonction des choix de l'utilisateur et peut être vu comme un dictionnaire bidimensionnel : PKT['IP']['TCP']....

Structures et Modèle OSI

Couches

Les couches mises en place par Scapy dans sa représentation des paquets sont celles modélisées par le modèle OSI, en effet, chaque interaction avec la librairie ou l'outil cible un couche particulières afin de pouvoir donner à l'utilisateur un contrôle total. Le principale impacte de cette structure en couche sur l'utilisation de scapy, est en réalité étalé sur deux aspects:

  • Dans un premier temps cela permet de structurer notre objet et de compartimenter les données selon le modèle OSI, ce qui as pour but d'en augmenter la lisibilité et la facilité d'utilisation.

  • Dans un second temps, et en conséquence du choix précédemment exposé de mise en place d'une stack TCP/IP séparée, cela signifie que la couche Ethernet est un peu spéciale.

Scapy offre donc deux types de fonction interagissant avec le réseau :

  • Les fonctions de niveau 3 et plus, utilisant la stack TCP/IP du système par défaut (ex : sr(),send()).
  • Les fonctions de niveau 2 spécifiant l'utilisation de la stack de Scapy (srp(),sendp()).

Installation

Scapy est suporté par *nix, macOS et Windows, mais seules les procédures sous Linux seront documentées ici. Pour plus d'information concernant les procédures d'installation propre à d'autres plateformes, se réferer à la documentation officielle

Debian

Installation sous Debian :

# apt install python3-scapy

Puis vérifier que Scapy est bien installé :

# scapy

Vérification de la version (2.4.4 à ce jour) :

>>> conf.version
'2.4.4'

Utilisation

L'utilisation principale de Scapy se fait au travers de linterpréteur python directement, et l'outil nécessitant l'accès au mode promiscuous, il requiert donc les droits root au lancement :

# scapy

Une fois scapy lancé, linterpréteur python est alors le nouvel environnement de travail.

Configuration

Dans l'interpréteur, toute la configuration de Scapy se fait via l'objet conf mis à disposition. Les champs qui le composent permettent de moduler le comportement de l'outil en fonction de vos attentes. Nous pouvons retrouver dans ce dictionnaire les champs concernant la stack TCP/IP propre à Scapy, notamment la table de routage, les interfaces, la taille de buffer etc...

>>> conf
[...]
iface      = 'enp0s31f6'
promisc    = True
route      = Network Netmask Gateway Iface Output IP Metric 0.0.0.0 0.0.0.0 ...
bufsize    = 65536
[...]

La modification d'un champs de configuration se fait naturellement, à la manière d'une allocation de variable traditionnelle :

>>> conf.iface = 'vio0'      # renommage de l'interface en vio0

Python étant la base sur laquelle se construit Scapy, nous pouvons facilement en déduire que la connaissance de quelques bases de python sont intéressantes.

Intégration Python

Scapy est avant tout un module python, il est donc facilement possible dintégrer les fonctionnalités de Scapy à ses scripts python :

from scapy.all import *
from scapy.utils import *

[...]

Après quoi, il vous sera possible de facilement créer des scripts :

# Exemple 3way handshake :
#--------------------------
from scapy.all import *

port=RandNum(1024,65535)
# création du packet SYN
SYN=ip/TCP(sport=port, dport=80, flags="S", seq=42)
# SYN,ACK
SYNACK=sr1(SYN)
# ACK 
ACK=ip/TCP(sport=SYNACK.dport, dport=80, flags="A", seq=SYNACK.ack, ack=SYNACK.seq + 1) / get
# envoi de la reqête GET
reply,error=sr(ACK)

print reply.show()

Création de paquets

La bibliothèque de Scapy mets en place plusieurs méthodes et surcharges dopérateurs nous permettant de construire un paquet "de bas en haut" suivant le modèle OSI. En parallèle plusieurs classes dérivant du type PKT sont mises à disposition afin de représenter les protocoles et le processus d'encapsulation visuellement lors de la construction du paquet.

Pour clarifier, attribuons à une variable x, le contenu d'un paquet au niveau 3 pour le protocole IP :

>>> x = IP()
>>> x.show()
###[ IP ]### 
  version= 4
  ihl= None
  tos= 0x0
  len= None
  id= 1
  flags= 
  frag= 0
  ttl= 64
  proto= hopopt
  chksum= None
  src= 127.0.0.1
  dst= 127.0.0.1
  \options\

Dans le paquet que nous venons de créer, x est un objet layer de niveau trois appartenant à la spécialisation IP et contenant tous les champs destinés à ce niveau. Chaque champs est accessible de la même façon que l'on accéderait à un champs de dictionnaire traditionnel :

>>> x["IP"].dst = "9.9.9.9"

Construisons maintenant les autres "layers" entourant celui-ci :

>>> y = x / TCP()    # OU
>>> y = IP() / TCP() 
>>> y.show()
###[ IP ]### 
  version= 4
  ihl= None
  tos= 0x0
  len= None
  id= 1
  flags= 
  frag= 0
  ttl= 64
  proto= tcp
  chksum= None
  src= 127.0.0.1
  dst= 127.0.0.1
  \options\
###[ TCP ]### 
     sport= ftp_data
     dport= http
     seq= 0
     ack= 0
     dataofs= None
     reserved= 0
     flags= S
     window= 8192
     chksum= None
     urgptr= 0
     options= []

Ici l'opérateur / est l'opérateur d'encapsulation, nous permettant de visualiser la structure du paquet et de la manipuler. L'inverse se fait au travers de la fonction del(), versatile, qui permet de supprimer un layer, et des champs.

#Suppression d'un layer
>>> del(y["TCP"])
>>> y.show()
###[ IP ]### 
  version= 4
  ihl= None
[...]
#Suppression du contenu (reset) d'un champs
>>> y = IP(src="10.1.1.1",dst="9.9.9.9")
>>> y.summary()
'10.1.1.1 > 9.9.9.9 hopopt'
>>> del(y.src)
>>> y.summary()
'192.X.X.X > 9.9.9.9 hopopt'        # Récupération de l'addresse IP par défaut, celle de la stack du systeme.

Génération de collections de paquets

La génération de paquets peut se faire par expansion comme suit :

Par expansion simple :

>>> a = IP(dst="192.168.1.1/30")
>>> a
>>> [ p for p in a]
[<IP  dst=192.168.1.0 |>,
 <IP  dst=192.168.1.1 |>,
 <IP  dst=192.168.1.2 |>,
 <IP  dst=192.168.1.3 |>]

[] pour les listes d'attributs. () pour les ranges d'attributs.

>>> 
>>> b=IP(ttl=[1,2,(5,9)])
>>> b
<IP ttl=[1, 2, (5, 9)] |>

Par expansion conjuguée sur plusieurs champs et plusieurs couches :

>>> a = IP(dst="192.168.1.1/30")
>>> c=TCP(dport=[80,443])
>>> [x for x in a/c]
[<IP  frag=0 proto=tcp dst=192.168.1.0 |<TCP  dport=https |>>,
 <IP  frag=0 proto=tcp dst=192.168.1.0 |<TCP  dport=http |>>,
 <IP  frag=0 proto=tcp dst=192.168.1.1 |<TCP  dport=https |>>,
 <IP  frag=0 proto=tcp dst=192.168.1.1 |<TCP  dport=http |>>,
 <IP  frag=0 proto=tcp dst=192.168.1.2 |<TCP  dport=https |>>,
 <IP  frag=0 proto=tcp dst=192.168.1.2 |<TCP  dport=http |>>,
 <IP  frag=0 proto=tcp dst=192.168.1.3 |<TCP  dport=https |>>,
 <IP  frag=0 proto=tcp dst=192.168.1.3 |<TCP  dport=http |>>]

Modification de paquets

Lors de la génération, Scapy remplis les champs par des valeurs par défaut cohérentes, incluant adresses IPs, ports, checksum, TTL etc.. Pour entrer des informations spécifiques dans ces champs, nous pouvons :

Afin de lister les Champs d'une couche et leur valeurs par défaut :

>>> ls(IP)
version    : BitField  (4 bits)                  = (4)
ihl        : BitField  (4 bits)                  = (None)
tos        : XByteField                          = (0)
len        : ShortField                          = (None)
id         : ShortField                          = (1)
flags      : FlagsField  (3 bits)                = (<Flag 0 ()>)
frag       : BitField  (13 bits)                 = (0)
ttl        : ByteField                           = (64)
proto      : ByteEnumField                       = (0)
chksum     : XShortField                         = (None)
src        : SourceIPField                       = (None)
dst        : DestIPField                         = (None)
options    : PacketListField                     = ([])

Utiliser le constructeur :

>>> a = IP(dst="192.168.1.1/30")
>>> a
<IP  dst=Net('192.168.1.1/30') |>

Modifier un champs par accès directe :

>>> a.ttl = 9
<IP  ttl=9 dst=Net('192.168.1.1/30') |>

Retourner aux défauts proposés :

>>> del(a.ttl)
<IP dst=Net('192.168.1.1/30') |>

Écoute du réseau

>>> x = sniff(filter="icmp and host 127.0.0.1", count=2, iface="lan0")
<Sniffed: TCP:0 UDP:0 ICMP:0 Other:2>
>>> x.show()
0001 Ether / ARP who has XXX.XXX.XXX says XXX.XXX.XXX / Padding
0002 Ether / ARP who has XXX.XXX.XXX says XXX.XXX.XXX / Padding

Il est possible de fournir une lambda fonction à exécuter sur chaque paquet :

Ici execute la méthode show() pour chaque paquet reçu.

>>> x = sniff(iface="lan0", prn=lambda x: x.show())

Afficher uniquement les adresses ip :

>>> x = sniff(iface="lan0", prn=lambda x: x.sprintf("%IP.src% --> %IP.dst%"))

Sauvegarde de communication écoutées

Enregistrement dans des fichiers PCAP
>>> wrpcap("filename.pcap", sniff(iface="lan0", prn=lambda x: x.sprintf("%IP.src% --> %IP.dst%")))
Lecture de fichier PCAP
>>> x = rdpcap("filename.pcap")
Analyse de contenu

Manuel :

>>> x = rdpcap("filename.pcap")
>>> for p in x : 
>>>	p.show()

En utilisant la fonction sniff :

>>> sniff(offline="test.pcap",prn=lambda x: x.show())

Émissions sur les réseau

Emission d'un paquet sur le réseau puis écoute de réponse sur un seul paquet :

>>> reponse = sr1(IP(dst="127.0.0.1")/ICMP()/"XXXXXXXXXXX")

Ecoute sur plusieurs paquets, classés answered et unanswered :

>>> ans,unans = sr(IP(dst="127.0.0.1")/ICMP()/"XXXXXXXXXXX")

A partir de fichiers PCAP

>>> ans,unans = sr(rdpcap("filename.pcap"))