Skip to content
ncouturePublic

Latest commit

 

History

28 Commits

Folders and files

Repository files navigation

AirTell

Exposing the digital body language of Bluetooth Low Energy.

Python Version License Status

Modern mobile operating systems treat Bluetooth Low Energy (BLE) privacy as an address-randomization problem. It isn't. Across advertising intervals, scan request timings, and proprietary payload structures, devices broadcast a continuous, identifiable "body language" long before a connection is ever established.

Standard single-dongle sniffers periodically hop across advertising channels (37, 38, and 39), leaving predictable blind spots during channel transitions. AirTell eliminates this blindness. Built on top of NCC Group's Sniffle firmware, AirTell orchestrates multiple CC2652-based radio dongles over UART or an in-memory virtual radio bus. It pairs active stimulators with synchronized passive observers to evaluate, benchmark, and expose the BLE tracking mechanisms documented across Privacy Enhancing Technologies Symposium (PETS) research.

A public Google Notebook containing architectural notes, research references, and packet dissection logs is available at Google NotebookLM.


Table of Contents


Architecture Overview

The testbed coordinates two Sniffle radio dongles over UART or an in-memory virtual radio bus:

                        ┌───────────────────────────┐
                        │   Testbed Orchestration   │
                        │   (Topology A / B / CLI)  │
                        └──────┬─────────────┬──────┘
                               │             │
                    ┌──────────┴───┐     ┌───┴────────────┐
                    │ Device 1 Ctrl│     │ Device 2 Ctrl  │
                    └────────┬─────┘     └───┬────────────┘
                             │               │
               ┌─────────────┴─────┐     ┌─────┴─────────────┐
               │  SerialPort /     │     │  SerialPort /     │
               │  MockSerial (Dev1)│     │  MockSerial (Dev2)│
               └─────────────┬─────┘     └─────┬─────────────┘
                             │               │
                        ▲    ▼               ▲    ▼
               ╔════════════════════════════════════════════╗
               ║   Physical BLE Air / VirtualRadioBus       ║
               ║   (Channels 37, 38, 39, 1 us Timestamps)   ║
               ╚════════════════════════════════════════════╝

Key subsystems include:

  1. protocol:
    • wire: Base64+CRLF line framing with word-count prefix, command opcodes, and binary message parsing (PacketMessage, MarkerMessage, StateMessage, MeasurementMessage).
    • mac: Synthetic palindromic MAC address generator ensuring endianness invariance ($B_0 \dots B_5 = B_5 \dots B_0$) across LE radio bytes and network logs, with full support for Static Random, NRPA, RPA, and Public address types.
    • beacons: Standardized bit-for-bit AD encoders for Apple iBeacon, AltBeacon, Google Eddystone (UID, URL, TLM), and Google/Apple Exposure Notification (GAEN 0xFD6F).
    • probing: Active probe constructors (SCAN_REQ) and response frames (SCAN_RSP) eliciting Complete Local Names and service UUIDs.
  2. device:
    • serial_port: Hardware autodetection (Sonoff Zigbee Dongle Plus, TI XDS110, CatSniffer), non-conflicting dual port allocation, CP2102 baud rate capping (921,600 baud), and serial synchronization (mark_and_flush).
    • mock: In-memory MockSerialInterface and VirtualRadioBus for deterministic, offline testing without hardware dongles.
    • controller: High-level controller (SniffleDeviceController) managing advertising injection, active scanning, passive sniffing, and queued packet retrieval.
  3. topology:
    • topology_a: Stimulator (Device 1) broadcasts synthesized beacons or burst sequences while Observer (Device 2) captures over-the-air packets.
    • topology_b: Dual-Channel Passive Observer locking Device 1 to Ch 37 and Device 2 to Ch 38, merging captures into a single timestamp-ordered stream to eliminate channel-hop blindness.
    • lifecycle: Graceful shutdown coordinator handling SIGINT/SIGTERM, safely terminating reader threads, and releasing serial ports.

Operational Topologies

