CHALLENGE DESCRIPTION
Someone leaked the new Espresso firmware, can you try to figure out what it does?
Datos iniciales:
firmware.bin: Archivo de firmware, de momento desconocemos más información.
Enumeración inicial#
De momento, intentamos buscar cualquier información disponible.
Ejecutamos file pero obtenemos poca información.
| |
Buscando strings#
Miramos strings. En este caso uso un script custom que:
- Dice si las strings están en Little o Big Endian en función de lo que dice
file. Como aquí no ha dicho nada tendremos que mirarlo manualmente. - Mira strings ASCII, UTF-16 y UTF-32 en la correspondiente codificación (LE/BE)
Si ejecutamos el binario strings original en Big Endian:
| |
No hay ningún string relevante, así que muy posiblemente el programa esté en Little Endian. Ahora ejecutamos mi script custom y encontramos, entre todo, algunas cosas interesantes (strings en LE):
| |
Si buscamos v6.1-dev-2748-g490691bc61 en Internet, encontraremos bastantes páginas acerca del ESP32. Esto concuerda bastante con todos los nombres de señales que se listan también, que comienzan con ESP_. Podemos asumir con bastante certeza que se trata de un archivo de firmware del ESP32. Además, el nombre del challenge (Espresso) coincide bastante con la empresa fabricante de los ESP32, Espressif.
Además, si buscamos algo con flag:
| |
Y echamos un vistazo:
| |
En los strings aparece: “flag did not generate correctly. It seems you are running the firmware on cloned hadware. Buy the real hardware, or perhaps try to emulate it. ;)”.
Nota: Archivo bin
Aunque se indique “flag did not generate correctly. It seems you are running the firmware on cloned hadware…”, hay que tener en cuenta que el archivo firmware.bin es código compilado. No es un log, son mensajes definidos por el programador en el código fuente. Si el código se ejecuta y detecta que es “hardware clonado”, imprimirá ese string, pero no indica que ese error “haya pasado ya” y nosotros estemos viendo el resultado.
Enum. específica ESP32#
Antes de emular nada, vamos a buscar herramientas específicas para el ESP32 que permitan sacar más información del binario. Tras una búsqueda, encuentro esp32_image_parser.
Lo ejecutamos primero para buscar particiones.
| |
En teoría, cada una de las particiones sirve para lo siguiente:
nvs: Almacenamiento no volátil. Sirve como base de datos para info. que no debe perderse al reiniciar. Guarda configuraciones, credenciales, etc. Si hay números de serie o claves que se comprueban para determinar si el hardware es “clonado”, posiblemente estén aquí.phy_init: Datos y parámetros de bajo nivel para inicializar antenas wifi y bluetooth. No relevante aquí.factory: De tipo[APP], es la aplicación que arranca por defecto cuando inicia el ESP32. Contiene todo el código compilado y los datos estáticos.
Con el script descargado podemos convertir la partición factory a ELF.
| |
Al parecer el script ha fallado y además ha ignorado completamente el nombre que queríamos darle al output, pero igualmente se ha creado un archivo. Vamos a comprobar si realmente nos ha dado algo útil o al menos reconocible.
| |
Y vemos que sí.
Descompilando código#
Ahora lo intentamos abrir con Ghidra. La aplicación lo detecta como Raw Binary y nos pide introducir el lenguaje a usar. Como el ESP32 usa Tensilica Xtensa, lo buscamos y seleccionamos. Elegimos Xtensa 32-bit little-endian.
El problema es que Ghidra de primeras asume que el código empieza en la línea 0 porque no conoce el entry point (que como ha dicho file es 0x400814AC), por lo que las instrucciones no tienen sentido para él. Como se ve en la imagen, la mayoría de instrucciones en ensamblador se reconocen como ?? o como saltos undefined.

Para solucionar esto, la idea es cargar un plugin para Ghidra que le permita entender directamente estos binarios. Si usas la versión v1.1.0 de Releases, ten en cuenta que necesitarás instalar Ghidra 11.3.1 y que no irá con las versiones más nuevas.
Una vez instalado, lo importamos. Ahora sí que detecta directamente el lenguaje como Xtensa:LE:32:default:default. Lo abrimos.

