ARCHIVO / 01 · CUADERNO DE CAMPO

Vulnerando la Badge de DEF CON 34

15 horas de hiperfoco

Una crónica técnica de quince horas investigando la badge de DEF CON 34 — hardware, hipótesis propias e IA agéntica como multiplicador, bajo dirección y responsabilidad humanas.

Volver, mirar y decidir

Volví a DEF CON después de más de diez años. JMA Integra hizo posible el viaje y yo llegué con la intención razonable de asistir a charlas, reencontrarme con la comunidad y aprender. Esa intención duró hasta que tuve la badge en las manos. No parecía un simple recuerdo: era un sistema completo, expresivo y deliberadamente diseñado para invitar a desmontar sus supuestos.

Normalmente no escribo así: mis informes suelen ser serios, corporativos y bastante menos personales. En esta ocasión quería conservar el contexto humano, pero sin permitir que se comiera el análisis técnico.

La charla de Clifford Stoll terminó de fijar el tono. Stoll conserva una forma contagiosa de convertir la curiosidad en trabajo serio sin quitarle alegría. Salí recordando por qué me había interesado en seguridad en primer lugar: no por acumular respuestas, sino por perseguir una pregunta hasta que el sistema ya no pudiera esconder cómo funcionaba.

Estoy junto a Clifford Stoll durante DEF CON 34
Con Clifford Stoll en DEF CON 34, justo antes de empezar el sprint técnico.

Mi reconocimiento comenzó con el protocolo visible. La badge intercambiaba códigos QR autenticados, así que intenté capturarlos, comparar estados y entender qué partes del flujo estaban realmente protegidas. La pantalla introdujo enseguida una dificultad mundana: su flicker deformaba cuadros completos y una fotografía podía parecer evidencia de un dato distinto cuando solo era una exposición diferente.

Código QR de la badge deformado por el flicker de la pantalla
El flicker convirtió mi captura del protocolo QR en el primer problema práctico.

Antes de tocar nada asumí lo que había en juego. La badge guardaba secretos que solo existían dentro de ella, y una escritura mal calculada podía bloquearlos o borrarlos de forma irreversible. No había un botón de deshacer ni una segunda oportunidad garantizada: perder un secreto por precipitación habría terminado la investigación sin remedio. Por eso me impuse desde el principio una disciplina estricta: cambios mínimos, una hipótesis por experimento, controles conservados y restauración verificada del dispositivo entre etapas. Las quince horas del título van de las 18:18 a las 09:18: portátil, badge conectada y la decisión de no aceptar una coincidencia como prueba.

Mesa completa de la habitación con el portátil y la badge conectada
Mi laboratorio improvisado a las 18:18: portátil, badge y una noche por delante.

El primer obstáculo: entrar en modo de carga

Antes de cualquier idea elegante, tuve que resolver algo puramente físico: cómo poner la badge en el modo que permite cargar código. No estaba claro cómo forzar ese estado, y apagarla por software sencillamente no funcionaba: el aparato no quedaba en la condición que yo necesitaba para actualizar.

La secuencia que acabó funcionando fue manual y poco intuitiva: quitar las dos pilas, pulsar un botón para descargar los condensadores y dejar el circuito realmente a cero, colocar una pila, mantener pulsado un botón, y solo entonces insertar la segunda pila. Esa coreografía —y no el apagado por software— era la que dejaba la badge lista para enumerar y aceptar una carga.

Parece un detalle menor y no lo es. Sin ese estado no había siquiera un punto de partida, y cada intento fallido consumía tiempo de una ventana que no era infinita. Resolver el modo de carga fue la condición previa a todo lo demás.

La badge de DEF CON 34 con las pilas retiradas y los portapilas abiertos
Pilas fuera y condensadores descargados: la coreografía física que dejaba la badge lista para aceptar código.

Superficie de ataque: autenticación no significa cobertura total

Partí del recorrido que ya había observado en los QR. Los intercambios reales estaban autenticados y no tenía sentido asumir que la criptografía iba a ceder por insistencia. El objetivo inicial fue más modesto: dibujar la superficie de ataque, separar datos, transporte y control de flujo, y preguntar qué verificaba exactamente el loader antes de ejecutar una actualización.

