Skip to content

[WIP] Adding more BTLE PDUs #2215

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 240 additions & 15 deletions scapy/layers/bluetooth4LE.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
PPI_BTLE
from scapy.packet import Packet, bind_layers
from scapy.fields import BitEnumField, BitField, ByteEnumField, ByteField, \
Field, FlagsField, LEIntField, LEShortEnumField, LEShortField, \
MACField, PacketListField, SignedByteField, X3BytesField, XBitField, \
XByteField, XIntField, XShortField, XLEIntField, XLEShortField
Field, FlagsField, LEIntField, LEShortEnumField, LEShortField, LEFieldLenField,StrFixedLenField, \
MACField, PacketListField, SignedByteField, LEX3BytesField, \
XBitField, XByteField, XIntField, XShortField, XLEIntField, XLEShortField

from scapy.layers.bluetooth import EIR_Hdr, L2CAP_Hdr
from scapy.layers.ppi import PPI_Element, PPI_Hdr
Expand Down Expand Up @@ -110,7 +110,7 @@ class BTLE(Packet):
name = "BT4LE"
fields_desc = [
XLEIntField("access_addr", 0x8E89BED6),
X3BytesField("crc", None)
LEX3BytesField("crc", None)
]

@staticmethod
Expand Down Expand Up @@ -199,14 +199,35 @@ class BTLE_DATA(Packet):
BitField("SN", 0, 1),
BitField("NESN", 0, 1),
BitEnumField("LLID", 0, 2, {1: "continue", 2: "start", 3: "control"}),
ByteField("len", None),
ByteField("len", None), # BLE 4.2 and upwards can use 1 entire byte for length
]

def post_build(self, p, pay):
if self.len is None:
p = p[:-1] + chb(len(pay))
return p + pay

def do_dissect_payload(self, s):
if s is not None:
cls = self.guess_payload_class(s)
try:
p = cls(s, _internal=1, _underlayer=self)
except KeyboardInterrupt:
raise
except Exception:
if conf.debug_dissector:
if issubtype(cls, Packet):
log_runtime.error("%s dissector failed" % cls.__name__)
else:
log_runtime.error("%s.guess_payload_class() returned [%s]" % (self.__class__.__name__, repr(cls))) # noqa: E501
if cls is not None:
raise
p = conf.raw_layer(s, _internal=1, _underlayer=self)
self.add_payload(p)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a duplicate the default func.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, gonna change this by using dissection_done as proposed by @guedou


class BTLE_EMPTY_PDU(Packet):
name = "Empty data PDU"


class BTLE_ADV_IND(Packet):
name = "BTLE ADV_IND"
Expand Down Expand Up @@ -260,8 +281,8 @@ class BTLE_CONNECT_REQ(Packet):
BDAddrField("InitA", None),
BDAddrField("AdvA", None),
# LLDATA
XIntField("AA", 0x00),
X3BytesField("crc_init", 0x0),
XLEIntField("AA", 0x00),
LEX3BytesField("crc_init", 0x0),
XByteField("win_size", 0x0),
XLEShortField("win_offset", 0x0),
XLEShortField("interval", 0x0),
Expand All @@ -274,23 +295,209 @@ class BTLE_CONNECT_REQ(Packet):


BTLE_Versions = {
7: '4.1'
6:'4.0',
7: '4.1',
8: '4.2',
9: '5.0',
10: '5.1',
}


BTLE_Versions_Supported_Opcode = {
'4.0':0x0B,
}


BTLE_Corp_IDs = {
0xf: 'Broadcom Corporation'
0xf: 'Broadcom Corporation',
0x59: 'Nordic Semiconductor ASA'
}


BTLE_CtrlPDU_optcode = {
0x00: 'LL_CONNECTION_UPDATE_REQ',
0x01: 'LL_CHANNEL_MAP_REQ',
0x02: 'LL_TERMINATE_IND',
0x03: 'LL_ENC_REQ',
0x04: 'LL_ENC_RES',
0x05: 'LL_START_ENC_REQ',
0x06: 'LL_START_ENC_RES',
0x07: 'LL_UNKNOWN_RSP',
0x08: 'LL_FEATURE_REQ',
0x09: 'LL_FEATURE_RSP', # OK
0x0A: 'LL_PAUSE_ENC_REQ',
0x0B: 'LL_PAUSE_ENC_RES',
0x0C: 'LL_VERSION_IND', # OK
0x0D: 'LL_REJECT_IND',
0x0E: 'LL_SLAVE_FEATURE_REQ',
0x0F: 'LL_CONNECTION_PARAM_REQ',
0x10: 'LL_CONNECTION_PARAM_RES',
0x14: 'LL_LENGTH_REQ',
0x15: 'LL_LENGTH_RSP',
}


