Una nota massima dice che "se ascolto dimentico, se vedo ricordo, se faccio capisco", il "fare", come lo scrivere codice e non usare strumenti già pronti è la chiave per essere un buon Penetration Tester. Non è un caso che Chris Miller dice che "la differenza stra uno script kiddies e i professionisti è la mera differenza tra chi usa strumenti di altri o i propri" Ovviamente questo presuppone una profonda conoscenza di quello che si sta facendo - una tecnica di attacco particolare, i protocolli utilizzati, dei sistemi, delle aplicazioni e così via. Quindi scrivere i propri strumenti è un modo di imparare realmente quello che accade sotto al "motore" di altri strumenti e come funzionano gli attacchi. Durante il talk vedremo in particolare i raw socket su linux e come scrivere uno sniffer.
4. «Ricordatevi, la differenza tra uno
script kiddie e un professionista è la
differenza tra chi meramente utilizza
gli strumenti di altri e chi scrive i
propri»
-- Charlie Miller (Prefazione a Black Hat Python)
5. Penetration Testing e strumenti
L’OSSTMM indica che l’analista deve conoscere i propri strumenti, da
dove vengono, come funzionano e li deve aver provati prima in un
ambiente controllato.
Sono molte le implicazioni di questo concetto. Focalizziamoci su due
casi specifici. Gli hanno fatti altri, sappiamo come funzionano e se
funzionano? Possono fare danni? Contenere back door o codice
malevolo? Se li abbiamo fatti noi, conosciamo bene l’attacco che
stiamo facendo? I protocolli? I sistemi o la rete bersaglio?
6. «se ascolto dimentico, se vedo
ricordo, se faccio capisco»
-- Antico proverbio cinese || Confucio
7. Perché Python e non <inserire-altro-linguaggio>? E perché Python 2.7 e non Python
3?
https://wiki.python.org/moin/Python2orPython3
8. Perché Python
«Python è un linguaggio a ridotta
complessità, ha molte librerie di
terze parti* e un basso costo
d’ingresso. Su OSX e Linux è già
installato. Molti altri strumenti
sono già fatti in Python.»
-- TJ O'Connor (Violent Python)
https://github.com/dloss/python-pentest-tools
9. Hello World
• E’ un linguaggio interpretato
• Abbiamo tre modi principali per
farlo girare:
• Codice nella riga di comando
$ python -c "print 'Hello
World'"
• Codice in un file
$ python helloworld.py
• Codice interattivo
$ python
>>> print 'Hello World'
10. Python da Zero in tre passi
• Anzitutto va installato python (se non lo è già) e.g. su debian
$ sudo apt-get install python
• Si installa PIP (il gestore di pacchetti di python)
$ sudo apt-get install python-pip
• Si installa virtualenv – che serve per creare ambienti virtuali (rende
tutto può portabile e si evita di appesantire il sistema dove
lavoriamo)
$ sudo pip install virtualenv
http://docs.python-guide.org/en/latest/starting/installation/
https://packaging.python.org/installing/
http://docs.python-guide.org/en/latest/dev/virtualenvs/
11. Iniziamo a «codare»
• Quando si parte, a livello di prototipazione e testare quello che
facciamo, conviene utilizzare l’interprete in maniera interattiva e
vedere quello che succede, eventuali errori ecc….
• Lo scopo di questa introduzione è guidarci verso lo sviluppo di
strumenti di rete, alcune cose saranno intro, man mano che vediamo
il codice, l’importante è capire l’approccio!
• Quando ci sono delle cose nuovo le introduciamo, per alcuni alcune
cose saranno scontate, per altre nuove. Intanto si possono prendere
«così come sono» e poi fare con calma debug.
12. Use case: Sniffer HTTP con i RAW Socket
• Una delle attività tipiche che si fa sulla rete è quello di sniffare il
traffico, magari siamo già in una posizione di rete privilegiata (oppure
su un hub) e quindi non abbiamo bisogno di utilizzare tecniche
particolari per farci arrivare il traffico (quelle le vediamo dopo).
• A livello teorico dobbiamo conoscere quindi come funziona la suite di
protocolli TCP/IP e, considerando che il nostro scopo è quello di
leggere il traffico HTTP, dobbiamo partire dai protocolli che sono più
in basso.
13. La suite di protocolli TCP/IP
• Application: Servizi applicativi
per l’utente (nel nostro caso
HTTP).
• Transport: Organizza I dati per il
trasporto e la loro trasmissioni
(nel nostro caso TCP).
• Internet/Gateway: Gestisce
l’indirizzamento logico tra due
sistemi, anche quelli che ono
sono connessi direttamente,
come anche il percordo per
arrivare a questi sistemi (nel
nostro caso IPv4).
• Network : Converte le
informazioni logiche sui
dispositivi fisici (nel nostro caso
Ethernet).
+------+ +-----+ +-----+ +-----+
|Telnet| | FTP | |Voice| ... | | Application Level
+------+ +-----+ +-----+ +-----+
| | | |
+-----+ +-----+ +-----+
| TCP | | RTP | ... | | Host Level
+-----+ +-----+ +-----+
| | |
+-------------------------------+
| Internet Protocol & ICMP | Gateway Level
+-------------------------------+
|
+---------------------------+
| Local Network Protocol | Network Level
+---------------------------+
Protocol Relationships
https://tools.ietf.org/html/rfc793
15. Vediamo i pacchetti con un RAW socket
1. $ sudo python # serve sudo per l’accesso ai RAW socket
2. >>> import socket # importo il modulo per i raw socket BSD
3. >>> s = socket.socket(socket.SOCK_RAW, socket.AF_INET, socket.IPPROTO_TCP) #
creo il socket per avere i pacchetti RAW, con IP v4 e TCP
>>> packets = [] # creo una lista per contenere i miei pacchetti (poi vediamo
come ci si accede)
4. >>> for i in range(5): # prendi 5 pacchetti
5. ... packet = s.recvfrom(65565) # da notare che siamo indentati (4 spazi),
ricevi i pacchetti con un buffer passato dall’utente
6. ... packets.append(packet) # mette i pacchetti catturati nella lista
7. ...
>>> len(packets) # controllo quanti pacchetti ho effettivamente catturato
8. 5 # bene, abbiamo i nostri pacchetti, 5
9. >>> packets # vediamo il contenuto, il fatidico flusso di bit
10.[('Ex00x00LB=@x00x80x06xdex9bxc0xa8,x01xc0xa8,x81x9cx8ex00x165
xc8x856x19xfdF
Px18x00xffxcbx00x00xadx85xfd6yx06>xabxffxfa>:xe4xaex06xaex05
Zxcexf2xae7xc1xfbx9fmXxd3x84x0f:x83xb8ux11xef', ('192.168.44.1',
0)),
11.…
16. La nostra prima parte dello sniffer
1. #!/usr/bin/env python
2. import socket
3. import struct
4. s = socket.socket(socket.SOCK_RAW,
socket.AF_INET,socket.IPPROTO_TCP)
5. while 1: # qui modifico in un while
6. print s.recvfrom(65565) # stampo
direttamente
7. '''
8. Come possiamo vedere per controllare il
flusso abbiamo usato un while, in python non
abbiamo le parentesi come in C – per esempio
– ma l’indentazione, di norma deve essere
fatta con quattro spazi. Le varie regole su
come scrivere codice con stile in Pyhon sono
contenuti nella PEP-008
9. ''' https://www.python.org/dev/peps/pep-0008/
17. Andiamo in profondità
Quando abbiamo creato il socket, abbiamo visto alcune impostazioni
inserite, se le voglio sapere tutte, in modalità interattiva dopo aver
importato il modulo possiamo usare ‘dir()’ per avere la lista di elementi
che possiamo avere (e.g. le costanti) e help() per avere l’aiuto.
>>> dir(socket)
>>> help(socket)
Inoltre, dato che stiamo usando i socket BSD, quando andiamo a
cercare variabili e informazioni, possiamo fare riferimento alla
documentazione relativa e ai file sul nostro sistema.
Attenzione a cercare le informazioni su Google / Stack Overflow
18. Dissezionare i protocolli
• Con i RAW socket abbiamo sicuramente i
pacchetti ma le informazioni sono in
formato binario, per renderle leggibili
dobbiamo scrivere dei dissector per
poterle leggerle in formato «umano».
• Utilizzeremo quindi il modulo struct che
serve ad interpretare i dati binari e poterli
leggere.
• In caso di emergenza fare riferimento a:
• RFC (attenzione agli aggiornamenti)
• file di inclusione che troviamo nel nostro
sistema (l’open source sia lodato)
• man.
20. Il formato di struct
Format C Type Python type Standard size Notes
x pad byte no value
c char string of length 1 1
b signed char integer 1 (3)
B unsigned char integer 1 (3)
? _Bool bool 1 (1)
h short integer 2 (3)
H unsigned short integer 2 (3)
i int integer 4 (3)
I unsigned int integer 4 (3)
l long integer 4 (3)
L unsigned long integer 4 (3)
q long long integer 8 (2), (3)
Q unsigned long long integer 8 (2), (3)
f float float 4 (4)
d double float 8 (4)
s char[] string
p char[] string
P void * integer (5), (3)
https://docs.python.org/2/library/struct.html#format-characters
21. Dissezionare IP - /usr/include/linux/ip.h
1.struct iphdr {
2.#if defined(__LITTLE_ENDIAN_BITFIELD)
3.#elif defined (__BIG_ENDIAN_BITFIELD)
4. __u8 version:4,
ihl:4;
5.#else
#error "Please fix <asm/byteorder.h>«
#endif
__u8 tos;
__be16 tot_len;
__be16 id;
__be16 frag_off;
__u8 ttl;
__u8 protocol;
__sum16 check;
__be32 saddr;
__be32 daddr;
/*The options start here. */
6.};
Big Endian (Network) !
[0] __u8 > unsigned char > B # version ihl
[1] __u8 > unsigned char > B # tos
[2] __be16 > unsigned short > H # tot_len
[3] __be16 > unsigned short > H # id
[4] __be16 > unsigned short > H #frag_off;
[5] __u8 > unsigned char > B #ttl;
[6]__u8 > unsigned char > B # protocol;
[7]__sum16 > unsigned short > H # check;
[8]__be32 > unsigned int > I # source address
[9]__be32 > unsigned int > I # destination address
Il nostro formato corretto è !BBHHHBBHII ma possiamo anche usare !BBHHHBBH4s4s
- sostituendo i due unsigned int (32 bit) con quattro byte - per praticità di conversione
22. Dissezionare IP - Logica
1. ip_size = 20 # prendo la prima parte dell’hueader IP
2. ip_start = 0 # partiamo da 0 (secondo il tipo di RAW socket che usiamo)
3. ip_finish = ip_start + ip_size # calcolo la fine
4. ip_header_slice = packet[0][ip_start:ip_finish] # prendo il pezzo – slice – che
miinteressa, il primo campo del pacchetto e lo ‘taglio’ come mi interessa
5. ip_header_unpack = unpack('!BBHHHBBH4s4s' , ip_header_slice) # uso il formato che abbiamo
ottenuto
6. ip_version_ihl = ip_header_unpack[0] # prendo la prima parte che mi interessa
7. ip_version = ip_version_ihl >> 4 # recupero i primi bit della versione
8. ip_ihl = ip_version_ihl & 0xF # recupero la lunghezza
9. ip_header_length = ip_ihl * 4 # converto per avere la lunghezza
10.ip_tos = ip_header_unpack[1] # prendo il TOS
11.ip_lenght = ip_header_unpack[2] # prendo la lunghezza
12.ip_id = ip_header_unpack[3] # prendo l’IPID*
13.ip_fragmentation = ip_header_unpack[4] # prendo la frammentazione
14.ip_ttl = ip_header_unpack[5] # prendo il TTL
15.ip_protocol = ip_header_unpack[6] # prendo il protocollo
16.ip_checksum = ip_header_unpack[7] # prendo il checksum
17.ip_source_address = socket.inet_ntoa(ip_header_unpack[8]) # prendo l’IP sorgente e lo
converto tramite una funziona che converte il binario il notazione dotted (questo il
motivo per cui abbiamo preso dei byte e non degli unsigned int, per praticità)
18.ip_destination_address = socket.inet_ntoa(ip_header_unpack[9]) # prendo l’IP destinazione
23. Dissezionare IP – Stampa
1. print "= IP ========="
2. print "tIP Version: " +
str(ip_version)
3. print "tIP Header Length: " +
str(ip_header_length)
4. print "tIP TOS: " + str(ip_tos)
5. print "tIP length: " + str(ip_lenght)
6. print "tIPID: " + str(ip_id)
7. print "tIP Fragmentation: " +
str(ip_fragmentation)
8. print "tIP TTL: " + str(ip_ttl)
9. print "tIP Protocol: " +
str(ip_protocol)
10.print "tIP Source: " +
ip_source_address
11.print "tIP Destination: " +
ip_destination_address
= IP =========
IP Version: 4
IP Header Length: 20
IP TOS: 0
IP length: 5
IPID: 1640
IP Fragmentation: 0
IP TTL: 128
IP Protocol: 6
IP Source: 104.28.13.52
IP Destination: 192.168.44.129
25. Dissezionare TCP - /usr/include/linux/tcp.h
Big Endian (Network) !
[0] 16bit >>> sport H
[1] 16bit >>> dport H
[2] 32bit >>> sequence L
[3] 32bit >>> ack-number L
[4] 4bit >>> offset H
4bit >>> reserved
6bit >>> flags
1bit >>> CWR
1bit >>> ECE
1bit >>> URG
1bit >>> ACK
1bit >>> PSH
1bit >>> RST
1bit >>> SYN
1bit >>> FIN
[5] 16bit >>> window H
[6] 16bit >>> check H
[7] 16bit >>> urg_ptr H
1. struct tcphdr {
2. __be16 source;
3. __be16 dest;
4. __be32 seq;
5. __be32 ack_seq;
6. #if defined(__LITTLE_ENDIAN_BITFIELD)
7. #elif defined(__BIG_ENDIAN_BITFIELD)
8. __u16 doff:4,
9. res1:4,
10. cwr:1,
11. ece:1,
12. urg:1,
13. ack:1,
14. psh:1,
15. rst:1,
16. syn:1,
17. fin:1;
18.#else
19.#error "Adjust your <asm/byteorder.h> defines"
20.#endif
21. __be16 window;
22. __sum16 check;
23. __be16 urg_ptr;
24.};
Il nostro formato corretto è !HHLLHHHH
26. Dissezionare TCP – RFC 3168
https://tools.ietf.org/html/rfc3168
The Transmission Control Protocol (TCP) included a 6-bit Reserved
field defined in RFC 793, reserved for future use, in bytes 13 and 14
of the TCP header, as illustrated below. The other six Control bits
are defined separately by RFC 793.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| | | U | A | P | R | S | F |
| Header Length | Reserved | R | C | S | S | Y | I |
| | | G | K | H | T | N | N |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
RFC 3168 defines two of the six bits from the Reserved field to be
used for ECN, as follows:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| | | C | E | U | A | P | R | S | F |
| Header Length | Reserved | W | C | R | C | S | S | Y | I |
| | | R | E | G | K | H | T | N | N |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
27. Dissezionare TCP - Codice
1. tcp_size = 20 # dimensione di un header TCP minimz
2. tcp_start = ip_header_length # inizio da dove finisce IP
3. tcp_finish = tcp_start + tcp_size # fine
4. tcp_header_slice = packet[0][tcp_start:tcp_finish] # prendo la parte che mi interessa
5. tcp_header_unpack = unpack('!HHLLHHHH' , tcp_header_slice) # spacchetto con il formato
6. tcp_source_port = tcp_header_unpack[0] # prendo la porta sorgente
7. tcp_dest_port = tcp_header_unpack[1] # prendo la porta destinazione
8. tcp_sequence = tcp_header_unpack[2] # prendo il sequence number
9. tcp_acknowledgement = tcp_header_unpack[3] # prendo l’acknowledge number
10.tcp_osf = bin(tcp_header_unpack[4])[2:].zfill(16) # prendo la porzione dei bit di controllo
11.tcp_offset = tcp_osf[0:4] # prendo l’offset
12.tcp_reserved = tcp_osf[4:8] # prendo la parte riservata
13.tcp_cwr = tcp_osf[8:9] # prendo il flag specifico CWR **
14.tcp_ece = tcp_osf[9:10] # prendo il flag specifico ECE **
15.tcp_urg = tcp_osf[10:11] # prendo il flag specifico URG **
16.tcp_ack = tcp_osf[11:12] # prendo il flag specifico ACK **
17.tcp_psh = tcp_osf[12:13] # prendo il flag specifico PSH **
18.tcp_rst = tcp_osf[13:14] # prendo il flag specifico RST **
19.tcp_syn = tcp_osf[14:15] # prendo il flag specifico SYN **
20.tcp_fin = tcp_osf[15:16] # prendo il flag specifico FIN **
21.tcp_window = tcp_header_unpack[5] # prendo la window
22.tcp_checksum = tcp_header_unpack[6] # prendo il checksum
23.tcp_urg_ptr = tcp_header_unpack[7] # prendo l’urgent pointer
24.tcp_header_length = int(tcp_offset,2) * 4 # mi serve la lunghezza per tagliare correttamente il
layer 7
29. Sopra il TCP… HTTP
Sappiamo che HTTP è un protocollo non
binario. Quindi per ora limitiamo vedere le
stringhe
header_size = ip_start + ip_header_length +
tcp_header_length # ecco a cosa mi serviva la
dimnesione
data = packet[0][header_size:] # recupero il payload
print "= Data ========"
print "tData: " + data # converto la stringa e
stampo (per HTML funziona abbastanza bene*)
print "nnn"
31. Introduzione a Scapy
• Ora che abbiamo imparato a dissezionare tramite «la via
difficile», vediamo come si può fare in maniera più facile.
• Python ha un’ottima libreria – chiamata Scapy – che è
on-top ai raw socket e che ha una grande quantità di
dissector.
• E’ quindi veloce, facile da usare ed estendibile.
• Ci sono però dei contro:
• A livello «didattico» molte cose sono già pronte quindi se ci può
essere utile per fare degli script in velocità va bene, ma può non
essere il massimo per approfondire totalmente la materia.
• Inoltre se dobbiamo fare il deploy di alcuni script, dobbiamo
necessariamente portarci dietro la libreria.
• Fare il debug potrebbe essere più complesso, al netto di
studiarsi a fondo la libreria.
• Si installa e.g. su debian con $ sudo apt-get
install scapy
http://www.secdev.org/project
s/scapy/doc/
32. Il nostro «sudato» HTTP sniffer con scapy
1. #!/usr/bin/env python
2. from scapy.all import *
3. sniff(filter="port 80", prn=lambda
x:x.sprintf("{IP:%IP.src%:%TCP.sport% >
%IP.dst%:%TCP.dport%n}{Raw:%Raw.load%n}"))
# abilito lo sniffing filtrano per la porta 80 –
default di HTTP – e uso una funzione lambda
(funzione in una riga, derivata dal LISP).
33. Running Sniffer - Summary
>>> sniff(filter="port 80", prn=lambda x:x.summary())
Ether / IP / TCP 192.168.44.129:33842 > 104.28.12.52:http S
Ether / IP / TCP 104.28.12.52:http > 192.168.44.129:33842 SA / Padding
Ether / IP / TCP 192.168.44.129:33842 > 104.28.12.52:http A
Ether / IP / TCP 192.168.44.129:33842 > 104.28.12.52:http PA / Raw
Ether / IP / TCP 104.28.12.52:http > 192.168.44.129:33842 A / Padding
Ether / IP / TCP 104.28.12.52:http > 192.168.44.129:33842 PA / Raw
Ether / IP / TCP 192.168.44.129:33842 > 104.28.12.52:http A
Ether / IP / TCP 104.28.12.52:http > 192.168.44.129:33842 PA / Raw
Ether / IP / TCP 192.168.44.129:33842 > 104.28.12.52:http A
36. Prossimi passi
• Fare quando scritto nelle diapositive nel
pratico
• Segnalare eventuali problemi
• Cambiare tipologia di socket per leggere
anche i pacchetti inviati e fare il dissector
per ARP
• Fare dei test con Scapy