La imagen del loader contenía comprobaciones de firma, hash y datos autenticados adicionales. Eso protegía una región amplia y hacía inútil una modificación arbitraria. Sin embargo, la primera instrucción era un JAL, y la cobertura dejaba fuera una ventana muy estrecha correspondiente a su inmediato. No había un gran espacio sin autenticar; había unos pocos bytes con influencia directa sobre el destino del salto. Conservando el opcode y alterando solo esa parte del inmediato, podía redirigir la ejecución hacia una etapa añadida en espacio disponible de la partición.

Eureka 1 — La frontera útil estaba en el control de flujo. La autenticación protegía el contenido esperado, pero su cobertura dejaba una ventana mínima en el primer JAL. La pregunta correcta no era cómo falsificar una firma, sino qué decisión de ejecución podía cambiar sin tocar lo autenticado.

Antes de llevarlo al hardware comparé el paquete original y el modificado. El diff debía mostrar únicamente el cambio previsto en el JAL y la etapa nueva en su ubicación separada. Luego comprobé que el loader aceptaba el paquete y transfería control. Incluso aquí aparecieron problemas que no tenían glamour: un cable USB sin datos y el momento exacto de liberar el botón de Update. Resolverlos fue parte de convertir una observación estática en un procedimiento físico repetible.

K0: dejar que la badge lea y que el host piense

El primer payload para buscar K0 intentó hacer demasiado. Un scanner BIO complejo atrapó de forma limpia; eso demostraba que había ejecutado, pero no que fuera una base fiable. Reduje entonces la tarea hasta su unidad mínima. BIO, el pequeño motor programable de entrada/salida, no tenía que reconocer claves ni ejecutar criptografía: solo debía leer SRAM de manera predecible y entregar los datos.

El lector final ocupaba 160 bytes de programa. Ese tamaño no era el volumen adquirido: cada operación devolvía bloques de 1 KiB. La primitiva importante era una ventana BDMA que el arranque había configurado y que persistía, permitiendo a BIO alcanzar SRAM a través del motor de transferencia entre dominios. Reutilicé esa ventana en vez de añadir complejidad dentro de la badge.

Trasladé al host todo lo que el host hacía mejor. El colector recibía el flujo serie, delimitaba cada bloque con framing explícito, comprobaba secuencias y errores, guardaba el resultado de forma reanudable y repetía solo las operaciones seguras. También tuve que corregir supuestos operativos: una FIFO que parecía independiente era compartida, algunos errores llegaban de manera asíncrona y el buffer serie predeterminado no soportaba el caudal. La robustez no vino de un payload más listo, sino de una separación más clara entre adquisición y análisis.

La captura fue parcial y dispersa. El archivo representaba una ventana lógica mayor que los bloques que realmente habían llegado; no lo presenté como un volcado completo. Eso no invalidaba lo adquirido, pero obligaba a registrar qué fragmentos existían y a evitar inferencias sobre los huecos. El host buscó candidatos solo en bloques materializados y conservó la procedencia de cada coincidencia.

Una secuencia de 32 bytes apareció como candidata plausible. Plausible no era suficiente. Una cadena puede tener la longitud correcta, encontrarse cerca de estructuras esperadas e incluso coincidir con una huella conocida por accidente. Primero comprobé de forma independiente su SHA-256 contra la referencia permitida. Después la utilicé con dos transcripciones reales y distintas del protocolo de la badge. En ambas, AES-256-GCM-SIV produjo un descifrado autenticado y el tag fue válido.

Esas dos verificaciones eran independientes entre sí: no repetían el mismo paquete ni dependían de una fotografía. Solo en ese punto marqué el resultado como demostrado. La badge había realizado una lectura mínima; el host había buscado, reconstruido y validado.

Eureka 2 — El dispositivo no necesitaba saber qué buscábamos. Un lector BIO de 160 bytes, bloques de 1 KiB y la ventana BDMA persistente bastaban. Al mover búsqueda, reintentos y validación al host, cada fallo tenía una interpretación más limpia y dos tags reales podían actuar como prueba independiente.

No publico la clave, las transcripciones ni el volcado. Para explicar el razonamiento basta la cadena verificable: cobertura estrecha, salto mínimo, lector reducido, adquisición parcial declarada y dos autenticaciones correctas.

Mensaje a un colega anunciando la recuperación de la primera clave
El instante en que K0 quedó verificada, contado en caliente a un colega.

