Comandos HCI ocultos en ESP32, detalles técnicos y casos de uso

El estándar de Bluetooth propone una separación de su arquitectura en dos componentes principales.
Por un lado, se encuentra el controller, compuesto por un hardware especializado con capacidad para implementar la capa física de radiofrecuencia y que corre un firmware encargado de las comunicaciones de bajo nivel, que requieren operaciones en tiempo real y de alta precisión.
Por otro, se encuentra el host, que es el software que se encarga de la gestión de las comunicaciones a alto nivel sin preocuparse por los detalles de los paquetes de bajo nivel y que orquesta al controller.
De manera muy simplificada, esto suele traducirse en que el host es un software ejecutado en un dispositivo, generalmente el procesador de la placa principal de un dispositivo IoT y que ejecuta acciones a alto nivel, y se comunica con el controller, que es el chip que se encarga de enviar paquetes, mantener la conexión, etc.
El software del host y el controller se comunican a través del protocolo HCI, donde se van encapsulando y anidando protocolos dentro de paquetes. Este protocolo HCI es estándar independiente del sistema operativo y puede funcionar sobre USB y UART/Serial.
En particular, el chip ESP32 permite ser usado en dos configuraciones distintas:
- Host y controller en el mismo chip, en la que la aplicación (host) se ejecuta en el mismo ESP32 y se comunica mediante una interfaz HCI virtual con el controller dentro del mismo chip.
- Solamente como controller, en la que la aplicación se ejecuta en otro chip u ordenador y se usa el ESP32 como controller mediante una interfaz HCI a través de la UART.
En este artículo nos centraremos en la segunda configuración en la que el chip ESP32 actúa solo como un controller Bluetooth y que mediante HCI es controlado por otro chip u ordenador para ver las acciones que se podrían llevar a cabo de forma local.
El protocolo HCI se compone principalmente de dos tipos de paquetes.
- Comandos: Los paquetes que el host envía al controller.
- Eventos: Por lo general paquetes de respuesta a comandos, con los que el controller envía información al host.
Las comunicaciones se inician por parte del host mediante el envío de un comando y este puede tener una o varias respuestas en forma de eventos por parte del controller.
Si se analiza la estructura de los paquetes HCI, estos comienzan con una cabecera de 1 byte en la que se indica de que tipo de paquete se trata:
Tipo HCI
Valor | Significado |
---|---|
0X01 | Comando |
0X04 | Evento |
En el caso de un comando, esta cabecera va seguida por otra cabecera que contiene dos campos, un opcode (2 bytes) y un indicador de longitud (1 byte).
HCI Command Header
Opcode | Longitud |
---|---|
2 bytes | 1 byte |
El opcode identifica el tipo de comando concreto y sirve para indicar que acción quiere realizar el host. Se trata de un valor compuesto que se puede separar en un OGF (Opcode Group Field) de 6 bits y un OCF (Opcode Command Field) de 10 bits. Es decir, el opcode está compuesto por dos partes, una primera que identifica el grupo de comandos al que pertenece y una segunda que indica el comando concreto dentro del grupo.
En el estándar de Bluetooth se reconoce un OGF dedicado a comandos propietarios de fabricante. Este es el OGF 0x3F. Todos los comandos en este grupo son implementados por el fabricante y con un formato arbitrario que puede estar documentado o no.
Mediante la ingeniería inversa de los archivos binarios de la ROM del ESP32 publicados en el SDK del chip se han identificado 29 comandos propietarios no documentados en el ESP32. Estos comandos permiten funcionalidades no contempladas mediante los comandos HCI estándar y ofrecen un mayor control sobre el controlador de Bluetooth. También son interesantes algunos comandos que habilitan el acceso a recursos internos del ESP32, como a la memoria RAM o la memoria flash. Estos últimos tienen implicaciones de seguridad, ya que permiten acceso a recursos que no se contemplan en el modelo de seguridad de las comunicaciones estándar de HCI.
También mediante ingeniería inversa se han obtenido los parámetros, su formato y el formato del evento de retorno de dichos comandos.
Opcode | Command |
---|---|
0xFC01 | Read memory |
0xFC02 | Write memory |
0xFC03 | Delete NVDS parameter |
0xFC05 | Get flash ID |
0xFC06 | Erase flash |
0xFC07 | Write flash |
0xFC08 | Read flash |
0xFC09 | Read NVDS parameter |
0xFC0A | Write NVDS parameter |
0xFC0B | Enable/disable coexistence |
0xFC0E | Send LMP packet |
0xFC10 | Read kernel stats |
0xFC11 | Platform reset |
0xFC12 | Read memory info |
0xFC30 | Register read |
0xFC31 | Register write |
0xFC32 | Set MAC address |
0xFC35 | Set CRC initial value |
0xFC36 | LLCP msgs discard |
0xFC37 | Reset RX count |
0xFC38 | Reset TX count |
0xFC39 | RF register read (Not implemented) |
0xFC3A | RF register write (Not implemented) |
0xFC3B | Set TX password |
0xFC40 | Set LE parameters |
0xFC41 | Write LE default values |
0xFC42 | LLCP pass through enable |
0xFC43 | Send LLCP packet |
0xFC44 | LMP msgs discard |
A continuación, se examinan algunos de los comandos encontrados que simplifican el llevar a cabo ataques Bluetooth contra terceros dispositivos, o que afectan a la seguridad del propio chip cuando existe la capacidad de ejecutar código en el host.
Set Mac Address command
Command | OCF | Command parameters | Return parameters |
---|---|---|---|
HCI_ESP32_CMD_SET_MAC | 0x32 | Bd_Addr | Status |
Description:
Configura la dirección MAC del controlador Bluetooth.
Command Parameters:
Bd_Addr Size: 6 Octets
Value | Parameter description |
---|---|
0xXXXXXXXXXXXX | MAC Address of the device |
Return Parameters:
Status Size: 1 Octet
Value | Parameter description |
---|---|
0x00 | HCI_Esp32_Set_Mac_Address command succeeded. |
0x01 to 0xFF | HCI_Esp32_Set_Mac_Address command failed. See [Vol 1] Part F, Controller Error Codes, for error codes and descriptions |
Este comando permite modificar la dirección MAC con la que se presenta el controlador e interactúa con otros dispositivos.
Esta es una funcionalidad muy interesante ya que, en Bluetooth, los dispositivos se identifican por su dirección MAC o datos derivados de la misma y por lo general los dispositivos Bluetooth no permiten modificar este valor.
Desde el punto de vista de la seguridad, la suplantación de dirección MAC se puede utilizar para realizar ataques como:
- Este mecanismo de cambio de dirección MAC permite invalidar o saturar mecanismos de tracking o identificación de dispositivos en base a su dirección MAC. Esto se realiza mediante la emisión de paquetes de advertising con diferentes MAC de manera continua, inyectando datos “falsos” en estos sistemas y mejorando la privacidad.
- Al suplantar la MAC de un dispositivo e iniciar un proceso de advertising desemboca en que dispositivos que conozcan esa dirección MAC inicien una conexión con nosotros. Esto permite establecer conexiones con dispositivos que pueden no estar anunciándose o que incluso se encuentren en un estado “no conectable”. Este es el comportamiento seguido por muchos dispositivos que no se anuncian de manera continua como hacen la gran mayoría de los teléfonos móviles. Estos permanecen a la escucha esperando paquetes de anuncio de dispositivos periféricos como auriculares a los que conectarse cuando se encienden.
- Contar con la capacidad de suplantar una MAC ya conocida para otro dispositivo habilita la realización de pruebas de reemparejamiento. Un ejemplo de esto es la posibilidad de suplantar la MAC de un teléfono móvil y tratar de iniciar una conexión con auriculares que se encuentren en modo “no emparejable”. Puesto que la MAC ya es conocida, se permite la conexión, pero si se envía un mensaje indicando que la clave de cifrado anterior ya no es válida, si el dispositivo lo soporta se puede renegociar la clave a pesar de no encontrarse los auriculares en un modo “emparejable”.
Esto es posible ya que en el estándar de Bluetooth se contempla la opción de renegociar claves para algunos modos de seguridad, estableciendo una nueva clave e invalidando la ya existente. En dispositivos en los que no existe hardware como botones para verificar esta renovación de claves, en ocasiones se realiza de forma automática sin confirmación de usuario. - La capacidad de suplantar MACs permite la denegación de servicio en algunos dispositivos. Un ejemplo es suplantar la dirección MAC, conectarse a un dispositivo y mantener la conexión abierta de manera indefinida de manera que el usuario no pueda conectarse a ese dispositivo. Esta denegación de servicio puede llegar a ser semi-permanente si además de establecer una conexión el dispositivo permite realizar un proceso de renegociación de claves, estableciendo una nueva e invalidando la clave original. Esto obliga al usuario a realizar el proceso de emparejamiento en el momento de usar el dispositivo.
- Con la capacidad de forzar al usuario a realizar múltiples procesos de emparejamiento se fuerza la realización de procesos sensibles a ataques de fuerza bruta y que permiten la construcción de problemas criptográficos. Más particularmente, el proceso de emparejamiento es de especial importancia porque requiere de la interacción de usuarios para la validación de números de baja complejidad. Esto hace que este proceso sea susceptible a ataques de fuerza bruta o problemas criptográficos que permiten recuperar claves de conexión legítimas.
Read Memory command
Command | OCF | Command parameters | Return parameters |
---|---|---|---|
HCI_ESP32_CMD_READ_MEM | 0x01 | Start_Address Access_Size Length |
Status Length Data |
Description:
Este comando permite la lectura de memoria del controlador Bluetooth. Los parámetros del comando indican la dirección en la que comienza la lectura de datos, el tamaño de las unidades a leer (8, 16 o 32 bits) y el número de unidades a leer.
Los valores posibles del parámetro Access_Size están limitados a los enteros 8, 16 o 32.
Command Parameters:
Start_Address – Size: 4 Octets
Value | Parameter description |
---|---|
0xXXXXXXXX | Memory address from where to start reading |
Access_Size – Size: 1 Octet
Value | Parameter description |
---|---|
0xXX | Valor del tamaño del acceso, los valores posibles son: 8, 16, 32 |
Length: – Size: 1 Octet
Value | Parameter description |
---|---|
0xXX | Number of elements of Access_Size to be read |
Return parameters:
Status – Size: 1 Octet
Value | Parameter description |
---|---|
0x00 | HCI_Esp32_Read_Memory command succeeded. |
0x01 to 0xFF | HCI_Esp32_Read_Memory command failed. See [Vol 1] Part F, Controller Error Codes, for error codes and descriptions |
Length – Size: 1 Octet
Value | Parameter description |
---|---|
0xXX | Number of elements returned |
Data – Length x Access_Size Octets
Value | Parameter description |
---|---|
0xXX | Read data |
Desde el punto de vista de la seguridad, este comando es muy interesante para obtener cualquier valor de memoria del dispositivo con los siguientes casos de uso:
- Exfiltración de secretos de la memoria del ESP32. Esto incluye secretos o claves usadas durante la operación habitual del chip como pueden ser claves de redes WiFi en caso de que el chip también opere como tarjeta WiFi, claves de cifrado de otros servicios y también permite extraer claves de emparejamiento del controller de Bluetooth presentes en el dispositivo y aquellas a las que el host no tenga acceso por motivos de privilegios y permisos.
Teniendo acceso sin privilegios a un dispositivo no es posible recuperar las claves de emparejamiento porque estas se almacenan en un lugar seguro. Con la capacidad de enviar paquetes HCI al controller Bluetooth y enviando un comando de conexión a un dispositivo concreto, se logra que el controller inicie la conexión, solicitando al kernel del host la clave de emparejamiento para ser usada desde el controller. Durante este proceso se extrae la clave del controller mediante la lectura de la memoria. - Mecanismo de depuración. Permite identificar regiones de memoria libres, así como ser usado mecanismo de depuración.
Write Memory command
Command | OCF | Command parameters | Return parameters |
---|---|---|---|
HCI_ESP32_CMD_WRITE_MEM | 0x02 | Start_Address Access_Size Length Data |
Status |
Description:
Este comando permite la escritura de datos en direcciones de memoria arbitrarias del ESP32. El parámetro Start_Address indica la dirección donde se inicia la escritura. El parámetro Access_Size indica el tamaño en bits de cada unidad a escribir y toma el valor 8, 16 o 32. El parámetro Length indica el número total de unidades a escribir y Data es un buffer con el contenido de datos a escribir, siendo su longitud Access_Size x Length.
Command Parameters:
Start_Address – Size: 4 Octets
Value | Parameter description |
---|---|
0xXXXXXXXX | Dirección de memoria desde la que leer |
Access_Size – Size: 1 Octet
Value | Parameter description |
---|---|
0xXX | Valor del tamaño del acceso, los valores posibles son: 8, 16, 32 |
Length – Size: 1 Octet
Value | Parameter description |
---|---|
0xXX | Length of the Data field |
Data – Length x Access_Size Octets
Value | Parameter description |
---|---|
0xXX | Read Data |
Return parameters:
Status – Size: 1 Octet
Value | Parameter description |
---|---|
0x00 | HCI_Esp32_Read_Memory command succeeded. |
0x01 to 0xFF | HCI_Esp32_Write_Memory command failed. See [Vol 1] Part F, Controller Error Codes, for error codes and descriptions |
Desde el punto de vista de la seguridad, este comando es el que tiene mayor interés entre los mencionados.

