Introduzione a Scapy
by admin on Mar.18, 2009, under Hacking, Networking, Sicurezza, Tools
Scapy è un potente tool per la manipolazione dei pacchetti.
E’ infatti in grado di assemblare o decodificare i pacchetti di un ampio numero di protocolli, inviarli, catturarli, filtrare richieste e risposte, e molto altro ancora.
Scapy non è sicuramente indicato per utenti novizi di Linux/UNIX, dato che richiede una conoscenza non superficiale dei protocolli di rete, dei layers e di altri concetti cardine del networking.
E’ possibile utilizzarlo per tanti scopi, ad esempio per testare la sicurezza della propria rete, ma può essere anche impegato per compiti di scansione, tracerouting, probing, attacco o network discovery. Inoltre può effettuare un mucchio di operazioni che la maggior parte degli altri tools non sono in grado di fare, come l’invio di invalid frames, l’iniezione di propri frames 802.11, l’ARP cache poisoning, la decodifica di un canale WEP crittato, etc.
Ciò che lo distingue dalla gran massa di altri network tools è che al contrario di questi, non restituisce una interpretazione del risultato, bensi l’esatta decodifica del risultato stesso.
Per fare un esempio pratico, uno scanner normalmente restituisce risultati del tipo “questa porta è aperta” invece di “ho ricevuto un SYN/ACK”. E’ una risposta sicuramente più comprensibile, specialmente per un principiante, ma spesso questa licenza interpretativa da parte del programma impedisce di comprendere quello che realmente accade in determinate circostanze.
Scapy tenta di superare tale limitazione, restituendo sempre, in risposta ad un qualsiasi test effettuato, dei pacchetti interamente decodificati.
Scapy inoltre separa nettamente la fase della raccolta delle informazioni da quella relativa all’analisi dei risultati: i pacchetti di ritorno, nell’ambito di qualsiasi test, contengono più informazioni del semplice risultato del test, e possono essere successivamente utilizzati per ulteriori indagini, senza dover effettuare un nuovo test.
Ciò riduce la quantità del traffico di rete generato e secondariamente il rischio di attivare qualche IDS.
Inoltre, affinare una interpretazione senza dover effettuare un nuovo test assicura che l’oggetto del test non sia cambiato nel frattempo, rendendo l’analisi più consistente.
Scapy ha, come è logico che sia, anche delle intrinseche limitazioni.
Non è ad esempio progettato per trattare un pesante throughput.
E’ scritto in Python, possiede molti livelli di astrazione, e questi fattori lo penalizzano notevolmente dal punto di vista prestazionale.
Inoltre consuma una notevole quantità di risorse.
Attualmente sono disponibili due differenti versioni di Scapy:
- Scapy v1.x. Consiste di un unico file e opera con Python 2.4. Di essa esistono tra l’altro packages per alcune distribuzioni. Ultima versione la 1.2.2.
- Scapy v2.x. Attuale versione di sviluppo, aggiunge diverse funzionalità rispetto alla precedente (es. IPv6). Scapy v2 necessita di Python 2.5.
Per installare quest’ultima:
$ cd /tmp
$ wget http://scapy.net
$ unzip scapy-latest.zip
$ cd scapy-2.*
$ sudo python setup.py install
Per poter sfruttare alcune speciali funzionalità occorre installare ulteriore software.
Nella fattispecie, relativamente ad una distribuzione Debian/Ubuntu:
$ sudo apt-get install tcpdump graphviz imagemagick python-gnuplot python-crypto python-pyx
Scapy non è una applicazione shell di tipo tradizionale. Una volta lanciato, rende disponibile all’utilizzatore una propria interfaccia a riga di comando.
In realtà rende disponibile l’interprete python, cui fornisce un certo numero di oggetti e funzioni che rendono possibile la manipolazione di pacchetti. Cio implica che durante tale fase si ha a disposizione tutta la potenza del linguaggio, con tutte le sue strutture ed il suo set di comandi.
# scapy
Welcome to Scapy (2.0.1)
>>>
é possibile richiamare un elenco di comandi specifici con:
>>> lsc()
arpcachepoison : Poison target’s cache with (your MAC,victim’s IP) couple
arping : Send ARP who-has requests to determine which hosts are up
bind_layers : Bind 2 layers on some specific fields’ values
corrupt_bits : Flip a given percentage or number of bits from a string
corrupt_bytes : Corrupt a given percentage or number of bytes from a string
defrag : defrag(plist) -> ([not fragmented], [defragmented],
defragment : defrag(plist) -> plist defragmented as much as possible
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”
etherleak : Exploit Etherleak flaw
fragment : Fragment a big IP datagram
fuzz : Transform a layer into a fuzzy layer by replacing some default values by random objects
getmacbyip : Return MAC address corresponding to a given IP address
hexdiff : Show differences between 2 binary strings
hexdump : –
hexedit : –
is_promisc : Try to guess if target is in Promisc mode. The target is provided by its ip.
linehexdump : –
ls : List available layers, or infos on a given layer
promiscping : Send ARP who-has requests to determine which hosts are in promiscuous mode
rdpcap : Read a pcap file and return a packet list
send : Send packets at layer 3
sendp : Send packets at layer 2
sendpfast : Send packets at layer 2 using tcpreplay for performance
sniff : Sniff packets
split_layers : Split 2 layers previously bound
sr : Send and receive packets at layer 3
sr1 : Send packets at layer 3 and return only the first answer
srbt : send and receive using a bluetooth socket
srbt1 : send and receive 1 packet using a bluetooth socket
srflood : Flood and receive packets at layer 3
srloop : Send a packet at layer 3 in loop and print the answer each time
srp : Send and receive packets at layer 2
srp1 : Send and receive packets at layer 2 and return only the first answer
srpflood : Flood and receive packets at layer 2
srploop : Send a packet at layer 2 in loop and print the answer each time
tshark : Sniff packets and print them calling pkt.show(), a bit like text wireshark
wireshark : Run wireshark on a list of packets
wrpcap : Write a list of packets to a pcap file
Se con la precedente versione di Scapy (1.2.x) era possibile ottenere ulteriori informazioni su uno specifico comando, ad esempio:
>>> lsc(traceroute)
Instant TCP traceroute
traceroute(target, [maxttl=30,] [dport=80,] [sport=80,] [verbose=conf.verb]) -> None
ora, con la versione 2.0.x lsc() non accetta più argomenti, e si ottengono le stesse informazioni con:
>>> help(traceroute)
Help on function traceroute in module scapy.layers.inet:
traceroute(target, dport=80, minttl=1, maxttl=30, sport=<RandShort>, l4=None, filter=None, timeout=2, verbose=None, **kargs)
Instant TCP traceroute
traceroute(target, [maxttl=30,] [dport=80,] [sport=80,] [verbose=conf.verb]) -> None
Per fare un altro esempio, un richiamo della funzione ls() fornisce un elenco dei protocolli supportati
>>> ls()
ARP : ARP
ASN1_Packet : None
BOOTP : BOOTP
CookedLinux : cooked linux
DHCP : DHCP options
DHCP6 : DHCPv6 Generic Message)
DHCP6OptAuth : DHCP6 Option - Authentication
…………………………
…………………………
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
Dot11QoS : 802.11 QoS
Dot11ReassoReq : 802.11 Reassociation Request
Dot11ReassoResp : 802.11 Reassociation Response
Dot11WEP : 802.11 WEP packet
…………………………
…………………………
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
USER_CLASS_DATA : user class data
VENDOR_CLASS_DATA : vendor class data
VENDOR_SPECIFIC_OPTION : vendor specific option data
X509Cert : None
X509RDN : None
X509v3Ext : None
_DHCP6GuessPayload : None
_DHCP6OptGuessPayload : None
_ICMPv6 : ICMPv6 dummy class
_ICMPv6Error : ICMPv6 errors dummy class
_ICMPv6ML : ICMPv6 dummy class
_IPv6ExtHdr : Abstract IPV6 Option Header
_MobilityHeader : Dummy IPv6 Mobility Header
>>>
Passando alla stessa funzione come argomento il nome di un layer, si ottiene invece la lista dei suoi specifici campi:
>>> ls(ICMP)
type : ByteEnumField = (8)
code : ByteField = (0)
chksum : XShortField = (None)
id : XShortField = (0)
seq : XShortField = (0)
>>> ls(Skinny)
len : LEIntField = (0)
res : LEIntField = (0)
msg : LEIntEnumField = (0)
>>> ls(TCP)
sport : ShortEnumField = (20)
dport : ShortEnumField = (80)
seq : IntField = (0)
ack : IntField = (0)
dataofs : BitField = (None)
reserved : BitField = (0)
flags : FlagsField = (2)
window : ShortField = (8192)
chksum : XShortField = (None)
urgptr : ShortField = (0)
options : TCPOptionsField = ({})
>>>
In particolare, nell’output prodotto la prima colonna rappresenta il nome del campo, la seconda il nome della classe utilizzata per gestirne il valore, mentre la terza il valore predefinito.
Un eventuale None qui presente significa che nessun valore è attualmente assegnato, ma verrà calcolato durante l’assemblaggio.
Un pacchetto di rete è suddivisibile in layers separati, e ciascuno di essi viene rappresentato da una istanza Python, perciò la manipolazione avviene tramite gli attributi ed i metodi di tale istanza.
La creazione di un pacchetto avviene attraverso la creazione di una pila di tante istanze quanti sono i layers coinvolti.
La separazione fra un layer e quelli adiacenti viene rappresentata dal carattere “/”.
Ad esempio:
>>> udp=IP(dst=”192.168.1.33″)/UDP()
con quanto precede abbiamo forgiato un pacchetto IP con destinazione 192.168.1.33 e con un payload rappresentato da un datagramma UDP.
mentre con:
>>> udp=IP(dst=”192.168.1.0/30″,ttl=(1,20))/UDP()
ne abbiamo forgiato addirittura 80, con ttl variabile da 1 a 20 e con quattro possibili destinazioni.
Ancora:
>>> a=IP()
>>> a.ttl=32
>>> b=TCP(dport=25)
>>> c=a/b
>>> c
<IP frag=0 ttl=32 proto=TCP |<TCP dport=smtp |>>
con la prima riga si è istanziato la classe IP(), originando l’oggetto a.
Con la seconda viene attribuito il valore 32 al suo attributo ttl.
Con la terza viene istanziata la classe TCP(), attribuendo direttamente il valore 25 all’attributo dport dell’istanza.
Con la quarta viene creato un ulteriore oggetto impilando i precedenti.
La quinta riga richiede la visualizzazione del risultato, che avviene puntualmente nella riga successiva.
Si è detto in precedenza che la funzione ls() mostra informazioni riguardo ad uno specifico layer (ovvero una classe).
Essa mostra anche le informazioni relative all’oggetto generato dall’istanza, come nel caso seguente.
>>> ls(c)
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 = 32 (64)
proto : ByteEnumField = 6 (0)
chksum : XShortField = None (None)
src : Emph = ‘127.0.0.1′ (None)
dst : Emph = ‘127.0.0.1′ (’127.0.0.1′)
options : IPoptionsField = ” (”)
–
sport : ShortEnumField = 20 (20)
dport : ShortEnumField = 25 (80)
seq : IntField = 0 (0)
ack : IntField = 0 (0)
dataofs : BitField = None (None)
reserved : BitField = 0 (0)
flags : FlagsField = 2 (2)
window : ShortField = 8192 (8192)
chksum : XShortField = None (None)
urgptr : ShortField = 0 (0)
options : TCPOptionsField = {} ({})
>>>
Ogni pacchetto può essere assemblato, oppure dissezionato:
>>> str(IP())
‘Ex00×00x14×00x01×00x00@x00|xe7×7fx00×00x01×7fx00×00x01′
>>> IP(_)
<IP version=4L ihl=5L tos=0×0 len=20 id=1 flags= frag=0L ttl=64
proto=ip chksum=0×7ce7 src=127.0.0.1 dst=127.0.0.1 |>
>>>
>>> a=Ether()/IP(dst=”www.voipandhack.it”)/TCP()/”GET /index.html HTTP/1.0 nn”
>>>
>>> hexdump(a)
0000 00 15 E9 F9 15 C6 00 15 AF 1E 44 65 08 00 45 00 ……….De..E.
0010 00 43 00 01 00 00 40 06 D6 3E C0 A8 01 0A 51 1F .C….@..>….Q.
0020 91 A4 00 14 00 50 00 00 00 00 00 00 00 00 50 02 …..P……..P.
0030 20 00 19 3C 00 00 47 45 54 20 2F 69 6E 64 65 78 ..<..GET /index
0040 2E 68 74 6D 6C 20 48 54 54 50 2F 31 2E 30 20 0A .html HTTP/1.0 .
0050 0A .
>>>
in Python _ (il carattere underscore) rappresenta l’ultimo risultato
Il metodo hide_defaults() esclude dalla visualizzazione tutti i campi i cui valori corrispondono a quelli di default:
>>> c=Ether(b)
>>> c
<Ether dst=00:15:e9:f9:15:c6 src=00:15:af:1e:44:65 type=0×800 |<IP version=4L ihl=5L tos=0×0 len=67 id=1 flags= frag=0L ttl=64 proto=tcp chksum=0xd63e src=192.168.1.10 dst=81.31.145.164 options=” |<TCP sport=ftp_data dport=www seq=0 ack=0 dataofs=5L reserved=0L flags=S window=8192 chksum=0×193c urgptr=0 options=[] |<Raw load=’GET /index.html HTTP/1.0 nn’ |>>>>
>>> c.hide_defaults()
>>> c
<Ether dst=00:15:e9:f9:15:c6 src=00:15:af:1e:44:65 type=0×800 |<IP ihl=5L len=67 frag=0 proto=tcp chksum=0xd63e src=192.168.1.10 dst=81.31.145.164 |<TCP dataofs=5L chksum=0×193c options=[] |<Raw load=’GET /index.html HTTP/1.0 nn’ |>>>>
>>>
Anche la funzione show() offre una visione dettagliata del pacchetto, mentre la funzione show2(), per il fatto che opera assemblando e disassemblando completamente il pacchetto, riesce a visualizzare anche quei valori. come lenght o checksum, che non sarebbe possibile conoscere in anticipo
>>> Ether.show2(c)
###[ Ethernet ]###
dst= 00:15:e9:f9:15:c6
src= 00:15:af:1e:44:65
type= 0×800
###[ IP ]###
version= 4L
ihl= 5L
tos= 0×0
len= 67
id= 1
flags=
frag= 0L
ttl= 64
proto= tcp
chksum= 0xd63e
src= 192.168.1.10
dst= 81.31.145.164
options= ”
###[ TCP ]###
sport= ftp_data
dport= www
seq= 0
ack= 0
dataofs= 5L
reserved= 0L
flags= S
window= 8192
chksum= 0×193c
urgptr= 0
options= []
###[ Raw ]###
load= ‘GET /index.html HTTP/1.0 nn’
>>>
Si noti la differenza rispetto a:
>>> c.show2
<bound method Ether.show2 of <Ether dst=00:15:e9:f9:15:c6 src=00:15:af:1e:44:65 type=0×800 |<IP ihl=5L len=67 frag=0 proto=tcp chksum=0xd63e src=192.168.1.10 dst=81.31.145.164 |<TCP dataofs=5L chksum=0×193c options=[] |<Raw load=’GET /index.html HTTP/1.0 nn’ |>>>>>
>>>
La potenza dell’interprete permette poi una visione personalizzata, come nel caso seguente, dove viene utilizzato il metodo sprintf:
>>> pkt=IP(dst=”192.168.1.33″)/UDP(dport=5060)
>>> pkt.sprintf(”Destinazione: %IP.dst% Porta: %r,UDP.dport% Servizio: %UDP.dport%”)
‘Destinazione: 192.168.1.33 Porta: 5060 Servizio: sip’
E’ possibile pure ottenere una rappresentazione grafica in formato eps/pdf del pacchetto costruito, impartendo qualcosa di simile a:
>>> pkt.psdump()