Flag 1: cuando más privilegio produce menos acceso

K0 no contenía Flag 1. La segunda pieza estaba asociada al slot 260 de Fw0, bajo ASID3, y el problema ya no era transportar SRAM sino ejecutar la lectura desde el contexto de acceso correcto. Mi primera hipótesis fue convencional: si una región estaba protegida, S-mode ofrecería más capacidad que U-mode.

Construí un lector acotado y lo ejecuté físicamente en S-mode. Obtuve exactamente 32 ceros y un CRC reproducible. El resultado era negativo, pero no ambiguo. Ruido, una lectura incompleta o una dirección inestable habrían producido variación; el mismo tamaño, el mismo contenido y el mismo CRC indicaban una denegación determinista. Tampoco demostraban que la región estuviera vacía. Demostraban que esa combinación de modo y dominio no revelaba el contenido.

Volví al RTL. CONTROL_INVERT_PRIV y la señal interna vex_mm mostraban que el privilegio efectivo para esa comprobación no seguía la intuición de «más alto abre más». Con la inversión activa, S-mode y M-mode llevaban al camino que devolvía cero; U-mode bajo ASID3 permitía formular el experimento correcto.

Preparé un mapa Sv32 mínimo para poder auditarlo entero. La página del stub era ejecutable y legible en U-mode; la página objetivo, solo legible en U-mode; el scratch, legible y escribible; el stack de retorno quedaba fuera de U-mode. El código copiaba la región byte a byte con una pareja simple de carga y almacenamiento y regresaba mediante la excepción prevista. No incluía un scanner, una búsqueda ni escrituras sobre el objetivo.

La ejecución física en U-mode produjo el resultado esperado de 32 bytes. Validé el digest autorizado y el CRC, repetí el recorrido y restauré el loader oficial. La evidencia no era una simulación favorable: era la diferencia reproducible entre el control S-mode a cero y una lectura U-mode verificada sobre la badge.

Eureka 3 — Más privilegio no era más acceso. Los ceros estables falsaron mi hipótesis inicial y señalaron el modelo real. El avance llegó al reducir el contexto a U-mode, ASID3 y un mapa Sv32 mínimo, no al elevar privilegios ni ampliar el payload.

Flag 2: primero el stepping, después la fila

La tercera etapa apuntaba a IFR, pero esa frase todavía ocultaba la pregunta decisiva: ¿qué revisión de silicio tenía físicamente? Una revisión de PCB, una foto o una expectativa sobre fabricación no podían responderlo. Antes de preparar una lectura IFR ejecuté un probe cuyo único propósito era identificar el stepping. La badge disponible respondió A0.

Diagnóstico físico de stepping que identifica el silicio como A0
El probe físico confirmó que mi badge utilizaba silicio A0.

Eureka 4 — La revisión del silicio era una condición del ataque. Confirmar A0 antes de tocar RRCCR convirtió una idea sobre el diseño en un experimento aplicable a la badge real. El stepping iba primero; cualquier conclusión sobre A1 debía permanecer separada de lo que yo había probado.

En A0 existía un camino estrecho mediante RRCCR. Diseñé la operación para cambiar un solo bit de forma temporal y reversible. El payload leía y guardaba el valor base, aplicaba el cambio mínimo, realizaba dos lecturas de la fila objetivo y dos del control, restauraba el registro y verificaba la restauración antes de mostrar nada. Si la pantalla, el cable o mi interpretación fallaban, el estado crítico ya debía estar repuesto. Aquí el riesgo de perder el secreto era más alto que nunca: cualquier descuido sobre un registro sensible podía dejar la pieza fuera de alcance.

Mi hipótesis de trabajo era que Flag 2 podía estar repartida y que la combinación de cuatro extractos daría una clave completa. La realidad fue más simple: no eran cuatro piezas que se sumaran, sino una sola fila candidata. R17 fue la única candidata compatible y no nula. Las dos lecturas coincidieron, su CRC fue estable y la estructura observada encajaba con la longitud y los locks esperados. Además, el RTL reservaba esa fila y el firmware normal no la consumía.

