From db7588259480ad72910e2b2c91af8c05b3df5ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martijn=20The=CC=81?= <martijn@pebble.com> Date: Tue, 12 Apr 2016 14:31:36 +0200 Subject: [PATCH] Bluetooth: add EIR / BLE Advertising packet classes --- scapy/fields.py | 4 ++ scapy/layers/bluetooth.py | 117 +++++++++++++++++++++++++++++++++++++- test/bluetooth.uts | 24 ++++++++ 3 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 test/bluetooth.uts diff --git a/scapy/fields.py b/scapy/fields.py index 3c446606..e82e24ee 100644 --- a/scapy/fields.py +++ b/scapy/fields.py @@ -283,6 +283,10 @@ class ThreeBytesField(X3BytesField, ByteField): def i2repr(self, pkt, x): return ByteField.i2repr(self, pkt, x) +class SignedByteField(Field): + def __init__(self, name, default): + Field.__init__(self, name, default, "b") + class ShortField(Field): def __init__(self, name, default): Field.__init__(self, name, default, "H") diff --git a/scapy/layers/bluetooth.py b/scapy/layers/bluetooth.py index 590e70eb..d9b258c1 100644 --- a/scapy/layers/bluetooth.py +++ b/scapy/layers/bluetooth.py @@ -312,6 +312,109 @@ class SM_Master_Identification(Packet): fields_desc = [ XLEShortField("ediv", 0), StrFixedLenField("rand", '\x00' * 8, 8), ] + +class EIR_Hdr(Packet): + name = "EIR Header" + fields_desc = [ + FieldLenField("len", 0, fmt="B"), + ByteEnumField("type", 0, { + 0x01: "flags", + 0x02: "incomplete_list_16_bit_svc_uuids", + 0x03: "complete_list_16_bit_svc_uuids", + 0x04: "incomplete_list_32_bit_svc_uuids", + 0x05: "complete_list_32_bit_svc_uuids", + 0x06: "incomplete_list_128_bit_svc_uuids", + 0x07: "complete_list_128_bit_svc_uuids", + 0x08: "shortened_local_name", + 0x09: "complete_local_name", + 0x0a: "tx_power_level", + 0x0d: "class_of_device", + 0x0e: "simple_pairing_hash", + 0x0f: "simple_pairing_rand", + 0x10: "sec_mgr_tk", + 0x11: "sec_mgr_oob_flags", + 0x12: "slave_conn_intvl_range", + 0x17: "pub_target_addr", + 0x18: "rand_target_addr", + 0x19: "appearance", + 0x1a: "adv_intvl", + 0x1b: "le_addr", + 0x1c: "le_role", + 0x14: "list_16_bit_svc_sollication_uuids", + 0x1f: "list_32_bit_svc_sollication_uuids", + 0x15: "list_128_bit_svc_sollication_uuids", + 0x16: "svc_data_16_bit_uuid", + 0x20: "svc_data_32_bit_uuid", + 0x21: "svc_data_128_bit_uuid", + 0x22: "sec_conn_confirm", + 0x22: "sec_conn_rand", + 0x24: "uri", + 0xff: "mfg_specific_data", + }), + ] + + def mysummary(self): + return self.sprintf("EIR %type%") + +class EIR_Element(Packet): + name = "EIR Element" + + def extract_padding(self, s): + # Needed to end each EIR_Element packet and make PacketListField work. + return '', s + + @staticmethod + def length_from(pkt): + # 'type' byte is included in the length, so substract 1: + return pkt.underlayer.len - 1 + +class EIR_Raw(EIR_Element): + name = "EIR Raw" + fields_desc = [ + StrLenField("data", "", length_from=EIR_Element.length_from) + ] + +class EIR_Flags(EIR_Element): + name = "Flags" + fields_desc = [ + FlagsField("flags", 0x2, 8, + ["limited_disc_mode", "general_disc_mode", + "br_edr_not_supported", "simul_le_br_edr_ctrl", + "simul_le_br_edr_host"] + 3*["reserved"]) + ] + +class EIR_CompleteList16BitServiceUUIDs(EIR_Element): + name = "Complete list of 16-bit service UUIDs" + fields_desc = [ + FieldListField("svc_uuids", None, XLEShortField("uuid", 0), + length_from=EIR_Element.length_from) + ] + +class EIR_IncompleteList16BitServiceUUIDs(EIR_CompleteList16BitServiceUUIDs): + name = "Incomplete list of 16-bit service UUIDs" + +class EIR_CompleteLocalName(EIR_Element): + name = "Complete Local Name" + fields_desc = [ + StrLenField("local_name", "", length_from=EIR_Element.length_from) + ] + +class EIR_ShortenedLocalName(EIR_CompleteLocalName): + name = "Shortened Local Name" + +class EIR_TX_Power_Level(EIR_Element): + name = "TX Power Level" + fields_desc = [SignedByteField("level", 0)] + +class EIR_Manufacturer_Specific_Data(EIR_Element): + name = "EIR Manufacturer Specific Data" + fields_desc = [ + XLEShortField("company_id", 0), + StrLenField("data", "", + length_from=lambda pkt: EIR_Element.length_from(pkt) - 2) + ] + + class HCI_Command_Hdr(Packet): name = "HCI Command header" fields_desc = [ XLEShortField("opcode", 0), @@ -477,7 +580,10 @@ class HCI_LE_Meta_Advertising_Report(Packet): ByteEnumField("atype", 0, {0:"public", 1:"random"}), LEMACField("addr", None), FieldLenField("len", None, length_of="data", fmt="B"), - StrLenField("data", "", length_from=lambda pkt:pkt.len), ] + PacketListField("data", [], EIR_Hdr, + length_from=lambda pkt:pkt.len), + SignedByteField("rssi", 0)] + class HCI_LE_Meta_Long_Term_Key_Request(Packet): name = "Long Term Key Request" @@ -521,6 +627,15 @@ bind_layers( HCI_Event_LE_Meta, HCI_LE_Meta_Connection_Complete, event=1) bind_layers( HCI_Event_LE_Meta, HCI_LE_Meta_Advertising_Report, event=2) bind_layers( HCI_Event_LE_Meta, HCI_LE_Meta_Long_Term_Key_Request, event=5) +bind_layers(EIR_Hdr, EIR_Flags, type=0x01) +bind_layers(EIR_Hdr, EIR_IncompleteList16BitServiceUUIDs, type=0x02) +bind_layers(EIR_Hdr, EIR_CompleteList16BitServiceUUIDs, type=0x03) +bind_layers(EIR_Hdr, EIR_ShortenedLocalName, type=0x08) +bind_layers(EIR_Hdr, EIR_CompleteLocalName, type=0x09) +bind_layers(EIR_Hdr, EIR_TX_Power_Level, type=0x0a) +bind_layers(EIR_Hdr, EIR_Manufacturer_Specific_Data, type=0xff) +bind_layers(EIR_Hdr, EIR_Raw) + bind_layers( HCI_ACL_Hdr, L2CAP_Hdr, ) bind_layers( L2CAP_Hdr, L2CAP_CmdHdr, cid=1) bind_layers( L2CAP_CmdHdr, L2CAP_CmdRej, code=1) diff --git a/test/bluetooth.uts b/test/bluetooth.uts new file mode 100644 index 00000000..4613e4e0 --- /dev/null +++ b/test/bluetooth.uts @@ -0,0 +1,24 @@ +% Scapy Bluetooth layer tests + ++ Bluetooth LE Advertising / Scan Response Data Parsing += Parse EIR_Flags, EIR_CompleteList16BitServiceUUIDs, EIR_CompleteLocalName and EIR_TX_Power_Level + +ad_report_raw_data = \ + "043e2b020100016522c00181781f0201020303d9fe1409" \ + "506562626c652054696d65204c452037314536020a0cde".decode('hex') +scapy_packet = HCI_Hdr(ad_report_raw_data) + +assert(scapy_packet[EIR_Flags].flags == 0x02) +assert(scapy_packet[EIR_CompleteList16BitServiceUUIDs].svc_uuids == [0xfed9]) +assert(scapy_packet[EIR_CompleteLocalName].local_name == 'Pebble Time LE 71E6') +assert(scapy_packet[EIR_TX_Power_Level].level == 12) + += Parse EIR_Manufacturer_Specific_Data + +scan_resp_raw_data = \ + "043e2302010401be5e0eb9f04f1716ff5401005f423331" \ + "3134374432343631fc00030c0000de".decode('hex') +scapy_packet = HCI_Hdr(scan_resp_raw_data) + +assert(scapy_packet[EIR_Manufacturer_Specific_Data].data == '\x00_B31147D2461\xfc\x00\x03\x0c\x00\x00') +assert(scapy_packet[EIR_Manufacturer_Specific_Data].company_id == 0x154) -- GitLab