Prerequisites: Week 10 lecture; bleak installed; nRF52840 dongle or BLE PCAP; authorized lab BLE target device Duration: ~90 min Points: 100
Authorization
- Lab Authorization Form signed
- BLE target: instructor-designated lab device only (address provided in class)
- Read-only enumeration first; write operations only to characteristics explicitly marked as lab targets
- No pairing attempts against devices not designated as the lab target
Objective
Perform a complete BLE security assessment: passive advertising capture, device fingerprinting, GATT enumeration, unauthenticated write testing, and passive pairing exchange analysis. Document findings in a GATT security profile that matches the capstone report format.
Part A — BLE Advertising Capture and Device Fingerprinting (20 min)
Option 1 — nRF52840 with Sniffle
python3 -m sniffle.sniff_receiver -e -a --rssi -50 2>&1 | tee ble_advertising.log
Run for 2 minutes. Ctrl+C when done.
Parse the output:
# parse_ble_adv.py
import re
with open('ble_advertising.log') as f:
content = f.read()
# Extract device addresses
addresses = re.findall(r'([0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2})', content)
unique_addresses = list(set(addresses))
print(f"Unique BLE devices seen: {len(unique_addresses)}")
for addr in unique_addresses[:20]:
print(addr)
Option 2 — Provided PCAP (virtual path)
Open lab10_ble_advertising.pcap in Wireshark. Filter: btle.advertising_header. List all unique advertiser addresses.
Device Fingerprinting
For each unique address, attempt to identify the device type from its AD structures:
# Using bleak BleakScanner for richer AD data
import asyncio
from bleak import BleakScanner
async def scan_devices():
print("Scanning for 10 seconds...")
devices = await BleakScanner.discover(timeout=10.0, return_adv=True)
for addr, (device, adv) in devices.items():
print(f"\nAddress: {addr}")
print(f" Name: {device.name or 'Unknown'}")
print(f" RSSI: {adv.rssi} dBm")
print(f" Service UUIDs: {adv.service_uuids}")
if adv.manufacturer_data:
for company_id, data in adv.manufacturer_data.items():
print(f" Manufacturer ({company_id:#06x}): {data.hex()}")
asyncio.run(scan_devices())
Match manufacturer IDs to companies (Bluetooth SIG Assigned Numbers list -- linked from course portal). Apple is 0x004C; Nordic Semiconductor is 0x0059.
Deliverable A
Table: device address, inferred name/type, RSSI, service UUIDs from advertising, manufacturer data (decoded if possible).
Part B — GATT Enumeration (25 min)
Connect to the instructor's designated lab BLE device and enumerate its complete GATT profile:
# gatt_enum.py
import asyncio, json
from bleak import BleakClient
TARGET = "XX:XX:XX:XX:XX:XX" # instructor-provided
async def full_gatt_enum(addr):
profile = {}
async with BleakClient(addr, timeout=20.0) as client:
for service in client.services:
svc_entry = {
'uuid': service.uuid,
'description': service.description,
'characteristics': {}
}
for char in service.characteristics:
char_entry = {
'uuid': char.uuid,
'description': char.description,
'properties': list(char.properties),
'value': None,
'read_error': None
}
if 'read' in char.properties:
try:
val = await client.read_gatt_char(char.uuid)
char_entry['value'] = val.hex()
except Exception as e:
char_entry['read_error'] = str(e)
svc_entry['characteristics'][char.uuid] = char_entry
profile[service.uuid] = svc_entry
return profile
profile = asyncio.run(full_gatt_enum(TARGET))
print(json.dumps(profile, indent=2))
with open('gatt_profile.json', 'w') as f:
json.dump(profile, f, indent=2)
Security analysis from GATT profile
For each characteristic, classify:
| UUID | Description | Properties | Security Risk |
|---|---|---|---|
| ... | ... | ... | Critical / High / Med / Low / None |
Security risk criteria:
- Critical: write-without-response to an actuator (lock, relay, motor) with no authentication
- High: write (with response) to actuator; or cleartext PII/health data in notify characteristic
- Medium: writable characteristic with unauthenticated access to config (could affect device behavior)
- Low: readable characteristic exposes device metadata (firmware version, serial) useful for fingerprinting
- None: read-only non-sensitive characteristic
Deliverable B
gatt_profile.json + the security analysis table.
Part C — Unauthenticated Write Test (15 min)
The instructor has designated one specific characteristic UUID as the write test target. This characteristic controls an LED on the lab device and has no access restrictions.
# write_test.py
import asyncio
from bleak import BleakClient
TARGET = "XX:XX:XX:XX:XX:XX"
WRITE_CHAR_UUID = "XXXX-XXXX" # instructor provides this
async def test_write():
async with BleakClient(TARGET, timeout=20.0) as client:
# Turn LED on
await client.write_gatt_char(WRITE_CHAR_UUID, bytes([0x01]))
print("Wrote 0x01 (LED ON) -- observe the LED")
import asyncio; await asyncio.sleep(2)
# Turn LED off
await client.write_gatt_char(WRITE_CHAR_UUID, bytes([0x00]))
print("Wrote 0x00 (LED OFF)")
asyncio.run(test_write())
Observe the LED state changing on the lab device. This demonstrates unauthenticated write-to-actuator access.
Deliverable C
Screenshot or video still of LED state change + written answer: in a real IoT pentest, what would this class of vulnerability enable? Provide a realistic attack scenario (not hypothetical -- describe an actual IoT deployment category where this is present).
Part D — Pairing Exchange Analysis (15 min)
The instructor will re-pair a phone to the lab BLE device while you observe with btmon (on a machine participating in the pairing) or via the Sniffle sniffer.
# btmon to observe HCI-level pairing
sudo btmon 2>&1 | grep -A5 "Pairing"
Identify from the output:
- Which pairing method was used (Just Works, Passkey Entry, Numeric Comparison, OOB)?
- What is the IO Capability reported by each device?
- Is Secure Connections enabled (look for "Secure Connections" in the HCI events)?
Virtual Path Alternative
Analyze the provided lab10_pairing_btmon.log (a btmon log from a Just Works pairing). Answer the same three questions from the log.
Deliverable D
Answers to the three questions + 1 paragraph: for the pairing method observed, what is the attack surface? Specifically: is the derived LTK vulnerable to passive eavesdropping + offline recovery? Explain why or why not based on the pairing method.
Write-up Questions
- You found a
write-without-responsecharacteristic with no authentication requirement on a smart home thermostat. The characteristic controls the target temperature setpoint. CVSS v3.1 score this finding. Write the full vector string. - The lab device uses
notifyto stream temperature sensor data over BLE. The pairing method was Just Works. An attacker sits in the cafe next to the user with an nRF52840 sniffer. Can they read the temperature data? Explain the attack path. - MAC address randomization is enabled on the lab device (address changes every ~15 minutes). Does this prevent the GATT enumeration you performed in Part B? Why or why not?
- The KNOB attack (CVE-2019-9506) forces Bluetooth to negotiate 1-byte entropy. This applies to Classic Bluetooth. Does a BLE Secure Connections pairing using ECDH P-256 share the same vulnerability? Explain the difference.
Submission
Zip into lab10_YOURNAME.zip:
ble_advertising.logor Wireshark screenshot (Part A)deliverable_A_table.mdgatt_profile.json+deliverable_B_security_table.mddeliverable_C.md+ screenshotdeliverable_D.mdgatt_enum.py,write_test.py,parse_ble_adv.pywriteup.md