• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# SPDX-License-Identifier: GPL-2.0-or-later
2# This file is part of Scapy
3# See https://scapy.net/ for more information
4# Copyright (C) Michael Farrell <micolous+git@gmail.com>
5
6# scapy.contrib.description = iBeacon BLE proximity beacon
7# scapy.contrib.status = loads
8"""
9scapy.contrib.ibeacon - Apple iBeacon Bluetooth LE proximity beacons.
10
11Packet format documentation can be found at at:
12
13* https://en.wikipedia.org/wiki/IBeacon#Packet_Structure_Byte_Map (public)
14* https://developer.apple.com/ibeacon/ (official, requires license)
15
16"""
17
18from scapy.fields import ByteEnumField, ConditionalField, LenField, \
19    PacketListField, ShortField, SignedByteField, UUIDField
20from scapy.layers.bluetooth import EIR_Hdr, EIR_Manufacturer_Specific_Data, \
21    LowEnergyBeaconHelper
22from scapy.packet import bind_layers, Packet
23
24APPLE_MFG = 0x004c
25
26
27class Apple_BLE_Submessage(Packet, LowEnergyBeaconHelper):
28    """
29    A basic Apple submessage.
30    """
31
32    name = "Apple BLE submessage"
33    fields_desc = [
34        ByteEnumField("subtype", None, {
35            0x01: "overflow",
36            0x02: "ibeacon",
37            0x05: "airdrop",
38            0x07: "airpods",
39            0x09: "airplay_sink",
40            0x0a: "airplay_src",
41            0x0c: "handoff",
42            0x10: "nearby",
43        }),
44        ConditionalField(
45            # "overflow" messages omit `len` field
46            LenField("len", None, fmt="B"),
47            lambda pkt: pkt.subtype != 0x01
48        ),
49    ]
50
51    def extract_padding(self, s):
52        # Needed to end each EIR_Element packet and make PacketListField work.
53        if self.subtype == 0x01:
54            # Overflow messages are always 16 bytes.
55            return s[:16], s[16:]
56        return s[:self.len], s[self.len:]
57
58    # These methods are here in case you only want to send 1 submessage.
59    # It creates an Apple_BLE_Frame to wrap your (single) Apple_BLE_Submessage.
60    def build_frame(self):
61        """Wraps this submessage in a Apple_BLE_Frame."""
62        return Apple_BLE_Frame(plist=[self])
63
64    def build_eir(self):
65        """See Apple_BLE_Frame.build_eir."""
66        return self.build_frame().build_eir()
67
68
69class Apple_BLE_Frame(Packet, LowEnergyBeaconHelper):
70    """
71    The wrapper for a BLE manufacturer-specific data advertisement from Apple
72    devices.
73
74    Each advertisement is composed of one or multiple submessages.
75
76    The length of this field comes from the EIR_Hdr.
77    """
78    name = "Apple BLE broadcast frame"
79    fields_desc = [
80        PacketListField("plist", None, Apple_BLE_Submessage)
81    ]
82
83    def build_eir(self):
84        """Builds a list of EIR messages to wrap this frame."""
85
86        return LowEnergyBeaconHelper.base_eir + [
87            EIR_Hdr() / EIR_Manufacturer_Specific_Data() / self
88        ]
89
90
91class IBeacon_Data(Packet):
92    """
93    iBeacon broadcast data frame. Composed on top of an Apple_BLE_Submessage.
94    """
95    name = "iBeacon data"
96    fields_desc = [
97        UUIDField("uuid", None, uuid_fmt=UUIDField.FORMAT_BE),
98        ShortField("major", None),
99        ShortField("minor", None),
100        SignedByteField("tx_power", None),
101    ]
102
103
104bind_layers(EIR_Manufacturer_Specific_Data, Apple_BLE_Frame,
105            company_id=APPLE_MFG)
106bind_layers(Apple_BLE_Submessage, IBeacon_Data, subtype=2)
107