Scapy : Trames et paquets de données

From Deimos.fr / Bloc Notes Informatique
Jump to: navigation, search

1 Introduction

Scapy est un utilitaire permettant de forger, de recevoir et d'envoyer des paquets ou des trames de données sur un réseau pour une multitude de protocoles. On retrouve pour cette introduction la présentation de cet utilitaire en Python qui permet entre autres de réaliser de la capture de trafic ainsi que du mapping réseau, de l'arp cache poisoning, du VLAN Hopping ou de la reconnaissance passive de système d'exploitation.

L'utilitaire Scapy est un programme développé en Python, par Philippe Biondi (EADS CCR) ; il permet notamment de forger, de recevoir et d'emettre via un réseau des paquets et/ou des trames de données vers ou depuis une infrastructure informatique et cela pour une multitude de protocoles réseaux différents (IP, TCP, UDP, ARP, SNMP, ICMP, DNS, DHCP, ...) avec précision et rapidité.

Scapy se présente donc sous la forme d'un fichier unique de type script Python soit 13 342 lignes de code pour la version 1.1.1 que l'on va utiliser par la suite dans ce document ; parmi les autres fonctionnalités marquantes de Scapy on notera ses capacités à la dissection de paquets et/ou de trames de données ainsi que le décodage de certains protocoles.

En outre on peut aussi effectuer grâce à Scapy de la surveillance et de la capture de trafic réseau au même titre que de la lecture de captures au format pcap provenant d'un autre analyseur de trafic comme Wireshark par exemple.

Il est par ailleurs possible avec Scapy d'effectuer de la génération de graphiques en 2D et/ou en 3D à partir de paquets et/ou de trames de données ou bien encore du scan de ports à la NMAP like ( et de la reconnaissance passive de systèmes d'exploitation à distance à la PoF like.

Selon son auteur, Scapy est capable à lui tout seul de remplacer l'ensemble des utilitaires suivants : - hping, 85 % de NMAP, arpspoof, arp-sk, arping, tcpdump, tethereal, p0f et encore bien d'autres commandes système (traceroute, ping, route, ...).

Pour l'équivalent d'une soixantaine de lignes de code en langage C, le couple composé de Python et Scapy ne nécessite que quelques lignes la plupart du temps afin de réaliser ces différentes opérations de manipulation de paquets et/ou de trames de données d'où un gain de temps considérable pour toutes personnes devant effectuer des manipulations de ce type sur un réseau.

