Search code examples
pythonctypesendianness

Confused by Pythons ctypes LittleEndianStructure


I'm experimenting with using the ctypes module to parse packets captured using the socket module and I'm confused by what I see when using the LittleEndianStructure.

Using Python 3.6.8 on a CentOS 7 64bit VM, I'm capturing a UDP packet with the payload 0x8401

I then want to parse the payload into the following structure, and print out the field values.

class MSG(BigEndianStructure):
    _fields_ = [
        ("A", c_ubyte, 4),
        ("B", c_ubyte, 3),
        ("C", c_ubyte, 1),
        ("D", c_ubyte, 4),
        ("E", c_ubyte, 4)
    ]

when I use BigEndianStructure, I get the results I expect

MSG
  A: 8
  B: 2
  C: 0
  D: 0
  E: 1 

I then try using LittleEndianStructure where I expected the payload to be swapped to 0x0184 and produce the following

MSG
  A: 0
  B: 0
  C: 1
  D: 8
  E: 4 

However I instead get the following, and I don't understand why.

MSG
  A: 4
  B: 0
  C: 1
  D: 1
  E: 0 

here's the full code. I appreciate any help.

import socket
import sys
import struct
from ctypes import *

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_uint32),
        ("dst",          c_uint32)
    ]

    def __new__(self, socket_buffer=None):
        return self.from_buffer_copy(socket_buffer)

    def __init__(self, socket_buffer=None):
        pass

class UDP(BigEndianStructure):
    _fields_ = [
        ("sport", c_ushort),
        ("dport", c_ushort),
        ("length", c_ushort),
        ("checksum", c_ushort)
    ]

    def __new__(self, socket_buffer):
        return self.from_buffer_copy(socket_buffer)

    def __init__(self, socket_buffer):
        pass

class MSG(LittleEndianStructure):
    _fields_ = [
        ("A", c_ubyte, 4),
        ("B", c_ubyte, 3),
        ("C", c_ubyte, 1),
        ("D", c_ubyte, 4),
        ("E", c_ubyte, 4)
    ]

    def __new__(self, socket_buffer):
        return self.from_buffer_copy(socket_buffer)

    def __init__(self, socket_buffer):
        pass

def sniff():

    eth_length = 14
    ip_length = 20
    udp_length = 8
    msg_length = 2

    try:
        # Capture all packets
        protocol = socket.ntohs(0x0003)  # captures all packets
        sniffer = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, protocol)

    except KeyboardInterrupt:
        sys.exit()

    while True:
        # read a packet
        raw_buffer = sniffer.recvfrom(65535)[0]

        eth_header = raw_buffer[:eth_length]
        eth_payload = raw_buffer[eth_length:]

        eth = struct.unpack('!6s6sH', eth_header)
        eth_protocol = socket.ntohs(eth[2])

        # IP packet
        if eth_protocol == 8:
            # create an IP header from the first 20 bytes
            ip_header = IP(eth_payload[:ip_length])
            ip_data = eth_payload[ip_length:]

            # UDP packet
            if ip_header.protocol_num == 17:
                udp_header = UDP(ip_data[:udp_length])
                udp_data = ip_data[udp_length:]

                if udp_header.dport == 8001:
                    msg_header = MSG(udp_data[:msg_length])
                    print("MSG")
                    print("   A: %s" % msg_header.A)
                    print("   B: %s" % msg_header.B)
                    print("   C: %s" % msg_header.C)
                    print("   D: %s" % msg_header.D)
                    print("   E: %s" % msg_header.E)

if __name__ == '__main__':
    sniff()

Solution

  • The actual little-endian result is fetching bits from the lowest first.

    Bytes: 84 01

    Big endian word 840116 (10000100000000012) and divide the bits starting with high bit:

    A    B   C D    E
    1000 010 0 0000 0001 so ABCDE = 82001
    

    Little endian word 018416 (00000001100001002) and divide the bits starting with the low bit:

    E    D    C B   A
    0000 0001 1 000 0100 so ABCDE = 40110