Skip to content
Merged
Show file tree
Hide file tree
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
229 changes: 229 additions & 0 deletions scapy/contrib/bluetooth_vsc_zephyr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
# SPDX-License-Identifier: GPL-2.0-only
# This file is part of Scapy
# See https://scapy.net/ for more information
#
# scapy.contrib.description = Zephyr Bluetooth HCI Vendor-Specific Commands
# scapy.contrib.status = loads
#
# Information sources:
# - Zephyr: https://github.com/zephyrproject-rtos/zephyr
# - include/zephyr/bluetooth/hci_vs.h
# - subsys/bluetooth/controller/hci/hci.c


from scapy.packet import Packet, bind_layers
from scapy.fields import (
ByteEnumField,
ByteField,
FieldLenField,
LEIntField,
LEMACField,
LEShortEnumField,
LEShortField,
PacketListField,
XStrFixedLenField,
XStrLenField,
)

from scapy.layers.bluetooth import (
HCI_Command_Hdr,
HCI_Event_Command_Complete,
)

# Vendor OpCode Group Field for all VSCs
OGF_VENDOR_SPECIFIC = 0x3F

# --- OCF values (BT_HCI_OP_VS_*, hci_vs.h) --------------------------------
OCF_VS_READ_VERSION_INFO = 0x001 # opcode 0xFC01
OCF_VS_READ_SUPPORTED_COMMANDS = 0x002 # opcode 0xFC02
OCF_VS_READ_SUPPORTED_FEATURES = 0x003 # opcode 0xFC03
OCF_VS_WRITE_BD_ADDR = 0x006 # opcode 0xFC06
OCF_VS_READ_BUILD_INFO = 0x008 # opcode 0xFC08
OCF_VS_READ_STATIC_ADDRS = 0x009 # opcode 0xFC09
OCF_VS_READ_KEY_HIERARCHY_ROOTS = 0x00A # opcode 0xFC0A

# BT_HCI_VS_HW_PLAT_* (hci_vs.h)
_zephyr_hw_platform = {
0x0000: "reserved",
0x0001: "Intel",
0x0002: "Nordic Semiconductor",
0x0003: "NXP Semiconductors",
0x0004: "IMG",
}

# BT_HCI_VS_HW_VAR_NORDIC_* (hci_vs.h) -- meaning is platform-dependent; these
# are the Nordic variants (valid when hw_platform == Nordic Semiconductor).
_zephyr_hw_variant_nordic = {
0x0001: "nRF51x",
0x0002: "nRF52x",
0x0003: "nRF53x",
0x0004: "nRF54Hx",
0x0005: "nRF54Lx",
}

# BT_HCI_VS_FW_VAR_* (hci_vs.h)
_zephyr_fw_variant = {
0x00: "standard_controller",
}


# ---------------------------------------------------------------------------
# Commands
# ---------------------------------------------------------------------------
class HCI_Cmd_VSC_Zephyr_Read_Version_Info(Packet):
"""Read Version Info (OCF 0x001, opcode 0xFC01). No parameters."""
name = "Zephyr Read Version Info"


class HCI_Cmd_VSC_Zephyr_Read_Supported_Commands(Packet):
"""Read Supported Commands (OCF 0x002, opcode 0xFC02). No parameters."""
name = "Zephyr Read Supported Commands"


class HCI_Cmd_VSC_Zephyr_Read_Supported_Features(Packet):
"""Read Supported Features (OCF 0x003, opcode 0xFC03). No parameters."""
name = "Zephyr Read Supported Features"


class HCI_Cmd_VSC_Zephyr_Write_BD_Addr(Packet):
"""
Write BD_ADDR (OCF 0x006, opcode 0xFC06).

Sets the controller public device address to ``bd_addr``. The value persists
across an HCI ``Reset`` (it is only re-initialised on a real power cycle /
re-enumeration).
"""
name = "Zephyr Write BD_ADDR"
fields_desc = [LEMACField("bd_addr", None)]


class HCI_Cmd_VSC_Zephyr_Read_Build_Info(Packet):
"""Read Build Info (OCF 0x008, opcode 0xFC08). No parameters."""
name = "Zephyr Read Build Info"


