> For the complete documentation index, see [llms.txt](https://www.impacket.wiki/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://www.impacket.wiki/tutorials/build-a-packet-sniffer-in-python.md).

# Build a Packet Sniffer in Python

> Building custom network packets with Impacket Structure class

## 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.

```python
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:

```python
'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

```python
':'    # 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

```python
'?-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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://www.impacket.wiki/tutorials/build-a-packet-sniffer-in-python.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