Topology A: Stimulator + Observer

  • Device 1 (Stimulator): Configured as an active transmitter broadcasting parameterized beacon profiles (e.g., iBeacon, AltBeacon, Eddystone, GAEN) or replaying recorded advertisement sequences with custom transmission intervals (20 ms to 1000 ms) and palindromic MAC addresses.
  • Device 2 (Observer): Concurrently sniffs primary advertising channels (37, 38, 39) to capture peripheral advertisements, mobile device scan requests (SCAN_REQ), and connection attempts elicited by the stimulator.

Topology B: Dual-Channel Passive Observer

Standard single-dongle sniffers periodically hop across channels 37, 38, and 39, causing blind spots during channel transitions. Topology B coordinates two devices simultaneously locked to fixed primary advertising channels:

  • Device 1: Locked to Channel 37 (hop3=False).
  • Device 2: Locked to Channel 38 (hop3=False) or Channel 39. Captured packets from both dongles are merged in real time into a strictly timestamp-ordered queue, providing continuous multi-channel visibility.

Topology C: Master / Slave Link Layer Traffic Relay

Sniffle performs link-layer relaying of Bluetooth LE traffic:

  • Device 1 (/dev/ttyUSB0): Acts as BLE central using relay_master.py positioned near genuine peripheral. Captures peripheral advertisements and binds TCP socket 7352 on 0.0.0.0.
  • Device 2 (/dev/ttyUSB1): Acts as BLE peripheral using relay_slave.py positioned near genuine central. Clones advertising payload, accepts connections, and tunnels link-layer packets bidirectionally over TCP.
  • See docs/relay_specifications.md for complete specifications, timing parameters, and security advisories.

Hardware Requirements

Supported Dongles & Bridge Limits

  • Sonoff Zigbee 3.0 USB Dongle Plus (TI CC2652P, USB VID: 0x10C4, PID: 0xEA60).
    • Note: Older Sonoff dongles use Silicon Labs CP2102 (non-N) bridge chips limited to 921,600 baud. The framework automatically caps the baud rate to 921,600 baud when CP2102 bridges are detected. Newer CP2102N revisions operate at 2,000,000 baud.
  • Texas Instruments CC26x2 LaunchPad (CC2652R / CC2652RB / CC1352P with XDS110 Debug Probe, VID: 0x0451, PID: 0xBEF3). Default: 2,000,000 baud.
  • Electronic Cats CatSniffer V3 (VID: 0x2E8A, PID: 0x00C0).

Port Auto-Discovery & In-Use Verification

The discover_sniffle_ports() and allocate_dual_ports() utilities automatically scan available serial interfaces, match hardware descriptors, and assign two distinct ports (port1 != port2) to Device 1 and Device 2 without manual configuration. When only a single dongle is present or specified, allocate_dual_ports(allow_single=True) enables single-dongle passive sniffing mode (topology-a) on the observer device while stimulus injection is gracefully deactivated.

Device Usage Validation (lsof): To determine at all times whether a Sniffle device is open and being used:

lsof /dev/ttyUSB0  # Return code 0 = OPEN and IN USE; Non-zero = IDLE / NOT OPEN
lsof /dev/ttyUSB1

The application checks this status continuously via airtell.device.serial_port.check_device_usage(), displaying real-time telemetry badges in the top app bar, sidebar, and radio controls view. See docs/device_status.md.


Centralized Documentation

Full technical documentation is centralized in docs/:


GitHub Operations & AI Code Review

