Skip to main content

Overview

Impacket’s Structure class (impacket/structure.py) provides a powerful framework for defining and manipulating binary data structures. It’s the foundation for all protocol implementations in Impacket.

The Structure Class

Basic Concept

The Structure class allows you to define data structures declaratively using format specifiers similar to Python’s struct module, with additional features for complex protocols.
from impacket.structure import Structure

class MyPacket(Structure):
    structure = (
        ('field1', 'B'),      # Unsigned byte
        ('field2', 'H'),      # Unsigned short
        ('field3', '16s'),    # 16-byte string
    )

Format Specifiers

Standard struct Format

From Python’s struct module:
'B'    # Unsigned byte (1 byte)
'H'    # Unsigned short (2 bytes)
'I'    # Unsigned int (4 bytes)
'Q'    # Unsigned long long (8 bytes)
'b'    # Signed byte
'h'    # Signed short
'i'    # Signed int
'q'    # Signed long long
's'    # String (must specify length: '16s')
'<'    # Little-endian
'>'    # Big-endian
'!'    # Network byte order (big-endian)

Extended Impacket Specifiers

':'    # Raw bytes (copy field as-is)
'z'    # Null-terminated string (ASCIIZ)
'u'    # UTF-16LE null-terminated string
'w'    # DCE-RPC/NDR string format
'_'    # Don't pack this field

Dynamic Specifiers

'?-field'       # Length of 'field' formatted as ?
'?1*?2'         # Array of ?2 elements, count stored as ?1
'?=expression'  # Evaluate expression and pack as ?
'?&fieldname'   # Address/presence of fieldname

Creating Structures

Simple Packet

from impacket.structure import Structure

class EthernetHeader(Structure):
    structure = (
        ('destination', '6s="\x00" * 6'),  # MAC address with default
        ('source', '6s="\x00" * 6'),
        ('type', '>H=0x0800'),              # Network byte order, default 0x0800 (IPv4)
    )

# Create instance
eth = EthernetHeader()
eth['destination'] = b'\xff\xff\xff\xff\xff\xff'  # Broadcast
eth['source'] = b'\x00\x11\x22\x33\x44\x55'
eth['type'] = 0x0800

# Get binary data
packet_data = eth.getData()
print(packet_data.hex())

Variable-Length Fields

class SMBHeader(Structure):
    structure = (
        ('Protocol', '4s="\xffSMB"'),
        ('Command', 'B=0'),
        ('ErrorClass', 'B=0'),
        ('Reserved', 'B=0'),
        ('ErrorCode', '<H=0'),
        ('Flags', 'B=0'),
        ('Flags2', '<H=0'),
        ('PIDHigh', '<H=0'),
        ('SecurityFeatures', '8s="\x00" * 8'),
        ('Reserved2', '<H=0'),
        ('TID', '<H=0'),
        ('PIDLow', '<H=0'),
        ('UID', '<H=0'),
        ('MID', '<H=0'),
    )

smb = SMBHeader()
smb['Command'] = 0x72  # SMB_COM_NEGOTIATE
data = smb.getData()

Length-Prefixed Data

class MessagePacket(Structure):
    structure = (
        ('MessageLength', '<H-Message'),  # Auto-calculated from Message field
        ('Message', ':'),                 # Raw bytes
    )

msg = MessagePacket()
msg['Message'] = b'Hello, World!'
data = msg.getData()
# MessageLength will automatically be set to len(b'Hello, World!')

Arrays

class ArrayPacket(Structure):
    structure = (
        ('Count', '<H'),           # Number of items
        ('Items', '<H*Count'),     # Array of Count items, each <H
    )

pkt = ArrayPacket()
pkt['Items'] = [1, 2, 3, 4, 5]
data = pkt.getData()
# Count will be automatically set to 5

# Fixed-size array
class FixedArrayPacket(Structure):
    structure = (
        ('Items', '10*<H'),  # Exactly 10 unsigned shorts
    )

Nested Structures