El problema de fondo era que, a diferencia de K0, no tenía forma de comprobar por mí mismo si Flag 2 era buena. No existía un oráculo criptográfico ni un digest público contra el que validar los 16 bytes. Podía acumular indicios —estructura, locks, reserva en el RTL, controles negativos— pero no podía cerrar la prueba yo solo. Esa distinción marcó todo lo que hice después: tratarla como hipótesis de alta confianza, nunca como hecho, hasta recibir confirmación externa.

R19, leída con el mismo cambio temporal, devolvió cero y actuó como control negativo primario. Después repetí el patrón sobre R7–R11 para descartar explicaciones alternativas relacionadas con otro material protegido. Esas filas tampoco produjeron una candidata compatible. Así evité seleccionar R17 solo porque era «lo único que parecía interesante».

La restauración tenía su propia evidencia. No bastaba con ejecutar una escritura de vuelta: comparé el valor restaurado con la copia base conservada antes del cambio, y organicé la presentación para que esa verificación ocurriera antes de depender del display. El orden importaba porque una pérdida de alimentación o una captura ilegible no debía dejar abierto el acceso temporal mientras yo resolvía un problema secundario.

El flicker de la pantalla volvió a ser una fuente de falsos desacuerdos. Dos exposiciones de la misma pantalla podían resaltar mitades distintas y parecer valores diferentes. Por eso la evidencia dependía de lecturas repetidas, CRC, estructura y controles, no de escoger la fotografía más legible.

A1: la hipótesis que bunnie confirmó

El camino de RRCCR que usé existía en A0. Mi badge era exactamente eso: una auditoría de Boot1 la identificó como A0, OEM, Boot1 v0.10.1, de modo que esa unidad no podía validar un camino A1. Aun así, revisando el RTL del tapeout A1 me formé una teoría fuerte sobre cómo se podría extraer Flag 2 en A1 pese al parche.

La observación era ésta: info_access_error_pre está cualificada por axi_info & data_op, mientras que el iBus del Vex usa ARPROT=3'b110, lo que hace que un fetch de instrucción tenga inst_op=1 y data_op=0. Como IFR también queda fuera de codesel, parecía que una fila IFR bloqueada para lectura todavía podía llegar a la CPU a través del fetch de instrucción, aunque un lw normal —una carga de datos— quedara enmascarado.

Propuse dos pruebas en simulación. La primera: un canario no sensible en una fila IFR bloqueada, obtenido como código, registrando mcause, mepc, mtval y los deltas controlados de registros; como RVC complica la recuperación exacta, modelaría primero el decodificador y el comportamiento del trap. La segunda: interleaving de una lectura dBus denegada con tráfico iBus y backpressure, porque el RRC parece cargar la fila cruda de 256 bits en ahb_rd_buf antes de enmascarar hrdata, así que valía la pena comprobar que los metadatos de la petición y los datos en buffer nunca pudieran quedar mal asociados.

No pude probarla, porque no tenía una badge A1 delante, y me negué a presentarla como algo más que una hipótesis. Cuando se la planteé a Andrew “bunnie” Huang, la confirmó: en A1, los fetch de instrucción sí pueden leer de una fila IFR bloqueada. El arreglo consiste en marcar la región IFR como no ejecutable desde el subsistema de memoria virtual; alguien llegó incluso a hacer un PoC de esa lectura por fetch, usando el vector de excepción ilegal para sacar la instrucción fallida. No se recupera el 100% de las posiciones, pero sí alrededor de la mitad, que ya es suficiente para ser un problema.

Mensaje de bunnie confirmando que en A1 los fetch de instrucción pueden leer de una fila IFR bloqueada
La confirmación de bunnie: en A1, los fetch de instrucción sí pueden leer de una fila IFR bloqueada.

Fue una de las partes que más me gustaron de todo el ejercicio: no solo cerrar el reto sobre el silicio que tenía, sino entender el sistema lo bastante bien como para anticipar su comportamiento en una versión que nunca llegué a tocar.

Muchos ojos, una sola responsabilidad

Conviene ser preciso con el papel de la IA. Las hipótesis fueron mías: atacar el control de flujo en vez de la firma, reducir el lector al mínimo, bajar a U-mode cuando el privilegio alto devolvía ceros, fijar el stepping antes de tocar RRCCR, la teoría sobre A1. Lo que la IA aportó fue paralelismo —muchos ojos a mi disposición— que volvió la investigación más rápida: revisar código, generar variantes, comparar diffs, endurecer el colector serie y comprobar de forma independiente hashes, tags, CRC y estructura. También se equivocó, y mi trabajo era dirigirla, exigir trazabilidad y decidir qué se daba por probado.

