Mechanism
Above the link layer, a BLE peripheral exposes its functionality as a GATT table: a tree of services, each holding characteristics that carry values and a set of properties (read, write, notify, indicate) together with the security a client must satisfy to use them - none, an encrypted link, or an authenticated (MITM-protected) one (Vol 3 Part G) [bt-core-spec]. A device that leaves a sensitive characteristic readable, or an actuating one writable, without first requiring an authenticated encrypted connection is exposed to any peer in range. Deciding whether a given characteristic is over-exposed - whether that read leaks sensitive data, whether that write should have demanded authentication - is the application-layer judgement, and RFSAM defers it to BSAM (BSAM-SE-03 service access control, BSAM-DI-04 sensitive-data exposure). This control owns only the prerequisite: reaching the GATT table with a controller you drive.
The RF/host-controller prerequisite is what makes the CatSniffer relevant here. catnip’s virtual-HCI bridge presents the CatSniffer (running Sniffle firmware) to the Linux kernel as a standard Bluetooth LE controller through /dev/vhci, which BlueZ registers as hciN; any BlueZ-speaking tool then operates the target through the CatSniffer radio, no separate USB dongle required [catnip-tools]. From there the enumeration is ordinary GATT client work with BlueZ (bluetoothctl), Bleak, or bettercap.
Link-layer security alone does not settle the application-layer question. The canonical demonstration is the GATTacker relay (Jasek, Black Hat USA 2016): clone a peripheral’s advertising and GATT profile, stand between it and the real central, and forward or tamper with ATT traffic - so a design that trusts “the app talked to the device” without app-layer authentication is defeated regardless of the link [jasek2016gattacker]. That is the threat context; this control’s active scope is the passive-to-interactive enumeration and read/write test of what a device exposes without pairing.
This control has an interactive, connect-and-read/write step. Run it only against your own devices or with explicit written authorisation; a write to an actuating characteristic can change device state.
Procedure
Authorised testing only: your own device or written permission. A write step can change device state - read first, and only write to a characteristic you understand.
-
Load the kernel module and check prerequisites. The bridge needs
hci_vhciloaded so/dev/vhciexists:sudo modprobe hci_vhci catnip vhci checkExpected:
catnip vhci checkreports thathci_vhciis loaded and/dev/vhciis present, and lists any existing HCI controllers. -
Start the virtual-HCI bridge. Present the CatSniffer as a host controller:
sudo catnip vhci startExpected: the bridge prints a line such as
Created hci1- note the index N (hciN). Add-d Nto pick a specific CatSniffer,-p /dev/ttyACM0to force the port,-vfor verbose HCI logging. -
Enumerate the GATT table with BlueZ. Drive the new
hciNfrombluetoothctl:sudo bluetoothctlThen, at the prompt:
select <BD_ADDR>(the bridge controller, shown at startup as[NEW] Controller),scan on,connect <MAC>(the target),menu gatt,list-attributes <MAC>,select-attribute 2a00,read. Expected: the full service / characteristic tree, and readable values in the clear where no authentication is enforced (for example the Device Name characteristic0x2A00). -
Script the enumeration and read/write test with Bleak. Save this as
enum_gatt.py(setADDRto the target), which walks every service, prints each characteristic’s properties, and reads those marked readable:import asyncio from bleak import BleakClient ADDR = "34:85:18:00:35:F6" # target device MAC async def main(): async with BleakClient(ADDR) as client: for service in client.services: print("service", service.uuid, service.description) for char in service.characteristics: print(" char", char.uuid, char.properties) if "read" in char.properties: try: value = await client.read_gatt_char(char.uuid) print(" value", value.hex()) except Exception as exc: print(" read failed:", exc) asyncio.run(main())python3 enum_gatt.pyExpected: the same handles
bluetoothctlshowed, with values for every characteristic readable without pairing. To test an unauthenticated write, addawait client.write_gatt_char(<uuid>, bytes.fromhex("..."))for a characteristic whose effect you understand. -
Enumerate interactively with bettercap (optional cross-check). Over the same
hciN:sudo bettercapThen
ble.recon on,ble.show(list discovered devices),ble.enum <MAC>(dump services and characteristics). Expected: an interactive services / characteristics listing that corroborates the Bleak walk. -
Record and hand off. List every characteristic readable or writable without pairing, with its value or effect, and pass the over-exposure judgement to BSAM-SE-03 (should this service have required authenticated access?) and BSAM-DI-04 (is an exposed value sensitive?). RFSAM’s part ends at producing that enumerated, exercised GATT map.
Field case
Illustrative walkthrough - substitute the values you capture; do not assert a finding until it is measured on real hardware. With Sniffle flashed to the CatSniffer, sudo catnip vhci start brings up the bridge and BlueZ registers it as hciN [catnip-tools]. bluetoothctl on that controller connects to the bench target and menu gatt walks the table; a typical starting point is the Generic Access service, whose Device Name characteristic 0x2A00 reads in the clear without pairing. The enum_gatt.py Bleak walk then dumps the full tree and flags which characteristics returned a value with no authentication.
For a real engagement, record: [FILL: target device model], the assigned [FILL: hciN index], the [FILL: enumerated services and characteristic UUIDs], and specifically [FILL: which characteristics were readable/writable without pairing, and the value or effect observed]. Those unauthenticated reads and writes are exactly the input the BSAM judgement consumes - BSAM-SE-03 weighs whether the service should have gated access, BSAM-DI-04 whether an exposed value is sensitive. Do not fabricate handle values; capture them.
Remediation
Layered, since the exposure originates in the device’s GATT design but is inherited by integrators and operators.
- Developer (device firmware): Require LE Secure Connections and set the appropriate authentication / encryption permission on every sensitive read and every actuating write, so an unpaired peer enumerating over any controller (a CatSniffer
hciNincluded) gets nothing meaningful (BSAM-SE-03) [bt-core-spec]. Do not rely on the app being “the only client” - an app-layer relay clones the profile and sits in the middle (BSAM-DI-04) [jasek2016gattacker]. Keep secrets out of freely readable characteristics. - Integrator: During acceptance, run this enumeration against the shipped device and reject builds that expose sensitive reads or actuating writes before authentication. Confirm any “secure mode” is enabled in the shipped configuration, not merely available.
- Operator: Treat the GATT table as reachable by anyone in range with a sub-$50 radio presented as a host controller. Where the threat model warrants it, deploy only devices whose sensitive functions are gated behind authenticated encryption, and re-test after firmware updates that touch the Bluetooth stack or GATT layout.