Understanding Raw Sockets for Packet Injection and Network Penetration Testing

Packet injection is the process of disrupting an established network connection by constructing arbitrary protocols (TCP…UDP…) and then sending them using raw sockets. This method is widely used in network penetration testing, such as DDOS, port scanning, etc.

A packet consists of IP header information, TCP/UDP header information, and data:

Packet = IP Header + TCP/UDP Header + Data

Most operating systems’ socket APIs support packet injection (especially those based on Berkeley Sockets). Microsoft has restricted the capabilities of raw sockets in Windows XP and later to prevent packet sniffing. This article only applies to UNIX/Unix-like systems.

The TCP protocol is widely used for data transmission on the Internet. It is a connection-oriented, reliable, IP-based transport layer protocol.

TCP Header Format:

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                              |-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-

—Source Port is the source port, 16 bits.

—Destination Port is the destination port, 16 bits.

—Sequence Number is the sequence number of the first byte in the packet, 32 bits.

—Acknowledgment Number is the acknowledgment sequence number, 32 bits.

—Data Offset is the data offset, 4 bits, and the value of this field is the length of the TCP header (including options) multiplied by 4.

—Flags: 6 bits, URG indicates that the Urgent Pointer field is significant:

ACK indicates that the Acknowledgment Number field is significant

PSH indicates the Push function, RST indicates resetting the TCP connection

SYN indicates a SYN packet (used when establishing a TCP connection)

FIN indicates no more data to send (used when closing a TCP connection)

Window indicates the free space in the receive buffer, 16 bits, used to tell the TCP connection peer the maximum data length it can receive.

—Checksum is the checksum, 16 bits.

—Urgent Pointer is the urgent pointer, 16 bits, and this field is significant only when the URG flag is set, indicating the offset of the urgent data relative to the sequence number (the value of the Sequence Number field).

More detailed information about the TCP protocol can be easily found online, and will not be elaborated here.

To create a packet where we can construct our own data, we use the “SOCK_RAW” socket format with the “IPPROTO_RAW” protocol, which tells the system that we will provide the network layer and transport layer.

s = socket.socket(socket.AF_INET,socket.SOCK_RAW,)

Through this simple class, we can construct IP header information

class ip(object):
    def __init__(self, source, destination):
        self.version = 4
        self.ihl = 5 # Internet Header Length
        self.tos = 0 # Type of Service
        self.tl = 0 # total length will be filled by kernel
        self.id = 54321
        self.flags = 0 # More fragments
        self.offset = 0
        self.ttl = 255
        self.protocol = socket.IPPROTO_TCP
        self.checksum = 0 # will be filled by kernel
        self.source = socket.inet_aton(source)
        self.destination = socket.inet_aton(destination)
    def pack(self):
        ver_ihl = (self.version << 4) + self.ihl
        flags_offset = (self.flags << 13) + self.offset
        ip_header = struct.pack("!BBHHHBBH4s4s",
                    ver_ihl,
                    self.tos,
                    self.tl,
                    self.id,
                    flags_offset,
                    self.ttl,
                    self.protocol,
                    self.checksum,
                    self.source,
                    self.destination)

The “pack” method will pack the IP header elements and return it

ipobj = ip("127.0.0.1", "127.0.0.2") # Creating an ip object instancei 
pobj.source = "localhost" # Changing IP element value

Constructing TCP Header Information

The TCP class allows us to easily manipulate TCP header elements and pack them