class HCI_Cmd_VSC_Zephyr_Read_Static_Addresses(Packet):
"""Read Static Addresses (OCF 0x009, opcode 0xFC09). No parameters."""
name = "Zephyr Read Static Addresses"


class HCI_Cmd_VSC_Zephyr_Read_Key_Hierarchy_Roots(Packet):
"""Read Key Hierarchy Roots (OCF 0x00A, opcode 0xFC0A). No parameters."""
name = "Zephyr Read Key Hierarchy Roots"


# ---------------------------------------------------------------------------
# Command Complete returns
# ---------------------------------------------------------------------------
class HCI_Cmd_Complete_VSC_Zephyr_Read_Version_Info(Packet):
"""
Read Version Info (0xFC01) command complete.

``bt_hci_rp_vs_read_version_info``: hardware platform/variant and the
controller firmware variant/version/revision/build.
"""
name = "Zephyr Read Version Info complete"
fields_desc = [
LEShortEnumField("hw_platform", 0, _zephyr_hw_platform),
LEShortEnumField("hw_variant", 0, _zephyr_hw_variant_nordic),
ByteEnumField("fw_variant", 0, _zephyr_fw_variant),
ByteField("fw_version", 0),
LEShortField("fw_revision", 0),
LEIntField("fw_build", 0),
]


class HCI_Cmd_Complete_VSC_Zephyr_Read_Supported_Commands(Packet):
"""Read Supported Commands (0xFC02) command complete: 64-byte support bitmap."""
name = "Zephyr Read Supported Commands complete"
fields_desc = [XStrFixedLenField("commands", b"\x00" * 64, 64)]


class HCI_Cmd_Complete_VSC_Zephyr_Read_Supported_Features(Packet):
"""Read Supported Features (0xFC03) command complete: 8-byte feature bitmap."""
name = "Zephyr Read Supported Features complete"
fields_desc = [XStrFixedLenField("features", b"\x00" * 8, 8)]


class HCI_Cmd_Complete_VSC_Zephyr_Read_Build_Info(Packet):
"""Read Build Info (0xFC08) command complete: the build-info string."""
name = "Zephyr Read Build Info complete"
fields_desc = [
XStrLenField("build_info", b"",
length_from=lambda p: p.underlayer.underlayer.len - 4)
]


class ZephyrStaticAddr(Packet):
"""One ``bt_hci_vs_static_addr`` entry: a static random address + its IR."""
name = "Zephyr Static Address"
fields_desc = [
LEMACField("addr", None),
XStrFixedLenField("ir", b"\x00" * 16, 16),
]

def extract_padding(self, s):
return b"", s


class HCI_Cmd_Complete_VSC_Zephyr_Read_Static_Addresses(Packet):
"""
Read Static Addresses (0xFC09) command complete.

``num`` static addresses, each a 6-byte address followed by its 16-byte
Identity Root (IR). Read primitive: discloses the factory static random
address and identity-root key material.
"""
name = "Zephyr Read Static Addresses complete"
fields_desc = [
FieldLenField("num", None, count_of="addrs", fmt="B"),
PacketListField("addrs", [], ZephyrStaticAddr,
count_from=lambda p: p.num),
]


class HCI_Cmd_Complete_VSC_Zephyr_Read_Key_Hierarchy_Roots(Packet):
"""
Read Key Hierarchy Roots (0xFC0A) command complete.

The Identity Root (``ir``) and Encryption Root (``er``), 16 bytes each.
Read primitive: discloses the seeds of the BLE key hierarchy (IRK, and
LTK/CSRK derivation).
"""
name = "Zephyr Read Key Hierarchy Roots complete"
fields_desc = [
XStrFixedLenField("ir", b"\x00" * 16, 16),
XStrFixedLenField("er", b"\x00" * 16, 16),
]