Para ver la función en la que se hacían comprobaciones y se creaba el flag, vamos a Window -> Defined Strings y buscamos “flag”. Ahí pulsamos en el string “flag did not generate correctly.”. Vemos que el string está en la dirección 0x3f4036a4.
Pulsar en el string nos lleva a la función en la que se usa: “FUN_40088e74”. Si vamos a esa función:
| |
Además, si miramos referencias a la dirección del string (0x3f4036a4), encontramos otra, FUN_400d5c34:
| |
Vemos que en esta última función se llama a la anterior en múltiples ocasiones. Además, en cada llamada se le pasa un múmero diferente como primer argumento. Si el if se cumple, se le pasa 3, si no, se le pasa 1.
Respecto a los demás argumentos, se pasan punteros a memoria (RamABCDABCD;). Si vamos a esas direcciones (p.ej 400d07c0), veremos que todo se interpreta como Bad Instruction, por lo que no son instrucciones.
Si vamos específicamente a 0x400d07cc (por Ram400d07cc en el else), encontraremos lo siguiente:
| |
Y aquí vemos que precisamente 0x400d07cc es un puntero hacia 0x3f4036a4, es decir, hacia el string “flag did not generate correctly.”. Posiblemente se trate de una literal pool puesta cerca de las funciones, por eso Ghidra no nos muestra los strings directamente.
De hecho, si ahora hacemos lo mismo para Ram400d07d4, veremos que nos lleva hacia 0x3f4036a4, que es el string “It seems you are running the firmware on cloned hadware.”
Esto significa que si no se cumple la condición del if, no se generará el flag.
Analizando el if#
Ahora sabemos que para conseguir generar el flag, el if debe cumplirse, por lo que podemos centrarnos en la primera rama únicamente y dejar de lado el else.
Además, sabiendo que a FUN_40088e74 se le pasan strings de debug o de error y códigos (1/3), es posible que se trate simplemente de una función de registro que muestre en pantalla el último argumento de todos (sexto).
| |
Tenemos que comprobar varias cosas:
- COMPROBACIÓN DE HARDWARE: Qué comprueba FUN_400d5c04?
- Es la función de la que depende todo el if principal (y cuyo return va hacia
bVar4).
- Es la función de la que depende todo el if principal (y cuyo return va hacia
- CREACIÓN DE FLAG: Qué hace FUN_400d5cb0 sobre
local_40?- Este
local_40es importante porque es un string de 64 caracteres. Posiblemente el flag.
- Este
Si entendemos cómo se genera el flag, posiblemente ni necesitemos emular el hardware del ESP32 y por tanto no necesitemos saber cómo se comprueba el hardware, así que vamos a por la segunda función.
Creación del flag#
Miramos la llamada a la función sin más.
| |
Y por otro lado la función por dentro.
| |
A esto podemos cambiarle nombres:
| |
Y como el segundo argumento en la función caller es 32, siempre mayor que 31, podemos incluso quitar el if en este caso para abstraerlo del todo.
| |
Además, echamos un ojo a 0x400d07d8. Ghidra nos muestra lo siguiente:
| |
Y aquí Ghidra se está equivocando de nuevo. En realidad esto sería un puntero junto con los otros 2 bytes siguientes, pero Ghidra ha cogido solo los dos primeros (0x3f40) y ha visto que coinciden con la instrucción de Xtensa l32i.n, por lo que los ha interpretado como instrucción. Para solucionar esto seleccionamos l32i.n, pulsamos C (Clear) y luego P (Pointer). Ahora hemos obtenido la dirección 0x3f407688.
Vamos a esa dirección y nos encontramos esto.
| |
Tiene bastante sentido: El flag no ha sido creado todavía.
Al final, la función para crear el flag queda:
| |
Creando el flag#
Ahora que tenemos todo lo necesario, podemos crear el flag nosotros mismos con un script sencillo.
| |
Lo ejecutamos y…
| |
Hemos acabado.




