Jonathan
Jonathan

Reputation: 128

Scapy: How to implement a list of conditional fields?

I am new to Scapy and I want to dissect a complicated protocol. Here is where I get blocked: I have a packet holding, in two fields, the type and the number of records. The rest of the packet constitutes the records.

I implemented it with two layers: the first one, the MainPacket class, holding the record_type, the record_nb and a list of Records. The second layer, the Record class, is fully composed of conditional fields, one per record type, as shown below:

from scapy.all import Packet, IntField, IntEnumField, FieldLenField\
,PacketListField, StrNullField, ConditionalField

class Record(Packet):
    fields_desc = [
        #IntField("data_long_record", 0)
        #StrNullField("data_sz_record", "")
        ConditionalField(IntField("data_long_record", 0),
                         lambda pkt:pkt.underlayer.record_type==3),
        ConditionalField(StrNullField("data_sz_record", ""),
                         lambda pkt:pkt.underlayer.record_type==8)
    ]
    def extract_padding(self, s):
        return '', s

class MainPacket(Packet):
    fields_desc = [
        IntEnumField("record_type", 1, {
            3:"DATA_LONG",            
            8:"DATA_SZ"}),
        FieldLenField("record_nb", 0, fmt="I", count_of="records"),
        PacketListField("records", None, Record, count_from=lambda pkt:pkt.record_nb)
    ]
    def extract_padding(self, s):
        return '', s

if __name__ == "__main__":
    p1 = MainPacket("\x00\x00\x00\x03" # Type is DATA_LONG
               "\x00\x00\x00\x04" # 4 records
               "\x00\x00\x00\x01" 
               "\x00\x00\x00\x02"
               "\x00\x00\x00\x03"
               "\x00\x00\x00\x04")
    p1.show()
    p2 = MainPacket("\x00\x00\x00\x08" # Type is DATA_SZ
               "\x00\x00\x00\x02" # 2 records
               "Hello\x00"
               " world.\x00")
    p2.show()

Here is the result I get when testing the code:

WARNING: No route found for IPv6 destination :: (no default route?)
###[ MainPacket ]###
  record_type= DATA_LONG
  record_nb = 4
  \records   \
   |###[ Raw ]###
   |  load      = '\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04'
###[ MainPacket ]###
  record_type= DATA_SZ
  record_nb = 2
  \records   \
   |###[ Raw ]###
   |  load      = 'Hello\x00 world.\x00'

The sublayer is not dissected. However, Replacing the conditionals by the IntField or the StrNullField works well, except that it cannot handle all cases...

My Python version is 2.7.6. My Scapy version is 2.3.2. I use Linux Mint 17.

Do you have any clue?

Upvotes: 1

Views: 3261

Answers (2)

Pierre
Pierre

Reputation: 6237

Maybe you can move the condition to MainPacket? Also, you can use bind_layers() instead of overwriting .extract_padding().

from scapy.all import Packet, IntField, IntEnumField, FieldLenField, \
PacketListField, StrNullField, ConditionalField, Padding, bind_layers

class RecordLONG(Packet):
    fields_desc = [
        IntField("data_long_record", 0),
    ]

class RecordSZ(Packet):
    fields_desc = [
        StrNullField("data_sz_record", ""),
    ]

bind_layers(RecordLONG, Padding)
bind_layers(RecordSZ, Padding)

class MainPacket(Packet):
    fields_desc = [
        IntEnumField("record_type", 1, {
            3:"DATA_LONG",            
            8:"DATA_SZ"}),
        FieldLenField("record_nb", 0, fmt="I", count_of="records"),
        ConditionalField(PacketListField("records", None, RecordLONG, count_from=lambda pkt:pkt.record_nb), lambda pkt: pkt.record_type == 3),
        ConditionalField(PacketListField("records", None, RecordSZ, count_from=lambda pkt:pkt.record_nb), lambda pkt: pkt.record_type == 8),
    ]

if __name__ == "__main__":
    p1 = MainPacket("\x00\x00\x00\x03" # Type is DATA_LONG
               "\x00\x00\x00\x04" # 4 records
               "\x00\x00\x00\x01" 
               "\x00\x00\x00\x02"
               "\x00\x00\x00\x03"
               "\x00\x00\x00\x04")
    p1.show()
    p2 = MainPacket("\x00\x00\x00\x08" # Type is DATA_SZ
               "\x00\x00\x00\x02" # 2 records
               "Hello\x00"
               " world.\x00")
    p2.show()

Output:

###[ MainPacket ]###
  record_type= DATA_LONG
  record_nb = 4
  \records   \
   |###[ RecordLONG ]###
   |  data_long_record= 1
   |###[ RecordLONG ]###
   |  data_long_record= 2
   |###[ RecordLONG ]###
   |  data_long_record= 3
   |###[ RecordLONG ]###
   |  data_long_record= 4
###[ MainPacket ]###
  record_type= DATA_SZ
  record_nb = 2
  \records   \
   |###[ RecordSZ ]###
   |  data_sz_record= 'Hello'
   |###[ RecordSZ ]###
   |  data_sz_record= ' world.'

Upvotes: 3

Jonathan
Jonathan

Reputation: 128

After diving into Scapy, I found the reason: pkt.underlayer in the lambda function was equal to None. Scapy did not report the error.

The solution I found was to store the underlayer in a global variable, as shown here under:

from scapy.all import Packet, IntField, IntEnumField, FieldLenField\
,PacketListField, StrNullField, ConditionalField, PacketLenField

current_main_packet = None

class Record(Packet):
    current_main_packet
    fields_desc = [
        #IntField("data_long_record", 0)
        #StrNullField("data_sz_record", "")
        ConditionalField(IntField("data_long_record", 0),
                         lambda pkt:pkt.underlayer.record_type==3),
        ConditionalField(StrNullField("data_sz_record", ""),
                         lambda pkt:pkt.underlayer.record_type==8)
    ]
    def extract_padding(self, s):
        return '', s

    def pre_dissect(self, s):
        self.underlayer = current_main_packet
        return s



class MainPacket(Packet):
    fields_desc = [
        IntEnumField("record_type", 1, {
            3:"DATA_LONG",            
            8:"DATA_SZ"}),
        FieldLenField("record_nb", 0, fmt="I", count_of="records"),
        PacketListField("records", None, Record, count_from=lambda pkt:pkt.record_nb)
    ]
    def extract_padding(self, s):
        return '', s

    def pre_dissect(self, s):
        global current_main_packet
        current_main_packet = self
        return s

if __name__ == "__main__":
    p1 = MainPacket("\x00\x00\x00\x03" # Type is DATA_LONG
               "\x00\x00\x00\x04" # 4 records
               "\x00\x00\x00\x01" 
               "\x00\x00\x00\x02"
               "\x00\x00\x00\x03"
               "\x00\x00\x00\x04")
    p1.show()
    p2 = MainPacket("\x00\x00\x00\x08" # Type is DATA_SZ
               "\x00\x00\x00\x02" # 2 records
               "Hello\x00"
               " world.\x00")
    p2.show()

I am still waiting for more elegant solutions :-)

Upvotes: 0

Related Questions