class CtrlPDU(Packet):
name = "CtrlPDU"
fields_desc = [
XByteField("optcode", 0),
ByteEnumField("version", 0, BTLE_Versions),
LEShortEnumField("Company", 0, BTLE_Corp_IDs),
XShortField("subversion", 0)
ByteEnumField("optcode", 0, BTLE_CtrlPDU_optcode)
]


def do_dissect_payload(self, s):
if s is not None:
cls = self.guess_payload_class(s)
try:
p = cls(s, _internal=1, _underlayer=self)
except KeyboardInterrupt:
raise
except Exception:
if conf.debug_dissector:
if issubtype(cls, Packet):
log_runtime.error("%s dissector failed" % cls.__name__)
else:
log_runtime.error("%s.guess_payload_class() returned [%s]" % (self.__class__.__name__, repr(cls))) # noqa: E501
if cls is not None:
raise
p = conf.raw_layer(s, _internal=1, _underlayer=self)
self.add_payload(p)



class LL_CONNECTION_UPDATE_REQ(Packet):
name = 'LL_CONNECTION_UPDATE_REQ'
fields_desc = [
XByteField("win_size", 0x0),
XLEShortField("win_offset", 0x0),
XLEShortField("interval", 0x0),
XLEShortField("latency", 0x0),
XLEShortField("timeout", 0x0),
XLEShortField("instant", 0x0),
]

class LL_CHANNEL_MAP_REQ(Packet):
name = 'LL_CHANNEL_MAP_REQ'
fields_desc = [
BTLEChanMapField("chM", 0),
XLEShortField("instant", 0x0),
]

class LL_TERMINATE_IND(Packet):
name = 'LL_TERMINATE_IND'
fields_desc = [
XByteField("code", 0x0),
]

class LL_ENC_REQ(Packet):
name = 'LL_ENC_REQ'
fields_desc = [
StrFixedLenField("rand","",length=8),
StrFixedLenField("ediv", "",length=2),
StrFixedLenField("skdm", "",length=8),
StrFixedLenField("ivm", "",length=4),
]



class LL_ENC_RSP(Packet):
name = 'LL_ENC_RSP'
fields_desc = [
StrFixedLenField("skds","",length=8),
StrFixedLenField("ivs", "",length=4),
]

class LL_START_ENC_REQ(Packet):
name = 'LL_START_ENC_REQ'


class LL_START_ENC_RSP(Packet):
name = 'LL_START_ENC_RSP'

class LL_UNKNOWN_RSP(Packet):
name = 'LL_UNKNOWN_RSP'
fields_desc = [
XByteField("code", 0x0),
]

class LL_FEATURE_REQ(Packet):
name = "LL_FEATURE_REQ"
fields_desc = [
FlagsField("feature_set", 0, -16,[# 4.0
'le_encryption',
# 4.1
'conn_par_req_proc','ext_reject_ind','slave_init_feat_exch',
# 4.2
'le_ping',
'le_data_len_ext','ll_privacy','ext_scan_filter',
# 5.0
'll_2m_phy', 'tx_mod_idx','rx_mod_idx','le_coded_phy',
'le_ext_adv','le_periodic_adv',
'ch_sel_alg','le_pwr_class']),
BitField("reserved", 0, 48),
]

class LL_FEATURE_RSP(Packet):
name = "LL_FEATURE_RSP"
fields_desc = [
FlagsField("feature_set", 0, -16,['le_encryption', # 4.0
'conn_par_req_proc','ext_reject_ind','slave_init_feat_exch',
'le_ping', # 4.1
'le_data_len_ext','ll_privacy','ext_scan_filter', # 4.2
'll_2m_phy', 'tx_mod_idx','rx_mod_idx','le_coded_phy',
'le_ext_adv','le_periodic_adv',
'ch_sel_alg','le_pwr_class']),
BitField("min_used_channels", 0, 1),
BitField("reserved", 0, 47),
]


class LL_VERSION_IND(Packet):
name = "LL_VERSION_IND"
fields_desc = [
ByteEnumField("version", 8, BTLE_Versions),
LEShortEnumField("Company", 0, BTLE_Corp_IDs),
XShortField("subversion", 0)
]

class LL_REJECT_IND(Packet):
name = "LL_REJECT_IND"
fields_desc = [
XByteField("code", 0x0),
]

class LL_SLAVE_FEATURE_REQ(Packet):
name = "LL_SLAVE_FEATURE_REQ"
fields_desc = [
FlagsField("feature_set", 0, -16,['le_encryption', # 4.0
'conn_par_req_proc','ext_reject_ind','slave_init_feat_exch',
'le_ping', # 4.1
'le_data_len_ext','ll_privacy','ext_scan_filter', # 4.2
'll_2m_phy', 'tx_mod_idx','rx_mod_idx','le_coded_phy',
'le_ext_adv','le_periodic_adv',
'ch_sel_alg','le_pwr_class']),
BitField("min_used_channels", 0, 1),
BitField("reserved", 0, 47),
]