class tcp(object):
    def __init__(self, srcp, dstp):
        self.srcp = srcp
        self.dstp = dstp
        self.seqn = 0
        self.ackn = 0
        self.offset = 5 # Data offset: 5x4 = 20 bytes
        self.reserved = 0
        self.urg = 0
        self.ack = 0
        self.psh = 1
        self.rst = 0
        self.syn = 0
        self.fin = 0
        self.window = socket.htons(5840)
        self.checksum = 0
        self.urgp = 0
        self.payload = ""
    def pack(self, source, destination):
        data_offset = (self.offset << 4) + 0
        flags = self.fin + (self.syn << 1) + (self.rst << 2) + (self.psh << 3) + (self.ack << 4) + (self.urg << 5)
        tcp_header = struct.pack('!HHLLBBHHH',
                     self.srcp,
                     self.dstp,
                     self.seqn,
                     self.ackn,
                     data_offset,
                     flags, 
                     self.window,
                     self.checksum,
                     self.urgp)
        #pseudo header fields
        source_ip = source
        destination_ip = destination
        reserved = 0
        protocol = socket.IPPROTO_TCP
        total_length = len(tcp_header) + len(self.payload)
        # Pseudo header
        psh = struct.pack("!4s4sBBH",
              source_ip,
              destination_ip,
              reserved,
              protocol,
              total_length)
        psh = psh + tcp_header + self.payload
        tcp_checksum = checksum(psh)
        tcp_header = struct.pack("!HHLLBBH",
                  self.srcp,
                  self.dstp,
                  self.seqn,
                  self.ackn,
                  data_offset,
                  flags,
                  self.window)
        tcp_header+= struct.pack('H', tcp_checksum) + struct.pack('!H', self.urgp)

We know that the TCP protocol is a connection-oriented, reliable, IP-based transport layer protocol that provides a connection-oriented, reliable byte stream service. Connection-oriented means that two applications using TCP (usually a client and a server) must establish a TCP connection before exchanging packets with each other.

The forged header information has five different areas and includes the source IP and destination IP

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 IP address                    |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|                        Destination IP address                 |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|     Reserved  |   Protocol    |          Total Length         |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
•Source IP address (32 bits): Sender's IP address•Destination IP address (32 bits): Receiver's IP address•Reserved (8 bits): Zeroed•Protocol (8 bits): Transport protocol (6 for TCP, 17 for UDP)

In the TCP header checksum calculation, the checksum field must be zeroed. Once the value is calculated, it must be inserted into the field before sending the packet.

Constructing Header Fields

source_ip = source
destination_ip = destination
reserved = 0
protocol = socket.IPPROTO_TCP

Pack the forged header and insert it into the TCP header and data:

# Forged header
psh = struct.pack("!4s4sBBH",
              source_ip,
              destination_ip,
              reserved,
              protocol,
              total_length)
psh = psh + tcp_header + self.payload

Checksum Function:

def checksum(data):
    s = 0
    n = len(data) % 2
    for i in range(0, len(data)-n, 2):
        s+= ord(data[i]) + (ord(data[i+1]) << 8)
    if n:
        s+= ord(data[i+1])
    while (s >> 16):
        print("s >> 16: ", s >> 16)
        s = (s & 0xFFFF) + (s >> 16)
    print("sum:", s)
    s = ~s & 0xffff

A small example:

s = socket.socket(socket.AF_INET,
                  socket.SOCK_RAW,
                  socket.IPPROTO_RAW)
src_host = "10.0.2.15"
dest_host = socket.gethostbyname("www.reddit.com")
data = "TEST!!"# IP Header
ipobj = ip(src_host, dest_host)
iph = ip_object.pack()# TCP Header
tcpobj = tcp(1234, 80)
tcpobj.data_length = len(data)  # Used in pseudo header
tcph = tcpobj.pack(ipobj.source,
                   ipobj.destination)# Injection
packet = iph + tcph + data
Pinject.py
Running the script:
python pinject.py --src=10.0.2.15 --dst=www.reddit.com
[+] Local Machine: 10.0.2.15[+] Remote Machine: 198.41.209.142[+] Raw socket created
[+] Data to inject: TEST!![+] Constructing IP Header[+] Constructing TCP Header

Screenshot from Wireshark:

Raw sockets

Project Address:https://github.com/offensive-python/Pinject