[Trames et paquets de données avec Scapy – Partie 7] Orientation et représentation objet

Par Xavier Poli, secuobs.com
Le 01/10/2007


Résumé : Scapy laisse une grande fléxibilité dans la manipulation des différentes commandes disponibles, il est ainsi possible de gérer chaque champ d'un paquet ou d'une fonction à la manière d'un objet que l'on pourra définir dans une variable puis que l'on aura la possibilité de représenter et de visualiser de plusieurs façons.



Afficher les spécificités du protocole DNS :

>>> ls(DNS)
id : ShortField = (0)
qr : BitField = (0)
opcode : BitEnumField = (0)
aa : BitField = (0)
tc : BitField = (0)
rd : BitField = (0)
ra : BitField = (0)
z : BitField = (0)
rcode : BitEnumField = (0)
qdcount : DNSRRCountField = (None)
ancount : DNSRRCountField = (None)
nscount : DNSRRCountField = (None)
arcount : DNSRRCountField = (None)
qd : DNSQRField = (None)
an : DNSRRField = (None)
ns : DNSRRField = (None)
ar : DNSRRField = (None)



On récupére une liste de serveurs DNS en affichant le contenu du fichier /etc/resolv.conf :

root@casper:~# cat /etc/resolv.conf
nameserver 62.4.17.69
nameserver 62.4.16.70



On effectue une requête DNS en UDP à l'aide de la fonction d'envoi sr1 sur le serveur dns 62.4.16.70 pour le FQDN exoscan.net :

an= ns=> ar= |>>>


On vérifie la valeur de _ en lui ajoutant l'attribut summary() :

>>> _.summary()
'IP / UDP / DNS Ans "213.186.41.29" '



On affiche plus en détail le résultat avec l'attribut display() à la place de summary() :

>>> _.display()
###[ IP ]###
version= 4L
ihl= 5L
tos= 0x0
len= 135
id= 13021
flags=
frag= 0L
ttl= 62
proto= udp
chksum= 0x3a95
src= 62.4.16.70
dst= 192.168.0.2
options= ''
###[ UDP ]###
sport= domain
dport= domain
len= 115
chksum= 0x7504
###[ DNS ]###
id= 0
qr= 1L
opcode= QUERY
aa= 0L
tc= 0L
rd= 1L
ra= 1L
z= 0L
rcode= ok
qdcount= 1
ancount= 1
nscount= 2
arcount= 1
\qd\
|###[ DNS Question Record ]###
| qname= 'exoscan.net.'
| qtype= A
| qclass= IN
\an\
|###[ DNS Resource Record ]###
| rrname= 'exoscan.net.'
| type= A
| rclass= IN
| ttl= 3469
| rdlen= 0
| rdata= '213.186.41.29'
\ns\
|###[ DNS Resource Record ]###
| rrname= 'exoscan.net.'
| type= NS
| rclass= IN
| ttl= 28243
| rdlen= 0
| rdata= 'ns7.gandi.net.'
|###[ DNS Resource Record ]###
| rrname= 'exoscan.net.'
| type= NS
| rclass= IN
| ttl= 28243
| rdlen= 0
| rdata= 'custom2.gandi.net.'
\ar\
|###[ DNS Resource Record ]###
| rrname= 'ns7.gandi.net.'
| type= A
| rclass= IN
| ttl= 643
| rdlen= 0
| rdata= '217.70.177.44'



On lance maintenant une capture sur l'ensemble du trafic, toujours à l'aide de la commande snif(), en ne demandant à capturer qu'un seul enregistrement (toujours avec l'option count) que l'on placera comme à l'habitude dans la variable sn :

>>> sn=sniff(count=1)


On affiche cet enregistrement à l'aide de l'attribut display() affecté à la variable sn :

>>> sn.display()
0000 Ether / IP / TCP 127.0.0.1:50018 > 127.0.0.1:microsoft_ds S



Comme on l'a déjà remarqué précédemment Scapy suit une orientation objet en proposant une importante fléxibilité dans les opérations de manipulation des différentes fonctions qui y sont présentes mais aussi dans les paramètres et les attributs qui se référent à ces fonctions ; on affiche maintenant à nouveau les spécificités du protocole IP à l'aide de la commande ls() :

