Liberando Bluetooth en el ESP32

El ESP32 se ha convertido en una plataforma omnipresente, utilizada en todo tipo de proyectos: desde millones de dispositivos comerciales hasta prototipos puntuales creados por las comunidades hacker y maker. Aunque su pila Wi-Fi ya ha sido objeto de trabajos previos de ingeniería inversa, su subsistema Bluetooth sigue estando en gran medida sin documentación y con código cerrado, a pesar de estar presente en millones de dispositivos.
Este trabajo de ingeniería inversa busca documentar la pila Bluetooth propietaria de Espressif, con especial énfasis en habilitar acceso de bajo nivel para investigadores, analistas de seguridad y desarrolladores, con el fin de mejorar el ecosistema existente de herramientas Bluetooth asequibles y abiertas.
Esta investigación se llevó a cabo originalmente como parte de nuestra línea de investigación en seguridad Bluetooth y amplía las notas y hallazgos compartidos en la charla 39C3.
Como parte de la línea de investigación Bluetooth desarrollada en los últimos años, se han publicado numerosos trabajos. Se han identificado vulnerabilidades como BlueTrust y BlueSpy. También se logró un gran avance en estandarización con la publicación de BSAM, una metodología para evaluación de seguridad Bluetooth con licencia Creative Commons.
Actualmente, a pesar de contar con información organizada, sigue siendo difícil implementar algunas de las propuestas y comprobaciones de seguridad debido a la falta de herramientas asequibles para llevarlas a cabo y ejecutarlas. Por ello, el esfuerzo de la línea de investigación se centra ahora en lograr herramientas y recursos accesibles que permitan aplicar la metodología existente.
El nuevo reto es disponer de acceso a las capas inferiores de Bluetooth de forma bien documentada y accesible. Sorprendentemente, hay pocos ejemplos de controladores Bluetooth de código abierto.
¿Por qué el ESP32?
Al buscar un dispositivo Bluetooth asequible para realizar ingeniería inversa, es imprescindible tener en cuenta el Espressif ESP32. Su precio por unidad y su disponibilidad son difíciles de superar, y además se suman otras razones que lo convierten en uno de los mejores candidatos para pasarlo a código abierto:
- El dispositivo admite tanto Bluetooth Classic como Bluetooth Low Energy, algo que muchos otros no ofrecen.
- El SDK del dispositivo ya es casi completamente de código abierto, por lo que solo quedaría hacer ingeniería inversa de las piezas restantes.
- Los binarios (“blobs”) que aún permanecen cerrados dentro del SDK están licenciados bajo Apache, lo que permite publicar los trabajos derivados que se generen a partir de ellos.
Recopilación de código del ESP32
Espressif publica todos sus blobs de código cerrado como parte de su SDK. Principalmente nos interesaban las ROM del ESP32, las bibliotecas Bluetooth y las bibliotecas del periférico de radiofrecuencia.
Todos los componentes anteriores se publican en bibliotecas separadas, lo que dificulta su análisis en Ghidra. Afortunadamente, podemos generar un «golden binary» que los combina en un único archivo ELF. En el pasado ya documentamos cómo hacerlo, usando la toolchain de ESP y las herramientas del enlazador.
Como alternativa sencilla para obtener un binario dorado, hemos creado un proyecto estándar de ESP-IDF que cualquiera puede compilar indicando como dependencias las bibliotecas que queremos analizar mediante ingeniería inversa y configurando CMake para que no haga strip/trim del binario resultante, manteniendo todas las funciones y el código aunque no se utilicen. Puedes encontrar el proyecto del binario dorado en GitHub.
Figura 1. Proyecto del «Golden binary».
Espressif también publica, dentro de su SDK, linker scripts que permiten compilar aplicaciones de usuario enlazándolas contra sus ROM internas. Estos scripts de enlazado contienen información útil, como nombres y direcciones de funciones, que puede simplificar enormemente la tarea de comprender el código interno.
Figura 2. Ejemplo de archivo linker script del SDK del ESP32.
Se ha creado un plugin para Ghidra que permite importar automáticamente los nombres y direcciones de las funciones. El plugin se llama GhidraLinkerScript y está disponible en GitHub.
De este modo, el proceso de ingeniería inversa se beneficia del contexto adicional que aportan los nombres de las funciones, lo que ayuda mucho a entender el propósito de algunas partes del código interno de la ROM del ESP32.
Información sobre periféricos conocidos
Una vez que se carga en Ghidra toda la información relevante relacionada con el código, nos encontramos con un par de obstáculos.
El ELF solo contiene la memoria asociada al código y parte de la RAM. Por desgracia, el código interactúa directamente con memoria de periféricos cuya información no se ha cargado en el mapa de memoria de Ghidra…
El datasheet del ESP32 contiene información sobre estas regiones de memoria, pero no es especialmente detallada y, además, resulta tedioso incorporarla a mano. Afortunadamente, esta información también está disponible en un formato procesable llamado SVD. SVD es un estándar basado en XML concebido originalmente para ARM, pero que se ha extendido ampliamente para documentar información de chips, de modo que otras herramientas puedan generar código o facilitar tareas de depuración.
Espressif publica sus propios archivos SVD, pero están bastante desactualizados. La comunidad Rust de Espressif mantiene un repositorio que corrige esos SVD antiguos para producir versiones más actuales aquí.
Ghidra no admite de forma nativa la importación de información SVD, pero se ha desarrollado un plugin para ello disponible también en GitHub.
Figura 3. Proceso de importación SVD en Ghidra.
Tras importar la información SVD, Ghidra podrá mostrar correctamente el nombre de regiones de memoria que antes eran desconocidas, al menos para aquellas ya documentadas en el archivo SVD.
Los fragmentos que faltan
Ahora que toda la información disponible está cargada en Ghidra, queremos documentar las regiones de memoria que aún faltan, en particular aquellas que pueden usarse intensivamente en código relacionado con Bluetooth.
El código en Ghidra intenta acceder a regiones de memoria mapeadas a periféricos, pero Ghidra no nos permite listar y enumerar el código que intenta acceder a regiones desconocidas. Para ello se ha creado el plugin GhidraInvalidMemoryRefs.
El plugin simplemente lista los puntos del programa en los que hay fragmentos de código que intentan acceder a memoria que todavía no ha sido reconocida ni etiquetada. Con la tabla resultante, podemos localizar fácilmente funciones con nombres relacionados con Bluetooth que intentan acceder a memoria ubicada en 0x3ff71000.
Figura 4. El plugin Invalid Memory References listando funciones relacionadas con Bluetooth.
Con el plugin se identifica la región más relacionada con el código Bluetooth. Ahora podemos explorar todas las funciones que interactúan con esta región de memoria e intentar entender qué hace cada bit y cómo puede utilizarse el periférico.
Preparación y procedimiento
En gran medida, lo único que queda es pasar incontables horas leyendo cantidades obscenas de código. La idea es, a partir de la información existente, extraer todo el contexto posible para entender cómo y por qué se accede a esas direcciones de memoria en ese punto del código.
¡Cada detalle cuenta! Los nombres de funciones, la información de depuración sobre nombres de archivos y líneas del código fuente, las cadenas de assert que filtran nombres de registros e incluso el uso de máscaras de bits (bitmasks) nos permiten entender cómo se divide un registro en campos más pequeños…
Leer no siempre es suficiente, y hubo que realizar muchas pruebas. Para ello desarrollamos múltiples pruebas de firmware, usadas para leer y escribir valores en registros y evaluar el impacto de los cambios…
Para facilitar la validación de algunas pruebas específicas, desarrollamos placas ESP32 personalizadas con conectores JTAG y SMA, de forma que pudiéramos depurar en tiempo real la ejecución del firmware mientras conectábamos la parte de RF del chip ESP32 a otros equipos de prueba, como SDRs y sniffers Bluetooth, y así realizar mediciones sin influencias externas.
Figura 5. Placa ESP32 personalizada conectada a un ElectronicCats CatSniffer para pruebas.
Hallazgos
Tras muchas horas de ingeniería inversa y pruebas, empezamos a documentar una arquitectura sorprendente que compone este periférico Bluetooth.
El periférico BTDM (BlueTooth Dual Mode) se encarga de ayudar al procesador principal con operaciones sensibles al tiempo que ocuparían demasiado tiempo del procesador principal del ESP32 si este periférico no estuviera presente.
El periférico depende de otros periféricos, como la Radio, el Módem o un árbitro de coexistencia, para enviar y recibir información RF. Las interfaces utilizadas para comunicarse con esos periféricos aún no han sido objeto de ingeniería inversa, lo que concentra el esfuerzo en ingeniería inversa del periférico principal de Bluetooth, que se sabe que es posible controlar por el usuario.
Además, el núcleo BTDM se comunica con el núcleo principal de procesamiento del ESP32 mediante múltiples interfaces que hemos estudiado:
- Interrupciones, que son señales que el núcleo BTDM puede usar para interrumpir al núcleo principal, notificando al usuario que algún evento Bluetooth necesita procesamiento o que ha ocurrido algún error.
- Registros, que son una porción especial de memoria que desencadena comportamientos dentro del núcleo BTDM en función de su valor. El núcleo principal puede leerlos o escribirlos para cambiar opciones generales del periférico, como habilitarlo o deshabilitarlo, el reporte de errores, la temporización y configuración de reloj, la configuración de memoria de intercambio, etc.
- Memoria de intercambio, que es una porción de la RAM de propósito general compartida entre el BTDM y el núcleo principal. Esto permite que el núcleo BTDM acceda a la RAM general del mismo modo que lo hace el núcleo principal. La memoria de intercambio se usará como el principal mecanismo de intercambio de información Bluetooth.
Figura 6. Arquitectura del periférico Bluetooth.
Se han generado nuevos archivos SVD actualizados para documentar los registros del BTDM.
Hemos documentado más de 40 registros relacionados con Bluetooth y campos de bits para más de 20 registros en el repositorio de Tarlogic en GitHub. Esperamos que esta documentación pueda acabar integrándose en el proyecto original de la comunidad Rust de Espressif y en los repositorios oficiales de Espressif.
La memoria de intercambio es muy extensa y contiene muchas tablas enlazadas. Cada una de estas tablas gestiona una parte del intercambio de información Bluetooth:
- ¿Qué debe hacer el periférico en cada momento? Escanear, anunciarse (advertise), conectarse…
- Control de frecuencia y saltos (hop) para ese slot…
- Búferes de transmisión (TX) y recepción (RX)…
Como resumen, en el ESP32 la memoria de intercambio está mapeada de la siguiente manera:
| Dirección | Estructura |
|---|---|
| 0x3ffb0000 | Tabla de intercambio |
| 0x3ffb0040 | Tabla de frecuencias |
| 0x3ffb0090 | ?? |
| 0x3ffb0098 | Cifrado BLE |
| 0x3ffb00b8 | Estructuras de control BLE |
| 0x3ffb0480 | Lista blanca BLE |
| 0x3ffb0510 | Lista de resolución de direcciones BLE |
| 0x3ffb05ac | Descriptores TX de BLE |
| 0x3ffb0934 | Descriptores RX de BLE |
| 0x3ffb0994 | Búferes de control de TX |
| 0x3ffb0b82 | Búferes de datos de TX |
| 0x3ffb15aa | Búferes de RX |
| … | Elementos de BR/EDR |
La documentación relevante sobre cómo se organiza esta memoria de intercambio no debería incluirse en un archivo SVD, porque puede cambiar y remapearse a cualquier otra región en cualquier momento, y además no describe hardware. Por este motivo, hemos optado por documentarla en forma de archivos de cabecera (header files) en un repositorio aparte.
Un detalle interesante del funcionamiento del dispositivo reside en entender dos dificultades de este diseño:
- Tanto el núcleo principal como el núcleo BTDM pueden modificar la memoria de intercambio simultáneamente. Esto ocurriría, por ejemplo, si se recibe un paquete y el BTDM intenta escribir en la memoria de intercambio mientras el usuario ya estaba escribiendo la información para una solicitud de transmisión. Esto puede producir condiciones de carrera…
- Las operaciones Bluetooth son muy sensibles al tiempo y deben ejecutarse sincronizadas con un reloj muy ajustado…
El periférico Bluetooth del ESP32 resuelve esto mediante un mecanismo de “pre-fetch”. La tabla principal de intercambio define 16 slots. El núcleo BTDM iterará continuamente esas 16 entradas de la tabla y ejecutará la operación de cada slot sincronizada con un reloj de 625 µs.
Figura 7. Gráfico de ejecución de slots.
Instantes antes de que un slot deba ejecutarse, el núcleo BTDM va a hacer pre-fetch de los datos del slot y verificará si hay alguna operación pendiente que deba ejecutarse. Si es así, el núcleo BTDM esperará la señal de reloj y ejecutará la acción.
Figura 8. Prefetch de la información del slot.
El usuario debe tener cuidado, debe preparar toda la información en la memoria de intercambio dentro de un slot vacío y luego esperar a la interrupción de prefetch, que señaliza en la tabla de intercambio que un slot puede procesarse. Cuando todo esté listo, el núcleo BTDM alcanzará el slot correspondiente y obtendrá la información para ejecutarla. En ese momento, el usuario no debería modificar la información existente; de lo contrario, es posible que los cambios no se apliquen.
Durante la ejecución del slot o tras finalizar la acción, el núcleo BTDM notificará actualizaciones o acciones pendientes mediante interrupciones al núcleo principal.
¿Qué se puede hacer?
Tener un conocimiento profundo del funcionamiento del núcleo BTDM del ESP32 nos permite realizar directamente mediante el hardware todas las operaciones Bluetooth habituales: escaneo, advertising, conexión, etc.
Esto permitiría a cualquiera escribir una pila de controlador Bluetooth de código abierto que funcione en el ESP32.
Además de eso, existe suficiente documentación para realizar algunas operaciones de prueba de radiofrecuencia que permiten la transmisión y recepción de paquetes de prueba Bluetooth. Esto puede ser útil para interferir (jammering) continuamente en un solo canal…
Asimismo, comprender la interfaz de hardware nos permite modificar la implementación existente del software del controlador Bluetooth para incorporar comportamientos personalizados, como respuestas inesperadas. Esto nos permite adaptar pruebas de concepto existentes para verificaciones de seguridad y realizar fuzzing a bajo nivel.
Un proyecto interesante para la investigación en seguridad sería implementar paquetes HCI personalizados para permitir funcionalidades específicas en nuestro dispositivo ESP32, tales como:
- Registrar tráfico de bajo nivel para observar y analizar los protocolos RF subyacentes que no pueden verse en los niveles HCI, con el fin de estudiar el cumplimiento del estándar.
- Realizar escaneos por canal, para obtener información detallada de los dispositivos que nos permita identificarlos y perfilarlos según su comportamiento, incluso cuando utilicen mecanismos de anonimización como direcciones MAC aleatorias.
- Enviar tráfico Bluetooth de bajo nivel arbitrario para probar la resiliencia de otros dispositivos frente a paquetes inesperados mediante técnicas de fuzzing, o implementar técnicas conocidas para la extracción y explotación de información.
- Seguir y espiar conexiones cuya creación se haya capturado, para monitorizar la comunicación entre dispositivos sin intervenir en el intercambio de datos.
- Interferir continuamente en un solo canal para impedir anuncios, conexiones o intercambios de datos en esos canales, forzando la comunicación en los canales restantes. Esto es útil para aumentar las probabilidades de que ataques difíciles de implementar funcionen en escenarios reales.
- Escucha limitada de tráfico de terceros. En Bluetooth, los dispositivos conectados intercambian paquetes precedidos por una “palabra de sincronización” única para esa conexión. Existe un mecanismo documentado que permite, incluso si hay cierto número de errores en los bits de la “palabra de sincronización”, capturar posibles paquetes malformados, de modo que se pueda recibir información usando palabras de sincronización parcialmente conocidas..
¿Qué no se puede hacer?
En Bluetooth, los dispositivos conectados intercambian paquetes precedidos por una «palabra de sincronización» (synchronization word) que es única para esa conexión. Por desgracia, hasta la fecha no se ha encontrado ninguna forma de forzar el periférico BTDM a un «modo monitor completo» (full monitor mode) en el que se puedan recibir paquetes sin conocer la «palabra de sincronización» de estos.
También se sospecha que ciertos paquetes se procesan internamente, por lo que es posible que no todos los paquetes Bluetooth estén disponibles desde este periférico.
Trabajo futuro
Hay documentación suficiente para empezar a escribir una pila de controlador de código abierto que sea fácilmente modificable y extensible, aunque implicaría una cantidad significativa de trabajo.
La implementación existente, junto con la documentación sobre el hardware del periférico, es suficiente para portar al ESP32 la mayoría de las herramientas públicas existentes de bajo nivel para Bluetooth, así como ataques y vulnerabilidades.
Por último, sería interesante realizar ingeniería inversa de otras partes de los chips ESP32, como el módem o los periféricos de radiofrecuencia, para evaluar si las limitaciones encontradas podrían resolverse.
Referencias:
- GhidraLinkerScript: https://github.com/antoniovazquezblanco/GhidraLinkerScript
- GhidraSVD: https://github.com/antoniovazquezblanco/GhidraSVD
- GhidraInvalidMemoryRefs: https://github.com/antoniovazquezblanco/GhidraInvalidMemoryRefs
- SVDs: https://github.com/TarlogicSecurity/esp-pacs
- Documentation: https://github.com/TarlogicSecurity/ESP32-Bluetooth-Reversing
- Slides: https://github.com/TarlogicSecurity/talks
- Ponencia: https://media.ccc.de/v/39c3-liberating-bluetooth-on-the-esp32







