
Escáner de Bluetooth Low Energy (BLE) con resolución de direcciones privadas resolubles (RPA) mediante claves de resolución de identidad (IRKs)
Un escáner Bluetooth Low Energy (BLE) con resolución avanzada de direcciones privadas resolubles (RPA). Descubre dispositivos BLE cercanos, rastrea un dispositivo específico por dirección MAC o resuelve direcciones aleatorizadas por privacidad mediante una clave de resolución de identidad (IRK).
Escrito por: David Kennedy (@HackingDave) Empresa: TrustedSec
-o -)El etiquetado de ubicación GPS requiere que el daemon gpsd esté en ejecución con un receptor GPS conectado. Si gpsd no está en ejecución, btrpa-scan continúa con normalidad sin GPS.
Para verificar que gpsd funciona:
# Check that gpsd is listening
gpspipe -w -n 5
# Or use the curses monitor
cgps
Este proyecto usa pyproject.toml (PEP 621), el estándar moderno de empaquetado de Python. Define el proyecto como un paquete instalable con un comando CLI registrado — no es necesario ejecutar archivos .py directamente.
uvx btrpa-scan --all
uvx --from git+https://github.com/hackingdave/btrpa-scan.git btrpa-scan --all
uv tool install btrpa-scan
O directamente desde GitHub:
uv tool install git+https://github.com/hackingdave/btrpa-scan.git
pip install btrpa-scan
Para soporte de GUI (interfaz de radar basada en Flask):
pip install btrpa-scan[gui]
git clone https://github.com/hackingdave/btrpa-scan.git
cd btrpa-scan
pip install .
usage: btrpa-scan [-h] [-a] [--irk HEX] [--irk-file PATH] [-t TIMEOUT]
[--output {csv,json,jsonl}] [-o FILE] [--log FILE]
[-v | -q] [--min-rssi DBM] [--rssi-window N] [--active]
[--environment {free_space,indoor,outdoor}]
[--ref-rssi DBM] [--name-filter PATTERN]
[--alert-within METERS] [--tui] [--gui] [--gui-port PORT]
[--no-gps] [--adapters LIST] [mac]
BLE Scanner — discover all devices or hunt for a specific one
positional arguments:
mac Target MAC address to search for (omit to scan all)
optional arguments:
-h, --help show this help message and exit
-a, --all Scan for all broadcasting devices
--irk HEX Resolve RPAs using this Identity Resolving Key (32 hex chars)
--irk-file PATH Read IRK(s) from a file (one per line, hex format)
-t, --timeout TIMEOUT Scan timeout in seconds (default: 30, or infinite for --irk)
--output {csv,json,jsonl}
Batch output format written at end of scan
-o, --output-file FILE
Output file path (default: btrpa-scan-results.<format>;
use - for stdout)
--log FILE Stream detections to a CSV file in real time
-v, --verbose Verbose mode — show additional details
-q, --quiet Quiet mode — suppress per-device output, show summary only
--min-rssi DBM Minimum RSSI threshold (e.g. -70) — ignore weaker signals
--rssi-window N RSSI sliding window size for averaging (default: 1 = no averaging)
--active Use active scanning (sends SCAN_REQ for additional data)
--environment {free_space,indoor,outdoor}
Distance estimation path-loss model (default: free_space)
--ref-rssi DBM Calibrated RSSI at 1 metre for distance estimation
--name-filter PATTERN Filter devices by name (case-insensitive substring match)
--alert-within METERS Proximity alert when device is within this distance
--tui Live-updating terminal table instead of scrolling output
--gui Launch web-based radar interface in the browser
--gui-port PORT Port for GUI web server (default: 5000)
--no-gps Disable GPS location stamping (GPS is on by default via gpsd)
--adapters LIST Comma-separated Bluetooth adapter names (e.g. hci0,hci1)
Escanea todos los dispositivos BLE que estén emitiendo (tiempo de espera predeterminado de 30 segundos):
btrpa-scan --all
Con un tiempo de espera personalizado:
btrpa-scan --all -t 60
Busca un dispositivo específico por dirección MAC:
btrpa-scan AA:BB:CC:DD:EE:FF
Resuelve direcciones privadas resolubles mediante una clave de resolución de identidad. Este modo se ejecuta indefinidamente por defecto hasta que se detiene con Ctrl+C:
btrpa-scan --irk 0123456789ABCDEF0123456789ABCDEF
La IRK se puede proporcionar en varios formatos:
| Formato | Ejemplo |
|---|---|
| Hex simple | 0123456789ABCDEF0123456789ABCDEF |
| Separada por dos puntos |
Carga una o más IRKs desde un archivo. Cada línea debe contener una IRK en cualquier formato hex admitido. Las líneas que comienzan con # se tratan como comentarios:
btrpa-scan --irk-file keys.txt
Ejemplo de keys.txt:
# Alice's phone
0123456789ABCDEF0123456789ABCDEF
# Bob's watch
FEDCBA9876543210FEDCBA9876543210
Cuando se cargan varias IRKs, cada RPA detectada se comprueba contra todas las claves. El resumen muestra las coincidencias totales entre todas las claves.
Define la variable de entorno BTRPA_IRK para evitar pasar la clave en la línea de comandos:
export BTRPA_IRK=0123456789ABCDEF0123456789ABCDEF
btrpa-scan
Prioridad: --irk > --irk-file > BTRPA_IRK
Muestra únicamente los dispositivos con intensidad de señal por encima de un umbral:
btrpa-scan --all --min-rssi -70
El RSSI de BLE es inherentemente ruidoso. Usa un promedio con ventana deslizante para estimaciones de distancia más estables y para filtrar detecciones débiles espurias:
btrpa-scan --all --rssi-window 5
Cuando el ventaneo está activo, la pantalla muestra tanto el RSSI bruto como el promediado (p. ej., RSSI: -65 dBm (avg: -62 dBm over 5 readings)), y la estimación de distancia usa el valor promediado. El filtro --min-rssi también se aplica al RSSI promediado, evitando que un único pico ruidoso descarte un dispositivo.
Filtra dispositivos por nombre mediante una coincidencia de subcadena sin distinguir mayúsculas:
btrpa-scan --all --name-filter "AirPods"
Solo se mostrarán los dispositivos cuyo nombre anunciado contenga el patrón indicado. Los dispositivos sin nombre se excluyen cuando hay un filtro de nombre activo.
El escaneo pasivo (el predeterminado) solo ve los anuncios. El escaneo activo envía SCAN_REQ y obtiene SCAN_RSP, lo que puede revelar UUIDs de servicios y nombres de dispositivo adicionales:
btrpa-scan --all --active
Nota: En macOS, CoreBluetooth siempre escanea de forma activa independientemente de esta opción. En Linux/BlueZ, el escaneo activo puede requerir root o
CAP_NET_ADMIN.
La estimación de distancia usa un exponente de pérdida de trayecto que varía según el entorno. El predeterminado (free_space, n=2.0) asume que no hay obstrucciones. Para estimaciones más realistas en interiores:
btrpa-scan --all --environment indoor
| Preset | Exponente de pérdida de trayecto (n) | Caso de uso |
|---|---|---|
free_space | 2.0 |
Los valores de n más altos producen estimaciones de distancia mayores para el mismo RSSI, reflejando la atenuación de la señal por muros y obstáculos.
Por defecto, btrpa-scan deriva el RSSI esperado a 1 metro a partir de la potencia TX anunciada usando un desplazamiento de 59 dB validado empíricamente (el estándar iBeacon). Para una precisión aún mayor, puedes proporcionar un valor calibrado medido en tu propio entorno:
--ref-rssi:btrpa-scan --all --ref-rssi -55
Cuando se establece --ref-rssi, la potencia TX se ignora por completo. Esto también permite estimar la distancia de dispositivos que no anuncian potencia TX.
Activa una campanilla audible y una alerta visual cuando se estima que un dispositivo está dentro de una distancia determinada. Requiere que el dispositivo objetivo anuncie la potencia TX:
btrpa-scan AA:BB:CC:DD:EE:FF --alert-within 5.0
Funciona en todos los modos, incluida la resolución IRK:
btrpa-scan --irk <key> --alert-within 3.0
Reemplaza la salida en scroll por una tabla de terminal de actualización en vivo, ordenada por intensidad de señal:
btrpa-scan --all --tui
La TUI muestra todos los dispositivos detectados en una tabla compacta con dirección, nombre, RSSI, RSSI promediado, distancia estimada, número de detecciones y hora de última aparición. Las coincidencias IRK resueltas se muestran en negrita y los dispositivos dentro del umbral de --alert-within se resaltan.
Combínala con otras opciones:
btrpa-scan --irk <key> --tui --rssi-window 5 --environment indoor --alert-within 5.0
Lanza una interfaz de radar basada en navegador con barrido animado, seguimiento de dispositivos en tiempo real y mapa GPS:
btrpa-scan --all --gui
La GUI incluye:
El modo GUI escanea de forma continua por defecto (sin tiempo de espera de 30 segundos). Pulsa Ctrl+C para detenerlo. Usa -t para establecer una duración de escaneo específica:
# Scan for 60 seconds with indoor path-loss model
btrpa-scan --all --gui -t 60 --environment indoor
# Custom port
btrpa-scan --all --gui --gui-port 8080
# Combine with RSSI averaging and proximity alerts
btrpa-scan --all --gui --rssi-window 5 --alert-within 5.0
Nota:
--guirequiere Flask y flask-socketio (pip install btrpa-scan[gui]). No se puede combinar con--tuio--quiet.
Transmite cada detección a un archivo CSV a medida que ocurre (útil para escaneos de larga duración en los que quieres datos incrementales):
btrpa-scan --all --log scan.csv
Esto se puede combinar con --output para una exportación por lotes separada:
btrpa-scan --all --log live.csv --output json -o results.json
Exporta todos los resultados al final del escaneo en formato CSV, JSON o JSONL (JSON Lines):
btrpa-scan --all --output json -o results.json -t 30
btrpa-scan --all --output csv -t 30
btrpa-scan --all --output jsonl -o results.jsonl -t 30
JSONL escribe un objeto JSON por línea, lo que facilita canalizarlo con jq:
btrpa-scan --all --output jsonl -o results.jsonl -t 10
cat results.jsonl | jq .
Escribe la salida en stdout para canalizarla:
btrpa-scan --all --output json -o - -t 10 -q | jq .
Escanea con varios adaptadores Bluetooth simultáneamente para una mayor cobertura:
btrpa-scan --all --adapters hci0,hci1
Cada adaptador ejecuta su propia instancia de escáner que comparte el mismo callback de detección. Todas las detecciones se fusionan en una única salida.
El GPS está activado por defecto. Cada detección se etiqueta con las coordenadas GPS actuales de gpsd. El escáner también rastrea la mejor fijación GPS por dispositivo — las coordenadas de la detección con el RSSI más fuerte (mayor proximidad = ubicación más precisa).
Si gpsd no está en ejecución, el escáner imprime una nota y continúa con normalidad sin GPS:
# With gpsd running — detections include lat/lon
btrpa-scan --all --output json
# Without gpsd — works fine, GPS fields are empty
btrpa-scan --all
# Explicitly disable GPS (skips connection attempt)
btrpa-scan --all --no-gps
Las coordenadas GPS aparecen en:
latitude, longitude, gps_altitudeEjecuta en modo silencioso (solo resumen, sin salida por dispositivo — útil con --output o --log):
btrpa-scan --all -q --output json -t 30
Ejecuta en modo detallado (muestra las RPA que no coinciden en modo IRK):
btrpa-scan --irk 0123456789ABCDEF0123456789ABCDEF -v
Los dispositivos Bluetooth Low Energy usan direcciones privadas resolubles (RPA) para evitar el rastreo. Una RPA es una dirección MAC temporal que cambia periódicamente, pero cualquiera que posea la clave de resolución de identidad (IRK) del dispositivo puede resolverla.
Una RPA consta de:
01AES-128-ECB(IRK, padding || prand) truncado a 3 bytesbtrpa-scan implementa la función ah() de la especificación central de Bluetooth (Vol 3, Parte H, Sección 2.2.2) para resolver estas direcciones en tiempo real.
Nota sobre AES-ECB: El uso de AES en modo ECB para la función
ah()está exigido por la especificación central de Bluetooth. Dado que solo se cifra un único bloque de 16 bytes, la falta de difusión de ECB entre bloques es irrelevante — esto no es una vulnerabilidad.
ps. Para evitar exponer la IRK, usa --irk-file para leerla desde un archivo o define la variable de entorno BTRPA_IRK. La salida de consola enmascara las IRK por defecto (mostrando solo los primeros y últimos 4 caracteres hex).--no-gps para desactivar el GPS por completo.--output-file y --log escriben en la ruta especificada. Asegúrate de que el destino tenga los permisos adecuados para tu caso de uso.pip install pytest
python -m pytest test_btrpa_scan.py -v
Mode: DISCOVER ALL - showing every broadcasting device
Scanning: passive
GPS: connected (37.774929, -122.419418)
Timeout: 30s | Press Ctrl+C to stop
------------------------------------------------------------
============================================================
DEVICE #1 - seen 1x
============================================================
Address : AA:BB:CC:DD:EE:FF
Name : MyDevice
RSSI : -45 dBm
TX Power : -59 dBm
Est. Distance: ~0.4 m
Manufacturer : 0x004C -> 0215abcdef
Best GPS : 37.774929, -122.419418
Timestamp : 14:32:07
============================================================
------------------------------------------------------------
Scan complete - 30.0s elapsed
Total detections : 142
Unique devices : 12
Results written to btrpa-scan-results.json
Pulsa Ctrl+C en cualquier momento para detener el escaneo de forma controlada y mostrar las estadísticas resumidas.
| Plataforma | Instalación | Inicio |
|---|
| macOS | brew install gpsd | gpsd -n /dev/tty.usbserial-* |
| Debian/Ubuntu | sudo apt install gpsd gpsd-clients | sudo systemctl start gpsd |
| Fedora/RHEL | sudo dnf install gpsd gpsd-clients | sudo systemctl start gpsd |
| Arch | sudo pacman -S gpsd | sudo systemctl start gpsd |
| Windows | Usa gpsd mediante WSL o MSYS2 | Consulta las instrucciones de WSL anteriores |
| Plataforma | Notas |
|---|
| macOS | Usa CoreBluetooth. El modo IRK aprovecha una API no documentada para obtener direcciones Bluetooth reales en lugar de UUIDs. --active no tiene efecto — CoreBluetooth siempre escanea de forma activa. |
| Linux | Puede requerir root o la capacidad CAP_NET_ADMIN para escanear. |
| Windows | API Bluetooth WinRT nativa — direcciones MAC reales disponibles de forma nativa. La TUI requiere pip install windows-curses. |
01:23:45:67:89:AB:CD:EF:01:23:45:67:89:AB:CD:EF| Separada por guiones | 01-23-45-67-89-AB-CD-EF-01-23-45-67-89-AB-CD-EF |
| Con prefijo 0x | 0x0123456789ABCDEF0123456789ABCDEF |
| Aire libre, línea de visión |
outdoor | 2.2 | Parques, estacionamientos |
indoor | 3.0 | Oficinas, hogares, edificios |