Basic Sniffer
The most basic form of a sniffer would be :
1 |
#Packet sniffer in python |
2 |
#For Linux |
3 |
4 |
import socket |
5 |
6 |
#create an INET, raw socket |
7 |
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP) |
8 |
9 |
# receive a packet |
10 |
while True : |
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
1 |
#Packet sniffer in python |
2 |
#For Linux |
3 |
4 |
import socket |
5 |
from struct import * |
6 |
7 |
#create an INET, STREAMing socket |
8 |
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP) |
9 |
10 |
# receive a packet |
11 |
while True : |
12 |
packet = s.recvfrom( 65565 ) |
13 |
|
14 |
#packet string from tuple |
15 |
packet = packet[ 0 ] |
16 |
|
17 |
#take first 20 characters for the ip header |
18 |
ip_header = packet[ 0 : 20 ] |
19 |
|
20 |
#now unpack them :) |
21 |
iph = unpack( '!BBHHHBBH4s4s' , ip_header) |
22 |
|
23 |
version_ihl = iph[ 0 ] |
24 |
version = version_ihl >> 4 |
25 |
ihl = version_ihl & 0xF |
26 |
|
27 |
ttl = iph[ 5 ] |
28 |
protocol = iph[ 6 ] |
29 |
s_addr = socket.inet_ntoa(iph[ 8 ]); |
30 |
d_addr = socket.inet_ntoa(iph[ 9 ]); |
31 |
|
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) |
33 |
|
34 |
tcp_header = packet[ 20 : 40 ] |
35 |
|
36 |
#now unpack them :) |
37 |
tcph = unpack( '!HHLLBBHHH' , tcp_header) |
38 |
|
39 |
source_port = tcph[ 0 ] |
40 |
dest_port = tcph[ 1 ] |
41 |
sequence = tcph[ 2 ] |
42 |
acknowledgement = tcph[ 3 ] |
43 |
doff_reserved = tcph[ 4 ] |
44 |
tcph_length = doff_reserved >> 4 |
45 |
|
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) |
47 |
|
48 |
h_size = ihl * 4 + tcph_length * 4 |
49 |
data_size = len (packet) - h_size |
50 |
|
51 |
#get data from the packet |
52 |
data = packet[data_size:] |
53 |
|
54 |
print 'Data : ' + data |
55 |
print |
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,?? |
5 |
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 |
9 |
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? |
13 |
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 |
16 |
Data : 2=?D?? ??????? |
According to RFC 791 an IP header looks like this :
1 |
0 1 2 3 |
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
10 |
| Source Address | |
11 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
12 |
| Destination Address | |
13 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
14 |
| Options | Padding | |
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 :
1 |
0 1 2 3 |
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
6 |
| Sequence Number | |
7 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
8 |
| Acknowledgment Number | |
9 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
10 |
| Data | |U|A|P|R|S|F| | |
11 |
| Offset| Reserved |R|C|S|S|Y|I| Window | |
12 |
| | |G|K|H|T|N|N| | |
13 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
14 |
| Checksum | Urgent Pointer | |
15 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
16 |
| Options | Padding | |
17 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
18 |
| data | |
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 :
For UDP and ICMP the declaration has to be :
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
You might be tempted to think of doing :
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 :
needs to be changed to :
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 :
1 |
#Packet sniffer in python |
2 |
#For Linux - Sniffs all incoming and outgoing packets :) |
3 |
4 |
import socket |
5 |
from struct import * |
6 |
7 |
#Convert a string of 6 characters of ethernet address into a dash separated hex string |
8 |
def eth_addr (a) : |
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 ])) |
10 |
return b |
11 |
12 |
#create an PACKET , RAW SOCKET |
13 |
#define ETH_P_ALL 0x0003 /* Every packet (be careful!!!) */ |
14 |
s = socket.socket( socket.AF_PACKET , socket.SOCK_RAW , socket.ntohs( 0x0003 )) |
15 |
16 |
# receive a packet |
17 |
while True : |
18 |
packet = s.recvfrom( 65565 ) |
19 |
|
20 |
#packet string from tuple |
21 |
packet = packet[ 0 ] |
22 |
|
23 |
#parse ethernet header |
24 |
eth_length = 14 |
25 |
|
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) |
30 |
|
31 |
#Parse IP packets |
32 |
if eth_protocol = = 8 : |
33 |
#Parse IP header |
34 |
#take first 20 characters for the ip header |
35 |
ip_header = packet[eth_length: 20 + eth_length] |
36 |
|
37 |
#now unpack them :) |
38 |
iph = unpack( '!BBHHHBBH4s4s' , ip_header) |
39 |
|
40 |
version_ihl = iph[ 0 ] |
41 |
version = version_ihl >> 4 |
42 |
ihl = version_ihl & 0xF |
43 |
|
44 |
iph_length = ihl * 4 |
45 |
|
46 |
ttl = iph[ 5 ] |
47 |
protocol = iph[ 6 ] |
48 |
s_addr = socket.inet_ntoa(iph[ 8 ]); |
49 |
d_addr = socket.inet_ntoa(iph[ 9 ]); |
50 |
|
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) |
52 |
|
53 |
#TCP protocol |
54 |
if protocol = = 6 : |
55 |
t = iph_length + eth_length |
56 |
tcp_header = packet[t:t + 20 ] |
57 |
|
58 |
#now unpack them :) |
59 |
tcph = unpack( '!HHLLBBHHH' , tcp_header) |
60 |
|
61 |
source_port = tcph[ 0 ] |
62 |
dest_port = tcph[ 1 ] |
63 |
sequence = tcph[ 2 ] |
64 |
acknowledgement = tcph[ 3 ] |
65 |
doff_reserved = tcph[ 4 ] |
66 |
tcph_length = doff_reserved >> 4 |
67 |
|
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) |
69 |
|
70 |
h_size = eth_length + iph_length + tcph_length * 4 |
71 |
data_size = len (packet) - h_size |
72 |
|
73 |
#get data from the packet |
74 |
data = packet[data_size:] |
75 |
|
76 |
print 'Data : ' + data |
77 |
|
78 |
#ICMP Packets |
79 |
elif protocol = = 1 : |
80 |
u = iph_length + eth_length |
81 |
icmph_length = 4 |
82 |
icmp_header = packet[u:u + 4 ] |
83 |
|
84 |
#now unpack them :) |
85 |
icmph = unpack( '!BBH' , icmp_header) |
86 |
|
87 |
icmp_type = icmph[ 0 ] |
88 |
code = icmph[ 1 ] |
89 |
checksum = icmph[ 2 ] |
90 |
|
91 |
print 'Type : ' + str (icmp_type) + ' Code : ' + str (code) + ' Checksum : ' + str (checksum) |
92 |
|
93 |
h_size = eth_length + iph_length + icmph_length |
94 |
data_size = len (packet) - h_size |
95 |
|
96 |
#get data from the packet |
97 |
data = packet[data_size:] |
98 |
|
99 |
print 'Data : ' + data |
100 |
|
101 |
#UDP packets |
102 |
elif protocol = = 17 : |
103 |
u = iph_length + eth_length |
104 |
udph_length = 8 |
105 |
udp_header = packet[u:u + 8 ] |
106 |
|
107 |
#now unpack them :) |
108 |
udph = unpack( '!HHHH' , udp_header) |
109 |
|
110 |
source_port = udph[ 0 ] |
111 |
dest_port = udph[ 1 ] |
112 |
length = udph[ 2 ] |
113 |
checksum = udph[ 3 ] |
114 |
|
115 |
print 'Source Port : ' + str (source_port) + ' Dest Port : ' + str (dest_port) + ' Length : ' + str (length) + ' Checksum : ' + str (checksum) |
116 |
|
117 |
h_size = eth_length + iph_length + udph_length |
118 |
data_size = len (packet) - h_size |
119 |
|
120 |
#get data from the packet |
121 |
data = packet[data_size:] |
122 |
|
123 |
print 'Data : ' + data |
124 |
|
125 |
#some other IP packet like IGMP |
126 |
else : |
127 |
print 'Protocol other than TCP/UDP/ICMP' |
128 |
|
129 |
print |
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? |
5 |
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 |
10 |
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`?? |
15 |
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
8 |
| Type code | | |
9 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
UDP Header according to RFC 768 :
1 |
0 7 8 15 16 23 24 31 |
2 |
+--------+--------+--------+--------+ |
3 |
| Source | Destination | |
4 |
| Port | Port | |
5 |
+--------+--------+--------+--------+ |
6 |
| | | |
7 |
| Length | Checksum | |
8 |
+--------+--------+--------+--------+ |
9 |
| |
10 |
| data octets ... |
11 |
+---------------- ... |
ICMP Header according to RFC 792 :
1 |
0 1 2 3 |
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
6 |
| unused | |
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.