Basic Sniffer
The most basic form of a sniffer would be :
7 |
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP) |
11 |
print s.recvfrom( 65565 ) |
Run this with root privileges or sudo on ubuntu :
sudo python sniffer.py
The above sniffer works on the principle that a raw socket is capable of receiving all (of its type , like AF_INET) incoming traffic in Linux.
The output could look like this :
1 |
$ sudo python raw_socket.py |
2 |
( "E \x00x\xcc\xfc\x00\x000\x06j%J}G\x13\xc0\xa8\x01\x06\x01\xbb\xa3\xdc\x0b\xbeI\xbf\x1aF[\x83P\x18\xff\xff\x88\xf6\x00\x00\x17\x03\x01\x00\x1c\xbbT\xb3\x07}\xb0\xedqE\x1e\xe7;-\x03\x9bU\xb7\xb1r\xd2\x9e]\xa1\xb8\xac\xa4V\x9a\x17\x03\x01\x00*\xed\x1f\xda\xa4##Qe\x9a\xe9\xd6\xadN\xf4\x9b\xc4\xf0C'\x01\xc4\x82\xdb\xb2\x8d(\xa5\xd0\x06\x95\x13WO\x0f\x8e\x1c\xa6f\x1d\xdf\xe1x" , ('74.125.71.19', 0)) |
3 |
( 'E \x00I\xcc\xfd\x00\x000\x06jSJ}G\x13\xc0\xa8\x01\x06\x01\xbb\xa3\xdc\x0b\xbeJ\x0f\x1aF[\x83P\x18\xff\xff:\x11\x00\x00\x17\x03\x01\x00\x1c\xaa];\t\x81yi\xbbC\xb5\x11\x14(Ct\x13\x10wt\xe0\xbam\xa9\x88/\xf8O{' , ( '74.125.71.19' , 0)) |
4 |
( 'E \x00(\xcc\xfe\x00\x000\x06jsJ}G\x13\xc0\xa8\x01\x06\x01\xbb\xa3\xdc\x0b\xbeJ0\x1aFa\x19P\x10\xff\xff\xe5\xb0\x00\x00' , ( '74.125.71.19' , 0)) |
5 |
( 'E \x00(\xcc\xff\x00\x000\x06jrJ}G\x13\xc0\xa8\x01\x06\x01\xbb\xa3\xdc\x0b\xbeJ0\x1aFbtP\x10\xff\xff\xe4U\x00\x00' , ( '74.125.71.19' , 0)) |
The above is a dump of the network packets in hex. They can be parsed using the unpack function.
Parsing the sniffed packet
Here is the code to parse a TCP packet
8 |
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP) |
12 |
packet = s.recvfrom( 65565 ) |
18 |
ip_header = packet[ 0 : 20 ] |
21 |
iph = unpack( '!BBHHHBBH4s4s' , ip_header) |
24 |
version = version_ihl >> 4 |
25 |
ihl = version_ihl & 0xF |
29 |
s_addr = socket.inet_ntoa(iph[ 8 ]); |
30 |
d_addr = socket.inet_ntoa(iph[ 9 ]); |
32 |
print 'Version : ' + str (version) + ' IP Header Length : ' + str (ihl) + ' TTL : ' + str (ttl) + ' Protocol : ' + str (protocol) + ' Source Address : ' + str (s_addr) + ' Destination Address : ' + str (d_addr) |
34 |
tcp_header = packet[ 20 : 40 ] |
37 |
tcph = unpack( '!HHLLBBHHH' , tcp_header) |
42 |
acknowledgement = tcph[ 3 ] |
43 |
doff_reserved = tcph[ 4 ] |
44 |
tcph_length = doff_reserved >> 4 |
46 |
print 'Source Port : ' + str (source_port) + ' Dest Port : ' + str (dest_port) + ' Sequence Number : ' + str (sequence) + ' Acknowledgement : ' + str (acknowledgement) + ' TCP header length : ' + str (tcph_length) |
48 |
h_size = ihl * 4 + tcph_length * 4 |
49 |
data_size = len (packet) - h_size |
52 |
data = packet[data_size:] |
54 |
print 'Data : ' + data |
The above code breaks down the packet into IP Header + TCP Header + Data.
The unpack function is used to break down the packet. Documentation
The output of the code should look like this :
1 |
$ sudo python raw_socket.py |
2 |
Version : 4 IP Header Length : 5 TTL : 48 Protocol : 6 Source Address : 74.125.71.104 Destination Address : 192.168.1.6 |
3 |
Source Port : 80 Dest Port : 36454 Sequence Number : 3689394456 Acknowledgement : 2825932501 TCP header length : 5 |
4 |
Data : E (%?0=J}Gh?P?f?????pN?P,?? |
6 |
Version : 4 IP Header Length : 5 TTL : 48 Protocol : 6 Source Address : 74.125.71.132 Destination Address : 192.168.1.6 |
7 |
Source Port : 80 Dest Port : 46534 Sequence Number : 2071060663 Acknowledgement : 2858668979 TCP header length : 5 |
8 |
Data : E (?H0;?J}G??P??{q?c?P |
10 |
Version : 4 IP Header Length : 5 TTL : 48 Protocol : 6 Source Address : 74.125.71.132 Destination Address : 192.168.1.6 |
11 |
Source Port : 80 Dest Port : 46533 Sequence Number : 377985304 Acknowledgement : 2869878012 TCP header length : 5 |
12 |
Data : E (??0JqJ}G??P????????PR? |
14 |
Version : 4 IP Header Length : 5 TTL : 48 Protocol : 6 Source Address : 74.125.71.17 Destination Address : 192.168.1.6 |
15 |
Source Port : 443 Dest Port : 59643 Sequence Number : 183723837 Acknowledgement : 3530935779 TCP header length : 5 |
According to RFC 791 an IP header looks like this :
2 |
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
3 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
4 |
|Version| IHL |Type of Service| Total Length | |
5 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
6 |
| Identification |Flags| Fragment Offset | |
7 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
8 |
| Time to Live | Protocol | Header Checksum | |
9 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
11 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
12 |
| Destination Address | |
13 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
15 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
If the IHL is 5 then total size is 20 bytes hence options+padding is absent.
For TCP packets the protocol is 6. Source address is the source IPv4 address in long format.
Next comes the TCP header :
2 |
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
3 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
4 |
| Source Port | Destination Port | |
5 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
7 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
8 |
| Acknowledgment Number | |
9 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
10 |
| Data | |U|A|P|R|S|F| | |
11 |
| Offset| Reserved |R|C|S|S|Y|I| Window | |
13 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
14 |
| Checksum | Urgent Pointer | |
15 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
17 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
19 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
and the balance after the TCP header is the data portion.
The C version of the code is here.
The Php version of the code is here.
Note :
1. The above sniffer picks up only TCP packets, because of the declaration :
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
For UDP and ICMP the declaration has to be :
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_UDP)
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
You might be tempted to think of doing :
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)
but this will not work , since IPPROTO_IP is a dummy protocol not a real one.
2. This sniffer picks up only incoming packets.
3. This sniffer delivers only IP frames , which means ethernet headers are not available.
Better Sniffer
Now let us see how we can overcome the above mentioned drawbacks. The solutions is quite simple.
This line :
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
needs to be changed to :
s = socket.socket( socket.AF_PACKET , socket.SOCK_RAW , socket.ntohs(0×0003))
Now the same socket will receive :
1. All incoming and outgoing traffic.
2. All Ethernet frames , which means all kinds of IP packets(TCP , UDP , ICMP) and even other kinds of packets(like ARP) if there are any.
3. It will also provide the ethernet header as a part of the received packet.
Here is the source code :
9 |
b = "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x" % ( ord (a[ 0 ]) , ord (a[ 1 ]) , ord (a[ 2 ]), ord (a[ 3 ]), ord (a[ 4 ]) , ord (a[ 5 ])) |
14 |
s = socket.socket( socket.AF_PACKET , socket.SOCK_RAW , socket.ntohs( 0x0003 )) |
18 |
packet = s.recvfrom( 65565 ) |
26 |
eth_header = packet[:eth_length] |
27 |
eth = unpack( '!6s6sH' , eth_header) |
28 |
eth_protocol = socket.ntohs(eth[ 2 ]) |
29 |
print 'Destination MAC : ' + eth_addr(packet[ 0 : 6 ]) + ' Source MAC : ' + eth_addr(packet[ 6 : 12 ]) + ' Protocol : ' + str (eth_protocol) |
32 |
if eth_protocol = = 8 : |
35 |
ip_header = packet[eth_length: 20 + eth_length] |
38 |
iph = unpack( '!BBHHHBBH4s4s' , ip_header) |
41 |
version = version_ihl >> 4 |
42 |
ihl = version_ihl & 0xF |
48 |
s_addr = socket.inet_ntoa(iph[ 8 ]); |
49 |
d_addr = socket.inet_ntoa(iph[ 9 ]); |
51 |
print 'Version : ' + str (version) + ' IP Header Length : ' + str (ihl) + ' TTL : ' + str (ttl) + ' Protocol : ' + str (protocol) + ' Source Address : ' + str (s_addr) + ' Destination Address : ' + str (d_addr) |
55 |
t = iph_length + eth_length |
56 |
tcp_header = packet[t:t + 20 ] |
59 |
tcph = unpack( '!HHLLBBHHH' , tcp_header) |
64 |
acknowledgement = tcph[ 3 ] |
65 |
doff_reserved = tcph[ 4 ] |
66 |
tcph_length = doff_reserved >> 4 |
68 |
print 'Source Port : ' + str (source_port) + ' Dest Port : ' + str (dest_port) + ' Sequence Number : ' + str (sequence) + ' Acknowledgement : ' + str (acknowledgement) + ' TCP header length : ' + str (tcph_length) |
70 |
h_size = eth_length + iph_length + tcph_length * 4 |
71 |
data_size = len (packet) - h_size |
74 |
data = packet[data_size:] |
76 |
print 'Data : ' + data |
80 |
u = iph_length + eth_length |
82 |
icmp_header = packet[u:u + 4 ] |
85 |
icmph = unpack( '!BBH' , icmp_header) |
91 |
print 'Type : ' + str (icmp_type) + ' Code : ' + str (code) + ' Checksum : ' + str (checksum) |
93 |
h_size = eth_length + iph_length + icmph_length |
94 |
data_size = len (packet) - h_size |
97 |
data = packet[data_size:] |
99 |
print 'Data : ' + data |
102 |
elif protocol = = 17 : |
103 |
u = iph_length + eth_length |
105 |
udp_header = packet[u:u + 8 ] |
108 |
udph = unpack( '!HHHH' , udp_header) |
110 |
source_port = udph[ 0 ] |
115 |
print 'Source Port : ' + str (source_port) + ' Dest Port : ' + str (dest_port) + ' Length : ' + str (length) + ' Checksum : ' + str (checksum) |
117 |
h_size = eth_length + iph_length + udph_length |
118 |
data_size = len (packet) - h_size |
121 |
data = packet[data_size:] |
123 |
print 'Data : ' + data |
127 |
print 'Protocol other than TCP/UDP/ICMP' |
Run with root privileges.
The output should be something like this :
1 |
Destination MAC : 00-1c-c0-f8-79-ee Source MAC : 00-25-5e-1a-3d-f1 Protocol : 8 |
2 |
Version : 4 IP Header Length : 5 TTL : 57 Protocol : 6 Source Address : 64.131.72.23 Destination Address : 192.168.1.6 |
3 |
Source Port : 80 Dest Port : 58928 Sequence Number : 1392138007 Acknowledgement : 2935013912 TCP header length : 6 |
4 |
Data : ??y?%^?=E ,@9?c@?H?P?0R?W????`?5t? |
6 |
Destination MAC : 00-25-5e-1a-3d-f1 Source MAC : 00-1c-c0-f8-79-ee Protocol : 8 |
7 |
Version : 4 IP Header Length : 5 TTL : 64 Protocol : 6 Source Address : 192.168.1.6 Destination Address : 64.131.72.23 |
8 |
Source Port : 58928 Dest Port : 80 Sequence Number : 2935013912 Acknowledgement : 1392138008 TCP header length : 5 |
9 |
Data : %^?=???yE(mU@@?2?@?H?0P????R?W?PJc |
11 |
Destination MAC : 00-1c-c0-f8-79-ee Source MAC : 00-25-5e-1a-3d-f1 Protocol : 8 |
12 |
Version : 4 IP Header Length : 5 TTL : 55 Protocol : 17 Source Address : 78.141.179.8 Destination Address : 192.168.1.6 |
13 |
Source Port : 34049 Dest Port : 56295 Length : 28 Checksum : 25749 |
14 |
Data : @7?YN?????d??????r'?y@?f?h`?? |
16 |
Destination MAC : 00-1c-c0-f8-79-ee Source MAC : 00-25-5e-1a-3d-f1 Protocol : 8 |
17 |
Version : 4 IP Header Length : 5 TTL : 118 Protocol : 17 Source Address : 173.181.21.51 Destination Address : 192.168.1.6 |
18 |
Source Port : 5999 Dest Port : 56295 Length : 26 Checksum : 22170 |
19 |
Data : s)vL??3?o???V?Z???cw?k??pIQ |
It parses the Ethernet header and also the UDP and ICMP headers.
Ethernet header looks like this :
1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
2 |
| Ethernet destination address (first 32 bits) | |
3 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
4 |
| Ethernet dest (last 16 bits) |Ethernet source (first 16 bits)| |
5 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
6 |
| Ethernet source address (last 32 bits) | |
7 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
9 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
UDP Header according to RFC 768 :
2 |
+--------+--------+--------+--------+ |
3 |
| Source | Destination | |
5 |
+--------+--------+--------+--------+ |
8 |
+--------+--------+--------+--------+ |
ICMP Header according to RFC 792 :
2 |
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
3 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
4 |
| Type | Code | Checksum | |
5 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
7 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
8 |
| Internet Header + 64 bits of Original Data Datagram | |
9 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
This kind of a sniffer does not depend on any external libraries like libpcap.
The C version of this code is here.
References :
1. Python Socket documentation.