Investigación de ANZEN: cómo comprometimos Keystone mediante una vulnerabilidad USB

Puntos clave
• La falta de validación de la longitud controlada por el host en el USB SDK provoca lecturas y escrituras fuera de límites en un búfer fijo
• Un atacante puede secuestrar la tabla de callbacks de USB class para convertir una escritura fuera de límites en ejecución arbitraria de código en la MCU
• Una configuración incorrecta de la MPU permite que la shellcode en la SRAM se ejecute directamente
• Tras obtener el Passcode, la OTP y el material criptográfico del chip seguro, es posible reconstruir la clave AES y extraer la frase mnemónica BIP39
• Los SDK de terceros también forman parte de la frontera de seguridad crítica de una hardware wallet y deben someterse a auditorías exhaustivas
Actualización de seguridad (julio de 2026): El problema tratado en este artículo fue descubierto por OneKey Anzen en marzo de 2026 durante una auditoría de seguridad conjunta con Keystone, y afecta al firmware de Keystone 3 Pro que integra el USB SDK del proveedor de MCU. Keystone publicó el firmware v2.4.0 el 1 de abril de 2026 para solucionar por completo el problema; hasta el momento de la publicación del anuncio conjunto, ninguna de las partes ha encontrado pruebas de que la vulnerabilidad se haya utilizado para atacar a usuarios de Keystone. Una explotación exitosa requiere que el atacante tenga la posesión física del dispositivo, conozca el PIN correcto, desbloquee el dispositivo, obtenga la aprobación del usuario para la conexión USB y conecte el dispositivo a un ordenador controlado por el atacante. Los usuarios que ejecuten un firmware inferior a v2.4.0 deben actualizar de inmediato a la versión más reciente; el uso de la firma QR aislada permite evitar esta ruta de ataque USB específica, pero aun así se recomienda completar la actualización. Para más información, consulte la Actualización de seguridad conjunta de Keystone y OneKey Anzen.
A principios de agosto se celebró en Las Vegas la conferencia Black Hat USA 2026. Debido a cuestiones de visado, nuestra presentación pasó a ser en formato on-demand. Esta charla surgió a raíz de una auditoría que realizamos a la billetera Keystone, donde descubrimos un grave problema en el USB SDK del proveedor del chip, lo que nos permitió finalmente extraer la frase mnemónica.
De hecho, no es la primera vez que surgen problemas en la pila de protocolos USB. En la última década, numerosos ataques graves contra diversos dispositivos han estado relacionados con las pilas USB: desde las vulnerabilidades en controladores USB y la redirección remota en 2014, pasando por Fusée Gelée, checkm8 y Kamakiri, hasta la publicación de usbliter8 en 2026.
Vulnerabilidades frecuentes en el protocolo USB
2014: ¿Realmente requieren acceso físico las vulnerabilidades USB?
En 2014, NCC Group presentó en Black Hat Asia el trabajo «USB Attacks Need Physical Access Right? Not Any More...».
En aquel entonces, los fallos USB solían clasificarse como «ataques físicos locales»: el atacante debía conectar un dispositivo malicioso al equipo objetivo para explotar vulnerabilidades en el controlador mediante descriptores malformados o datos específicos de clase (class-specific data).
En dicha investigación, los autores utilizaron RemoteFX USB Redirection para redirigir dispositivos USB de bajo nivel a un Windows Server remoto. El dispositivo USB malicioso estaba conectado físicamente al cliente del propio atacante, pero el controlador que procesaba los datos USB maliciosos se ejecutaba en el servidor remoto.
2018, Fusée Gelée: Confundir la longitud de la solicitud con la del objeto
Fusée Gelée, hecho público en 2018, es uno de los casos más clásicos de vulnerabilidades en BootROM USB. El fallo se encontraba en el Recovery Mode (RCM) de NVIDIA Tegra. El dispositivo inicializaba la pila USB en un entorno de BootROM sumamente temprano a la espera de que el host enviara una imagen de recuperación. El punto vulnerable residía en una solicitud de control GET_STATUS aparentemente ordinaria.
De acuerdo con la semántica de USB, un objeto status ocupa solo 2 bytes. El host indica mediante wLength la cantidad máxima de datos que está dispuesto a recibir, y el dispositivo debería devolver min(longitud solicitada por el host, longitud real de datos disponibles).
Sin embargo, al procesar el destinatario del endpoint, la BootROM ejecutaba erróneamente una lógica similar a la siguiente:
status = get_usb_endpoint_status(index);
/* Error: se utiliza la longitud solicitada por el host */
size_to_tx = setup_packet.length;
memcpy(dma_buffer, &status, size_to_tx);
Es decir, no se empleaba la longitud real del objeto de 2 bytes; en su lugar, el valor wLength controlado por el atacante se convertía directamente en la longitud del memcpy, pudiendo alcanzar teóricamente los 65.535 bytes.
Por si fuera poco, antes de verificar la firma, RCM ya permitía al atacante situar un payload de gran tamaño en memoria; dicha área de payload se encontraba justo cerca de la pila activa de la BootROM. Al seleccionar el DMA buffer y diseñar la disposición de la memoria, la copia fuera de límites permitía sobrescribir la pila de ejecución con datos controlados por el atacante.
En aquella época, el entorno de la BootROM carecía de stack canary, ASLR o mecanismos eficaces de protección de ejecución en memoria. El resultado final fue que, antes de que se aplicara cualquier bloqueo de seguridad o reducción de privilegios, el atacante ya había obtenido la ejecución arbitraria de código en el contexto de la BootROM.
Esto guarda una similitud muy directa con la vulnerabilidad de hardware wallet que abordamos más adelante.
2019, checkm8: El atacante no solo controla los datos, sino también lo «incompleto»
checkm8, divulgado en 2019, es uno de los fallos de mayor impacto en la SecureROM de Apple. Afectó a múltiples generaciones de dispositivos iOS, permitiendo a los atacantes obtener ejecución de código en el contexto de la BootROM en modo DFU, lo que a su vez facilitaba el volcado de la SecureROM, el descifrado del keybag del firmware o el cambio del dispositivo a un estado de degradación (demotion) que habilitaba JTAG. Dado que el fallo reside en la BootROM integrada durante la fabricación del chip, los dispositivos ya distribuidos no pueden parchear esta vulnerabilidad mediante actualizaciones de iOS.
A diferencia de Fusée Gelée, checkm8 no se debe a una simple omisión en la validación de longitud. Su núcleo es un use-after-free en la máquina de estados USB de DFU de Apple: una fase de datos USB no concluía de forma regular, por lo que la variable global que almacenaba el estado de la fase de datos no se limpiaba; sin embargo, el DFU buffer subyacente sí se liberaba al salir de la pila USB.
En condiciones normales, cuando la BootROM recibe una solicitud DFU_DNLOAD con fase de DATA, guarda el buffer de recepción y el wLength. Al completarse la recepción de datos, el callback de finalización se encarga de limpiar estos estados; al salir de DFU, se libera el buffer de E/S correspondiente.
DFU_DNLOAD(setup)
{
ep0_buffer = dfu_io_buffer;
ep0_remaining = setup.wLength;
}
DATA_complete()
{
ep0_buffer = NULL;
ep0_remaining = 0;
}
DFU_exit()
{
free(dfu_io_buffer);
}
checkm8 se aprovecha precisamente de la ventana entre estos pasos. El atacante inicia primero una solicitud con fase de DATA para que la BootROM registre el puntero del buffer y la longitud restante, y luego omite deliberadamente el envío completo de los datos. Al cancelar la transferencia o forzar un timeout, el callback de DATA_complete no se ejecuta; a continuación, se fuerza un cambio de estado en DFU o un USB reset, haciendo que la pila USB termine y libere el buffer original.
En este punto, la memoria se ha liberado, pero el puntero antiguo sigue presente en el estado de recepción:
free(dfu_io_buffer);
ep0_buffer = old_dfu_io_buffer; // Puntero colgante
El siguiente paso requiere moldear el heap. El exploit público provoca fugas de varios objetos de solicitud USB para alterar las asignaciones posteriores, logrando que el nuevo DFU buffer se aloje en otra parte mientras la dirección antigua es reocupada por objetos como usb_device_io_request.
Al enviar datos USB nuevamente, la BootROM continuará escribiendo a través del ep0_buffer residual, sobrescribiendo en realidad el nuevo objeto. El exploit modifica campos como callback y next para que la dirección del callback apunte al payload previamente posicionado. La próxima vez que se complete una solicitud USB, la SecureROM saltará al código del atacante.
2020, Kamakiri: Cómo una solicitud USB se convierte en un salto indirecto
Kamakiri se dirigió contra el modo de descarga en la BootROM de una serie de SoC de MediaTek. Permitía a los atacantes eludir la ruta de autenticación que normalmente restringe a los Download Agent no autorizados, ejecutando un payload de primera etapa sin firmar en el contexto de la BootROM. Con esta capacidad de ejecución, los payloads posteriores podían manipular la memoria flash, volcar la memoria, modificar el estado de arranque o recuperar dispositivos que previamente no podían iniciar con normalidad.
La cadena de explotación de Kamakiri es muy corta, ya que la propia BootROM de MediaTek ofrece la primera capacidad requerida: cargar datos en una región conocida de SRAM mediante el protocolo de descarga. El atacante deposita el payload de primera etapa y luego activa la vulnerabilidad a través de una USB control request especial.
La vulnerabilidad se encuentra precisamente en el despacho de las USB control requests. El análisis de ingeniería inversa independiente resume la lógica en una estructura similar a la siguiente:
handler = handler_array[value * 13];
handler();
El problema crítico radica en que el valor de selección proveniente de la solicitud USB se utiliza para indexar la tabla de punteros a funciones sin una verificación de límites adecuada. Con solo encontrar un valor de control adecuado, el atacante puede hacer que la entrada de la tabla leída coincida con la dirección del payload cargado previamente en la SRAM, transformando una simple solicitud USB en una llamada indirecta al payload. Como la distribución de la BootROM varía entre diferentes SoC, el índice específico y la dirección del payload pueden requerir adaptación o enumeración.
La PoC pública envía la siguiente transferencia de control tras cargar la fase 1:
ctrl_transfer(
0xA1, # class request, device-to-host, interface recipient
0,
0,
10, # wIndex
0
)
2026, usbliter8: El controlador USB desplaza el puntero de escritura DMA antes del buffer
Publicado en junio de 2026, usbliter8 volvió a extender la explotación de BootROM no parcheable por software a los SoC A12 y A13, así como a los S4 y S5 de Apple.
Una vez explotado con éxito, el atacante puede ejecutar código en el contexto EL1 de la SecureROM, modificar el comportamiento de DFU, degradar temporalmente el production mode del chip o eludir las verificaciones de firma para arrancar un iBoot original sin firmar. Esto supone la ruptura de la cadena de confianza en el arranque del procesador de aplicaciones, un fallo que no puede corregirse mediante futuras actualizaciones de iOS.
usbliter8 difiere de los casos anteriores, pues aprovecha principalmente la semántica del puntero DMA cuando el controlador USB Synopsys DWC2 procesa las SETUP transactions.
Según la especificación USB, la sección de datos de una SETUP transaction está fijada en 8 bytes. El controlador DWC2 permite recibir de forma consecutiva tres paquetes SETUP y escribirlos secuencialmente en memoria mediante DMA. Tres paquetes SETUP normales ocupan un total de 24 bytes.
Cuando llega una cuarta SETUP transaction, el controlador resta de forma fija 24 bytes al puntero de escritura DOEPDMA, retrocediendo la dirección al inicio de dicho conjunto de buffers SETUP. Para paquetes SETUP normales de 8 bytes, esta lógica cuadra a la perfección.
Sin embargo, el controlador también acepta paquetes de datos SETUP anómalos de longitud inferior a 8 bytes y hace avanzar el puntero DMA según la longitud real escrita, con una granularidad de escritura interna de 4 bytes. Si el atacante envía reiteradamente paquetes cortos de 4 bytes, los primeros tres paquetes solo desplazarán la dirección 12 bytes en total hacia adelante; cuando llegue el cuarto paquete, el controlador restará igualmente los 24 bytes fijos. En consecuencia, la siguiente ronda de datos SETUP no se escribirá en el buffer original, sino 12 bytes antes de su inicio. Al repetir este proceso de forma continua, la posición de escritura DMA retrocede hacia direcciones más bajas a un ritmo predecible, generando un buffer underflow estable.
Por lo general, un DMA fuera de límites en un controlador USB no garantiza la corrupción de memoria arbitraria del sistema. Los SoC modernos suelen utilizar una IOMMU para limitar el rango de direcciones accesibles por el controlador USB. Sin embargo, en el entorno SecureROM de los chips A12 y A13, el DART USB se encuentra en modo bypass, dejando al DMA del controlador sin un aislamiento de direcciones efectivo. Esto permite que el underflow no quede confinado a un reducido SETUP buffer, sino que alcance y sobrescriba otros objetos en la SRAM.
En el A12, el USB DMA buffer se encuentra contiguo a la pila de la tarea USB, por lo que el exploit puede sobrescribir el link register guardado y tomar el control del contador de programa durante el cambio de tarea. El A13 incorporó Pointer Authentication, lo que dificulta la sobrescritura directa de la dirección de retorno; por ello, el exploit público adoptó una cadena de corrupción de memoria más compleja, modificando finalmente el puntero al manejador de interrupciones USB en la sección BSS para saltar al código del atacante en la siguiente interrupción USB. Ambas vías logran finalmente la ejecución en SecureROM EL1.
De lecturas y escrituras fuera de límites por USB a la ejecución arbitraria
La causa raíz de la vulnerabilidad presentada en esta edición de Black Hat USA reside también en el USB SDK.
Las solicitudes de line-coding en CDC requieren únicamente una estructura fija y reducida. No obstante, el USB SDK integrado preparó para ello un CmdBuff fijo de 8 bytes y pasó directamente el req->wLength suministrado por el host a las funciones subyacentes de transmisión o recepción. El código no realizaba comprobaciones de longitud ni exigía que coincidiera con la longitud fija estipulada por el protocolo. Como resultado, se produjeron lecturas y escrituras fuera de límites.
Código de la vulnerabilidad de lectura y escritura fuera de límites en el USB SDK
Disponer de primitivas de lectura y escritura fuera de límites es solo el primer paso: proporciona únicamente una primitiva de corrupción de memoria, pero el objetivo final era lograr la ejecución arbitraria de código.
Dado que el fallo se produce en la ruta de USB CDC, examiné la pila de protocolos USB siguiendo el flujo de despacho de la solicitud actual. Al analizar g_usbDev, me percaté de la presencia de dev.class_cb. No se trataba de un puntero de datos convencional, sino de la tabla de callbacks en tiempo de ejecución del USB class driver; el USB core utiliza esta tabla al procesar solicitudes para invocar funciones como Setup, DataIn y DataOut. Bastaba con poder sobrescribir class_cb para que una posterior solicitud USB estándar pasara por el puntero a función bajo nuestro control, convirtiéndolo en un objetivo de secuestro ideal.
El exploit completo se divide en dos solicitudes USB.
La primera solicitud utiliza SET_LINE_CODING para desencadenar la escritura fuera de límites. En esta escritura, debo lograr tres objetivos simultáneamente: situar la shellcode en CmdBuff, disponer una tabla de callbacks falsa en la SRAM cercana y, por último, cambiar class_cb a la dirección de esta tabla falsa.
Posteriormente, envío una solicitud GET_LINE_CODING estándar. El USB core procesará la solicitud como de costumbre e invocará el callback Setup del class driver mediante class_cb->Setup(...). La diferencia radica en que, en este punto, class_cb ya no apunta a la tabla original, sino a la tabla falsa que hemos construido; a su vez, la función Setup falsa apunta a la shellcode ubicada en CmdBuff. De este modo, la segunda solicitud USB dirige el PC directamente hacia el código que escribimos en la SRAM.
Dado que la shellcode reside en la SRAM, inicialmente existía la preocupación sobre las protecciones de no-ejecución en zonas de datos. Sin embargo, el firmware empleaba un formato de tamaño erróneo al configurar la MPU, lo que derivó en un rango de protección efectivo de 0; la región de la MPU destinada a restringir la ejecución en la BSS no tenía efecto real. Por tanto, el código alojado en CmdBuff pudo ejecutarse de forma directa.
Secuestro de la tabla de callbacks USB para ejecutar shellcode
Una vez confirmada la viabilidad teórica, pasé a la práctica. Primero evalué el alcance real de las lecturas y escrituras fuera de límites. Diseñé transferencias de control de 64 bytes: la lectura fuera de límites permitía filtrar de forma continua unos 24 KB de SRAM, pero la escritura provocaba un bloqueo sistemático al alcanzar la posición de 4.352 bytes.
Al examinar el motivo, comprobé que en esa posición los datos sobrescribían punteros en uso en CmdBuff + 0x1130, lo que provocaba que EP0 perdiera de inmediato la capacidad de recibir datos subsiguientes.
Ubicación donde la escritura fuera de límites provoca el bloqueo del dispositivo
La solución fue relativamente sencilla: gracias a la capacidad de lectura fuera de límites, leímos previamente el estado real de la memoria e incorporamos los datos volcados en la shellcode. Así se completó la lógica de ejecución arbitraria de código.
Aunque ya podíamos sobrescribir class_cb, era crucial no inutilizar el canal USB durante el secuestro. La escritura larga posiciona primero la shellcode y la tabla falsa en la SRAM, haciendo que class_cb apunte temporalmente a la tabla falsa; a continuación, al enviar GET_LINE_CODING, el USB core salta a la shellcode a través del Setup falso. Una vez completada la tarea, la shellcode escribe el resultado tras el CmdBuff, restaura el class_cb original e invoca el Setup legítimo para que la transferencia EP0 actual finalice con normalidad. De esta forma, cada secuestro representa solo un breve desvío (detour), manteniendo el USB operativo para inyectar tareas posteriores.
El firmware intentaba además marcar la BSS como no ejecutable mediante la MPU, pero el código de configuración utilizó un formato de tamaño incorrecto, dejando el rango de protección calculado en cero.
De la ejecución arbitraria en la MCU a la extracción de la frase mnemónica
Lograr la ejecución arbitraria en la MCU no implica automáticamente poder extraer la frase mnemónica. La arquitectura específica de la hardware wallet se ilustra a continuación: en síntesis, la información cifrada de la frase mnemónica se almacena en el SE DS28S60, mientras que en la clave AES utilizada para descifrar dicha información participan la OTP y dos chips SE (ATECC608 y DS28S60).
Arquitectura de seguridad de la hardware wallet
El proceso de generación de esta clave AES se detalla a continuación:
Flujo de generación de la clave AES
Las credenciales que interactúan con los SE y generan la clave AES se derivan principalmente del Passcode (contraseña de desbloqueo) y de la OTP, siguiendo este flujo:
Flujo de generación de credenciales con Passcode y OTP
El Passcode puede obtenerse mediante fuerza bruta tras extraer el hash de la contraseña; por tanto, solo resta conseguir el salt de la OTP para completar la derivación de la clave AES. Para la extracción de la OTP, dado que la shellcode se ejecuta en el mismo nivel de privilegios que el firmware ordinario, es posible escribir en los registros de la MPU mediante la vulnerabilidad USB para desactivarla y leer la OTP.
El método de explotación definitivo se resume en el siguiente esquema: una vez obtenida la clave AES, se descifra la entropía BIP39 para reconstruir la frase mnemónica.
Cadena completa de ataque desde la vulnerabilidad USB hasta la extracción de la frase mnemónica
Conclusión
Durante esta investigación, se recurrió ampliamente a la IA para el análisis de vulnerabilidades y el desarrollo de exploits. La integración de la IA mejora enormemente la eficiencia de los investigadores de seguridad y está redefiniendo el coste asociado a este tipo de análisis. Anteriormente, descubrir un fallo y convertirlo en un exploit estable demandaba una inversión sustancial de tiempo manual; hoy en día, la comprensión de código, el emparejamiento de estructuras, la generación de scripts y el diagnóstico de fallos pueden acelerarse notablemente.
Por último, tanto los USB SDK como otros componentes de terceros representan áreas que con frecuencia se descuidan en las auditorías de seguridad de productos. Y, con frecuencia, es precisamente este eslabón más débil el que compromete por completo la seguridad de todo el dispositivo.






