代码改变世界

Code a network packet sniffer in python for Linux

2012-07-14 00:17  Rollen Holt  阅读(7985)  评论(0编辑  收藏  举报

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 = 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 = 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 :

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 :

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   = "%.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 = 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       = 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       = 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       = 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.