class LL_LENGTH_REQ(Packet):
name = ' LL_LENGTH_REQ'
fields_desc = [
XLEShortField("max_rx_bytes", 251),
XLEShortField("max_rx_time", 2120),
XLEShortField("max_tx_bytes", 251),
XLEShortField("max_tx_time", 2120),
]

class LL_LENGTH_RSP(Packet):
name = ' LL_LENGTH_RSP'
fields_desc = [
XLEShortField("max_rx_bytes", 251),
XLEShortField("max_rx_time", 2120),
XLEShortField("max_tx_bytes", 251),
XLEShortField("max_tx_time", 2120),
]

# Advertisement (37-39) channel PDUs
bind_layers(BTLE, BTLE_ADV, access_addr=0x8E89BED6)
bind_layers(BTLE, BTLE_DATA)
bind_layers(BTLE_ADV, BTLE_ADV_IND, PDU_type=0)
Expand All @@ -301,9 +508,27 @@ class CtrlPDU(Packet):
bind_layers(BTLE_ADV, BTLE_CONNECT_REQ, PDU_type=5)
bind_layers(BTLE_ADV, BTLE_ADV_SCAN_IND, PDU_type=6)

bind_layers(BTLE_DATA, L2CAP_Hdr, LLID=2) # BTLE_DATA / L2CAP_Hdr / ATT_Hdr
# Data channel (0-36) PDUs
# LLID=1 -> Continue
bind_layers(BTLE_DATA, CtrlPDU, LLID=3)
bind_layers(BTLE_DATA, L2CAP_Hdr, LLID=2) # BTLE_DATA / L2CAP_Hdr / ATT_Hdr
bind_layers(BTLE_DATA, CtrlPDU, LLID=3) # BTLE_DATA / CtrlPDU
bind_layers(BTLE_DATA, BTLE_EMPTY_PDU, len=0) # BTLE_DATA / CtrlPDU
bind_layers(CtrlPDU, LL_CONNECTION_UPDATE_REQ, optcode=0x00) # BTLE_DATA / CtrlPDU / LL_FEATURE_RSP
bind_layers(CtrlPDU, LL_CHANNEL_MAP_REQ, optcode=0x01) # BTLE_DATA / CtrlPDU / LL_FEATURE_RSP
bind_layers(CtrlPDU, LL_TERMINATE_IND, optcode=0x02) # BTLE_DATA / CtrlPDU / LL_TERMINATE_IND
bind_layers(CtrlPDU, LL_ENC_REQ, optcode=0x03) # BTLE_DATA / CtrlPDU / LL_ENC_REQ
bind_layers(CtrlPDU, LL_ENC_RSP, optcode=0x04) # BTLE_DATA / CtrlPDU / LL_ENC_RSP
bind_layers(CtrlPDU, LL_START_ENC_REQ, optcode=0x05) # BTLE_DATA / CtrlPDU / LL_START_ENC_REQ
bind_layers(CtrlPDU, LL_START_ENC_RSP, optcode=0x06) # BTLE_DATA / CtrlPDU / LL_START_ENC_RSP
bind_layers(CtrlPDU, LL_UNKNOWN_RSP, optcode=0x07) # BTLE_DATA / CtrlPDU / LL_UNKNOWN_RSP
bind_layers(CtrlPDU, LL_FEATURE_REQ, optcode=0x08) # BTLE_DATA / CtrlPDU / LL_FEATURE_REQ
bind_layers(CtrlPDU, LL_FEATURE_RSP, optcode=0x09) # BTLE_DATA / CtrlPDU / LL_FEATURE_RSP
bind_layers(CtrlPDU, LL_VERSION_IND, optcode=0x0C) # BTLE_DATA / CtrlPDU / LL_VERSION_IND
bind_layers(CtrlPDU, LL_REJECT_IND, optcode=0x0D) # BTLE_DATA / CtrlPDU / LL_SLAVE_FEATURE_REQ
bind_layers(CtrlPDU, LL_SLAVE_FEATURE_REQ, optcode=0x0E) # BTLE_DATA / CtrlPDU / LL_SLAVE_FEATURE_REQ
bind_layers(CtrlPDU, LL_LENGTH_REQ, optcode=0x14) # BTLE_DATA / CtrlPDU / LL_LENGTH_REQ
bind_layers(CtrlPDU, LL_LENGTH_RSP, optcode=0x15) # BTLE_DATA / CtrlPDU / LL_LENGTH_RSP
# TODO: more optcodes

conf.l2types.register(DLT_BLUETOOTH_LE_LL, BTLE)
conf.l2types.register(DLT_BLUETOOTH_LE_LL_WITH_PHDR, BTLE_RF)
Expand Down