Scapy : Trames et paquets de données
Contents
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 :
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 :
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 :
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 ). );