scapy vs hping3 : spectrographe de distribution ISN
By castlebbs on Wednesday 21 March 2007, 17:58 - Tools - Permalink
scapy et hping3 sont des outils de manipulation de
paquets réseau. Ce sont des couteaux suisses de la fabrication, de l'envoi et
de la réception de paquets. M'intéressant particulièrement à Python, je me suis
naturellement intéressé à scapy. Il existe d'autres outils et bibliothèques
pour python (par exemple impaquet, pcapy). Le les présenterai dans d'autres billets. Hping3 est
un autre outil très puissant intégrant un interpréteur tcl, je me suis donc
intéressé à comparer ces deux produits. Ce billet présente mes débuts de
comparaison et n'est pas fait pour être exhaustif sur le sujet. Il est juste là
pour recueillir mes premières impressions
Pour commencer ma comparaison j'ai décidé d'écrire en Python/Scapy le programme isn-spectrogram écrit en tcl/hping3. La création de paquet, envoi et réception a été réécrite avec scapy, la fenêtre graphique réécrite avec Tkinter (isn-spectrogram utilisant Tk, le portage a été facile :-)). Ce programme analyse les numéros de séquence renvoyés par le destinataire lors de l'initialisation d'une connexion TCP. Ce programme dessine un spectrogramme qui représente la distribution des écarts de numéros de séquence renvoyés entre les différents paquets. Ces écarts correspondent à des incréments aléatoires et plus le spectre sera large, plus il sera difficile de déterminer les numéros de séquences.
Spectrogramme linux 2.4.27 :

Spectrogramme d'un routeur Zyxel (!) :