Oltre che di manipolare pacchetti, Scapy possiede la capacità di catturare pacchetti in arrivo o in transito, operando quindi come uno sniffer.
Anche qui è molto semplice ottenere una rappresentazione grafica del flusso dei vari pacchetti, come testimonia la figura seguente, ottenuta in seguito a:
>>> a=sniff(count=150)
>>> a.conversations()

Oltre a singoli pacchetti si possono generare estesi set di questi ultimi, come nel caso seguente:
>>> a=IP(dst=”bkom.net/30″)
>>> [p for p in a]
[<IP dst=82.165.55.164 |>, <IP dst=82.165.55.165 |>,
<IP dst=82.165.55.166 |>, <IP dst=82.165.55.167 |>]
>>> c=TCP(dport=[139,445])
>>> [p for p in a/c]
[<IP frag=0 proto=tcp dst=82.165.55.164 |<TCP dport=netbios_ssn |>>,
<IP frag=0 proto=tcp dst=82.165.55.164 |<TCP dport=microsoft_ds |>>,
<IP frag=0 proto=tcp dst=82.165.55.165 |<TCP dport=netbios_ssn |>>,
<IP frag=0 proto=tcp dst=82.165.55.165 |<TCP dport=microsoft_ds |>>,
<IP frag=0 proto=tcp dst=82.165.55.166 |<TCP dport=netbios_ssn |>>,
<IP frag=0 proto=tcp dst=82.165.55.166 |<TCP dport=microsoft_ds |>>,
<IP frag=0 proto=tcp dst=82.165.55.167 |<TCP dport=netbios_ssn |>>,
<IP frag=0 proto=tcp dst=82.165.55.167 |<TCP dport=microsoft_ds |>>]
>>>
In una successiva puntata vedremo come inviare i pacchetti manipolati e per quali scopi.

