From 05f74b167ba9bb279fa7c5a289f09b10f3f00ca9 Mon Sep 17 00:00:00 2001 From: Michael Farrell Date: Sat, 9 Mar 2019 13:50:24 +1100 Subject: [PATCH] ibeacon: New layer! - Decodes iBeacon as a submessage of Apple's BLE broadcast frame format. Does not implement support for other types of Apple BLE broadcasts. - Adds tests and example documentation. --- doc/scapy/graphics/ble_ibeacon.png | Bin 0 -> 8428 bytes doc/scapy/layers/bluetooth.rst | 143 ++++++++++++++++++++++++++++- scapy/contrib/ibeacon.py | 101 ++++++++++++++++++++ scapy/contrib/ibeacon.uts | 70 ++++++++++++++ 4 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 doc/scapy/graphics/ble_ibeacon.png create mode 100644 scapy/contrib/ibeacon.py create mode 100644 scapy/contrib/ibeacon.uts diff --git a/doc/scapy/graphics/ble_ibeacon.png b/doc/scapy/graphics/ble_ibeacon.png new file mode 100644 index 0000000000000000000000000000000000000000..0f133b043438804bfdaae81ffa1a66c1921f6c73 GIT binary patch literal 8428 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 literal 0 HcmV?d00001 diff --git a/doc/scapy/layers/bluetooth.rst b/doc/scapy/layers/bluetooth.rst index a945f1529ff..4c56914d1d6 100644 --- a/doc/scapy/layers/bluetooth.rst +++ b/doc/scapy/layers/bluetooth.rst @@ -443,11 +443,51 @@ __ https://github.com/google/eddystone/tree/master/eddystone-url 'https://scapy.net').build_set_advertising_data()) Once :ref:`advertising has been started `, the beacon may then be -detected with the `Eddystone Validator`__ (Android): +detected with `Eddystone Validator`__ or `Beacon Locator`__ (Android): .. image:: ../graphics/ble_eddystone_url.png __ https://github.com/google/eddystone/tree/master/tools/eddystone-validator +__ https://github.com/vitas/beaconloc + +.. _adv-ibeacon: + +iBeacon +^^^^^^^ + +`iBeacon`__ is a proximity beacon protocol developed by Apple, which uses their +manufacturer-specific data field. :ref:`Apple/iBeacon framing ` +(below) describes this in more detail. + +__ https://en.wikipedia.org/wiki/IBeacon + +This example sets up a virtual iBeacon: + +.. code-block:: python3 + + # Load the contrib module for iBeacon + load_contrib('ibeacon') + + # Beacon data consists of a UUID, and two 16-bit integers: "major" and + # "minor". + # + # iBeacon sits ontop of Apple's BLE protocol. + p = Apple_BLE_Submessage()/IBeacon_Data( + uuid='fb0b57a2-8228-44cd-913a-94a122ba1206', + major=1, minor=2) + + # build_set_advertising_data() wraps an Apple_BLE_Submessage or + # Apple_BLE_Frame into a HCI_Cmd_LE_Set_Advertising_Data payload, that can + # be sent to the BLE controller. + bt.sr(p.build_set_advertising_data()) + +Once :ref:`advertising has been started `, the beacon may then be +detected with `Beacon Locator`__ (Android): + +.. image:: ../graphics/ble_ibeacon.png + +__ https://github.com/vitas/beaconloc + .. _le-adv-start: @@ -495,3 +535,104 @@ __ https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers __ https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile +.. _apple-ble: + +Apple/iBeacon broadcast frames +============================== + +.. note:: + + This describes the wire format for Apple's Bluetooth Low Energy + advertisements, based on (limited) publicly available information. It is not + specific to using Bluetooth on Apple operating systems. + +`iBeacon`__ is Apple's proximity beacon protocol. Scapy includes a contrib +module, ``ibeacon``, for working with Apple's :abbr:`BLE (Bluetooth Low Energy)` +broadcasts: + +__ https://en.wikipedia.org/wiki/IBeacon + +.. code-block:: pycon + + >>> load_contrib('ibeacon') + +:ref:`Setting up advertising for iBeacon ` (above) describes how to +broadcast a simple beacon. + +While this module is called ``ibeacon``, Apple has other "submessages" which are +also advertised within their manufacturer-specific data field, including: + + * `AirDrop`__ + * AirPlay + * AirPods + * `Handoff`__ + * Nearby + +__ https://en.wikipedia.org/wiki/AirDrop +__ https://en.wikipedia.org/wiki/OS_X_Yosemite#Continuity + +For compatibility with these other broadcasts, Apple BLE frames in Scapy are +layered on top of ``Apple_BLE_Submessage`` and ``Apple_BLE_Frame``: + + * ``HCI_Cmd_LE_Set_Advertising_Data``, ``HCI_LE_Meta_Advertising_Report``, + ``BTLE_ADV_IND``, ``BTLE_ADV_NONCONN_IND`` or ``BTLE_ADV_SCAN_IND`` contain + one or more... + * ``EIR_Hdr``, which may have a payload of one... + * ``EIR_Manufacturer_Specific_Data``, which may have a payload of one... + * ``Apple_BLE_Frame``, which contains one or more... + * ``Apple_BLE_Submessage``, which contains a payload of one... + * ``Raw`` (if not supported), or ``IBeacon_Data``. + +This module only presently supports ``IBeacon_Data`` submessages. Other +submessages are decoded as ``Raw``. + +One might sometimes see multiple submessages in a single broadcast, such as +Handoff and Nearby. This is not mandatory -- there are also Handoff-only and +Nearby-only broadcasts. + +Inspecting a raw BTLE advertisement frame from an Apple device: + +.. code-block:: python3 + + p = BTLE(hex_bytes('d6be898e4024320cfb574d5a02011a1aff4c000c0e009c6b8f40440f1583ec895148b410050318c0b525b8f7d4')) + p.show() + +Results in the output: + +.. code-block:: text + + ###[ BT4LE ]### + access_addr= 0x8e89bed6 + crc= 0xb8f7d4 + ###[ BTLE advertising header ]### + RxAdd= public + TxAdd= random + RFU= 0 + PDU_type= ADV_IND + unused= 0 + Length= 0x24 + ###[ BTLE ADV_IND ]### + AdvA= 5a:4d:57:fb:0c:32 + \data\ + |###[ EIR Header ]### + | len= 2 + | type= flags + |###[ Flags ]### + | flags= general_disc_mode+simul_le_br_edr_ctrl+simul_le_br_edr_host + |###[ EIR Header ]### + | len= 26 + | type= mfg_specific_data + |###[ EIR Manufacturer Specific Data ]### + | company_id= 0x4c + |###[ Apple BLE broadcast frame ]### + | \plist\ + | |###[ Apple BLE submessage ]### + | | subtype= handoff + | | len= 14 + | |###[ Raw ]### + | | load= '\x00\x9ck\x8f@D\x0f\x15\x83\xec\x89QH\xb4' + | |###[ Apple BLE submessage ]### + | | subtype= nearby + | | len= 5 + | |###[ Raw ]### + | | load= '\x03\x18\xc0\xb5%' diff --git a/scapy/contrib/ibeacon.py b/scapy/contrib/ibeacon.py new file mode 100644 index 00000000000..fddae43190a --- /dev/null +++ b/scapy/contrib/ibeacon.py @@ -0,0 +1,101 @@ +# -*- mode: python3; indent-tabs-mode: nil; tab-width: 4 -*- +# ibeacon.py - protocol handlers for iBeacons and other Apple devices +# +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Michael Farrell +# This program is published under a GPLv2 (or later) license +# +# scapy.contrib.description = iBeacon BLE proximity beacon +# scapy.contrib.status = loads +""" +scapy.contrib.ibeacon - Apple iBeacon Bluetooth LE proximity beacons. + +Packet format documentation can be found at at: + +* https://en.wikipedia.org/wiki/IBeacon#Packet_Structure_Byte_Map (public) +* https://developer.apple.com/ibeacon/ (official, requires license) + +""" + +from scapy.fields import ByteEnumField, LenField, PacketListField, \ + ShortField, SignedByteField, UUIDField +from scapy.layers.bluetooth import EIR_Hdr, EIR_Manufacturer_Specific_Data, \ + LowEnergyBeaconHelper +from scapy.packet import bind_layers, Packet + +APPLE_MFG = 0x004c + + +class Apple_BLE_Submessage(Packet, LowEnergyBeaconHelper): + """ + A basic Apple submessage. + """ + + name = "Apple BLE submessage" + fields_desc = [ + ByteEnumField("subtype", None, { + 0x02: "ibeacon", + 0x05: "airdrop", + 0x07: "airpods", + 0x09: "airplay_sink", + 0x0a: "airplay_src", + 0x0c: "handoff", + 0x10: "nearby", + }), + LenField("len", None, fmt="B") + ] + + def extract_padding(self, s): + # Needed to end each EIR_Element packet and make PacketListField work. + return s[:self.len], s[self.len:] + + # These methods are here in case you only want to send 1 submessage. + # It creates an Apple_BLE_Frame to wrap your (single) Apple_BLE_Submessage. + def build_frame(self): + """Wraps this submessage in a Apple_BLE_Frame.""" + return Apple_BLE_Frame(plist=[self]) + + def build_eir(self): + """See Apple_BLE_Frame.build_eir.""" + return self.build_frame().build_eir() + + +class Apple_BLE_Frame(Packet, LowEnergyBeaconHelper): + """ + The wrapper for a BLE manufacturer-specific data advertisement from Apple + devices. + + Each advertisement is composed of one or multiple submessages. + + The length of this field comes from the EIR_Hdr. + """ + name = "Apple BLE broadcast frame" + fields_desc = [ + PacketListField("plist", None, Apple_BLE_Submessage) + ] + + def build_eir(self): + """Builds a list of EIR messages to wrap this frame.""" + + return LowEnergyBeaconHelper.base_eir + [ + EIR_Hdr() / EIR_Manufacturer_Specific_Data() / self + ] + + +class IBeacon_Data(Packet): + """ + iBeacon broadcast data frame. Composed on top of an Apple_BLE_Submessage. + """ + name = "iBeacon data" + fields_desc = [ + UUIDField("uuid", None, uuid_fmt=UUIDField.FORMAT_BE), + ShortField("major", None), + ShortField("minor", None), + SignedByteField("tx_power", None), + ] + + +bind_layers(EIR_Manufacturer_Specific_Data, Apple_BLE_Frame, + company_id=APPLE_MFG) +bind_layers(Apple_BLE_Submessage, IBeacon_Data, subtype=2) diff --git a/scapy/contrib/ibeacon.uts b/scapy/contrib/ibeacon.uts new file mode 100644 index 00000000000..e5c67dbd74f --- /dev/null +++ b/scapy/contrib/ibeacon.uts @@ -0,0 +1,70 @@ +% iBeacon unit tests +# +# Type the following command to launch start the tests: +# $ test/run_tests -P "load_contrib('ibeacon')" -t scapy/contrib/ibeacon.uts + ++ iBeacon tests + += Presence check + +Apple_BLE_Frame +IBeacon_Data +Apple_BLE_Submessage + += Apple multiple submessages + +# Observed in the wild; handoff + nearby message. +# Meaning unknown. +d = hex_bytes('D6BE898E4024320CFB574D5A02011A1AFF4C000C0E009C6B8F40440F1583EC895148B410050318C0B525B8F7D4') +p = BTLE(d) + +assert len(p[Apple_BLE_Frame].plist) == 2 +assert p[Apple_BLE_Frame].plist[0].subtype == 0x0c # handoff +assert (raw(p[Apple_BLE_Frame].plist[0].payload) == + hex_bytes('009c6b8f40440f1583ec895148b4')) +assert p[Apple_BLE_Frame].plist[1].subtype == 0x10 # nearby +assert raw(p[Apple_BLE_Frame].plist[1].payload) == hex_bytes('0318c0b525') + += iBeacon (decode LE Set Advertising Data) + +# from https://en.wikipedia.org/wiki/IBeacon#Technical_details +d = hex_bytes('1E02011A1AFF4C000215FB0B57A2822844CD913A94A122BA120600010002D100') +p = HCI_Cmd_LE_Set_Advertising_Data(d) + +assert len(p[Apple_BLE_Frame].plist) == 1 +assert p[IBeacon_Data].uuid == UUID("fb0b57a2-8228-44cd-913a-94a122ba1206") +assert p[IBeacon_Data].major == 1 +assert p[IBeacon_Data].minor == 2 +assert p[IBeacon_Data].tx_power == -47 + +d2 = raw(p) +assert d == d2 + += iBeacon (encode LE Set Advertising Data) + +d = hex_bytes('1E0201021AFF4C000215FB0B57A2822844CD913A94A122BA120600010002D100') +p = Apple_BLE_Submessage()/IBeacon_Data( + uuid='fb0b57a2-8228-44cd-913a-94a122ba1206', + major=1, minor=2, tx_power=-47) + +sap = p.build_set_advertising_data()[HCI_Cmd_LE_Set_Advertising_Data] +assert d == raw(sap) + +pa = Apple_BLE_Frame(plist=[p]) +sapa = pa.build_set_advertising_data()[HCI_Cmd_LE_Set_Advertising_Data] +assert d == raw(sapa) + +# Also try to build with Submessage directly +sapa = p.build_set_advertising_data()[HCI_Cmd_LE_Set_Advertising_Data] +assert d == raw(sapa) + += iBeacon (decode advertising frame) + +# from https://en.wikipedia.org/wiki/IBeacon#Spoofing +d = hex_bytes('043E2A02010001FCED16D4EED61E0201061AFF4C000215B9407F30F5F8466EAFF925556B57FE6DEDFCD416B6B4') +p = HCI_Hdr(d) + +assert p[HCI_LE_Meta_Advertising_Report].addr == 'd6:ee:d4:16:ed:fc' +assert len(p[Apple_BLE_Frame].plist) == 1 +assert p[IBeacon_Data].uuid == UUID('b9407f30-f5f8-466e-aff9-25556b57fe6d') +