Search code examples
pythonsocketsbufferctypespacket-sniffers

Python packet sniffer using ctypes crashes when copying socket buffer


I'm trying to capture the first 20 bytes (full packet minus the options) of an IP packet, populate a struct of ctype members, and print the information I want to the screen (protocol, source and destination address). My IP class is as follows:

import socket
import os
import struct
from ctypes import *

# host to listen on
host = "192.168.0.187"
# our IP header
class IP(Structure):
        _fields_ = [
            ("ihl", c_ubyte, 4),
            ("version", c_ubyte, 4),
            ("tos", c_ubyte),
            ("len", c_ushort),
            ("id", c_ushort),
            ("offset", c_ushort),
            ("ttl", c_ubyte),
            ("protocol_num", c_ubyte),
            ("sum", c_ushort),
            ("src", c_ulong),
            ("dst", c_ulong)
        ]
    def __new__(self, socket_buffer=None):
        return self.from_buffer_copy(socket_buffer)
    def __init__(self, socket_buffer=None):
        # map protocol constants to their names
        self.protocol_map = {1:"ICMP", 6:"TCP", 17:"UDP"}
        # human readable IP addresses
        self.src_address = socket.inet_ntoa(struct.pack("<L",self.src))
        self.dst_address = socket.inet_ntoa(struct.pack("<L",self.dst))
        # human readable protocol
        try:
            self.protocol = self.protocol_map[self.protocol_num]
        except:
            self.protocol = str(self.protocol_num)

Now, I create the socket, bind it to the host, and loop to get the packets:

# create socket and bind to public interface (os dependent)
if os.name == "nt":
    socket_protocol = socket.IPPROTO_IP
else:
    socket_protocol = socket.IPPROTO_ICMP

# create raw socket
sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)
sniffer.bind((host, 0))
# include header information
sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

if os.name == "nt":
    sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
    
try:
    while True:
        # read in a packet
        raw_buffer = sniffer.recvfrom(65565)[0]
        # create an IP header from the first 20 bytes of the buffer
        ip_header = IP(raw_buffer[0:20])
        # print out the protocol that was detected and the hosts
        print "Protocol: %s %s -> %s" % (ip_header.protocol, ip_header.src_¬
                                address, ip_header.dst_address)
# handle CTRL-C
except KeyboardInterrupt:
    # if we're using Windows, turn off promiscuous mode
    if os.name == "nt":
        sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)

When run using c_ulong as data type for the "src" and "dst" _fields_ struct members, I get the following error (running in one terminal while pinging nostarch.com in another):

enter image description here

I postulated that perhaps the size of the c_ulong was larger than a byte, thus throwing off my requirement for the first 20 bytes (I'm very new to python). I then changed the c_ulong to c_ushort and ran it again:

enter image description here

Actual ping path:

enter image description here

So, while the script ran without error, it's cutting off the src and dst addresses.

Why is it asking for at least 32 bytes when I'm telling it I only want the first 20?

(I'm in Kali64 VBox VM, running on Win7 host, using Python 2.7)

Any help is appreciated.


Solution

  • The size of IP should be verified, i.e.

    print(sizeof(IP))
    

    should return 20 bytes. Since ctypes.u_long is 8 in case of 64bit linux, the size will be 32 bytes (4 bytes extra due to padding, 8 bytes due to integer sizes). Either use ctypes.u_int or explicit sizes as follows:

    from ctypes import *
    
    class IP(Structure):
        _fields_ = [ ("version", c_uint8, 4),
                     ("ihl", c_uint8, 4),
                     ("tos", c_uint8),
                     ("len", c_uint16),
                     ("id", c_uint16),
                     ("offset", c_uint16),
                     ("ttl", c_uint8),
                     ("protocol_num", c_uint8),
                     ("sum", c_uint16),
                     ("src", c_uint32),
                     ("dst", c_uint32) ]
    
    print(sizeof(IP))