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()
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