Effective Python Penetration Testing
上QQ阅读APP看书,第一时间看更新

Raw socket programming

Everything we send and receive on the Internet involves packets; every web page and e-mail we receive comes as a series of packets, and everything we send leaves as a series of packets. Data breaks into packets of a certain size in bytes. Each packet carries the information to identify its destination, source, and other details of the protocols that the Internet uses, along with a part of the body of our data. Network packets are split into three parts:

  • Header: This contains the instructions about the data carried by the packet
  • Payload: This is the data of a packet
  • Trailer: This is the trailer, notify the end of the packet to receiving device

Headers for protocols like TCP/IP are provided by the kernel or operating system stack, but we can provide custom headers to this protocol with raw sockets. Raw sockets have support in the native socket API in Linux, but support is absent in Windows. Even though raw sockets are rarely used in applications, they are extensively used in network security applications.

All packets are structured in the same format consisting of, IP headers and a variable-length data field. First we have the Ethernet header, which is of a fixed size of 14 bytes, followed by the IP header if it is an IP packet, or TCP header if it is a TCP packet, based on the Ethernet type specified in the last two bytes of the Ethernet header:

In the Ethernet header, the first six bytes are the destination host, followed by a six-byte source host. The final two bytes are the Ethernet type:

The IP header is 20 bytes long; the first 12 bytes include version, IHL, Total Length, Flags, and so on, and the next four bytes represent the source address. Finally, the last four bytes are the destination address:

Tip

For more details on IP packet structure, go to http://www.freesoft.org/CIE/Course/Section3/7.htm.

Creating a raw socket

To create a raw socket with Python, the application must have root privileges on the system. The following example creates a IPPROTO_RAW socket, which is a raw IP packet:

import socket #Imported sockets module  
  
try: 
  #create an INET, raw socket  
  raw_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)  
except socket.error as e:  
  print 'Error occurred while creating socket. Error code: ' + str(e[0]) + ' , Error message : ' + e[1] 
  sys.exit() 

After creating a raw socket, we have to construct the packet which is to be sent. These packets are similar to structures in C, which are not available in Python, hence we have to use a Python struct module to pack and unpack packets in the structure specified previously.

Basic raw socket sniffer

The most basic form of a raw socket sniffer is as follows:

import socket #Imported sockets module  
  
try:  
  #create an raw socket  
  raw_socket = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.htons(0x0800))  
except socket.error, e:  
  print 'Error occurred while creating socket. Error code: ' + str(e[0]) + ' , Error message : ' + e[1] 
  sys.exit();  
 
while True:  
  packet = raw_socket.recvfrom(2048)  
  print packet 

As usual, we imported the socket module in the first line. Later we created a socket with the following:

raw_socket = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.htons(0x0800))

The first parameter indicates that the packet interface is PF_PACKET(Linux specific, we have to use AF_INET for Windows) and the second parameter specifies it is a raw socket. The third argument indicates the protocol we are interested in. The value 0x0800 specifies we are interested in the IP protocol. After that, we call the recvfrom method to receive the packet in an infinite loop:

while True:  
  packet = raw_socket.recvfrom(2048)  
  print packet 

Now we can parse the packet, as the first 14 bytes are the Ethernet header, of which the first six bytes are the destination host and the next six bytes are the source host. Let's rewrite the infinite loop and add code to parse the destination host and source host from the Ethernet header. First we can rip off the Ethernet header as follows:

ethernet_header = packet[0][0:14] 

Then we can parse and unpack the header with struct, as follows:

eth_header = struct.unpack("!6s6s2s", ethernet_header) 

This will return a tuple with three hex values in it. We can convert it to the hex value with hexlify in binascii module:

print "destination:" + binascii.hexlify(eth_header[0]) + " Source:" + binascii.hexlify(eth_header[1]) +  " Type:" + binascii.hexlify(eth_header[2] 

Similarly, we can get the IP header, which is the next 20 bytes in the packet. The first 12 bytes include version, IHL, Length, Flags, and so on, which we are not interested in, but the next eight bytes are the source and destination IP address as shown:

ip_header = packet[0][14:34] 
ip_hdr = struct.unpack("!12s4s4s", ip_header) 
print "Source IP:" + socket.inet_ntoa(ip_hdr[1]) + " Destination IP:" + socket.inet_ntoa(ip_hdr[2])) 

The final script will be as follows:

import socket #Imported sockets module  
import struct  
import binascii  
 
try:  
  #Create an raw socket  
  raw_socket = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.htons(0x0800))  
except socket.error, e:  
  print 'Error occurred while creating socket. Error code: ' + str(e[0]) + ' , Error message : ' + e[1] 
  sys.exit();  
 
while True:  
  packet = raw_socket.recvfrom(2048)  
  ethernet_header = packet[0][0:14]  
  eth_header = struct.unpack("!6s6s2s", ethernet_header)  
  print "destination:" + binascii.hexlify(eth_header[0]) + " Source:" + binascii.hexlify(eth_header[1]) +  " Type:" + binascii.hexlify(eth_header[2])  
  ip_header = packet[0][14:34]  
  ip_hdr = struct.unpack("!12s4s4s", ip_header)  
  print "Source IP:" + socket.inet_ntoa(ip_hdr[1]) + " Destination IP:" + socket.inet_ntoa(ip_hdr[2]) 

This will output the source and destination MAC addresses of the network card, as well as the source and destination IP of the packets. Make sure the packet interface set properly. PF_PACKE is Linux-specific, we have to use AF_INET for Windows. Similarly, we can parse the TCP headers.

Tip

For more details on the struct module, read https://docs.python.org/3/library/struct.html.

Raw socket packet injection

We can send custom crafted packets with a raw socket. As we did before, we can create a raw socket with a socket module, as follows:

import socket #Imported sockets module  
  
try:  
  #create an INET, raw socket  
  raw_socket = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.htons(0x0800))  
except socket.error, e:  
  print ('Error occurred while creating socket. Error code: ' + str(e[0]) + ' , Error message : ' + e[1])  
  sys.exit() 

To inject packets, we need to bind the socket to an interface:

raw_socket.bind(("wlan0", socket.htons(0x0800))) 

Now we can create an Ethernet packet using the pack method in struct, with the source address, destination address, and Ethernet type in it. Also, we can add some data to the packet and send it:

packet =  struct.pack("!6s6s2s", '\xb8v?\x8b\xf5\xfe', 'l\x19\x8f\xe1J\x8c', '\x08\x00') 
raw_socket.send(packet + "Hello") 

The whole script to inject an IP packet will be as follows:

import socket #Imported sockets module  
import struct  
 
try:  
  #Create an raw socket  
  raw_socket = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.htons(0x0800))  
except socket.error as e:  
  print 'Error occurred while creating socket. Error code: ' + str(e[0]) + ' , Error message : ' + e[1] 
  sys.exit();  
    
raw_socket.bind(("wlan0", socket.htons(0x0800)))  
packet =  struct.pack("!6s6s2s", '\xb8v?\x8b\xf5\xfe', 'l\x19\x8f\xe1J\x8c', '\x08\x00')  
raw_socket.send(packet + "Hello")