Implementacja sniffera sieciowego bazującego na surowych gniazdach, który przechwytuje ramki i przedstawia część informacji (np. źródłowy i docelowy adres MAC, źródłowy i docelowy adres IPv4, źródłowy i docelowy port) w postaci czytelnej dla człowieka. Tego typu oprogramowanie pozwala zaobserwować zastosowanie teoretycznego modelu TCP/IP w praktyce
Z uwagi na wykorzystanie surowego gniazda, program należy uruchamiać z opcją sudo
$ sudo python main.py
sposób użycia:
$ sudo python main.py -i <interface> [options]
opcje:
-h --help wyświetla tę pomoc
-i --interface=<interface> interfejs, z którym powiązać gniazdo
-v --verbose wyświetlanie danych na ekran
-o --outputfile=<filename> plik, do którego zapisać dane
-d --dump czy zrzucać surowe przechwycone dane do pliku
przykład:
$ sudo python main.py -i wlan0 --verbose -o captured.txt
Na chwilę obecną program obsługuje tylko protokoły:
- IPv4
- ARP
- ICMP
- UDP
- TCP
- DNS
- DHCP
W tym pliku znajdują się następujące klasy, próbujące odwzorować strukturę nagłówka danego protokołu:
EthernetHeader
IPHeader
ARPHeader
ICMPHeader
TCPHeader
UDPHeader
DNSHeader
DHCPHeader
Jako parametr konstruktora każda z tych klas przyjmuje bufor rozpoczynający się nagłówkiem danego protokołu i tworzy jego strukturę, podobnie jak w języku C dzięki modułowi ctypes
/* struktura naglowka ARP w C */
struct arpheader {
u_int16_t htype;
u_int16_t ptype;
u_char hlen;
u_char plen;
u_int16_t oper;
u_char sha[6];
u_char spa[4];
u_char tha[6];
u_char tpa[4];
};
from ctypes import *
class ARPHeader(Structure):
_fields_ = [
('htype', c_uint16),
('ptype', c_uint16),
('hlen', c_ubyte),
('plen', c_ubyte),
('oper', c_uint16),
('sha', c_ubyte * 6),
('spa', c_ubyte * 4),
('tha', c_ubyte * 6),
('tpa', c_ubyte * 4)
]
Przykładowo
ip_header = Dissector.IPHeader(buf)
Struktura ctypes
klasy IPHeader
mapuje pierwsze 20 bajtów otrzymanego bufora na przyjazny nagłówek IP (w pozostałych klasach jest analogicznie dla innych protokołów)
W tym samym pliku jest również funkcja service_lookup
, która na podstawie podanych portów zgaduje, jaka usługa jest uruchomiona
def service_lookup(src_port, dst_port=-1):
"""Zgaduje usługę za pomocą używanych numerów portów"""
port_to_service_map = {
20: 'FTP (data transfer)',
21: 'FTP (command)',
22: 'SSH',
23: 'Telnet',
25: 'SMTP',
43: 'WHOIS',
49: 'TACACS',
53: 'DNS',
80: 'HTTP',
88: 'Kerberos',
110: 'POP3',
143: 'IMAP',
161: 'SNMP',
162: 'SNMP (trap)',
443: 'HTTPS',
}
if src_port in port_to_service_map.keys():
return port_to_service_map[src_port]
elif dst_port in port_to_service_map.keys():
return port_to_service_map[dst_port]
else:
return 'Unknown'
Z tej funkcji korzystają klasy TCPHeader
oraz UDPHeader
W tym pliku znajduje się klasa Sniffer
odpowiedzialna za funkcjonalność narzędzia. Wykorzystuje klasy z modułu Dissector.py
do przedstawiania przechwyconych danych w czytelnej dla człowieka formie
sniffer = Sniffer("eth0", verbose=True, output_file='captured.txt', dump=False)
Konstruktor przujmuje argumenty:
interface
- NIC, z którego będą przechwytywane ramki (argument obowiązkowy)verbose
- czy wypisywać wyniki na stdout (opcjonalne, domyślnieTrue
)output_file
- plik, do którego zapisać wyniki (opcjonalne, domyślnieNone
)dump
- czy zrzucać surowe przechwycone dane (opcjonalne, domyślnieTrue
)
W metodzie __init__
jest tworzone surowe gniazdo, które następnie zostaje powiązane z podanym interfejsem
funkcja socket
z modułu socket
przyjmuje argumenty:
- rodzina
socket.AF_PACKET
- typ
socket.SOCK_RAW
- stała
ETH_P_ALL
(wszystkie protokoły)
Klasa Sniffer
zawiera metody:
hexdump(self, buffer)
- Tworzy zrzut szesnastkowy z otrzymanej ramki
start_sniffing(self)
- rozpoczyna przechwytywanie pakietów
- przełącza kartę sieciową w tryb mieszany (promiscuous)
- przechwytuje ramki i przekazuje je do dalszej analizy
- aby zatrzymać przechwytywanie należy nacisnąć Ctrl + C
promisc_mode(self, enable=True)
- jeśli parametr
enable
jest prawdą, przełącza kartę w tryb mieszany - jeśli
False
, wyłącza tryb mieszany
- jeśli parametr
stop_sniffing(self)
- wyłącza tryb mieszany na interfejsie
dissect_eth(self, frame)
- z otrzymanej ramki tworzy obiekt
Dissector.EthernetHeader
i określa protokół warstwy wyższej
- z otrzymanej ramki tworzy obiekt
dissect_ip(self, ip_header, packet)
- z otrzymanego pakietu IP tworzy obiekt
Dissector.IPHeader
i określa protokół warstwy wyżej - w przypadku warstwy transportowej (TCP, UDP) określa usługę
- z otrzymanego pakietu IP tworzy obiekt
dissect_app_layer(self, service, segment)
- na podstawie argumentu
service
określa protokół warstwy aplikacji i przetwarza jego dane
- na podstawie argumentu