El límite físico y la responsabilidad fueron míos. Nadie más conectó el cable, resolvió la secuencia de pilas, autorizó cada escritura temporal, restauró el dispositivo ni reportó el resultado. La IA amplió mi capacidad de leer, producir y contradecir; yo puse el contexto, el criterio y la responsabilidad.

Durante la entrega de premios y los comentarios en Discord noté cierta resistencia de la comunidad a dar crédito a cualquier trabajo en el que la IA haya participado. Entiendo el recelo, pero somos una comunidad que se alimenta literalmente de tecnologías nuevas: las aprendemos, las adaptamos, las entendemos, las rompemos y las estudiamos. Rechazar de plano una tecnología nueva, y de este calibre, no me parece alineado con el espíritu de lo que hacemos.

Todo el mundo tuvo acceso a la misma badge, al mismo reto, a la misma IA y a las mismas herramientas que yo. Solo dos personas resolvimos el puzzle. bunnie mencionó el reto y a quienes lo conseguimos en la ceremonia, pero sentí que parte de la comunidad y de la organización veían el logro como algo menor, a pesar del escaso número de personas que pudo lograrlo. Yo estoy muy contento con lo conseguido: poder charlar con bunnie sobre su trabajo, e incluso ayudarle a mejorarlo, ya es una recompensa enorme. Pero no quería dejar de decirlo.

Diapositiva BADGE RESULTS de la ceremonia de DEF CON 34 con quienes resolvieron el reto
La diapositiva de resultados en la ceremonia: solo dos personas recuperamos las tres piezas.

Confirmación, límites y contexto público

La confirmación directa de bunnie cerró la identidad de R17 esa misma mañana sin cambiar el orden de la evidencia: primero fue una lectura A0 reproducible y una hipótesis de alta confianza; después fue reportada; por último llegó la confirmación. Más allá de la confirmación de bunnie sobre A1, que sí incluyo, no reproduzco otras conversaciones privadas ni material sensible de esos intercambios. Resumo únicamente las conclusiones necesarias para delimitar lo demostrado y la aclaración sobre A1.

Más tarde pude agradecerle el reto en persona. La badge había cumplido exactamente la función que más valoro en este tipo de artefactos: convertir curiosidad, documentación y medición física en una conversación técnica con sus diseñadores.

Estoy junto a Andrew bunnie Huang durante DEF CON 34
Con Andrew “bunnie” Huang después de recibir la confirmación, durante DEF CON 34.

El Hall of Fame de CHEESO ofrece corroboración pública de que K0, Flag 1 y Flag 2 quedaron asociadas a mi entrada. Es contexto, no una prueba del timestamp ni del orden de llegada: la captura no contiene por sí sola una cronología y una tabla pública puede cambiar. La secuencia temporal descrita arriba se apoya en mis registros contemporáneos y en la confirmación directa, no en contar filas de una imagen.

Captura del Hall of Fame con Anthony Mattas y FJLdx mostrando K0, Flag 1 y Flag 2
Así se veía el Hall of Fame tras la validación; la captura no aporta por sí sola un timestamp.

Este texto se limita al trabajo realizado durante DEF CON y a la aclaración directa recibida esa mañana; no incorpora investigación posterior para mejorar retrospectivamente el relato. Las fotografías muestran los payloads dummy de Flag 1 y Flag 2 y momentos del proceso; no publico K0, los dumps, las transcripciones de protocolo ni otras conversaciones privadas. Expongo el método y las comprobaciones hasta el límite necesario para entender la cadena sin convertir el write-up en una liberación de material restringido.

Gracias a DEF CON por hacer posible un reto de esta calidad y por recordarme, tras más de una década, la alegría de investigar en comunidad. Gracias a bunnie por diseñar una badge extraordinaria y por confirmar con precisión el alcance del resultado. Y gracias a JMA Integra por la confianza y por darme la oportunidad de volver. La IA aportó velocidad y muchos ojos; la comunidad, el contexto; y la responsabilidad de distinguir una candidata de una prueba siguió siendo humana.