I am currently in the process of writing a few custom scapy layers to dissect/build packets for a protocol and I have it mostly working. Since the protocol sits on top of TCP, I've been using a StreamSocket to send/receive the packets, like so:
from scapy.all import *
s = socket.socket()
s.connect(('127.0.0.1', 1337))
ss = StreamSocket(s, MyLayer)
ans, unans = ss.sr(MyLayer())
I have overloaded my layer's answers() method to define what constitutes a "response". This protocol uses ACK numbers similar to TCP, but instead of ACK'ing the sequence number of the next packet, it instead ACK's the sequence number of the last packet received. In addition to the SEQ and ACK numbers, packets of this layer also contain a length field and two checksum fields, so I've overloaded the layer's post_build() method to compute them automatically. Altogether, my layer looks as follows:
from numpy import uint8
from random import randint
from scapy.all import *
class MyLayer(Packet):
name = 'MyLayer'
fields_desc = [
XShortField('sop', 0xFF5A),
XShortField('len', None),
FlagsField('ctl', 0x40, 8, [
'ZERO0',
'ZERO1',
'ZERO2',
'SUS',
'RST',
'EAK',
'ACK',
'SYN'
]),
ByteField('seq', None),
ByteField('ack', None),
ByteField('sess', 0),
XByteField('chk', None)
StrLenField('data', '', length_from=lambda pkt: pkt.len - 10),
XByteField('datachk', None)
]
send_seq = uint8(randint(0, 255))
recv_seq = uint8(0)
def post_build(self, p, pay):
if self.len is None:
len_off = 2
len_size = 2
p = p[:len_off] + struct.pack('!H', len(p)) + p[len_off+len_size:]
if self.seq is None:
seq_off = 5
seq_size = 1
# Increments send_seq before sending by examining the stack trace.
for line in traceback.format_stack():
if '_sndrcv_snd' in line or 'in send' in line:
MyLayer.send_seq = uint8(MyLayer.send_seq + 1)
break
p = p[:seq_off] + MyLayer.send_seq + p[seq_off+seq_size:]
if self.ack is None:
ack_off = 6
ack_size = 1
p = p[:ack_off] + MyLayer.recv_seq + p[ack_off+ack_size:]
if self.chk is None:
chk_off = 8
chk_size = 1
chk = uint8(0x100 - sum(p[:chk_off]))
p = p[:chk_off] + chk + p[chk_off+chk_size:]
if self.datachk is None:
datachk_off = len(p) - 1
datachk = uint8(0x100 - sum(p[:datachk_off]))
p = p[:datachk_off] + datachk
return p + pay
def update_recv_seq(self):
MyLayer.recv_seq = uint8(self.seq)
def answers(self, other):
self.update_recv_seq()
if isinstance(other, MyLayer) and self.ack == other.seq:
return 1
return 0
Unfortunately, when I set a packet's seq
to None
in order for it to be computed automatically, answers() fails to recognize the received packet as being a response to the one I sent because other.seq
is still set to None
. Adding the line self.seq = MyLayer.send_seq
to the end of post_build() doesn't seem to work either. I've resorted to recasting the raw bytes after rebuilding the layer, but this seems like a clumsy solution:
pkts = rdpcap('path/to/cap.pcap')
for pkt in pkts:
if pkt.haslayer(MyLayer):
del pkt[MyLayer].seq
del pkt[MyLayer].ack
del pkt[MyLayer].chk
del pkt[MyLayer].datachk
to_send = MyLayer(raw(pkt[MyLayer]))
ans, unans = ss.sr(to_send)
Is there a better way to address this problem?