Se han identificado los siguientes puntos relevantes desde el punto de vista de la seguridad haciendo uso de este comando HCI local:
- Ejecución de código en el ESP32. La manera más sencilla es sobrescribir parte de una función, por ejemplo, el callback “r_hci_cmd_received” del firmware, ejecutado durante el proceso de recepción de comandos HCI en el ESP32. Una vez sobrescrito el callback, al enviar el comando HCI se ejecuta el código inyectado en la memoria del ESP32 tomando el control de este.
- Bypass del “Secure Boot”, la protección contra la ejecución de código no firmado de Espressif. Dado que la escritura se realiza directamente sobre la memoria RAM del ESP32, y la escritura ocurre después de haber pasado las validaciones de los mecanismos de protección del firmware, estos controles no tienen efecto sobre las modificaciones realizadas una vez el firmware es cargado en la memoria RAM.
- Lectura de memoria flash cifrada. Con la capacidad de ejecución de código en el dispositivo, se pueden realizar lecturas de la memoria flash mediante las llamadas a las funciones internas del API de memoria flash esp_flash_read() para leer los contenidos de la flash en formato cifrado o esp_flash_read_encrypted() para leer los contenidos ya descifrados. Estas funciones en combinación con la capacidad de generar eventos HCI mediante código, permiten volcar el contenido completo del firmware descifrado, logrando un bypass de los mecanismos de cifrado de la memoria flash de Espressif y permitiendo la ingeniería inversa de los contenidos del firmware.
- Bypass de autenticación mediante inyección de claves. Mediante la primitiva de escritura en el ESP32 se modifica la función “r_llc_ltk_req_send” del firmware, sustituyéndola por una que verifique en la conexión entrante la dirección MAC de la que proviene la conexión. Esto permite que para una MAC concreta, en lugar de generar el evento que consulta al host por una clave de emparejamiento, esta misma función genera los eventos correspondientes al comando HCI LE Long Term Key Request Reply mediante una sola llamada a la función “r_hci_le_ltk_req_reply_cmd_handler”. De esta manera, el ESP32, ante una conexión entrante de una MAC determinada, utiliza una clave de cifrado prefijada que permite la conexión cifrada de un dispositivo sin alertar al host. De esta manera, podremos conectarnos al dispositivo modificado en cualquier momento de manera autenticada y cifrada sin necesidad de emparejamiento o interacción por parte del usuario.
- Instalación de una puerta trasera. La recepción de los paquetes Bluetooth LE se realiza en la función “llc_llcp_recv_handler” del firmware. Esta función verifica si los paquetes que han llegado del módulo RF son válidos y realiza las llamadas a las funciones correspondientes para su procesamiento. La modificación de esta función habilita el procesamiento en funciones programadas por nosotros de paquetes no estándar que en su versión original serían descartados como inválidos.
Esto permite el envío y la ejecución de código de manera remota en el ESP32 sin autenticación, lo que brinda el control absoluto del chip sin necesidad de tener un acceso físico mientras no se reinicie el dispositivo. A partir de este momento se pueden enviar remotamente nuevas instrucciones al chip para la captura o retransmisión de nuevas claves Bluetooth, WiFi u otros secretos intercambiados en la comunicación. - Instalación de puerta trasera persistente. Mediante la ejecución de código mencionada en los puntos anteriores llamando a las funciones internas de escritura de la memoria flash (esp_flash_write()) para almacenar código en el firmware se consigue permanencia ante reinicios del dispositivo.
La vía más sencilla para esto es la modificación de cualquier función que se ejecute durante el arranque del dispositivo, como por ejemplo “app_main”.
Este caso permitiría consolidar el control del chip sin necesidad de acceso físico, permitiendo la exfiltración de información de manera permanente o permitiendo el despliegue de nuevas modificaciones para implementar ataques que permitan la extracción de información de otros dispositivos.
Esta persistencia es posible cuando el ESP32 no implementa SecureBoot o utiliza la versión 1. En el caso de la versión 2 de SecureBoot, debido a las mejoras implementas por Espressif, es necesaria una clave criptográfica privada del desarrollador del firmware que no está presente en la memoria del chip.
En conclusión
Los comandos HCI propietarios encontrados en el ESP32 ofrecen un gran potencial no solo para la implementación de ataques Bluetooth sino desde el punto de vista de la ciberseguridad del propio chip.
Desde la versatilidad de realizar ataques de suplantación de identidad mediante el comando de modificación de la MAC del dispositivo hasta la implementación de ataques avanzados mediante la modificación del comportamiento del controlador.
También es posible, mediante el comando de escritura en memoria, la evasión de los mecanismos de protección contra ejecución de código no firmado y de los mecanismos de cifrado de flash para evitar la extracción de secretos del chip.
Este mismo comando podría permitir la instalación de puertas traseras mediante la inserción de claves en el dispositivo para permitir la conexión de dispositivos específicos o la modificación del código para permitir la ejecución de código remoto mediante comandos vía aire.
Espressif ha informado en su blog que las funciones reportadas por Tarlogic Security no suponen un problema de seguridad explotable remotamente o a través de comandos y señales Bluetooth inalámbricas. También ha notificado su interés en deshabilitar estos comandos de depuración no documentados en nuevos dispositivos de producción con el objetivo de mejorar la seguridad del chip.
Update
10/03/25: Ver artículo Hacking Bluetooth fácil con ESP32, HCI y comandos ocultos