Première rédaction de cet article le 17 décembre 2008
Il y a des tas de raisons de vouloir lire soi-même les paquets capturés sur le réseau. Cela peut être par curiosité, par souci de sécurité ou bien pour faire des statistiques. Pour un rapide coup d'œil, on utilise en général tcpdump, pour un examen interactif détaillé, l'excellent Wireshark mais, pour des études à long terme, il vaut mieux programmer et développer un programme d'analyse spécifique.
Souvent les articles sur la question parlent ensemble de la
capture des paquets et de leur
analyse. Ici, je ne mentionne que l'analyse,
supposant que la capture a été faite par d'autres moyens, par exemple
tcpdump -w FICHIER
ou bien pcapdump.
L'outil d'analyse le plus fréquent pour les programmeurs C est libpcap (dont je parle dans « .Lire des paquets capturés sur le réseau en C »). Et en Python ? Il existe plusieurs interfaces à la libpcap, notamment pylibpcap et pcapy. pcapy semble de plus haut niveau et c'est celle que j'utilise.
pcapy peut faire la capture et la lecture des fichiers au format pcap. Ses capacités d'analyse sont limitées, on utilise en général la bibliothèque impacket, des mêmes auteurs, ou bien carrément scapy.
Voici un programme simple de lecture d'un fichier pcap, tel que produit par tcpdump :
import pcapy reader = pcapy.open_offline("mydata.pcap") while True: try: (header, payload) = reader.next() print "Got a packet of length %d" % header.getlen() except pcapy.PcapError: break
Il ne semble pas y avoir de moyen de détecter la fin du fichier à part
en récupérant l'erreur très générique
PcapError
.
payload
est juste une chaîne non décodée d'octets, représentés en caractères. Si je veux l'analyser,
je peux le faire « à la main » avec les fonctions de manipulation
binaire de Python. Cela nécessite évidemment de lire les différentes
normes pour connaître le format des paquets :
import pcapy reader = pcapy.open_offline("mydata.pcap") while True: try: (header, payload) = reader.next() # Ethernet type is two-bytes long and starts at 12 (before, you # have the MAC addresses) if payload[12:14] == '\x86\xDD': # 0x86DD is the Ethernet type for IPv6 ip_payload = payload[14:] ip_version = (ord(ip_payload[0]) & 0xf0) >> 4 if (ip_version != 6): raise Exception("Packet has ethernet type of IPv6 but internal IP version is %d" % ip_version) next_header = ord(ip_payload[6]) # RFC 2460, section 3 to know the offset print "Next header (probably the transport protocol) is %d" % next_header except pcapy.PcapError: break
Ici, on teste le type indiqué dans l'en-tête
Ethernet pour ne garder que
0x86DD
(IPv6). On saute
les quatorze premiers octets (la taille de l'en-tête Ethernet) pour
récupérer le paquet IPv6 que l'on décode en suivant le RFC 2460. Le numéro de version fait moins d'un octet, d'où les
manipulations de bits avec
&
et
>>
.
Mais il peut être plus simple d'utiliser la bibliothèque impacket, conçue pour faciliter le décodage :
import pcapy import impacket.ImpactDecoder as Decoders reader = pcapy.open_offline("mydata.pcap") decoder = Decoders.EthDecoder() while True: try: (header, payload) = reader.next() result = decoder.decode(payload) # https://www.iana.org/assignments/ethernet-numbers if result.get_ether_type() == 0x86DD: # IPv6 print "Got an IPv6 packet of length %d" % header.getlen() except pcapy.PcapError: break
Ici, plus besoin de connaître la structure de l'en-tête Ethernet, on
peut utiliser get_ether_type()
au lieu de
payload[12:14]
.
Cela va permettre de s'attaquer à des protocoles de plus haut niveau, souvent plus complexes à décoder. Commençons par UDP :
import pcapy import impacket.ImpactDecoder as Decoders import impacket.ImpactPacket as Packets reader = pcapy.open_offline("mydata.pcap") eth_decoder = Decoders.EthDecoder() ip_decoder = Decoders.IPDecoder() udp_decoder = Decoders.UDPDecoder() while True: try: (header, payload) = reader.next() ethernet = eth_decoder.decode(payload) if ethernet.get_ether_type() == Packets.IP.ethertype: ip = ip_decoder.decode(payload[ethernet.get_header_size():]) if ip.get_ip_p() == Packets.UDP.protocol: udp = udp_decoder.decode( payload[ethernet.get_header_size()+ip.get_header_size():]) print "IPv4 UDP packet %s:%d->%s:%d" % (ip.get_ip_src(), udp.get_uh_sport(), ip.get_ip_dst(), udp.get_uh_dport()) except pcapy.PcapError: break
Ce programme affiche les paquets UDP sur IPv4 en indiquant les
adresses et ports de source et de destination. On note qu'il n'y a pas
eu besoin de lire le RFC 768, tout est pris en charge par
Impacket (y compris le problème, que j'ai soigneusement évité dans
l'exemple du décodage manuel, de l'ordre
des octets dans les champs multi-octets). Il faut juste
penser, à chaque fois qu'on grimpe d'une couche, à se décaler dans le paquet (du résultat de
get_header_size()
sur la couche précédente).
Pour le décodage de l'en-tête IP, Impacket ne dispose malheureusement que d'un décodeur IPv4. Pour IPv6, il faut l'écrire soi-même, peut-être plutôt en utilisant Scapy. Ce sera pour un autre article.
Version PDF de cette page (mais vous pouvez aussi imprimer depuis votre navigateur, il y a une feuille de style prévue pour cela)
Source XML de cette page (cette page est distribuée sous les termes de la licence GFDL)