# ---------------------------------------------------------------------------
# Bindings
# ---------------------------------------------------------------------------
bind_layers(HCI_Command_Hdr, HCI_Cmd_VSC_Zephyr_Read_Version_Info,
ogf=OGF_VENDOR_SPECIFIC, ocf=OCF_VS_READ_VERSION_INFO)
bind_layers(HCI_Command_Hdr, HCI_Cmd_VSC_Zephyr_Read_Supported_Commands,
ogf=OGF_VENDOR_SPECIFIC, ocf=OCF_VS_READ_SUPPORTED_COMMANDS)
bind_layers(HCI_Command_Hdr, HCI_Cmd_VSC_Zephyr_Read_Supported_Features,
ogf=OGF_VENDOR_SPECIFIC, ocf=OCF_VS_READ_SUPPORTED_FEATURES)
bind_layers(HCI_Command_Hdr, HCI_Cmd_VSC_Zephyr_Write_BD_Addr,
ogf=OGF_VENDOR_SPECIFIC, ocf=OCF_VS_WRITE_BD_ADDR)
bind_layers(HCI_Command_Hdr, HCI_Cmd_VSC_Zephyr_Read_Build_Info,
ogf=OGF_VENDOR_SPECIFIC, ocf=OCF_VS_READ_BUILD_INFO)
bind_layers(HCI_Command_Hdr, HCI_Cmd_VSC_Zephyr_Read_Static_Addresses,
ogf=OGF_VENDOR_SPECIFIC, ocf=OCF_VS_READ_STATIC_ADDRS)
bind_layers(HCI_Command_Hdr, HCI_Cmd_VSC_Zephyr_Read_Key_Hierarchy_Roots,
ogf=OGF_VENDOR_SPECIFIC, ocf=OCF_VS_READ_KEY_HIERARCHY_ROOTS)

bind_layers(HCI_Event_Command_Complete,
HCI_Cmd_Complete_VSC_Zephyr_Read_Version_Info, opcode=0xFC01)
bind_layers(HCI_Event_Command_Complete,
HCI_Cmd_Complete_VSC_Zephyr_Read_Supported_Commands, opcode=0xFC02)
bind_layers(HCI_Event_Command_Complete,
HCI_Cmd_Complete_VSC_Zephyr_Read_Supported_Features, opcode=0xFC03)
bind_layers(HCI_Event_Command_Complete,
HCI_Cmd_Complete_VSC_Zephyr_Read_Build_Info, opcode=0xFC08)
bind_layers(HCI_Event_Command_Complete,
HCI_Cmd_Complete_VSC_Zephyr_Read_Static_Addresses, opcode=0xFC09)
bind_layers(HCI_Event_Command_Complete,
HCI_Cmd_Complete_VSC_Zephyr_Read_Key_Hierarchy_Roots, opcode=0xFC0A)
104 changes: 104 additions & 0 deletions test/contrib/bluetooth_vsc_zephyr.uts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
% Zephyr Bluetooth Vendor-Specific Command (VSC) tests
# test/run_tests -P "load_contrib('bluetooth_vsc_zephyr')" -t test/contrib/bluetooth_vsc_zephyr.uts
# Frames captured live from an nRF52840 running Zephyr hci_usb (Zephyr OS v3.4.99).

+ Load the Zephyr VSC contrib module

= Load the contrib module
from scapy.layers.bluetooth import *
load_contrib("bluetooth_vsc_zephyr")
from scapy.contrib.bluetooth_vsc_zephyr import *


+ Zephyr no-parameter read commands build + dissect

= Read Version Info (opcode 0xFC01)
cmd = HCI_Command_Hdr() / HCI_Cmd_VSC_Zephyr_Read_Version_Info()
assert cmd.ogf == 0x3f
assert cmd.ocf == 0x001
assert cmd.opcode == 0xfc01
assert raw(cmd) == b'\x01\xfc\x00'
# A zero-length command payload is not re-instantiated on dissection (this is how
# scapy handles empty payloads -- core HCI_Cmd_Reset behaves the same); the opcode
# is what survives the round-trip.
p = HCI_Command_Hdr(raw(cmd))
assert p.opcode == 0xfc01

= Read Supported Commands / Features / Build Info / Static Addrs / Key Roots opcodes
assert (HCI_Command_Hdr() / HCI_Cmd_VSC_Zephyr_Read_Supported_Commands()).opcode == 0xfc02
assert (HCI_Command_Hdr() / HCI_Cmd_VSC_Zephyr_Read_Supported_Features()).opcode == 0xfc03
assert (HCI_Command_Hdr() / HCI_Cmd_VSC_Zephyr_Read_Build_Info()).opcode == 0xfc08
assert (HCI_Command_Hdr() / HCI_Cmd_VSC_Zephyr_Read_Static_Addresses()).opcode == 0xfc09
assert (HCI_Command_Hdr() / HCI_Cmd_VSC_Zephyr_Read_Key_Hierarchy_Roots()).opcode == 0xfc0a