class InnerStruct(Structure):
    structure = (
        ('value1', '<I'),
        ('value2', '<I'),
    )

class OuterStruct(Structure):
    structure = (
        ('header', '<I'),
        ('inner', ':', InnerStruct),  # Nested structure
        ('trailer', '<I'),
    )

outer = OuterStruct()
inner = InnerStruct()
inner['value1'] = 100
inner['value2'] = 200

outer['header'] = 1
outer['inner'] = inner
outer['trailer'] = 2

data = outer.getData()

Parsing Packets

From Binary Data

class TCPHeader(Structure):
    structure = (
        ('srcPort', '>H'),
        ('dstPort', '>H'),
        ('seqNum', '>I'),
        ('ackNum', '>I'),
        ('dataOff', 'B'),
        ('flags', 'B'),
        ('window', '>H'),
        ('checksum', '>H'),
        ('urgPtr', '>H'),
    )

# Parse from bytes
raw_data = b'\x00\x50\x1f\x90...'  # TCP packet bytes
tcp = TCPHeader(raw_data)

print(f"Source Port: {tcp['srcPort']}")
print(f"Dest Port: {tcp['dstPort']}")
print(f"Seq: {tcp['seqNum']}")

Conditional Fields

class ConditionalPacket(Structure):
    structure = (
        ('hasData', 'B'),
        ('_dataPresence', '_', 'B&data'),  # Mark data as conditional
        ('data', ':'),                      # Only present if hasData is set
    )

# With data
pkt1 = ConditionalPacket()
pkt1['hasData'] = 1
pkt1['data'] = b'Some data'

# Without data
pkt2 = ConditionalPacket()
pkt2['hasData'] = 0
# Don't set data field

Real-World Examples

SMB Packet Construction

from impacket.structure import Structure

class SMB2Header(Structure):
    structure = (
        ('ProtocolId', '4s="\xfeSMB"'),
        ('StructureSize', '<H=64'),
        ('CreditCharge', '<H=0'),
        ('Status', '<I=0'),
        ('Command', '<H=0'),
        ('CreditRequest', '<H=0'),
        ('Flags', '<I=0'),
        ('NextCommand', '<I=0'),
        ('MessageId', '<Q=0'),
        ('Reserved', '<I=0'),
        ('TreeId', '<I=0'),
        ('SessionId', '<Q=0'),
        ('Signature', '16s="\x00" * 16'),
    )

class SMB2NegotiateRequest(Structure):
    structure = (
        ('StructureSize', '<H=36'),
        ('DialectCount', '<H-Dialects'),
        ('SecurityMode', '<H=0'),
        ('Reserved', '<H=0'),
        ('Capabilities', '<I=0'),
        ('ClientGuid', '16s="\x00" * 16'),
        ('NegotiateContextOffset', '<I=0'),
        ('NegotiateContextCount', '<H=0'),
        ('Reserved2', '<H=0'),
        ('Dialects', '<H*DialectCount'),
    )

# Create SMB2 NEGOTIATE packet
header = SMB2Header()
header['Command'] = 0  # SMB2_NEGOTIATE
header['MessageId'] = 1

negotiate = SMB2NegotiateRequest()
negotiate['Dialects'] = [0x0202, 0x0210, 0x0300, 0x0302, 0x0311]  # SMB 2.0.2, 2.1, 3.0, 3.0.2, 3.1.1
negotiate['SecurityMode'] = 1  # Signing enabled

packet = header.getData() + negotiate.getData()

NTLM Authentication Packet

from impacket.structure import Structure

class NTLMAuthNegotiate(Structure):
    structure = (
        ('Signature', '8s="NTLMSSP\\x00"'),
        ('MessageType', '<I=1'),  # NEGOTIATE_MESSAGE
        ('NegotiateFlags', '<I=0'),
        ('DomainNameLen', '<H-DomainName'),
        ('DomainNameMaxLen', '<H-DomainName'),
        ('DomainNameOffset', '<I=0'),
        ('WorkstationLen', '<H-WorkstationName'),
        ('WorkstationMaxLen', '<H-WorkstationName'),
        ('WorkstationOffset', '<I=0'),
        ('VersionLen', '_-Version', '8 if self["NegotiateFlags"] & 0x02000000 else 0'),
        ('Version', '8s=""'),
        ('DomainName', ':'),
        ('WorkstationName', ':'),
    )