Nous allons nous concentrer sur les parties de code purement liées à hping3 et scapy. Construction des paquets, émission et réception via hping3 :
proc sendsyn {} {
global sport dport myip target
append syn "ip(saddr=$myip,daddr=$target,ttl=255)+"
append syn "tcp(sport=$sport,dport=$dport,flags=s)"
hping send $syn
incr sport
after 1 sendsyn
}
proc recvsynack {} {
global lastisn relative_attractor
set packets [hping recv eth0 0 0]
foreach p $packets {
if {![hping hasfield tcp flags $p]} continue
set isn [hping getfield tcp seq $p]
if {$relative_attractor} {
set tisn [expr abs($isn-$lastisn)]
set lastisn $isn
set isn $tisn
}
#puts "ISN: $isn"
displaypoint $isn
}
after 10 recvsynack
}
Deux fonctions sont utilisées, une pour envoyer des paquets, l'autre pour
recevoir les réponses, respectivement sendsyn et
recvsynack. Ces fonctions sont appelées par le gestionnaire
d'évènement de tcl : envoi d'un paquet toute les millisecondes, réceptions
des paquets de réponse toutes le 10 millisecondes. La fonction hping utilisée
pour envoyer est send, et recv pour recevoir. Ces
deux fonctions traitent des listes tcl au format APD.
Voici la méthode que j'avais choisi initialement avec scapy :
def sendSyns():
"Envoie des paquets SYN, reception de SYN-ACK"
port = 1
while True:
seq = sr1(IP(dst=hostname,ttl=255)/TCP(flags="S",sport=port,dport=dport),verbose=0).seq
diff = abs(oldseq - seq)
oldseq = seq
port += 1
displayPoint(diff)
L'avantage de scapy est clairement ici :
seq = sr1(IP(dst=hostname,ttl=255)/TCP(flags="S",sport=port,dport=dport),verbose=0).seq
En une seule ligne, je construis un paquet, je l'envoie, je récupère la
réponse et j'en extrait le numéro de séquence. Dans mon programme python,
j'aurai donc une unique fonction pour envoyer et recevoir les paquets :
sendSyns() Cette fonction est exécutée dans un thread (on utilise
pas cette notion de gestionnaire d'évènements tcl).
Cette fonction scapy sr1 qui permet d'envoyer et de recevoir un
paquet, c'est très pratique. Mais dans le cas de notre programme, c'est trop
lent. Il faut envoyer plusieurs milliers de paquets pour obtenir nos
spectrogrammes et c'est beaucoup trop lent d'itérer sur une fonction
sr1 de scapy qui enverra nouveau paquet seulement
après avoir reçu la réponse du paquet précédent.
J'ai donc utilisé une autre méthode que je vais vous décrire. Mais pour être honnête, dans tous les cas il semble que scapy soit beaucoup plus lent que hping3 pour ce qui concerne la création et l'envoi de paquets. La vitesse n'est surement pas le critère le plus important, mais la différence est suffisamment importante pour être notée :
Envoie de 10000 paquets sous hping3 :
hping3> for { set i 0 } { $i < 10000 } { incr i } { hping send "ip(saddr=192.168.1.2,daddr=192.168.1.1,ttl=255)+tcp(sport=$i dport=222,flags=s)" }
Ceci envoie 10000 paquets TCP avec le flag SYN et la valeur du port source qui incrémente à chaque paquet. Cette opération prend moins de deux secondes sur mon PC.
L'équivalent sous scapy va mettre plus de 3 minutes à envoyer les 10000 paquets :
>>> for i in range(10000): send(IP(src="192.168.1.2",dst="192.168.1.1",ttl=255)/TCP(sport=ri,dport=222,flags="S"),verbose=0,inter=0) ou >>> send(IP(src="192.168.1.2",dst="192.168.1.1",ttl=255)/TCP(sport=range(10000),dport=222,flags="S"),verbose=0,inter=0)
Bref, on l'a compris, ça sera plus lent de générer les spectrogrammes avec
scapy, mais avec la fonction sc1, c'est carrément inutilisable,
alors voici la solution que j'ai adoptée :
def sendSyns():
"Envoie des paquets SYN, reception de SYN-ACK"
sport = 1
while running==1:
r = sr(IP(dst=hostname,ttl=255)/ TCP(flags="S", sport=range(sport,sport+nbsyn), dport=dport),
verbose=0,timeout=0)
sport += nbsyn
for snd,rcv in r0:
diff = abs(oldseq - rcv.seq)
oldseq = rcv.seq
displayPoint(diff)
A la place de sr1, j'utilise la fonction sr pour
envoyer plusieurs paquets d'un coup, et recevoir l'ensemble des réponses
ensuite. le sport=range(sport,sport+nbsyn) va fournir une liste de
port à l'argument sport (port source) de l'objet TCP. Ceci à pour
conséquence la création de plusieurs paquets. nbsyn correspond au
nombre de paquets à envoyer d'une seule fois.
On gagne en vitesse par rapport à sr1 dans la mesure ou on
envoi par paquets de nbsyn paquets sans attendre la réponse. J'ai
testé avec différentes valeurs (on peut la spécifier par la ligne de commande)
mais la création du graphe reste quand même bien plus lente qu'avec le
programme sous hping3.
Nombre de lignes de code : vainqueur scapy Vitesse : vainqueur hping3
Le programme complet :
#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-
# David ROBERT david@ombrepixel.com
# isn-scaptogram v0.1
# Fortement inspiré de isn-spectrogram.htcl (projet hping3)
# Pour comparer les fonctionnalité de scripts de hping3 et python/scapy
from scapy import sr,IP,TCP
from Tkinter import *
import threading, sys, time
try:
hostname = sys.argv[1]
dport = int(sys.argv[2])
div = int(sys.argv[3])
nbsyn = int(sys.argv[4])
except:
print "Utilisation: spectoscapy.py <host> <open-tcp-port> <scale> <blocks>"
print "Exemple: spectoscapy.py www.example.com 80 100000 10"
print " scale : echelle du spectrogramme"
print " blocks : nombre de paquets SYN dans un bloc scapy"
sys.exit(1)
def quit():
# Pour arreter mon thread
global running
running=0
# Definition des paramètres TK
running=1
pastcol = {}
root = Tk()
root.title("isn-scaptogram")
root.config(background="#000000")
frame = Frame(root)
frame.config(background="#000000")
frame.pack(side=TOP)
button = Button(frame, text="QUIT", fg="red", command=quit)
button.pack(side=LEFT)
canvas = Canvas(root)
canvas.config(width=800,height=300)
canvas.config(background="#000000")
canvas.pack(fill=BOTH,expand=TRUE)
canvas.create_rectangle(40,250,139,250,fill="#FFFFFF",width=0)
canvas.create_text(90,270,fill="#FFFFFF",text=div*100)
canvas.create_text(10,10,fill="#FFFFFF",text= \
"Host : %s, Port : %s Nombre de SYN par blocs: %d" \
% (hostname, dport, nbsyn),anchor=W)
canvas.create_line(1,20,800,20,fill="#FFFFFF")
def sendSyns():
"Envoie des paquets SYN, reception de SYN-ACK"
oldseq=0
sport = 1
while running==1:
r = sr(IP(dst=hostname,ttl=255)/ TCP(flags="S", sport=range(sport,sport+nbsyn), dport=dport),
verbose=0,timeout=0)
sport += nbsyn
for snd,rcv in r[0]:
diff = abs(oldseq - rcv.seq)
oldseq = rcv.seq
displayPoint(diff)
frame.quit()
def displayPoint(isn):
"Affiche le ligne sur le spectrogramme"
isn = isn/div
y = 50
x = isn
if not pastcol.has_key((x,y)):
pastcol[(x,y)]=0
graylevel = 0
else:
pastcol[(x,y)]+=10
graylevel = pastcol[(x,y)]
if graylevel >= 256*3: graylevel = 256*3-1
if graylevel <= 255:
b = graylevel
g = r = 0
elif graylevel <= 511:
b = 0
g = graylevel - 256
r = 255
elif graylevel <= 767:
b = g = 255
r = graylevel - 512
canvas.create_rectangle(x,y,x+1,y+170,fill="#%02X%02X%02X" % (r,g,b) ,width=0)
t = threading.Thread(target = sendSyns, args = ())
t.start()
root.mainloop()