>>> ls(IP)
version : BitField = (4)
ihl : BitField = (None)
tos : XByteField = (0)
len : ShortField = (None)
id : ShortField = (1)
flags : FlagsField = (0)
frag : BitField = (0)
ttl : ByteField = (64)
proto : ByteEnumField = (0)
chksum : XShortField = (None)
src : Emph = (None)
dst : Emph = ('127.0.0.1')
options : IPoptionsField = ('')



Chacun des champs d'un paquet ou d'une trame peut être lui aussi considéré comme un objet et sa valeur peut alors être définie par une variable tout comme il est possible de la même manière de le faire pour des parties ou sous-parties des fonctions et cela avec un effet rebond direct sur les valeurs (de ces champs) qui étaient définies par défaut :

>>> IP()

>>> a=IP(dst="192.168.0.2")
>>> a

>>> ls(a)
version : BitField = 4 (4)
ihl : BitField = None (None)
tos : XByteField = 0 (0)
len : ShortField = None (None)
id : ShortField = 1 (1)
flags : FlagsField = 0 (0)
frag : BitField = 0 (0)
ttl : ByteField = 64 (64)
proto : ByteEnumField = 0 (0)
chksum : XShortField = None (None)
src : Emph = '192.168.0.2' (None)
dst : Emph = '192.168.0.2' ('127.0.0.1')
options : IPoptionsField = '' ('')
>>> a.dst
'192.168.0.2'
>>> a.ttl
64
>>> a.ttl=10
>>> a

>>> a

>>> del a.ttl
>>> a

>>> a.ttl
64
>>> del a.dst
>>> a.dst
'127.0.0.1'



Comme on vient de le constater en changeant les valeurs des variables a.dst et a.ttl les modifications sont directement impactées sur la variable a et les valeurs des paramètres ttl et dst y sont remplacés par les changements effectuées ou elles sont tout simplement supprimées au même titre que le paramètre lorsque celui-ci a été préalablement supprimé.

Les différents paramètres de la fonction reviennent cependant à des valeurs par défaut lors de leur suppression comme on peut le voir avec la valeur de a.dst qui est égale au final à 127.0.0.1 ou celle de a.ttl qui est elle équivalente à 64.

On peut aussi dans Scapy générer une représentation d'un objet de type paquet et/ou trame avec la fonction str dont les spécificités sont les suivantes :

>>> lsc(str)
str(object) -> string

Return a nice string representation of the object.
If the argument is a string, the return value is the same object.



On génére la représentation de la fonction IP() avec l'ensemble des valeurs par défaut :

>>> IP()


>>> str(IP())
'E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00
\x01\x7f\x00\x00\x01'



On peut également retrouver le paquet initial grâce à la fonction IP() de la façon suivante :

>>> IP(_)



On génére un paquet IP dont la destination est www.exoscan.net que l'on affecte à la variable sn :

>>> sn=IP(dst="www.exoscan.net")
>>> sn



On applique la fonction str sur cette variable dont le résultat est la représentation suivante :

>>> str(sn)
'E\x00\x00\x14\x00\x01\x00\x00@\x00\xbbg\xc0\xa8\x00
\x02\xd5\xba)\x1d'



Le même paquet pour le protocole TCP spécifiquement :

>>> IP(dst="www.exoscan.net")/TCP()
>

>>> str(IP(dst="www.exoscan.net")/TCP())
'E\x00\x00(\x00\x01\x00\x00@\x06\xbbM\xc0\xa8\x00\x02
\xd5\xba)\x1d\x00\x14\x00P\x00\x00\x00\x00\x00\x00\
x00\x00P\x02 \x00\xcf\xfc\x00\x00'



On génére maintenant une trame ethernet (niveau 2 couche de liaison de données dans le modèle OSI pour la suite de protocoles TCP/IP) correspondant à ce paquet qui couplait les fonctions IP() et TCP() et maintenant la fonction Ether() :

>>> Ether()/IP(dst="www.exoscan.net")/TCP()
>>