+ Zephyr Write BD_ADDR (opcode 0xFC06)

= Build a Write BD_ADDR command (little-endian on the wire)
cmd = HCI_Command_Hdr() / HCI_Cmd_VSC_Zephyr_Write_BD_Addr(bd_addr="00:11:22:33:44:55")
assert cmd.opcode == 0xfc06
assert raw(cmd) == b'\x06\xfc\x06\x55\x44\x33\x22\x11\x00'
p = HCI_Command_Hdr(raw(cmd))
assert p[HCI_Cmd_VSC_Zephyr_Write_BD_Addr].bd_addr == "00:11:22:33:44:55"

= Write BD_ADDR command complete (status only)
evt = HCI_Hdr(bytes.fromhex("040e040106fc00"))
assert evt[HCI_Event_Command_Complete].opcode == 0xfc06
assert evt[HCI_Event_Command_Complete].status == 0


+ Read Version Info command complete (opcode 0xFC01)

= Dissect the version info return (Nordic nRF52x, Zephyr v3.4 build 99)
evt = HCI_Hdr(bytes.fromhex("040e100101fc00020002000003040063000000"))
vi = evt[HCI_Cmd_Complete_VSC_Zephyr_Read_Version_Info]
assert evt[HCI_Event_Command_Complete].status == 0
assert vi.hw_platform == 0x0002 # Nordic Semiconductor
assert vi.hw_variant == 0x0002 # nRF52x
assert vi.fw_variant == 0x00 # standard_controller
assert vi.fw_version == 3
assert vi.fw_revision == 4
assert vi.fw_build == 99


+ Read Supported Commands / Features command complete

= Supported Commands is a 64-byte bitmap; byte0 advertises {FC01,02,03,06,08}
evt = HCI_Hdr(bytes.fromhex("040e440102fc00" + "a703" + "00" * 62))
sc = evt[HCI_Cmd_Complete_VSC_Zephyr_Read_Supported_Commands]
assert len(sc.commands) == 64
assert sc.commands[0] == 0xa7
assert sc.commands[1] == 0x03

= Supported Features is an 8-byte bitmap (all-zero on this build)
evt = HCI_Hdr(bytes.fromhex("040e0c0103fc000000000000000000"))
sf = evt[HCI_Cmd_Complete_VSC_Zephyr_Read_Supported_Features]
assert sf.features == b"\x00" * 8


+ Read Build Info command complete (opcode 0xFC08)

= The build-info string is length-derived from the event header
evt = HCI_Hdr(bytes.fromhex("040e160108fc005a6570687972204f532076332e342e393900"))
bi = evt[HCI_Cmd_Complete_VSC_Zephyr_Read_Build_Info]
assert bi.build_info == b"Zephyr OS v3.4.99\x00"


+ Read Static Addresses command complete (opcode 0xFC09) -- read primitive

= One static random address + its 16-byte Identity Root
evt = HCI_Hdr(bytes.fromhex("040e1b0109fc00015449a10d1bff9fca692d41532c66a76b0c22cf189318"))
sa = evt[HCI_Cmd_Complete_VSC_Zephyr_Read_Static_Addresses]
assert sa.num == 1
assert len(sa.addrs) == 1
assert sa.addrs[0].addr == "ff:1b:0d:a1:49:54"
assert sa.addrs[0].ir == bytes.fromhex("9fca692d41532c66a76b0c22cf189318")


+ Read Key Hierarchy Roots command complete (opcode 0xFC0A) -- read primitive

= Identity Root (IR) and Encryption Root (ER), 16 bytes each
evt = HCI_Hdr(bytes.fromhex("040e24010afc00" + "00" * 16 + "e37d530d4cfdaf8b89817f33ca1d8416"))
kr = evt[HCI_Cmd_Complete_VSC_Zephyr_Read_Key_Hierarchy_Roots]
assert kr.ir == b"\x00" * 16
assert kr.er == bytes.fromhex("e37d530d4cfdaf8b89817f33ca1d8416")
Loading