http://www.binarytides.com/python-packet-sniffer-code-linux/
Sniffers are programs that can capture/sniff/detect network traffic packet by packet and analyse them for various reasons. Commonly used in the field of network security. Wireshark is a very common packet sniffer/protocol analyzer. Packet sniffers can be written in python too. In this article we are going to write a few very simple sniffers in python for the linux platform. Linux because, although python is a portable, the programs wont run or give similar results on windows for example. This is due to difference in the implementation of the socket api.
Sniffers shown here dont use any extra libraries like libpcap. They just use raw sockets. So lets start coding them
The most basic form of a sniffer would be
1
2
3
4
5
6
7
8
9
10
11
|
#Packet sniffer in python
#For Linux
import
socket
#create an INET, raw socket
s
=
socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
# receive a packet
while
True
:
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 :
$ sudo python raw_socket.py ("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)) ('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)) ('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)) ('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.
Here is the code sniff and parse a TCP packet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
#Packet sniffer in python for Linux
#Sniffs only incoming TCP packet
import
socket, sys
from
struct
import
*
#create an INET, STREAMing socket
try
:
s
=
socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
except
socket.error , msg:
print
'Socket could not be created. Error Code : '
+
str
(msg[
0
])
+
' Message '
+
msg[
1
]
sys.exit()
# receive a packet
while
True
:
packet
=
s.recvfrom(
65565
)
#packet string from tuple
packet
=
packet[
0
]
#take first 20 characters for the ip header
ip_header
=
packet[
0
:
20
]
#now unpack them :)
iph
=
unpack(
'!BBHHHBBH4s4s'
, ip_header)
version_ihl
=
iph[
0
]
version
=
version_ihl >>
4
ihl
=
version_ihl &
0xF
iph_length
=
ihl
*
4
ttl
=
iph[
5
]
protocol
=
iph[
6
]
s_addr
=
socket.inet_ntoa(iph[
8
]);
d_addr
=
socket.inet_ntoa(iph[
9
]);
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)
tcp_header
=
packet[iph_length:iph_length
+
20
]
#now unpack them :)
tcph
=
unpack(
'!HHLLBBHHH'
, tcp_header)
source_port
=
tcph[
0
]
dest_port
=
tcph[
1
]
sequence
=
tcph[
2
]
acknowledgement
=
tcph[
3
]
doff_reserved
=
tcph[
4
]
tcph_length
=
doff_reserved >>
4
print
'Source Port : '
+
str
(source_port)
+
' Dest Port : '
+
str
(dest_port)
+
' Sequence Number : '
+
str
(sequence)
+
' Acknowledgement : '
+
str
(acknowledgement)
+
' TCP header length : '
+
str
(tcph_length)
h_size
=
iph_length
+
tcph_length
*
4
data_size
=
len
(packet)
-
h_size
#get data from the packet
data
=
packet[h_size:]
print
'Data : '
+
data
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 :
$ sudo python tcp_sniffer.py Version : 4 IP Header Length : 5 TTL : 56 Protocol : 6 Source Address : 74.125.236.85 Destination Address : 192.168.1.101 Source Port : 443 Dest Port : 38461 Sequence Number : 2809673723 Acknowledgement : 3312567259 TCP header length : 5 Data : 2X???@???0? ???k?/&???=?5Hz??>5QBp0?O???Z???$?? Version : 4 IP Header Length : 5 TTL : 56 Protocol : 6 Source Address : 74.125.236.85 Destination Address : 192.168.1.101 Source Port : 443 Dest Port : 38461 Sequence Number : 2809673778 Acknowledgement : 3312567259 TCP header length : 5 Data : ? ??j?!I??*??*??Z???;?L?]Y- Version : 4 IP Header Length : 5 TTL : 52 Protocol : 6 Source Address : 173.192.42.183 Destination Address : 192.168.1.101 Source Port : 80 Dest Port : 52813 Sequence Number : 1202422309 Acknowledgement : 3492657980 TCP header length : 5 Data : HTTP/1.1 502 Bad Gateway Server: nginx Date: Tue, 11 Sep 2012 08:56:00 GMT Content-Type: text/html Content-Length: 568 Connection: close502 Bad Gateway 502 Bad Gateway
nginx Version : 4 IP Header Length : 5 TTL : 56 Protocol : 6 Source Address : 74.125.236.85 Destination Address : 192.168.1.101 Source Port : 443 Dest Port : 38461 Sequence Number : 2809673811 Acknowledgement : 3312568679 TCP header length : 5 Data :
According to RFC 791 an IP header looks like this :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
0 1 2 3
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
0 1 2 3
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
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.
1. The above sniffer picks up only TCP packets, because of the declaration :
For UDP and ICMP the declaration has to be :
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.
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(0x0003))
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
#Packet sniffer in python
#For Linux - Sniffs all incoming and outgoing packets :)
#Silver Moon ([email protected])
import
socket, sys
from
struct
import
*
#Convert a string of 6 characters of ethernet address into a dash separated hex string
def
eth_addr (a) :
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
]))
return
b
#create a AF_PACKET type raw socket (thats basically packet level)
#define ETH_P_ALL 0x0003 /* Every packet (be careful!!!) */
try
:
s
=
socket.socket( socket.AF_PACKET , socket.SOCK_RAW , socket.ntohs(
0x0003
))
except
socket.error , msg:
print
'Socket could not be created. Error Code : '
+
str
(msg[
0
])
+
' Message '
+
msg[
1
]
sys.exit()
# receive a packet
while
True
:
packet
=
s.recvfrom(
65565
)
#packet string from tuple
packet
=
packet[
0
]
#parse ethernet header
eth_length
=
14
eth_header
=
packet[:eth_length]
eth
=
unpack(
'!6s6sH'
, eth_header)
eth_protocol
=
socket.ntohs(eth[
2
])
print
'Destination MAC : '
+
eth_addr(packet[
0
:
6
])
+
' Source MAC : '
+
eth_addr(packet[
6
:
12
])
+
' Protocol : '
+
str
(eth_protocol)
#Parse IP packets, IP Protocol number = 8
if
eth_protocol
=
=
8
:
#Parse IP header
#take first 20 characters for the ip header
ip_header
=
packet[eth_length:
20
+
eth_length]
#now unpack them :)
iph
=
unpack(
'!BBHHHBBH4s4s'
, ip_header)
version_ihl
=
iph[
0
]
version
=
version_ihl >>
4
ihl
=
version_ihl &
0xF
iph_length
=
ihl
*
4
ttl
=
iph[
5
]
protocol
=
iph[
6
]
s_addr
=
socket.inet_ntoa(iph[
8
]);
d_addr
=
socket.inet_ntoa(iph[
9
]);
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)
#TCP protocol
if
protocol
=
=
6
:
t
=
iph_length
+
eth_length
tcp_header
=
packet[t:t
+
20
]
#now unpack them :)
tcph
=
unpack(
'!HHLLBBHHH'
, tcp_header)
source_port
=
tcph[
0
]
dest_port
=
tcph[
1
]
sequence
=
tcph[
2
]
acknowledgement
=
tcph[
3
]
doff_reserved
=
tcph[
4
]
tcph_length
=
doff_reserved >>
4
print
'Source Port : '
+
str
(source_port)
+
' Dest Port : '
+
str
(dest_port)
+
' Sequence Number : '
+
str
(sequence)
+
' Acknowledgement : '
+
str
(acknowledgement)
+
' TCP header length : '
+
str
(tcph_length)
h_size
=
eth_length
+
iph_length
+
tcph_length
*
4
data_size
=
len
(packet)
-
h_size
#get data from the packet
data
=
packet[h_size:]
print
'Data : '
+
data
#ICMP Packets
elif
protocol
=
=
1
:
u
=
iph_length
+
eth_length
icmph_length
=
4
icmp_header
=
packet[u:u
+
4
]
#now unpack them :)
icmph
=
unpack(
'!BBH'
, icmp_header)
icmp_type
=
icmph[
0
]
code
=
icmph[
1
]
checksum
=
icmph[
2
]
print
'Type : '
+
str
(icmp_type)
+
' Code : '
+
str
(code)
+
' Checksum : '
+
str
(checksum)
h_size
=
eth_length
+
iph_length
+
icmph_length
data_size
=
len
(packet)
-
h_size
#get data from the packet
data
=
packet[h_size:]
print
'Data : '
+
data
#UDP packets
elif
protocol
=
=
17
:
u
=
iph_length
+
eth_length
udph_length
=
8
udp_header
=
packet[u:u
+
8
]
#now unpack them :)
udph
=
unpack(
'!HHHH'
, udp_header)
source_port
=
udph[
0
]
dest_port
=
udph[
1
]
length
=
udph[
2
]
checksum
=
udph[
3
]
print
'Source Port : '
+
str
(source_port)
+
' Dest Port : '
+
str
(dest_port)
+
' Length : '
+
str
(length)
+
' Checksum : '
+
str
(checksum)
h_size
=
eth_length
+
iph_length
+
udph_length
data_size
=
len
(packet)
-
h_size
#get data from the packet
data
=
packet[h_size:]
print
'Data : '
+
data
#some other IP packet like IGMP
else
:
print
'Protocol other than TCP/UDP/ICMP'
print
|
The above program must be run with root privileges.
The output should be something like this :
Destination MAC : 00-1c-c0-f8-79-ee Source MAC : 00-25-5e-1a-3d-f1 Protocol : 8 Version : 4 IP Header Length : 5 TTL : 57 Protocol : 6 Source Address : 64.131.72.23 Destination Address : 192.168.1.6 Source Port : 80 Dest Port : 58928 Sequence Number : 1392138007 Acknowledgement : 2935013912 TCP header length : 6 Data : ??y?%^?=E ,@9?c@?H?P?0R?W????`?5t? Destination MAC : 00-25-5e-1a-3d-f1 Source MAC : 00-1c-c0-f8-79-ee Protocol : 8 Version : 4 IP Header Length : 5 TTL : 64 Protocol : 6 Source Address : 192.168.1.6 Destination Address : 64.131.72.23 Source Port : 58928 Dest Port : 80 Sequence Number : 2935013912 Acknowledgement : 1392138008 TCP header length : 5 Data : %^?=???yE(mU@@?2?@?H?0P????R?W?PJc Destination MAC : 00-1c-c0-f8-79-ee Source MAC : 00-25-5e-1a-3d-f1 Protocol : 8 Version : 4 IP Header Length : 5 TTL : 55 Protocol : 17 Source Address : 78.141.179.8 Destination Address : 192.168.1.6 Source Port : 34049 Dest Port : 56295 Length : 28 Checksum : 25749 Data : @7?YN?????d??????r'?y@?f?h`?? Destination MAC : 00-1c-c0-f8-79-ee Source MAC : 00-25-5e-1a-3d-f1 Protocol : 8 Version : 4 IP Header Length : 5 TTL : 118 Protocol : 17 Source Address : 173.181.21.51 Destination Address : 192.168.1.6 Source Port : 5999 Dest Port : 56295 Length : 26 Checksum : 22170 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 :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Ethernet destination address (first 32 bits) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Ethernet dest (last 16 bits) |Ethernet source (first 16 bits)| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Ethernet source address (last 32 bits) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type code | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
UDP Header according to RFC 768 :
0 7 8 15 16 23 24 31 +--------+--------+--------+--------+ | Source | Destination | | Port | Port | +--------+--------+--------+--------+ | | | | Length | Checksum | +--------+--------+--------+--------+ | | data octets ... +---------------- ...
ICMP Header according to RFC 792 :
0 1 2 3 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Code | Checksum | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | unused | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Internet Header + 64 bits of Original Data Datagram | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
This kind of a sniffer does not depend on any external libraries like libpcap.
The C version of this code is here.
1. http://docs.python.org/library/socket.html