>>> str(Ether()/IP(dst="www.exoscan.net")/TCP())
'\x00\x13\xf7x\xcf\xea\x00\x0f\xb5\x0e\x89J\x08\x00E
\x00\x00(\x00\x01\x00\x00@\x06\xbbM\xc0\xa8\x00\x02
\xd5\xba)\x1d\x00\x14\x00P\x00\x00\x00\x00\x00\x00
\x00\x00P\x02 \x00\xcf\xfc\x00\x00'



On génére la même trame à l'exception que l'on spécifie les ports de destinations sur les ports 80 et 443 :

>>> Ether()/IP(dst="www.exoscan.net")/TCP(dport=[80,443])
>>

>>> str(Ether()/IP(dst="www.exoscan.net")/TCP(dport=[80,443]))
'\x00\x13\xf7x\xcf\xea\x00\x0f\xb5\x0e\x89J\x08\x00E
\x00\x00(\x00\x01\x00\x00@\x06\xbbM\xc0\xa8\x00\x02
\xd5\xba)\x1d\x00\x14\x00P\x00\x00\x00\x00\x00\x00
\x00\x00P\x02 \x00\xcf\xfc\x00\x00'



On peut retrouver la trame initiale à partir de cette représentation grâce à la fonction Ether() :

>>> Ether(_)
>>



On génére maintenant la même trame mais avec une requête de téléchargement de la page d'index du site "www.exoscan.net" :

>>> Ether()/IP(dst="www.exoscan.net")/TCP(dport=[80,443])/"GET / HTTP/1.0 \n\n"
>>>

>>> str(Ether()/IP(dst="www.exoscan.net")/TCP(dport=[80,443])/"GET / HTTP/1.0 \n\n")
'\x00\x0f\xb5\x0e\x89J\x00\x15mS\x1e\x87\x08\x00E\x00
\x009\x00\x01\x00\x00@\x06\xbb<\xc0\xa8\x00\x02\xd5
\xba)\x1d\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P
\x02 \x00\xe1U\x00\x00GET / HTTP/1.0 \n\n'

>>> Ether(_)
>>>



A noter que tous les champs ayant une valeur par défaut sont visibles, il est possible des les supprimer avec l'attribut hide_default() appliqué au résultat courant _ (ou à une variable) :

>>> _.hide_defaults()


On vérifie la valeur de _ :

>>> _
>><>



Il est également possible avec scapy d'opérer un hexdump afin d'obtenir une version héxadécimale des enregistrements que l'on reçoit (ou de ceux que l'envoie) :

>>> hexdump(sn)
0000 00 0F B5 0E 89 4A 00 15 6D 53 1E 87 08 00 45 00 .....J..mS....E.
0010 00 39 00 01 00 00 40 06 BB 3C C0 A8 00 02 D5 BA .9....@..<......
0020 29 1D 00 14 00 50 00 00 00 00 00 00 00 00 50 02 )....P........P.
0030 20 00 E1 55 00 00 47 45 54 20 2F 20 48 54 54 50 ..U..GET / HTTP
0040 2F 31 2E 30 20 0A 0A /1.0 ..

>>> hexedit(_)
'\x00\x0f\xb5\x0e\x89J\x00\x15mS\x1e\x87\x08\x00E\x00
\x009\x00\x01\x00\x00@\x06\xbb<\xc0\xa8\x00\x02\xd5
\xba)\x1d\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P
\x02 \x00\xe1U\x00\x00GET / HTTP/1.0 \n\n'



Autres ressources dans ce dossier :

[Trames et paquets de données avec Scapy – Partie 1] Présentation – lien

[Trames et paquets de données avec Scapy – Partie 2] Installation et configuration – lien

[Trames et paquets de données avec Scapy – Partie 3] Utilisation basique – lien

[Trames et paquets de données avec Scapy – Partie 4] Captures de données – lien

[Trames et paquets de données avec Scapy – Partie 5] Traceroute et visualisation 2D/3D – lien

[Trames et paquets de données avec Scapy – Partie 6] Manipulations de paquets et de trames – lien

[Trames et paquets de données avec Scapy – Partie 8] Compléments et webographie – lien