AirTell enforces strict repository governance and automated architectural quality controls:

  • Global Code Ownership (.github/CODEOWNERS): Designates @ncouture as global repository owner across all files, automatically assigning PR reviews and enforcing branch protection rules.
  • Community Discussions (.github/DISCUSSIONS.md): Dedicated forum for RF tradecraft, signal forensics, hardware rigs, and privacy engineering, decluttering the issue tracker.
  • Structured Issue Forms (.github/ISSUE_TEMPLATE/): Blank issues are disabled; bug reports require topology selection, hardware/firmware versions, and shell logs.
  • AI Code Review System (.github/skills/code-review/SKILL.md):
    • Powered by the Google Antigravity SDK (google-antigravity) and Gemini 3.8 Flash with Thinking = High (thinking_budget: 16384).
    • Audits 1 µs hardware timer precision, CC2652 UART buffer flow control (2,000,000 baud), cross-dongle synchronization, and zero-panic parsing of untrusted over-the-air BLE packets.
    • Grounded in the AirTell Research NotebookLM Workspace and its local synchronized index (docs/AirTell-PoPETs.md).
    • Enforced Dual-Gate Codeowner Policy: Code reviews execute only on PRs authored by a repository codeowner (ncouture) and only when explicitly requested by a codeowner via ChatOps (/review, /code-review), UI review requests, PR labels, or manual dispatch.
    • Implemented in scripts/code_review.py and automated via .github/workflows/code-review.yml.

Mock Simulation & Offline Harness

The testbed includes a full in-memory simulation subsystem:

  • MockSerialInterface: Emulates PySerial Serial operations (read, readline, write, cancel_read, close, in_waiting) in pure Python with thread-safe non-blocking buffers.
  • VirtualRadioBus: Routes link-layer packets between simulated dongles. When Device 1 transmits an advertisement (COMMAND_ADVERTISE), the bus wraps the payload into a standard PacketMessage with microsecond timestamps, RF channel, RSSI (-60 dBm), and valid CRC, delivering it to any listening device locked to that channel.
  • Enables 100% automated test coverage in CI/CD environments without physical hardware dongles.

Wire Protocol & Line Framing

Sniffle communicates over UART using Base64 ASCII records terminated by \r\n:

Host to Dongle:
  b0 = (len(cmd_bytes) + 3) // 3
  wire_line = base64_encode(bytes([b0, *cmd_bytes])) + "\r\n"

Dongle to Host:
  data = base64_decode(raw_line)
  word_cnt = data[0]
  msg_type = data[1]
  msg_body = data[2:]

PacketMessage Header Format (Message Type 0x10)

Packets received by the radio unpack a 10-byte header:

  • ts (4 bytes, uint32 LE): Microsecond timestamp ($1\ \mu\text{s}$ resolution).
  • l (2 bytes, uint16 LE): Flags bitfield:
    • Bit 15: Direction (0 = Central -> Peripheral, 1 = Peripheral -> Central).
    • Bit 14: CRC Error flag (1 = CRC error, 0 = Valid CRC).
    • Bits 13..0: Link Layer PDU length in bytes.
  • event (2 bytes, uint16 LE): Connection event counter.
  • rssi (1 byte, signed int8): Signal strength in dBm (-128 to 0 dBm).
  • chan (1 byte, uint8): Bits 5..0 = RF channel (0–39); Bits 7..6 = PHY mode (0=1M, 1=2M, 2=Coded S8, 3=Coded S2).
  • Followed by the raw Link Layer PDU body.

Installation & Quick Start

# Clone the repository
git clone https://github.com/ncouture/AirTell.git
cd AirTell

# Install in editable mode with development dependencies
pip install -e .[test]

AI Code Review (Local Execution)

# Run local code review on git diff using Gemini 3.8 Flash (Thinking=High)
python scripts/code_review.py --base origin/main --output review.md

# Review staged changes only
python scripts/code_review.py --staged

Adversarial Evaluation & CLI Quick Start

# Run trace deanonymization analysis on simulated multi-tag rotation traces
airtell deanonymize --mock

# Ingest an offline PCAP capture for trajectory reconstruction and static leakage detection
airtell deanonymize --pcap captures/session.pcap --max-gap 30.0 --max-rssi-delta 15.0

# Simulate rogue tracking beacon injection bursts in software
airtell rogue-inject --mock --network "Apple Find My" --count 5

# Over-the-air rogue beacon injection with multi-channel hopping and jitter
airtell rogue-inject --port /dev/ttyUSB0 --network "Google Find Hub" --count 10 --interval-ms 200 --channels 37,38,39