ntlm = NTLMAuthNegotiate()
ntlm['NegotiateFlags'] = 0xb2808205  # Typical flags
ntlm['DomainName'] = b'DOMAIN'
ntlm['WorkstationName'] = b'WORKSTATION'

auth_data = ntlm.getData()

Custom Protocol

class CustomProtocolHeader(Structure):
    structure = (
        ('magic', '4s="CUST"'),
        ('version', 'B=1'),
        ('type', 'B=0'),
        ('flags', '<H=0'),
        ('payloadLength', '<I-payload'),
        ('checksum', '<I=0'),
        ('payload', ':'),
    )

def create_packet(msg_type, flags, payload):
    pkt = CustomProtocolHeader()
    pkt['type'] = msg_type
    pkt['flags'] = flags
    pkt['payload'] = payload
    
    # Calculate checksum
    data = pkt.getData()
    checksum = sum(data) & 0xffffffff
    pkt['checksum'] = checksum
    
    return pkt.getData()

packet = create_packet(1, 0x0001, b'Hello')

Advanced Techniques

Evaluated Fields

class DynamicPacket(Structure):
    structure = (
        ('dataSize', '<I=len(self["data"]) if "data" in self.fields else 0'),
        ('flags', '<I=(1 if self["dataSize"] > 0 else 0)'),
        ('data', ':'),
    )

Common Header Pattern

class BasePacket(Structure):
    commonHdr = (
        ('signature', '4s="BASE"'),
        ('version', 'B=1'),
    )
    structure = (
        ('data', ':'),
    )

# commonHdr is automatically prepended to structure
pkt = BasePacket()
pkt['data'] = b'payload'
data = pkt.getData()
# Result: b'BASE\x01' + b'payload'

Alignment

class AlignedPacket(Structure):
    def __init__(self, data=None, alignment=4):
        Structure.__init__(self, data, alignment)
    
    structure = (
        ('field1', 'B'),
        ('field2', 'B'),
        # Will be padded to 4-byte alignment
    )

pkt = AlignedPacket()
pkt['field1'] = 1
pkt['field2'] = 2
data = pkt.getData()
# Result: b'\x01\x02\x00\x00' (padded to 4 bytes)

Debugging

from impacket.structure import Structure, hexdump

# Enable debug output
class DebugPacket(Structure):
    debug = 1  # Print packing operations
    structure = (
        ('field1', '<I'),
        ('field2', '<H'),
    )

pkt = DebugPacket()
pkt['field1'] = 0x12345678
pkt['field2'] = 0xabcd

data = pkt.getData()

# Hexdump the result
print("Packet data:")
hexdump(data)

Best Practices

  1. Use type hints in field names: ('length', '<H') is clearer than ('len', '<H')
  2. Specify default values: ('type', 'B=1') prevents uninitialized fields
  3. Use length prefixes: ('<H-Data') auto-calculates lengths
  4. Document complex formats: Add comments for protocol-specific fields
  5. Test with real data: Parse actual packets to validate structure definitions
  6. Handle endianness: Explicitly specify < (little) or > (big) endian

Common Pitfalls

  1. String encoding: Use b'' for byte strings, not regular strings
  2. Array lengths: Ensure count field is set before packing arrays
  3. Field order: Structure fields are packed in definition order
  4. Alignment: Be aware of padding in binary protocols
  5. Nested structures: Use : format specifier, not direct structure assignment

References

  • Python struct module documentation
  • Impacket source: impacket/structure.py
  • Protocol RFCs and Microsoft specifications for real-world examples

Build docs developers (and LLMs) love