Pour cela Scapy dispose de nombreuses fonctions pré-définies permettant de configurer l'injection d'un paquet (ou d'une trame) dans une connexion réseau donnée ; certaines fonctions spéciales de scapy permettent ainsi de réaliser avec une grande simplicité des attaques usuelles (liste non exhaustive) : - mapping d'infrasctures réseaux, ARP Cache Poisoning, Smurfing, VLAN Hopping ainsi que du spoofing IP et de la mise en place de rogue server DHCP.

Ces attaques peuvent être combinées les unes aux autres (ARP Cache Poisoning + VLAN Hopping par exemple) afin de réaliser des audits de sécurité spécifiquement adaptés à l'infrastructure en place dont on souhaite vérifier le niveau de sécurité.

On peut tout aussi bien si on le souhaite intercepter des communications de type VOIP (décodage des paquets/trames) et cela même sur une connexion sans-fil de type WIFI chiffrée à l'aide du protocole WEP et cela à partir du moment où on connait la clé de déchiffrement qui est associée à ces connexions ( sachant bien sur que le WEP est still secure).

Cette clé de chiffrement, on peut la configurer dans Scapy, toujours à partir du moment où on est en sa possession, afin que Scapy puisse s'en servir lors des opérations d'injection de paquets ou de trames de données dans le trafic d'un réseau sans-fil chiffré par le protocole WEP (cf. également l'utilitaire WIFITAP développé par Cédric Blancher [EADS CCR] pour l'injection de trafic dans des connexions WIFI).

2 Installation et configuration

Cette partie concerne l'installation de Scapy ainsi que de l'ensemble des éléments nécessaires à son bon fonctionnement sur un système de type GNU/LINUX. On peut y retrouver également le système de configuration interne à Scapy comme première approche de l'utilitaire ainsi que les différentes configurations réseaux nécessaires à son utilisation pour la suite de ce dossier.

On réalise maintenant l'installation préalable nécessaire à l'utilisation de Scapy sur un système d'exploitation Linux de type Debian/Ubuntu like :

root@casper:~# uname -a
Linux casper 2.6.20-15-generic #2 SMP Sun Apr 15 07:36:31 UTC 2007 i686 GNU/Linux
 
root@casper:~ # apt-get install python python-gnuplot python-pyx python-crypto graphviz imagemagick python-visual

On teste dans un premier temps le fait que l'interpréteur Python soit bien fonctionnel :

root@casper:~# python
Python 2.5.1 (r251:54863, May 2 2007, 16:56:35)
[GCC 4.1.2 (Ubuntu 4.1.2-0ubuntu4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>

Tapez "ctrl D" pour sortir de Python.


On accéde au répertoire /etc sur notre système :

root@casper:~ # cd /etc/

On récupérer ensuite le fichier ethertypes :

root@casper:/etc # wget www.secdev.org/projects/scapy/files/ethertypes
02:41:59 (936.05 KB/s) - `ethertypes' saved [1,317/1,317]

On accéde alors au répertoire personnel de l'utilisateur en cours de session (ici l'utilisateur root avec /root en répertoire personnel ~) :

root@casper:/etc # cd ~

On configure maintenant les interfaces réseau présentes sur le système d'exploitation de la machine que l'on utilise pour l'ensemble des tests de ce document ; ces informations permettant de mieux comprendre les différents tests que l'on va effectuer :

root@casper:~# lspci | grep 802.11
08:00.0 Ethernet controller: Atheros Communications, Inc. AR5212 802.11abg NIC (rev 01)
root@casper:~# wlanconfig ath0 destroy
root@casper:~# wlanconfig ath0 create wlandev wifi0 wlanmode adhoc
root@casper:~# iwconfig ath0 essid nat
root@casper:~# ifconfig ath0 192.168.0.2
root@casper:~# route add default gw 192.168.0.1
root@casper:~# ifconfig
ath0 Link encap:Ethernet HWaddr 00:15:6D:53:1E:87
inet addr:192.168.0.2 Bcast:192.168.0.255 Mask:255.255.255.0
inet6 addr: fe80::215:6dff:fe53:1e87/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:3867 errors:0 dropped:0 overruns:0 frame:0
TX packets:3719 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:3782362 (3.6 MiB) TX bytes:520446 (508.2 KiB)
 
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:50 errors:0 dropped:0 overruns:0 frame:0
TX packets:50 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:4307 (4.2 KiB) TX bytes:4307 (4.2 KiB)
 
wifi0 Link encap:UNSPEC HWaddr 00-15-6D-53-1E-87-00-00-00-00-00-00-00-00-00-00
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:179191 errors:0 dropped:0 overruns:0 frame:7162
TX packets:4355 errors:147 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:199
RX bytes:8345718 (7.9 MiB) TX bytes:668330 (652.6 KiB)
Interrupt:18
 
 
root@casper:~# iwconfig
lo no wireless extensions.
eth0 no wireless extensions.
wifi0 no wireless extensions.
 
ath0 IEEE 802.11g ESSID:"nat" Nickname:""
Mode:Ad-Hoc Frequency:2.462 GHz Cell: 02:15:6D:53:1E:87
Bit Rate:0 kb/s Tx-Power:16 dBm Sensitivity=1/1
Retry:off RTS thr:off Fragment thr:off
Encryption key:off
Power Management:off
Link Quality=26/70 Signal level=-70 dBm Noise level=-96 dBm
Rx invalid nwid:1181 Rx invalid crypt:0 Rx invalid frag:0
Tx excessive retries:0 Invalid misc:0 Missed beacon:0

La partie ci-dessus étant à adapter à chaque configuration de machine en fonction des interfaces réseaux qui y sont présentes ; pour la suite on teste que la connectivité réseau est bien fonctionnelle :

root@casper:~# ping -c 1 192.168.0.1
PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.
64 bytes from 192.168.0.1: icmp_seq=1 ttl=128 time=1.40 ms
 
--- 192.168.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.409/1.409/1.409/0.000 ms

C'est le cas, notre machine dont l'adresse IP est 192.168.0.2 est bien capable d'effectuer un ping vers la machine dont l'adresse IP est 192.168.0.1 ; on peut alors passer à l'installation de Scapy en le récupérant dans un premier temps :

root@casper:~ # wget www.secuobs.com/scapy.py
01:44:27 (61.29 KB/s) - `scapy.py' saved [364,749/364,749]

Il suffit alors de lancer Scapy à l'aide de l'interpréteur Python de la façon suivante :

root@casper:/trash# python scapy.py
Welcome to Scapy (v1.1.1 / -)
>>>

Si l'on souhaite récupérer des informations sur la configuration de la version de Scapy que l'on utilise, il suffit de taper conf puis de taper sur Entrée afin de valider et exécuter la commande :

>>> conf
Version = v1.1.1 / -
ASN1_default_codec = <ASN1Codec BER[1]>
AS_resolver = <__main__.AS_resolver_multi instance at 0x87f612c>
BTsocket = <class __main__.BluetoothL2CAPSocket at 0x8721bfc>
IPCountry_base = 'GeoIPCountry4Scapy.gz'
L2listen = <class __main__.L2ListenSocket at 0x8721aac>
L2socket = <class __main__.L2Socket at 0x8721a4c>
L3socket = <class __main__.L3PacketSocket at 0x8721a1c>
auto_fragment = 1
checkIPID = 0
checkIPaddr = 1
checkIPsrc = 1
check_TCPerror_seqack = 0
color_theme = <DefaultTheme>
countryLoc_base = 'countryLoc.csv'
debug_dissector = 0
debug_match = 0
ethertypes = </etc/ethertypes/ >
except_filter = ''
gnuplot_world = 'world.dat'
histfile = '/root/.scapy_history'
iface = 'ath0'
manufdb = </usr/share/wireshark/wireshark/manuf/ 00:06:A2 00:20:A7 00:06:A5 00:20:A6 00:05:46 00:20:A9 00:05:45 00:10:57 00:05:44 00:12:5B 00:10:55 00:0C:49 00:0C:48 00:0C:45 00:0C:44 00:0C:47 00:0C:46 00:0C:41 00:0C:40 00:0C:43 00:0C:42 00:40:93 00:40:92 00:40:91 00:40:90 00:40:97 00:10:50 00:40:95 00:40:94 00:40:99 00:40:98 47:54:43 00:C0:89 00:C0:88 00:40:9C:F5 ...................... 00:10:F4 00:11:D2 00:11:D3 00:11:D0 00:11:D1 00:11:D6 00:11:D7 00:11:D4 00:11:D5 00:11:D8 00:11:D9 AB-00-04-01-00-00/32 00:06:A9 00:05:49 00:11:DB 00:11:DC 00:11:DA 00:11:DF 00:11:DD 00:11:DE 00:0A:4F 00:0A:4E 00:0A:4D 00:0A:4C 00:0A:4B 00:0A:4A 00:06:A0 00:10:FC 00:10:FB 00:10:FA 00:10:FF 00:10:FE 00:10:FD>
mib = <MIB/ >
nmap_base = '/usr/share/nmap/nmap-os-fingerprints'
noenum = <Resolve []>
p0f_base = '/etc/p0f/p0f.fp'
padding = 1
prog = Version = v1.1.1 / -
display = 'display'
dot = 'dot'
hexedit = 'hexer'
pdfreader = 'acroread'
psreader = 'gv'
tcpdump = 'tcpdump'
tcpreplay = 'tcpreplay'
wireshark = 'wireshark'
promisc = 1
prompt = '>>> '
protocols = </etc/protocols/ pim ip ax_25 esp tcp ah ipv6_opts xtp ipv6_route igmp igp ddp etherip xns_idp ipv6_frag vrrp gre ipcomp encap ipv6 iso_tp4 sctp ipencap rsvp udp ggp hmp idpr_cmtp fc skip st icmp pup isis rdp l2tp ipv6_icmp egp ipip ipv6_nonxt eigrp idrp rspf ospf vmtp>
queso_base = '/etc/queso.conf'
resolve = <Resolve []>
route = Network Netmask Gateway Iface Output IP
127.0.0.0 255.0.0.0 0.0.0.0 lo 127.0.0.1
192.168.0.0 255.255.255.0 0.0.0.0 ath0 192.168.0.2
0.0.0.0 0.0.0.0 192.168.0.1 ath0 192.168.0.2
 
services_tcp = </etc/services-tcp/ kpop noclog svn cmip_man z3950 rootd ndtp gds_db ftps bacula_fd bgpsim isisd mysql bpdbm xdmcp rtcm_sc104 knetd systat mmcc enbd_cstatd daap radmin_port hylafax distmp3 hostmon snmp_trap isakmp dict ldap enbd_sstatd kshell tempo imaps pawserv afs3_errors x11_6 fax smux spamd krb_prop tproxy auth zebrasrv pop3 pop2 silc x11_5 ssh krbupdate afmbackup saft afbackup zope nntps loc_srv qotd msnp remotefs submission afs3_rmtsys prospero afs3_bos sysrqd webmin munin datametrics supfiledbg rfe kazaa ospf6d cvspserver venus imap2 imap3 afs3_fileserver webster gnutella_svc domain ripngd smsqp l2f afs3_vlserver ospfd ircs proofd rplay wipld omirr radius zebra kermit tfido sunrpc xtel pcrd zserv ftp rsync afs3_update imsp at_zis rmtcfg icpv2 daytime netnews afs3_volser kerberos4 finger x11 nfs irc nntp rpc2portmap kerberos_master rtelnet asp ulistserv moira_db ntp xmpp_server snpp netstat npmp_local ssmtp lotusnote sgi_cad csnet_ns fatserv ggz mrtd postgresql suucp rptp amanda distcc npmp_gui bgp afs3_kaserver vboxd csync2 discard re_mail_ck support tcpmux sip bpcd xinetd rtsp eklogin gopher socks echo ipp radius_acct webcache ipx ldaps rmiregistry conference bprd mdns sieve afpovertcp font_service swat netbios_dgm ripd snmp msp at_nbp frox amidxtape tinc iax https netbios_ns xpilot sane_port ninstall bacula_dir pop3s cfinger nessus remoteping moira_update shell clc_build_daemon xmpp_client courier skkserv codaauth2 openvpn kerberos afs3_prserver dircproxy telnet rje link netbios_ssn gnutella_rtr qmtp omniorb acr_nema x11_7 x11_4 pwdgen x11_2 x11_3 x11_1 gdomap tacacs mtp bacula_sd nextstep klogin www hkp login clearcase sa_msg_port nsca nqs at_echo mailq kamanda kerberos_adm vopied iprop kpasswd bpjava_msvc smtp xtelw sip_tls xtell afs3_callback ftp_data uucp_path wnn6 uucp supfilesrv whois telnets ospfapi unix_status ircd fido linuxconf microsoft_ds cfengine iso_tsap zope_ftp canna ms_sql_m prospero_np printer vnetd isdnlog nut exec ingreslock aol tacacs_ds amandaidx customs venus_se hostnames log_server nameserver ms_sql_s hmmp_ind supdup kx gpsd codasrv poppassd codasrv_se bootps sftp at_rtmp ftps_data binkp chargen time bgpd cmip_agent bootpc mon>
 
services_udp = </etc/services-udp/ noclog cmip_man z3950 rootd gds_db afs3_rmtsys bacula_fd mysql bpdbm xdmcp rtcm_sc104 x11_5 zephyr_hm daap radmin_port snmp_trap isakmp dict ldap ircs imaps pawserv afs3_errors x11_6 smux pop3 pop2 silc ssh afmbackup saft afbackup nntps loc_srv msnp isdnlog submission mandelspawn prospero afs3_bos sysrqd datametrics rfe kazaa cvspserver venus imap2 imap3 afs3_fileserver gnutella_svc domain smsqp l2f afs3_vlserver nut proofd rplay syslog omirr asp radius kermit sunrpc zserv ingreslock rsync imsp at_zis icpv2 daytime netwall kerberos4 x11 nfs irc rpc2portmap kerberos_master rtelnet aol ulistserv ntp xmpp_server snpp ggz lotusnote npmp_local fatserv hostmon rlp csnet_ns postgresql suucp rptp amanda route npmp_gui bgp afs3_kaserver vboxd timed discard re_mail_ck sgi_crsd gnutella_rtr passwd_server bpcd sgi_gcd rtsp gopher socks echo ipp radius_acct zephyr_clt ipx ldaps rmiregistry bprd mdns afpovertcp font_service netbios_dgm snmp msp at_nbp afs3_volser tinc iax https netbios_ns xpilot ninstall bacula_dir pop3s nessus svn xmpp_client codaauth2 openvpn kerberos afs3_prserver netbios_ssn sgi_cmsd qmtp omniorb acr_nema x11_7 x11_4 pwdgen x11_2 x11_3 x11_1 gdomap tacacs bacula_sd nextstep www hkp webster clearcase sa_msg_port nqs at_echo mailq kamanda vopied predict kpasswd bpjava_msvc distcc sip_tls zephyr_srv afs3_callback ntalk wnn6 fsp sip telnets mmcc microsoft_ds cfengine moira_ureg ms_sql_m prospero_np biff vnetd afs3_update who tacacs_ds customs venus_se tftp ms_sql_s hmmp_ind gpsd codasrv poppassd codasrv_se bootps at_rtmp chargen time cmip_agent bootpc talk mon>
 
session = ''
sniff_promisc = 1
stealth = 'not implemented'
verb = 2
warning_threshold = 5
wepkey = ''

L'utilitaire Scapy nous permet également de choisir de ne visualiser qu'une partie de la configuration si on le désire, ici on souhaite n'afficher que les informations relatives à la table de routage à l'aide de la commande conf.route :

>>> conf.route
Network Netmask Gateway Iface Output IP
127.0.0.0 255.0.0.0 0.0.0.0 lo 127.0.0.1
192.168.0.0 255.255.255.0 0.0.0.0 ath0 192.168.0.2
0.0.0.0 0.0.0.0 192.168.0.1 ath0 192.168.0.2

Par défaut ces informations sont bien équivalentes aux informations de routage délivrées par la commande système route :

root@casper:~# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.0.0 * 255.255.255.0 U 0 0 0 ath0
default 192.168.0.1 0.0.0.0 UG 0 0 0 ath0

Pour ajouter une entrée à la table de routage que l'on vient de visualiser on utilise la commande conf.route.add :

>>> conf.route.add(net="192.168.1.0/24",gw="192.168.0.1")

On vérifie que l'entrée de routage a bien été ajoutée pour le réseau 192.168.1.0/24 avec pour passerelle la machine dont l'adresse IP est 192.168.0.1 :

>>> conf.route
Network Netmask Gateway Iface Output IP
127.0.0.0 255.0.0.0 0.0.0.0 lo 127.0.0.1
192.168.0.0 255.255.255.0 0.0.0.0 ath0 192.168.0.2
0.0.0.0 0.0.0.0 192.168.0.1 ath0 192.168.0.2
192.168.1.0 255.255.255.0 192.168.0.1 ath0 192.168.0.2

On vérifie maintenant la table de routage système et cela pour la seconde fois :

root@casper:~# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.0.0 * 255.255.255.0 U 0 0 0 ath0
default 192.168.0.1 0.0.0.0 UG 0 0 0 ath0

Les tables de routage entre le système et Scapy sont différentes de par le fait que Scapy possède sa propre table de routage interne.

On supprime cette entrée avec la commande conf.route.delt, toujours pour le réseau 192.168.1.0/24 avec une machine faisant office de passerelle et dont l'adresse IP est 192.168.0.1 :

>>> conf.route.delt(net="192.168.1.0/24",gw="192.168.0.1")

On vérifie que l'entrée de routage correspondante a bien été supprimée au niveau de la table de routage interne à Scapy :

>>> conf.route
Network Netmask Gateway Iface Output IP
127.0.0.0 255.0.0.0 0.0.0.0 lo 127.0.0.1
192.168.0.0 255.255.255.0 0.0.0.0 ath0 192.168.0.2
0.0.0.0 0.0.0.0 192.168.0.1 ath0 192.168.0.2

C'est bien le cas l'entrée n'est plus présente dans la table de routage interne ; si l'on avait souhaité ajouter une route uniquement vers une machine en particulier on aurait pu utiliser la syntaxe suivante (host= à la place de net=) pour spécifier que les paquets/trames à destination de la machine dont l'adresse IP est 192.168.1.3 doivent être routés vers la machine dont l'adresse IP 192.168.0.1qui fait donc office de passerelle pour y accèder :

>>> conf.route.add(host="192.168.1.3",gw="192.168.0.1")

On peut également modifier l'interface réseau avec laquelle on souhaite travailler par défaut et cela à l'aide de la commande conf.iface :

>>> conf.iface='eth0'
 
>>> conf.iface
'eth0'
 
>>> conf.iface='ath0'
 
>>> conf.iface
'ath0'

Le système est maintenant entièrement fonctionnel et on peut passer aux fonctions de manipulation de paquets/trames de données qu'offre Scapy.

3 Utilisation basique

Cette partie du dossier Scapy propose de faire un tour d'horizon des commandes de base présentes dans Scapy. On y retrouve également différents exemples de leur utilisation qui permettront de se familiariser avec le fonctionnement interne de cet outil afin de mieux en comprendre les principes.

Premièrement on liste l'ensemble des protocoles supportés par Scapy à l'aide de la commande lsc() :

>>> ls()
ARP : ARP
ASN1_Packet : None
BOOTP : BOOTP
CookedLinux : cooked linux
DHCP : DHCP options
DNS : DNS
DNSQR : DNS Question Record
DNSRR : DNS Resource Record
Dot11 : 802.11
Dot11ATIM : 802.11 ATIM
Dot11AssoReq : 802.11 Association Request
Dot11AssoResp : 802.11 Association Response
Dot11Auth : 802.11 Authentication
Dot11Beacon : 802.11 Beacon
Dot11Deauth : 802.11 Deauthentication
Dot11Disas : 802.11 Disassociation
Dot11Elt : 802.11 Information Element
Dot11ProbeReq : 802.11 Probe Request
Dot11ProbeResp : 802.11 Probe Response
Dot11ReassoReq : 802.11 Reassociation Request
Dot11ReassoResp : 802.11 Reassociation Response
Dot11WEP : 802.11 WEP packet
Dot1Q : 802.1Q
Dot3 : 802.3
EAP : EAP
EAPOL : EAPOL
Ether : Ethernet
GPRS : GPRSdummy
GRE : GRE
HCI_ACL_Hdr : HCI ACL header
HCI_Hdr : HCI header
HSRP : HSRP
ICMP : ICMP
ICMPerror : ICMP in ICMP
IP : IP
IPerror : IP in ICMP
IPv6 : IPv6 not implemented here.
ISAKMP : ISAKMP
ISAKMP_class : None
ISAKMP_payload : ISAKMP payload
ISAKMP_payload_Hash : ISAKMP Hash
ISAKMP_payload_ID : ISAKMP Identification
ISAKMP_payload_KE : ISAKMP Key Exchange
ISAKMP_payload_Nonce : ISAKMP Nonce
ISAKMP_payload_Proposal : IKE proposal
ISAKMP_payload_SA : ISAKMP SA
ISAKMP_payload_Transform : IKE Transform
ISAKMP_payload_VendorID : ISAKMP Vendor ID
IrLAPCommand : IrDA Link Access Protocol Command
IrLAPHead : IrDA Link Access Protocol Header
IrLMP : IrDA Link Management Protocol
L2CAP_CmdHdr : L2CAP command header
L2CAP_CmdRej : L2CAP Command Rej
L2CAP_ConfReq : L2CAP Conf Req
L2CAP_ConfResp : L2CAP Conf Resp
L2CAP_ConnReq : L2CAP Conn Req
L2CAP_ConnResp : L2CAP Conn Resp
L2CAP_DisconnReq : L2CAP Disconn Req
L2CAP_DisconnResp : L2CAP Disconn Resp
L2CAP_Hdr : L2CAP header
L2CAP_InfoReq : L2CAP Info Req
L2CAP_InfoResp : L2CAP Info Resp
LLC : LLC
MGCP : MGCP
MobileIP : Mobile IP (RFC3344)
MobileIPRRP : Mobile IP Registration Reply (RFC3344)
MobileIPRRQ : Mobile IP Registration Request (RFC3344)
MobileIPTunnelData : Mobile IP Tunnel Data Message (RFC3519)
NBNSNodeStatusResponse : NBNS Node Status Response
NBNSNodeStatusResponseEnd : NBNS Node Status Response
NBNSNodeStatusResponseService : NBNS Node Status Response Service
NBNSQueryRequest : NBNS query request
NBNSQueryResponse : NBNS query response
NBNSQueryResponseNegative : NBNS query response (negative)
NBNSRequest : NBNS request
NBNSWackResponse : NBNS Wait for Acknowledgement Response
NBTDatagram : NBT Datagram Packet
NBTSession : NBT Session Packet
NTP : NTP
NetBIOS_DS : NetBIOS datagram service
NetflowHeader : Netflow Header
NetflowHeaderV1 : Netflow Header V1
NetflowRecordV1 : Netflow Record
NoPayload : None
PPP : PPP Link Layer
PPPoE : PPP over Ethernet
PPPoED : PPP over Ethernet Discovery
Packet : None
Padding : Padding
PrismHeader : Prism header
RIP : RIP header
RIPEntry : RIP entry
RTP : RTP
RadioTap : RadioTap dummy
Radius : Radius
Raw : Raw
SMBMailSlot : SMB Mail Slot Protocol
SMBNegociate_Protocol_Request_Header : SMBNegociate Protocol Request Header
SMBNegociate_Protocol_Request_Tail : SMB Negociate Protocol Request Tail
SMBNegociate_Protocol_Response_Advanced_Security : SMBNegociate Protocol Response Advanced Security
SMBNegociate_Protocol_Response_No_Security : SMBNegociate Protocol Response No Security
SMBNegociate_Protocol_Response_No_Security_No_Key : None
SMBNetlogon_Protocol_Response_Header : SMBNetlogon Protocol Response Header
SMBNetlogon_Protocol_Response_Tail_LM20 : SMB Netlogon Protocol Response Tail LM20
SMBNetlogon_Protocol_Response_Tail_SAM : SMB Netlogon Protocol Response Tail SAM
SMBSession_Setup_AndX_Request : Session Setup AndX Request
SMBSession_Setup_AndX_Response : Session Setup AndX Response
SNAP : SNAP
SNMP : None
SNMPbulk : None
SNMPget : None
SNMPinform : None
SNMPnext : None
SNMPresponse : None
SNMPset : None
SNMPtrapv1 : None
SNMPtrapv2 : None
SNMPvarbind : None
STP : Spanning Tree Protocol
SebekHead : Sebek header
SebekV1 : Sebek v1
SebekV2 : Sebek v3
SebekV2Sock : Sebek v2 socket
SebekV3 : Sebek v3
SebekV3Sock : Sebek v2 socket
Skinny : Skinny
TCP : TCP
TCPerror : TCP in ICMP
TFTP : TFTP opcode
TFTP_ACK : TFTP Ack
TFTP_DATA : TFTP Data
TFTP_ERROR : TFTP Error
TFTP_OACK : TFTP Option Ack
TFTP_Option : None
TFTP_Options : None
TFTP_RRQ : TFTP Read Request
TFTP_WRQ : TFTP Write Request
UDP : UDP
UDPerror : UDP in ICMP
_IPv6OptionHeader : IPv6 not implemented here.

Comme on peut le constater, le nombre de protocoles supporté est relativement conséquent ; chacun de ses protocoles possède ses propres spécifités que l'on peut lister à l'aide de la commande ls(). Ici on retrouve les détails du protocole ICMP :

>>> ls(ICMP)
type : ByteEnumField = (8)
code : ByteField = (0)
chksum : XShortField = (None)
id : XShortField = (0)
seq : XShortField = (0)

Pour avoir accès à l'ensemble de la lister des commandes/fonctions disponibles dans Scapy, on doit utiliser la commande lsc() :

>>> 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"
is_promisc : Try to guess if target is in Promisc mode. The target is provided by its ip.
promiscping : Send ARP who-has requests to determine which hosts are in promiscuous mode

Pour afficher la documentation d'une fonction en particulier il suffit de rajouter l'extension .__doc__ derrière la commande (sans les () finaux) ; soit pour afficher la documentation de la commande arping(), il sera nécessaire de taper la syntaxe suivante :

>>> arping.__doc__
'Send ARP who-has requests to determine which hosts are up\narping(net, [cache=0,] [iface=conf.iface,] [verbose=conf.verb]) -> None\nSet cache=True if you want arping to modify internal ARP-Cache'

Il est aussi possible d'obtenir le même résultat de documentation mais avec un peu plus de mise en page à l'aide de la commande lsc() qui nous a permis de lister les commandes disponibles dans Scapy précédemment ; il suffit de rajouter le nom de la commande entre les () dont on souhaite obtenir les informations de documentation :

>>> lsc(arping)
Send ARP who-has requests to determine which hosts are up
arping(net, [cache=0,] [iface=conf.iface,] [verbose=conf.verb]) -> None
Set cache=True if you want arping to modify internal ARP-Cache

4 Captures de données

Scapy peut fonctionner à la manière d'un analyseur de trafic réseaux afin d'effectuer des captures de données en vue de les visualiser par la suite. Cette partie du dossier propose différents exemples de captures ainsi que les nombreuses façons dont on dispose en interne pour visualiser les résultats de ces captures.

On affiche la documentation de la fonction sniff() à l'aide de la fonction lsc() :

>>> lsc(sniff)
Sniff packets
sniff([count=0,] [prn=None,] [store=1,] [offline=None,] [lfilter=None,] + L2ListenSocket args) -> list of packets
 
count: number of packets to capture. 0 means infinity
store: wether to store sniffed packets or discard them
prn: function to apply to each packet. If something is returned,
it is displayed. Ex:
ex: prn = lambda x: x.summary()
lfilter: python function applied to each packet to determine
if further action may be done
ex: lfilter = lambda x: x.haslayer(Padding)
offline: pcap file to read packets from, instead of sniffing them
timeout: stop sniffing after a given time (default: None)
L2socket: use the provided L2socket

On lance maintenant une écoute (sniff) sur l'ensemble du trafic équivalent au protocol de transport UDP et cela pour la machine dont l'adresse IP est 192.168.0.2 avec un maximum de 30 paquets collectés (à l'aide de count) :

>>> sniff(filter="udp and host 192.168.0.2", count=30)
<Sniffed: UDP:30 ICMP:0 TCP:0 Other:0>

Les 30 paquets ont bien été collectés pour le protocole UDP et la machine dont l'adresse IP est 192.168.0.2 ; on peut visualiser les résultats relatifs à cette capture en affectant les enregistrements à la variable sn via la variable _ et cela de la façon suivante :

>>> sn=_

Si l'on souhaite visualiser l'ensemble de ces enregistrements qui sont contenus dans la variable sn, il est nécessaire d'ajouter .nsummary() derrière le nom de la variable que l'on aura choisi lors de l'affectation des résultats d'une fonction (ici la fonction sniff et la variable sn) :

>>> sn.nsummary()
0000 Ether / IP / UDP 192.168.0.1:netbios_dgm > 192.168.0.255:netbios_dgm / NBTDatagram / Raw
0001 Ether / IP / UDP 192.168.0.1:netbios_dgm > 192.168.0.255:netbios_dgm / NBTDatagram / Raw
0002 Ether / IP / UDP 192.168.0.1:netbios_dgm > 192.168.0.255:netbios_dgm / NBTDatagram / Raw
0003 Ether / IP / UDP 192.168.0.1:netbios_dgm > 192.168.0.255:netbios_dgm / NBTDatagram / Raw
0004 Ether / IP / UDP 192.168.0.1:netbios_dgm > 192.168.0.255:netbios_dgm / NBTDatagram / Raw
0005 Ether / IP / UDP 192.168.0.1:netbios_dgm > 192.168.0.255:netbios_dgm / NBTDatagram / Raw
0006 Ether / IP / UDP 192.168.0.1:netbios_ns > 192.168.0.255:netbios_ns / NBNSQueryRequest
0007 Ether / IP / UDP 192.168.0.1:netbios_ns > 192.168.0.255:netbios_ns / NBNSQueryRequest
0008 Ether / IP / UDP 192.168.0.1:netbios_ns > 192.168.0.255:netbios_ns / NBNSQueryRequest
0009 Ether / IP / UDP 192.168.0.1:netbios_dgm > 192.168.0.255:netbios_dgm / NBTDatagram / Raw
0010 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0011 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0012 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0013 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0014 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0015 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0016 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0017 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0018 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0019 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0020 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0021 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0022 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0023 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0024 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0025 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0026 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0027 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0028 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw
0029 Ether / IP / UDP 192.168.0.1:1900 > 239.255.255.250:1900 / Raw

Sn est considéré ici comme un objet à part entière sur lequel on peut appliquer différentes opérations via des fonctions appropriées comme ici nsummary() ; pour visualiser en détail le premier enregistrement contenu dans la variable sn, on se doit d'ajouter entre [] le numéro de l'enregistrement que l'on veut visualiser dans le trafic que l'on vient de capturer et cela juste derrière le nom de la variable :

>>> sn[0]
<Ether dst=ff:ff:ff:ff:ff:ff src=00:0f:b5:0e:89:4a type=0x800 |<IP version=4L ihl=5L tos=0x0 len=229 id=2852 flags= frag=0L ttl=128 proto=udp chksum=0xac93 src=192.168.0.1 dst=192.168.0.255 options='' |<UDP sport=netbios_dgm dport=netbios_dgm len=209 chksum=0xa7d |<NBTDatagram Type=17 Flags=14 ID=32985 SourceIP=192.168.0.1 SourcePort=138 Length=187 Offset=0 SourceName='HEIDI ' SUFFIX1=file server service NULL=0 DestinationName='ARBEITSGRUPPE ' SUFFIX2=browser election service NULL=0 |<Raw load='\xffSMB%\x00\x00\x00\x00\x00\x00\x00\x00
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
\x00\x00\x00\x00\x00\x11\x00\x00!\x00\x00\x00\x00\x00\x00
\x00\x00\x00\xe8\x03\x00\x00\x00\x00\x00\x00\x00\x00!\x00V
\x00\x03\x00\x01\x00\x00\x00\x02\x002\x00\\MAILSLOT\\BROWSE
\x00\x0f\x00\x80\xfc\n\x00HEIDI\x00\x00\x00\x00\x00d\x00\x00\
x00\x00\x00\x05\x01\x03\x10\x05\x00\x0f\x01U\xaa\x00'|>>
;>>>

Le dernier enregistrement de paquet de donnée (le 30e en l'occurence pour notre cas) porte içi l'id 29 puisque le premier enregistrement porte lui un id égal à 0 :

>>> sn[29]
<Ether dst=01:00:5e:7f:ff:fa src=00:0f:b5:0e:89:4a type=0x800 |<IP version=4L ihl=5L tos=0x0 len=373 id=2938 flags= frag=0L ttl=1 proto=udp chksum=0xfc5a src=192.168.0.1 dst=239.255.255.250 options='' |<UDP sport=1900 dport=1900 len=353 chksum=0x23e2 |<Raw load='NOTIFY * HTTP/1.1\r\nHost:239.255.255.250:1900\r\nNT:upnp:rootdevice\r
\nNTS:ssdp:alive\r\nLocation:http:[click]
/udhisapi.dll?content=uuid:03cda088-f88e-4a2b-96a6-
0bbe0741838e\r\nUSN:uuid:03cda088-f88e-4a2b-96a6-
0bbe0741838e::upnp:rootdevice\r\nCache-Control:max-age=1800
\r\nServer:Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host
/1.0\r\n\r\n' |>>>>

On vérife bien que l'enregistrement d'id 30 n'existe pas dans la variable sn :

>>> sn[30]
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/trash/scapy.py", line 2450, in __getitem__
return self.res.__getitem__(item)
IndexError: list index out of range

Avec Scapy, on peut également réaliser des captures de trafic réseau et afficher directement les résultats tout en spécifiant l'interface réseau sur laquelle on veut effectuer cette opération si bien entendu elle était différente de celle définie dans la variable conf.iface ; cela s'effectue de la manière suivante :

>>> sniff(iface="ath0", prn=lambda x: x.summary(),count=30)
Ether / IP / TCP 192.168.0.2:50414 > 213.251.178.32:www PA / Raw
Ether / IP / TCP 213.251.178.32:www > 192.168.0.2:50414 A / Raw
Ether / IP / TCP 192.168.0.2:50414 > 213.251.178.32:www A
Ether / IP / TCP 213.251.178.32:www > 192.168.0.2:50414 A / Raw
Ether / IP / TCP 192.168.0.2:50414 > 213.251.178.32:www A
Ether / IP / TCP 213.251.178.32:www > 192.168.0.2:50414 A / Raw
Ether / IP / TCP 192.168.0.2:50414 > 213.251.178.32:www A
Ether / IP / TCP 213.251.178.32:www > 192.168.0.2:50414 A / Raw
Ether / IP / TCP 192.168.0.2:50414 > 213.251.178.32:www A
Ether / IP / TCP 213.251.178.32:www > 192.168.0.2:50414 A / Raw
Ether / IP / TCP 192.168.0.2:50414 > 213.251.178.32:www A
Ether / IP / TCP 213.251.178.32:www > 192.168.0.2:50414 A / Raw
Ether / IP / TCP 192.168.0.2:50414 > 213.251.178.32:www A
Ether / IP / TCP 213.251.178.32:www > 192.168.0.2:50414 A / Raw
Ether / IP / TCP 192.168.0.2:50414 > 213.251.178.32:www A
Ether / IP / TCP 213.251.178.32:www > 192.168.0.2:50414 A / Raw
Ether / IP / TCP 192.168.0.2:50414 > 213.251.178.32:www A
Ether / IP / TCP 213.251.178.32:www > 192.168.0.2:50414 A / Raw
Ether / IP / TCP 192.168.0.2:50414 > 213.251.178.32:www A
Ether / IP / TCP 213.251.178.32:www > 192.168.0.2:50414 A / Raw
Ether / IP / TCP 192.168.0.2:50414 > 213.251.178.32:www A
Ether / IP / TCP 213.251.178.32:www > 192.168.0.2:50414 A / Raw
Ether / IP / TCP 192.168.0.2:50414 > 213.251.178.32:www A
Ether / IP / TCP 213.251.178.32:www > 192.168.0.2:50414 A / Raw
Ether / IP / TCP 192.168.0.2:50414 > 213.251.178.32:www A
Ether / IP / TCP 213.251.178.32:www > 192.168.0.2:50414 A / Raw
Ether / IP / TCP 192.168.0.2:50414 > 213.251.178.32:www A
Ether / IP / TCP 213.251.178.32:www > 192.168.0.2:50414 A / Raw
Ether / IP / TCP 192.168.0.2:50414 > 213.251.178.32:www A
Ether / IP / TCP 213.251.178.32:www > 192.168.0.2:50414 A / Raw
<Sniffed: UDP:0 ICMP:0 TCP:30 Other:0>

On peut également visualiser les enregistrements d'une tout autre façon en remplaçant l'attribut x.summary() par l'attribut x.show() qui cette fois va nous permettre d'obtenir beaucoup plus de détails intrinséques au trafic collecté lors de la capture réseau ; la sortie pouvant être assez longue on limite ici avec count à 3 le nombre de paquets que l'on va capturer :

>>> sniff(iface="ath0", prn=lambda x: x.show(),count=3)
 
###[ Ethernet ]###
dst= 00:0f:b5:0e:89:4a
src= 00:15:6d:53:1e:87
type= 0x800
###[ IP ]###
version= 4L
ihl= 5L
tos= 0x0
len= 678
id= 37167
flags= DF
frag= 0L
ttl= 64
proto= tcp
chksum= 0x5e5c
src= 192.168.0.2
dst= 213.251.178.32
options= ''
###[ TCP ]###
sport= 34554
dport= www
seq= 1842608767
ack= 4117121824L
dataofs= 8L
reserved= 0L
flags= PA
window= 2003
chksum= 0x1c8f
urgptr= 0
options= [('NOP', None), ('NOP', None), ('Timestamp', (1035373, 3849016752L))]
###[ Raw ]###
load= 'GET / HTTP/1.1\r\nHost: www.secuobs.com\r\nUser-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.6) Gecko/20061201 Firefox/2.0.0.6 (Ubuntu-feisty)\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: fr-fr,en-us;q=0.7,en;q=0.3\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\nCookie: __utma=50890539.1734225999.1190320568.1190636274.1190638025
.62; __utmb=50890539; __utmz=50890539.1190339822.4.2.utmccn=
(organic)|utmcsr=google|utmctr=captive|utmcmd=organic; __
utmc=50890539\r\n\r\n'
###[ Ethernet ]###
dst= 00:15:6d:53:1e:87
src= 00:0f:b5:0e:89:4a
type= 0x800
###[ IP ]###
version= 4L
ihl= 5L
tos= 0x0
len= 1492
id= 38106
flags= DF
frag= 0L
ttl= 52
proto= tcp
chksum= 0x6383
src= 213.251.178.32
dst= 192.168.0.2
options= ''
###[ TCP ]###
sport= www
dport= 34554
seq= 4117121824L
ack= 1842609393
dataofs= 8L
reserved= 0L
flags= A
window= 15024
chksum= 0x3812
urgptr= 0
options= [('NOP', None), ('NOP', None), ('Timestamp', (3849021332L, 1035373))]
###[ Raw ]###
load= 'HTTP/1.1 200 OK\r\nDate: Mon, 24 Sep 2007 13:05:42 GMT\r\nServer: Apache\r\nKeep-Alive: timeout=7200\r\nConnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\nContent-Type: text/html\r\n\r\ncb3\r\n<html>\n<head>\n <title>SecuObs.com -\nL’observatoire de la sécurité internet</title>\n <meta name="description"\n content="L’observatoire de la securite internet - Site d’informations professionnelles francophone sur la sécurité informatique">\n <meta\n content="text/html; charset=ISO-8859-1"\n http-equiv="content-type">\n <title></title>\n'
###[ Ethernet ]###
dst= 00:0f:b5:0e:89:4a
src= 00:15:6d:53:1e:87
type= 0x800
###[ IP ]###
version= 4L
ihl= 5L
tos= 0x0
len= 52
id= 37168
flags= DF
frag= 0L
ttl= 64
proto= tcp
chksum= 0x60cd
src= 192.168.0.2
dst= 213.251.178.32
options= ''
###[ TCP ]###
sport= 34554
dport= www
seq= 1842609393
ack= 4117123264L
dataofs= 8L
reserved= 0L
flags= A
window= 1963
chksum= 0xd48a
urgptr= 0
options= [('NOP', None), ('NOP', None), ('Timestamp', (1035386, 3849021332L))]
<Sniffed: UDP:0 ICMP:0 TCP:3 Other:0>

On peut ainsi de la même façon capturer le trafic réseau pour un port donné ou plusieurs ports donnés ; ici on capture le trafic (5 enregistrements) relatif au port 22 (habituellement équivalent au trafic des connexions distantes chiffrées de type SSH) et le port 80 qui est lui habituellement synonyme de trafic vers un serveur web de type Apache (ou IIS) :

>>> sniff(filter="tcp and ( port 22 or port 80 )", prn=lambda x: x.sprintf("IP Source : %IP.src% ; Port Source : %TCP.sport% ---> IP Destination : %IP.dst% ; Port Destination : %TCP.dport% ; Flags TCP : %TCP.flags% ; Payload : %TCP.payload%"), count=5)
IP Source : 127.0.0.1 ; Port Source : 33917 ---> IP Destination : 127.0.0.1 ; Port Destination : ssh ; Flags TCP : FA ; Payload :
IP Source : 127.0.0.1 ; Port Source : 33917 ---> IP Destination : 127.0.0.1 ; Port Destination : ssh ; Flags TCP : FA ; Payload :
IP Source : 127.0.0.1 ; Port Source : ssh ---> IP Destination : 127.0.0.1 ; Port Destination : 33917 ; Flags TCP : R ; Payload :
IP Source : 127.0.0.1 ; Port Source : ssh ---> IP Destination : 127.0.0.1 ; Port Destination : 33917 ; Flags TCP : R ; Payload :
IP Source : 127.0.0.1 ; Port Source : 33917 ---> IP Destination : 127.0.0.1 ; Port Destination : ssh ; Flags TCP : FA ; Payload :
<Sniffed: UDP:0 TCP:5 ICMP:0 Other:0>

L'attribut x.sprintf dans l'exemple ci-dessus permet de mettre en page selon les convenances de chacun les résultats en sortie des fonctions de Scapy et cela à l'aide de variables pré-définies comme %IP.src% pour l'adresse IP source, %IP.dst% pour l'adresse IP de destination, %TCP.sport% pour le port source TCP, %TCP.dport% pour pour le port TCP de destination ainsi que %TCP.flags% pour les options activés dans les drapeaux TCP ainsi que %TCP.payload% pour la charge utile relative à l'enregistrement en cours de visualisation.

L'utilisation de ces variables et leur manipulation accentuent le coté orienté objet de l'utilitaire Scapy ; par exemple on peut afficher les informations relatives à un enregistrement de la manière suivante :

>>> IP(dst="192.168.0.2")
<IP dst=192.168.0.2 |>
>>> a.sprintf("IP Source %IP.src% ; IP Destination %IP.dst% ; TTL %IP.ttl%")
'IP Source 192.168.0.2 ; IP Destination 192.168.0.2 ; TTL 64'

On remarquer que, même si on a pas spécifié de valeurs pour le ttl, Scapy prend une valeur par défaut équivalente à 64 que l'on retrouve lors de l'affichage via la variable %IP.ttl%.

La suite de commandes, qui va suivre, se présente sous la forme d'une capture du trafic équivalent aux port 22 (SSH) et/ou 80 (WWW) pour un total de 5 enregistrements relatifs au protocole de transport TCP ; les enregistrements récoltés seront stockés dans une variable sn dont on peut afficher le premier enregistrement avec sn[0] :

>>> sniff(filter="tcp and ( port 22 or port 80 )", count=5)
<Sniffed: UDP:0 ICMP:0 TCP:5 Other:0>
>>> sn=_
>>> sn[0]
<Ether dst=00:00:00:00:00:00 src=00:00:00:00:00:00 type=0x800 |<IP version=4L ihl=5L tos=0x0 len=60 id=45016 flags=DF frag=0L ttl=64 proto=tcp chksum=0x8ce1 src=127.0.0.1 dst=127.0.0.1 options='' |<TCP sport=56283 dport=ssh seq=2821237960L ack=0 dataofs=10L reserved=0L flags=S window=32792 chksum=0x5b8a urgptr=0 options=[('MSS', 16396), ('SAckOK', ''), ('Timestamp', (1180432, 0)), ('NOP', None), ('WScale', 5)] |>>>

Cette suite de commandes peut également s'écrire de façon encore un peu plus simplifiée de la manière suivante :

>>> sn=sniff(filter="tcp and ( port 22 or port 80 )", count=5)
>>> sn[0]
<Ether dst=00:00:00:00:00:00 src=00:00:00:00:00:00 type=0x800 |<IP version=4L ihl=5L tos=0x0 len=60 id=35250 flags=DF frag=0L ttl=64 proto=tcp chksum=0xb307 src=127.0.0.1 dst=127.0.0.1 options='' |<TCP sport=56788 dport=ssh seq=2937645191L ack=0 dataofs=10L reserved=0L flags=S window=32792 chksum=0xb8bc urgptr=0 options=[('MSS', 16396), ('SAckOK', ''), ('Timestamp', (1204533, 0)), ('NOP', None), ('WScale', 5)] |>>>

5 Traceroute et visualisation 2D/3D

A travers différents exemples de traceroute réalisés à l'aide de Scapy, il est proposé de découvrir les fonctionnalités graphiques de Scapy afin de générer des graphiques en deux et en trois dimensions à partir des résultats de ces traceroute. Retrouver également les différentes façons dont vous pouvez exporter ces résultats en vue de les visualiser.

On affiche maintenant les informations relatives à la commande traceroute de Scapy :

>>> lsc(traceroute)
Instant TCP traceroute
traceroute(target, [maxttl=30,] [dport=80,] [sport=80,] [verbose=conf.verb]) -> None

On réalise alors un traceroute simple afin de définir le chemin enprunté par le trafic réseau pour aller de la machine de test vers l'adresse ip associée au niveau DNS au FQDN ( Full Qualified Domain Name ) et cela avec un temps de latence égal à 10 jiffies (nombre de période d'horloge) ; rappelons que les réseaux TCP/IP sont des réseaux à fragmentation et à commutation de paquets il est donc normal de retrouver un résultat de routage des paquets différent entre deux traceroute lancés à des intervalles différents :

>>> traceroute(["www.google.com"],maxttl=10)
Begin emission:
****Finished to send 10 packets.***
 
Received 7 packets, got 7 answers, remaining 3 packets
64.233.183.104:tcp80
1 192.168.0.1 11
2 192.168.2.1 11
3 192.168.1.1 11
4 81.62.144.1 11
7 195.186.0.234 11
9 72.14.198.57 11
10 64.233.174.34 11
(<Traceroute: UDP:0 ICMP:7 TCP:0 Other:0>, <Unanswered: UDP:0 ICMP:0 TCP:3 Other:0>)

On réalise ensuite un traceroute multiple vers les FQDN "www.free.fr" et "www.exoscan.net" avec un ttl toujours égal à 10 ; les résultats sont stockés dans la variable sn pour les enregistrements ayant eu une réponse alors que ceux sans réponse sont stockées dans la variable unans :

>>> sn,unans=traceroute(["www.free.fr","www.exoscan.net"],maxttl=10)
Begin emission:
****************Finished to send 20 packets.
 
Received 16 packets, got 16 answers, remaining 4 packets
212.27.48.10:tcp80 213.186.41.29:tcp80
1 192.168.0.1 11 192.168.0.1 11
2 62.4.16.248 11 62.4.16.248 11
3 62.4.16.7 11 62.4.16.7 11
4 194.79.131.146 11 194.79.131.146 11
5 213.228.3.225 11 194.79.131.129 11
6 - 213.186.32.141 11
7 212.27.57.213 11 213.186.32.30 11
8 212.27.50.6 11 -
9 212.27.48.10 SA -
10 212.27.48.10 SA -

Si l'on souhaite n'afficher que les enregistrements de non réponse, on peut utiliser l'attribut display() derrière la variable unans de la façon qui va suivre :

>>> unans.display()
0000 IP / TCP 192.168.0.2:16324 > 213.186.41.29:www S
0001 IP / TCP 192.168.0.2:9718 > 213.186.41.29:www S
0002 IP / TCP 192.168.0.2:17318 > 213.186.41.29:www S
0003 IP / TCP 192.168.0.2:15117 > 212.27.48.10:www S

Alors que pour n'afficher que les enregistrements représentant une réponse, on utilisera le même attribut display() mais cette fois sur la variable sn de la manière suivante :

>>> sn.display()
212.27.48.10:tcp80 213.186.41.29:tcp80
1 192.168.0.1 11 192.168.0.1 11
2 62.4.16.248 11 62.4.16.248 11
3 62.4.16.7 11 62.4.16.7 11
4 194.79.131.146 11 194.79.131.146 11
5 213.228.3.225 11 194.79.131.129 11
6 - 213.186.32.141 11
7 212.27.57.213 11 213.186.32.30 11
8 212.27.50.6 11 -
9 212.27.48.10 SA -
10 212.27.48.10 SA -

On réalise pour cette fois un traceroute multiple vers les FQDN suivants "www.google.com", "www.aol.com", "www.free.fr" et "www.secuobs.com" avec un ttl toujours égal à 10 :

>>> sn,unans=traceroute(["www.google.com","www.aol.com",
"www.free.fr","www.secuobs.com"],maxttl=10)
Begin emission:
************************
Finished to send 40 packets.
 
Received 24 packets, got 24 answers, remaining 16 packets
212.27.48.10:tcp80 213.251.178.32:tcp80 64.12.89.12:tcp80 64.233.183.104:tcp80
1 192.168.0.1 11 192.168.0.1 11 192.168.0.1 11 -
2 192.168.2.1 11 - 192.168.2.1 11 192.168.2.1 11
4 81.62.144.1 11 81.62.144.1 11 81.62.144.1 11 -
5 195.186.123.1 11 195.186.123.1 11 195.186.123.1 11 195.186.123.1 11
6 - - 195.186.0.229 11 195.186.0.229 11
8 138.187.129.45 11 138.187.129.45 11 138.187.129.74 11 -
9 138.187.130.73 11 138.187.130.73 11 194.42.48.4 11 72.14.198.57 11
10 - - 212.74.84.242 11 64.233.174.34 11

Les résultats du traceroute multiple sont stockés dans la variable sn pour les enregistrements représentant une réponse ; on peut tout aussi bien afficher le contenu de la variable sn de la manière suivante grâce à l'attribut show() :

>>> sn.show()
212.27.48.10:tcp80 213.251.178.32:tcp80 64.12.89.12:tcp80 64.233.183.104:tcp80
1 192.168.0.1 11 192.168.0.1 11 192.168.0.1 11 -
2 192.168.2.1 11 - 192.168.2.1 11 192.168.2.1 11
4 81.62.144.1 11 81.62.144.1 11 81.62.144.1 11 -
5 195.186.123.1 11 195.186.123.1 11 195.186.123.1 11 195.186.123.1 11
6 - - 195.186.0.229 11 195.186.0.229 11
8 138.187.129.45 11 138.187.129.45 11 138.187.129.74 11 -
9 138.187.130.73 11 138.187.130.73 11 194.42.48.4 11 72.14.198.57 11
10 - - 212.74.84.242 11 64.233.174.34 11

On peut de la même manière visualiser les enregistrements contenus dans la variable sn en positionnant l'attribut nsummary() en lieu et place de l'attribut show() pour le résultat suivant :

>>> sn.nsummary()
0000 IP / TCP 192.168.0.2:56554 > 64.12.89.12:www S ==> IP / ICMP 192.168.0.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0001 IP / TCP 192.168.0.2:38001 > 212.27.48.10:www S ==> IP / ICMP 192.168.0.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0002 IP / TCP 192.168.0.2:33842 > 213.251.178.32:www S ==> IP / ICMP 192.168.0.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0003 IP / TCP 192.168.0.2:55277 > 64.233.183.104:www S ==> IP / ICMP 192.168.2.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0004 IP / TCP 192.168.0.2:62887 > 64.12.89.12:www S ==> IP / ICMP 192.168.2.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0005 IP / TCP 192.168.0.2:amanda > 212.27.48.10:www S ==> IP / ICMP 192.168.2.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0006 IP / TCP 192.168.0.2:55421 > 64.12.89.12:www S ==> IP / ICMP 81.62.144.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0007 IP / TCP 192.168.0.2:46998 > 212.27.48.10:www S ==> IP / ICMP 81.62.144.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0008 IP / TCP 192.168.0.2:34599 > 213.251.178.32:www S ==> IP / ICMP 81.62.144.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0009 IP / TCP 192.168.0.2:58715 > 64.233.183.104:www S ==> IP / ICMP 195.186.123.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0010 IP / TCP 192.168.0.2:9093 > 64.12.89.12:www S ==> IP / ICMP 195.186.123.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0011 IP / TCP 192.168.0.2:14089 > 212.27.48.10:www S ==> IP / ICMP 195.186.123.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0012 IP / TCP 192.168.0.2:2258 > 213.251.178.32:www S ==> IP / ICMP 195.186.123.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0013 IP / TCP 192.168.0.2:60402 > 64.233.183.104:www S ==> IP / ICMP 195.186.0.229 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0014 IP / TCP 192.168.0.2:32073 > 64.12.89.12:www S ==> IP / ICMP 195.186.0.229 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0015 IP / TCP 192.168.0.2:18250 > 64.12.89.12:www S ==> IP / ICMP 138.187.129.74 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0016 IP / TCP 192.168.0.2:13695 > 212.27.48.10:www S ==> IP / ICMP 138.187.129.45 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0017 IP / TCP 192.168.0.2:19761 > 213.251.178.32:www S ==> IP / ICMP 138.187.129.45 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0018 IP / TCP 192.168.0.2:23264 > 64.233.183.104:www S ==> IP / ICMP 72.14.198.57 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0019 IP / TCP 192.168.0.2:32236 > 64.12.89.12:www S ==> IP / ICMP 194.42.48.4 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0020 IP / TCP 192.168.0.2:20611 > 212.27.48.10:www S ==> IP / ICMP 138.187.130.73 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0021 IP / TCP 192.168.0.2:40775 > 213.251.178.32:www S ==> IP / ICMP 138.187.130.73 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
0022 IP / TCP 192.168.0.2:57456 > 64.233.183.104:www S ==> IP / ICMP 64.233.174.34 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror / Padding
0023 IP / TCP 192.168.0.2:56892 > 64.12.89.12:www S ==> IP / ICMP 212.74.84.242 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror

La visualisation peut aussi se faire de cette façon avec l'attribut summary() toujours à la place de l'attribut show() :

>>> sn.summary()
IP / TCP 192.168.0.2:56554 > 64.12.89.12:www S ==> IP / ICMP 192.168.0.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:38001 > 212.27.48.10:www S ==> IP / ICMP 192.168.0.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:33842 > 213.251.178.32:www S ==> IP / ICMP 192.168.0.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:55277 > 64.233.183.104:www S ==> IP / ICMP 192.168.2.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:62887 > 64.12.89.12:www S ==> IP / ICMP 192.168.2.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:amanda > 212.27.48.10:www S ==> IP / ICMP 192.168.2.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:55421 > 64.12.89.12:www S ==> IP / ICMP 81.62.144.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:46998 > 212.27.48.10:www S ==> IP / ICMP 81.62.144.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:34599 > 213.251.178.32:!www S ==> IP / ICMP 81.62.144.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:58715 > 64.233.183.104:www S ==> IP / ICMP 195.186.123.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:9093 > 64.12.89.12:www S ==> IP / ICMP 195.186.123.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:14089 > 212.27.48.10:www S ==> IP / ICMP 195.186.123.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:2258 > 213.251.178.32:www S ==> IP / ICMP 195.186.123.1 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:60402 > 64.233.183.104:www S ==> IP / ICMP 195.186.0.229 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:32073 > 64.12.89.12:www S ==> IP / ICMP 195.186.0.229 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:18250 > 64.12.89.12:www S ==> IP / ICMP 138.187.129.74 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:13695 > 212.27.48.10:www S ==> IP / ICMP 138.187.129.45 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:19761 > 213.251.178.32:www S ==> IP / ICMP 138.187.129.45 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:23264 > 64.233.183.104:www S ==> IP / ICMP 72.14.198.57 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:32236 > 64.12.89.12:www S ==> IP / ICMP 194.42.48.4 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:20611 > 212.27.48.10:www S ==> IP / ICMP 138.187.130.73 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:40775 > 213.251.178.32:www S ==> IP / ICMP 138.187.130.73 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror
IP / TCP 192.168.0.2:57456 > 64.233.183.104:www S ==> IP / ICMP 64.233.174.34 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror / Padding
IP / TCP 192.168.0.2:56892 > 64.12.89.12:www S ==> IP / ICMP 212.74.84.242 > 192.168.0.2 time-exceeded 0 / IPerror / TCPerror

On lance un nouveau traceroute multiple mais cette fois juste vers les FQDN "www.free.fr" et "www.secuobs.com" avec un ttl toujours égal à 10, les réponses positives sont toujours stockées dans la variable sn alors que les enregistrements sans réponse vont dans la variable unans (pour unanswered) :

>>> sn,unans=traceroute(["www.free.fr","www.secuobs.com"],maxttl=10)
Begin emission:
***********Finished to send 20 packets.
****
Received 15 packets, got 15 answers, remaining 5 packets
212.27.48.10:tcp80 213.251.178.32:tcp80
1 192.168.0.1 11 -
2 192.168.2.1 11 -
3 192.168.1.1 11 -
4 - 81.62.144.1 11
5 195.186.123.1 11 -
6 195.186.0.229 11 195.186.0.229 11
7 195.186.0.234 11 195.186.0.234 11
8 138.187.129.45 11 138.187.129.45 11
9 138.187.130.73 11 138.187.130.73 11
10 138.187.129.10 11 138.187.129.10 11

A partir de ces résultats et à l'aide de l'attribut graph() on peut dès lors générer un graphique du routage emprunté par les flux de données afin d'arriver à ces deux différentes destinations et cela depuis la machine de test utilisée pour ce document :

>>> sn.graph()

On peut observer sur le graphique ci-dessous ces différents résultats de routage :

Scapy.jpg

On aurait également pû enregistrer le graphique généré directement vers un fichier image du système de fichier de notre système d'exploitation (ici dans le répertoire /tmp pour un format d'image en svg et un fichier image du nom de graph.svg) :

>>> sn.graph(target="> /tmp/graph.svg")

On vérifie l'existence et de la date de création du fichier graph.svg contenu dans le répertoire /tmp :

root@casper:~# ls -l /tmp/graph.svg
-rw-r--r-- 1 root root 11103 2007-09-24 15:56 /tmp/graph.svg

On peut visualiser ce fichier image graph.svg de la manière suivante avec la commande système display qui fait partie de la suite ImageMagick :

root@casper:~# display /tmp/graph.svg

Pour générer un graphique de ces résultats vers un fichier image au format JPEG il est nécessaire d'utiliser la commande suivante :

>>> sn.graph(target="> /tmp/graph.jpg")

On vérifie que le graphique généré au format JPEG vers le fichier image graph.jpg dans le répertoire /tmp est bien présent :

root@casper:~# ls -l /tmp/graph.jpg
-rw-r--r-- 1 root root 20997 2007-09-24 16:46 /tmp/graph.jpg

On peut visualiser ce fichier image /tmp/graph.jpg toujours avec la fonction display d'Image Magick :

root@casper:~# display /tmp/graph.jpg

On peut aussi imprimer directement ces graphiques à la seule condition qu'une imprimante soit branchée sur la machine et configurée au niveau du système d'exploitation permettant à celle-ci de fonctionner (ici une imprimante de type postcript) :

>>> sn.graph(type="ps",target="| lp")

On réalise maintenant un nouveau traceroute simple vers le FQDN "www.free.fr" avec un ttl équivalent à 10 :

>>> sn,unans=traceroute(["www.free.fr"],maxttl=10)
Begin emission:
********Finished to send 10 packets.
**
Received 10 packets, got 10 answers, remaining 0 packets
212.27.48.10:tcp80
1 192.168.0.1 11
2 192.168.2.1 11
3 192.168.1.1 11
4 81.62.144.1 11
5 195.186.123.1 11
6 195.186.0.229 11
7 195.186.0.234 11
8 138.187.129.45 11
9 138.187.130.73 11
10 138.187.129.10 11

La représentation des résultats de ce traceroute dans un graphique, cette fois en 3D, peut s'effectuer de la manière suivante grâce au plugin visual de vpython :

>>> sn.trace3D()

Le plugin visual de vpython, que l'on utilise ici, permet d'effectuer un zoom sur ce graphique à l'aide du 3e bouton de la souris ; si ce bouton est émulé, il suffit alors d'appuyer simultannément sur le bouton gauche et le bouton droit de la souris ou du touchpad. On peut également déplacer la représentation graphique avec le bouton gauche de la souris et effectuer des inclinaisons avec le bouton droit de celle-ci :

Scapy2.jpg

On effectue cette fois un traceroute multiple vers les FQDN "www.free.fr", "www.google.fr" et "www.microsoft.com" avec un ttl équivalent à 10 ; les réponses sont toujours stockées dans la variables sn alors que les enregistrements, qui correspondent à des non réponses, sont contenus dans la variable unans :

>>> sn,unans=traceroute(["www.free.fr","www.google.fr",
"www.microsoft.com"],maxttl=10)
Begin emission:
************************Finished to send 30 packets.
*
Received 25 packets, got 25 answers, remaining 5 packets
207.46.19.254:tcp80 212.27.48.10:tcp80 64.233.183.99:tcp80
1 192.168.0.1 11 192.168.0.1 11 192.168.0.1 11
2 192.168.2.1 11 192.168.2.1 11 192.168.2.1 11
3 192.168.1.1 11 192.168.1.1 11 192.168.1.1 11
4 81.62.144.1 11 - -
5 195.186.123.1 11 195.186.123.1 11 195.186.123.1 11
6 195.186.0.229 11 195.186.0.229 11 195.186.0.229 11
7 195.186.0.254 11 195.186.0.234 11 195.186.0.254 11
8 138.187.159.54 11 - 138.187.129.46 11
9 138.187.159.18 11 - 138.187.129.74 11
10 164.128.236.38 11 138.187.129.10 11 -

On trace le graphique en 3D, à l'aide de l'attribut trace3D(), des résultats qui sont contenus dans la variables sn et qui correspondent à ce traceroute multiple :

>>> sn.trace3D()

On obtient le résultat suivant comme graphique 3D des résultats :

Scapy3.jpg

6 Manipulations de paquets et de trames

Scapy est avant tout un utilitaire permettant de forger, recevoir et émettre des paquets et des trames de données sur un réseau. On retrouve dans cette partie les nombreuses fonctions disponibles à cet effet à travers différents exemples comme la réalisation d'un ping ICMP ou d'un scan de port.


On affiche maintenant 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 = ('')

On affiche également les spécificités du protocole ICMP toujours avec la commande ls() :

>>> ls(ICMP)
type : ByteEnumField = (8)
code : ByteField = (0)
chksum : XShortField = (None)
id : XShortField = (0)
seq : XShortField = (0)

On effectue un ping en destination de la machine dont l'adresse ip est équivalente à 192.168.0.2 ; dans un premier temps on visualise le paquet ICMP correspondant à ce ping :

>>> IP(dst='192.168.0.2')/ICMP()
<IP frag=0 proto=icmp dst=192.168.0.2 |<ICMP |>>

On affiche la documentation de la fonction sr1() à l'aide la fonction lsc() :

>>> lsc(sr1)
Send packets at layer 3 and return only the first answer
nofilter: put 1 to avoid use of bpf filters
retry: if positive, how many times to resend unanswered packets
if negative, how many times to retry when no more packets are answered
timeout: how much time to wait after the last packet has been sent
verbose: set verbosity level
multi: whether to accept multiple answers for the same stimulus
filter: provide a BPF filter
iface: listen answers only on the given interface

On envoie cette fois le même paquet ICMP et on accepte de ne recevoir qu'un seul paquet en réponse à l'aide de la commande sr1() :

>>> sr1(IP(dst='192.168.0.1')/ICMP())
Begin emission:
.Finished to send 1 packets.
*
Received 2 packets, got 1 answers, remaining 0 packets
<IP version=4L ihl=5L tos=0x0 len=28 id=31775 flags= frag=0L ttl=128 proto=icmp chksum=0x3d6e src=192.168.0.1 dst=192.168.0.2 options='' |<ICMP type=echo-reply code=0 chksum=0xffff id=0x0 seq=0x0 |>>

Le ping est bien effectif et il est semblable aux résultats de la commande système ping :

root@casper:~# ping -c 1 192.168.0.1
PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.
64 bytes from 192.168.0.1: icmp_seq=1 ttl=128 time=1.40 ms
 
--- 192.168.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.409/1.409/1.409/0.000 ms

On constate avec la fonction sr1() que l'on ne reçoit bien et qu'on n'enregistre dans les résultats qu'un seul paquet en réponse. Cette même fonction peut également servir à réaliser un scan de port ; on envoie un paquet TCP/IP vers www.secuobs.com à destination du port 80 avec le flags TCP Syn activé qui indique qu'on demande à établir une connexion non préalable :

>>> sr1(IP(dst="www.secuobs.com")/TCP(dport=80, flags="S"))
Begin emission:
Finished to send 1 packets.
*
Received 1 packets, got 1 answers, remaining 0 packets
<IP version=4L ihl=5L tos=0x0 len=44 id=0 flags=DF frag=0L ttl=52 proto=tcp chk sum=0xfe05 src=213.251.178.32 dst=192.168.0.2 options='' |<TCP sport=www dport= ftp_data seq=466528407 ack=1 dataofs=6L reserved=0L flags=SA window=5840 chksum= 0x73bd urgptr=0 options=[('MSS', 1452)] |>>

On constate ici que l'on a reçu un paquet en réponse, on peut en conclure que le port 80 (www) est ouvert sur le serveur dont l'adresse IP est affectée au FQDN "www.secuobs.com" ; on effectue un deuxième essai sur le port 22 (SSH) :

>>> sr1(IP(dst="www.secuobs.com")/TCP(dport=22, flags="S"))
Begin emission:
.Finished to send 1 packets.
*
Received 2 packets, got 1 answers, remaining 0 packets
<IP version=4L ihl=5L tos=0x0 len=44 id=0 flags=DF frag=0L ttl=52 proto=tcp chksum=0xfe05 src=213.251.178.32 dst=192.168.0.2 options='' |<TCP sport=ssh dport=ftp_data seq=771588344 ack=1 dataofs=6L reserved=0L flags=SA window=5840 chksum=0x8967 urgptr=0 options=[('MSS', 1452)] |>>

On reçoit également une réponse le port 22 est ouvert ; on réalise un dernier essai sur le port 53 (DNS) :

>>> sr1(IP(dst="www.secuobs.com")/TCP(dport=53, flags="S"))
Begin emission:
Finished to send 1 packets.
..............................
Received 30 packets, got 0 answers, remaining 1 packets

On en conclut que le port 53 n'est pas ouvert sur ce serveur, cela ne veut pas forcément dire qu'un serveur DNS ne tourne pas sur ce port, on peut juste en conclure que des rêgles de filtrage sont présentes afin d'empêcher les connexions extérieures vers ce port 53.

On affiche maintenant, à l'aide de la commande lsc(), les détails des options de la fonction sr() qui permet quant à elle de recevoir et de stocker plus d'un paquet en réponse à ceux préalablement envoyés :

>>> lsc(sr)
Send and receive packets at layer 3
nofilter: put 1 to avoid use of bpf filters
retry: if positive, how many times to resend unanswered packets
if negative, how many times to retry when no more packets are answered
timeout: how much time to wait after the last packet has been sent
verbose: set verbosity level
multi: whether to accept multiple answers for the same stimulus
filter: provide a BPF filter
iface: listen answers only on the given interface

On affiche les détails de la fonction send() à l'aide de la commande lsc() :

>>> lsc(send)
Send packets at layer 3
send(packets, [inter=0], [loop=0], [verbose=conf.verb]) -> None

On peut par ailleurs utiliser la fonction srloop de scapy afin d'exécuter une boucle sur l'envoi d'un paquets de données (ici de l'ICMP à destination de la machine dont l'adresse IP est égale à 192.168.0.2) :

>>> srloop(IP(dst='192.168.0.2')/ICMP())
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
 
Sent 9 packets, received 9 packets. 100.0% hits.
(<Results: UDP:0 TCP:0 ICMP:9 Other:0>, <PacketList: UDP:0 TCP:0 ICMP:0 Other:0>)

Ctrl C pour arrêter le srloop, ici on a envoyé et reçu 9 paquets ; on peut également affecter à srloop() un numéro d'envoi limité avec le paramètre count, ici 10 paquets :

>>> srloop(IP(dst='192.168.0.2')/ICMP(),count=10)
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
RECV 1: IP / ICMP 192.168.0.1 > 192.168.0.2 echo-reply 0
 
Sent 10 packets, received 10 packets. 100.0% hits.
(<Results: UDP:0 TCP:0 ICMP:10 Other:0>, <PacketList: UDP:0 TCP:0 ICMP:0 Other:0>)

Les fonctions sr, sr1, send et srloop envoient et/ou recoivent des paquets au niveau réseau soit la couche 3 du modèle OSI soit celle présente juste avant (émission) ou après (réception) le niveau correspondant à liaison de donnée qui se trouve en seconde position dans ce modèle à 7 couches.

La couche liaison de données se situe donc juste avant (émission) ou après (réception) le niveau correspondant à la couche physique du modèle OSI.

L'équivalent de ces fonctions du niveau réseau pour le niveau liaison de données dans Scapy sont les fonctions srp, srp1, srploop et sendp qui forgent, recoivent et/ou envoient des trames de données et non plus seulement des paquets de données sur le réseau ; à noter qu'au niveau de la couche physique du modèle OSI on parle en octet et non plus en paquet ou en trames de données.

On affiche la documentation de srp() :

>>> lsc(srp)
Send and receive packets at layer 2
nofilter: put 1 to avoid use of bpf filters
retry: if positive, how many times to resend unanswered packets
if negative, how many times to retry when no more packets are answered
timeout: how much time to wait after the last packet has been sent
verbose: set verbosity level
multi: whether to accept multiple answers for the same stimulus
filter: provide a BPF filter
iface: work only on the given interface

Soit la trame suivante :

>>> Ether()/IP(dst="www.secuobs.com")/TCP(dport=[80,443])/"GET / HTTP/1.0 \n\n"
Ether()/IP(dst="www.secuobs.com")/TCP(dport=[80,443])/"GET / HTTP/1.0 \n\n"
<Ether type=IPv4 |<IP frag=0 proto=tcp dst=Net('www.secuobs.com') |<TCP dport=['www', 'https'] |<Raw load='GET / HTTP/1.0 \n\n' |>>>>

On affecte cette trame à la variable sn :

>>> sn=Ether()/IP(dst="www.secuobs.com")/TCP(dport=[80,443])/"GET / HTTP/1.0 \n\n"
 
>>> sn
<Ether type=0x800 |<IP frag=0 proto=tcp dst=Net('www.secuobs.com') |<TCP dport=['www', 'https'] |<Raw load='GET / HTTP/1.0 \n\n' |>>>>

A l'aide de la fonction srp on envoie la trame contenu dans la variable sn :

>>> srp(sn)
Begin emission:
.Finished to send 2 packets.
**
Received 3 packets, got 2 answers, remaining 0 packets
(<Results: UDP:0 TCP:2 ICMP:0 Other:0>, <Unanswered: UDP:0 TCP:0 ICMP:0 Other:0>)

On aurait également pû affecter certaines valeurs à l'envoi de la trame comme le nombre de fois où le nombre de fois où elles doivent être réémises après une réponse négative et cela grâce au paramètre retry (ici 3 fois), l'intervalle de temps entre l'envoi de deux trames avec inter (ici 1) ou la durée limite (ici 2) à attendre après l'envoi de la dernière trame avec timeout (l'ensemble de ces paramètres étant également disponibles pour l'envoi de paquets via la fonction sr()) :

>>> srp(Ether()/IP(dst="www.secuobs.com")/TCP(dport=[80,443])/"GET / HTTP/1.0 \n\n",inter=1,retry=-3,timeout=2)
Begin emission:
...Finished to send 2 packets.
*..Begin emission:
..Finished to send 1 packets.
*
Received 9 packets, got 2 answers, remaining 0 packets
(<Results: UDP:0 TCP:2 ICMP:0 Other:0>, <Unanswered: UDP:0 TCP:0 ICMP:0 Other:0>)

7 Orientation et représentation objet

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 :

<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=<DNSQR qname='exoscan.net.' qtype=A qclass=IN |> an=<DNSRR rrname='exoscan.net.' type=A rclass=IN ttl=3469 rdata='213.186.41.29' |> ns=<DNSRR rrname='exoscan.net.' type=NS rclass=IN ttl=28243 rdata='ns7.gandi.net.' |<DNSRR rrname='exoscan.net.' type=NS rclass=IN ttl=28243 rdata='custom2.gandi.net.' |>> ar=<DNSRR rrname='ns7.gandi.net.' type=A rclass=IN ttl=643 rdata='217.70.177.44' |> |>>>

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()
<IP |>
>>> a=IP(dst="192.168.0.2")
>>> a
<IP dst=192.168.0.2 |>
>>> 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
<IP ttl=10 dst=192.168.0.2 |>
>>> a
<IP ttl=10 dst=192.168.0.2 |>
>>> del a.ttl
>>> a
<IP dst=192.168.0.2 |>
>>> 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()
<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(_)
<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 |>

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
<IP dst=Net('www.exoscan.net') |>

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()
<IP frag=0 proto=tcp dst=Net('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()
<Ether type=0x800 |<IP frag=0 proto=tcp dst=Net('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])
<Ether type=0x800 |<IP frag=0 proto=tcp dst=Net('www.exoscan.net') |<TCP dport=['www', 'https'] |>>>
 
>>> 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(_)
<Ether dst=00:13:f7:78:cf:ea src=00:0f:b5:0e:89:4a type=0x800 |<IP version=4L ihl=5L tos=0x0 len=40 id=1 flags= frag=0L ttl=64 proto=tcp chksum=0xbb4d src=192.168.0.2 dst=213.186.41.29 options='' |<TCP sport=ftp_data dport=www seq=0 ack=0 dataofs=5L reserved=0L flags=S window=8192 chksum=0xcffc urgptr=0 |>>>

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"
<Ether type=0x800 |<IP frag=0 proto=tcp dst=Net('www.exoscan.net') |<TCP dport=['www', 'https'] |<Raw load='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(_)
<Ether dst=00:0f:b5:0e:89:4a src=00:15:6d:53:1e:87 type=IPv4 |<IP version=4L ihl=5L tos=0x0 len=57 id=1 flags= frag=0L ttl=64 proto=tcp chksum=0xbb3c src=192.168.0.2 dst=213.186.41.29 options='' |<TCP sport=ftp_data dport=www seq=0 ack=0 dataofs=5L reserved=0L flags=S window=8192 chksum=0xe155 urgptr=0 options=[] |<Raw load='GET / HTTP/1.0 \n\n' |>>>>

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 _ :

>>> _
<Ether dst=00:0f:b5:0e:89:4a src=00:15:6d:53:1e:87 type=IPv4 |<IP ihl=5L len=57 frag=0 proto=tcp chksum=0xbb3c src=192.168.0.2 dst=213.186.41.29 |<TCP dataofs=5L chksum=0xe155 options=[] |<Raw load='GET / HTTP/1.0 \n\n' |>>><>

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'

8 Compléments et webographie

Cette partie du dossier consacré à Scapy permet de retrouver l'ensemble des adresses web relatives à ce dossier et à l'utilisation de Scapy. On y retrouve également un exemple d'outil propre développé à l'aide de Scapy et du langage Python soit pour notre cas un scanner de port TCP.

On peut retrouver différents slide au format PDF à propos de Scapy dont ceux de la conférence PacSec Core05, Hack.lu 2005, Summerschool Applied IT Security 2005,T2 2005, CanSecWest Core05 et LSM 2003.


Pour les utilisateurs des systèmes d'exploitation de type Microsoft Windows on peut récupérer le portage Windows de Scapy ( alors que les utilisateurs des systèmes d'exploitation OpenBSD peuvent lire ce document pour l'installation.

Si on souhaite réaliser des scripts spécifiques avec Scapy il est possible de consulter le document prévu à cet effet sur le site officiel de Scapy, SecDev.org. Par exemple si l'on veut réaliser un scanner de port TCP le script en python correspondant devrait ressembler plus ou moins à cela (ici pour les ports 22 à 25 compris) :

#!/usr/bin/env python
 
import sys
from scapy import *
 
target = sys.argv[1]
fl = 22
 
while (fl<=25):
   p=sr1(IP(dst=target)/TCP(dport=fl, flags="S"),retry=0,timeout=1)
   if p:
     print "\n Le port " + str(fl) + " TCP est ouvert sur " + str(target) + "\n"
   else:
     print "\n Le port " + str(fl) + " TCP n'est pas ouvert sur " + str(target) + "\n"
   fl = fl + 1

Ce script on l'enregistre sous le nom scan.py (à noter que le fichier doit être dans le même répertoire que le fichier scapy.py) que l'on va lancer avec l'interpréteur python en passant l'adresse ip ou le FQDN de la machine que l'on veut définir comme cible du scan de port soit de la manière suivante :

root@casper:~# python scan.py www.secuobs.com
 
Begin emission:
.......................Finished to send 1 packets.
.*
Received 25 packets, got 1 answers, remaining 0 packets
 
Le port 22 TCP est ouvert sur www.secuobs.com
 
Begin emission:
..Finished to send 1 packets.
.............................
Received 31 packets, got 0 answers, remaining 1 packets
 
Le port 23 TCP n'est pas ouvert sur www.secuobs.com
 
Begin emission:
..Finished to send 1 packets.
................................
Received 34 packets, got 0 answers, remaining 1 packets
 
Le port 24 TCP n'est pas ouvert sur www.secuobs.com
 
Begin emission:
..Finished to send 1 packets.
...*
Received 6 packets, got 1 answers, remaining 0 packets
 
Le port 25 TCP est ouvert sur www.secuobs.com

Le même script pour tester les 1024 premiers ports :

#!/usr/bin/env python
 
import sys
from scapy import *
 
target = sys.argv[1]
fl = 1
 
while (fl<=1024):
   p=sr1(IP(dst=target)/TCP(dport=fl, flags="S"),retry=0,timeout=1)
   if p:
     print "\n Le port " + str(fl) + " TCP est ouvert sur " + str(target) + "\n"
   else:
     print "\n Le port " + str(fl) + " TCP n'est pas ouvert sur " + str(target) + "\n"
   fl = fl + 1

Vu le nombre de ports à tester et la longueur du résultat il est préférable de le lancer de la manière suivante :

root@casper:~# python scanfull.py www.secuobs.com > portscan_full_secuobs

On peut tout de même visualiser le résultat en temps réel de la façon suivante :

root@casper:/trash# tail -f portscan_full_secuobs
Received 1 packets, got 0 answers, remaining 1 packets
 
Le port 1 TCP n'est pas ouvert sur www.secuobs.com
 
 
Received 0 packets, got 0 answers, remaining 1 packets
 
Le port 2 TCP n'est pas ouvert sur www.secuobs.com
 
 
Received 0 packets, got 0 answers, remaining 1 packets
 
Le port 3 TCP n'est pas ouvert sur www.secuobs.com

Vérification des ports ouverts :

root@casper:~# grep " est ouvert" portscan_full_secuobs
Le port 22 TCP est ouvert sur www.secuobs.com
Le port 25 TCP est ouvert sur www.secuobs.com
Le port 80 TCP est ouvert sur www.secuobs.com
Le port 443 TCP est ouvert sur www.secuobs.com
Le port 995 TCP est ouvert sur www.secuobs.com

Le résultat avec NMAP :

root@casper:~# nmap -P0 -sS www.secuobs.com
 
Starting Nmap 4.20 at 2007-10-02 17:19 CEST
Interesting ports on ns21533.ovh.net (213.251.178.32):
Not shown: 1691 filtered ports
PORT STATE SERVICE
22/tcp open ssh
25/tcp open smtp
80/tcp open http
443/tcp open https
995/tcp open pop3s

A savoir qu'il peut être nécessaire d'ajuster la valeur du timeout dans la ligne "p=sr1(IP(dst=target)/TCP(dport=fl, flags="S"),retry=0,timeout=1)" en fonction des localisations des différentes machines en présence (source et destination) afin d'obtenir des résultats satisfaisants.

Un chapitre entier consacré à Scapy a été écrit par l'auteur lui-même, Philippe Biondi ; ce chapitre est publié dans l'ouvrage Security Power Tools ; de 856 pages édité par O'Reilly Media ( ISBN-10 : 0596009631 ; ISBN-13 978-0596009632 ). );

9 Ressources

http://www.secuobs.com/news/01102007-scapy1.shtml