Running Tests

Execute the automated test suite with strict warning trapping:

# Run all unit tests
pytest -v -W error tests/

# Run with coverage report
pytest --cov=airtell --cov-report=term-missing tests/

# Run linter
ruff check src tests

Project Layout

airtell/
├── README.md
├── pyproject.toml
├── .github/                    # GitHub configurations, templates, & automation
│   ├── CODEOWNERS              # Global repository ownership (* @ncouture)
│   ├── DISCUSSIONS.md          # Discussions onboarding & maintainer protocol
│   ├── ISSUE_TEMPLATE/         # Structured issue forms (bug, feature, config)
│   ├── skills/code-review/     # Gemini 3.8 Flash (Thinking=High) review skill
│   └── workflows/              # GitHub Actions CI (code-review.yml)
├── app/                        # Material Design v3 Tactical Streamlit Dashboard
│   ├── components/             # Navigation rail, top app bar, PWA assets
│   ├── views/                  # Telemetry, RSSI trends, alerts, radio controls
│   └── streamlit_app.py        # Dashboard application entrypoint
├── docs/                       # Centralized documentation hub
│   ├── index.md                # Documentation navigation index
│   ├── GITHUB.md               # GitHub operations, governance, & AI code review
│   ├── AirTell-PoPETs.md       # Synchronized NotebookLM research paper index
│   ├── relay_specifications.md # Master/Slave link layer relay specification
│   ├── device_status.md        # Device in-use validation via lsof
│   ├── scripts_reference.md    # Comprehensive scripts directory guide
│   ├── howto.md                # Sniffle CLI operations guide
│   ├── airdrop.md              # Apple AirDrop advertisement dissection
│   ├── crackle_pcap_analysis.md # Crackle analysis & longitudinal tracking
│   ├── json_jq.md              # Parsing Wireshark JSON with JQ
│   ├── tshark.md               # TShark & PCAP dissection guide
│   └── hardware_setup.md       # Hardware flashing & multi-dongle guide
├── scripts/                    # Standalone CLI tools, relays, & decoders
│   ├── code_review.py          # Antigravity SDK review runner (Gemini 3.8 Flash)
│   ├── advertiser.py           # Connectable peripheral emulator
│   ├── ble_antistalking_tracker.py # SQLite logger & heuristic alert dispatcher
│   ├── ble_parser.py           # Standalone offline finding key parser
│   ├── crackle                 # BLE encryption cracking ELF binary
│   ├── initiator.py            # Active central connection tester
│   ├── pcap_decoder.py         # Offline PCAP AD structure parser
│   ├── radar_tomography.py     # 2D radio tomographic sensing engine
│   ├── relay_master.py         # Central relay forwarder (TCP 7352)
│   ├── relay_slave.py          # Peripheral relay emulator
│   ├── reset.py                # Firmware reset utility
│   ├── scanner.py              # Active discovery scanner & RSSI aggregator
│   ├── sniff_receiver.py       # Core packet sniffer & connection follower
│   ├── sniffle_extcap.py       # Wireshark extcap live capture plugin
│   ├── uart_test.py            # Serial round-trip latency benchmark
│   └── version_check.py        # Firmware version & API validator
├── src/
│   ├── airtell/                # Modular testbed framework (`import airtell`)
│   │   ├── adversarial/        # Deanonymizer, relay bridge, rogue beacons
│   │   ├── device/             # Serial port lifecycle, lsof check, mock bus
│   │   ├── extcap/             # Asynchronous Tshark runner & stream parser
│   │   ├── protocol/           # Wire protocol, beacons, probing, MAC generator
│   │   └── topology/           # Topologies A & B, graceful shutdown
│   └── sniffle/                # Embedded Sniffle C firmware & CLI library
└── tests/                      # Unit, integration, and E2E test suites
    └── test_codeowner_enforcement.py # Codeowner authorization policy tests

Releases

Packages

Used by

Contributors

Languages