Análisis de binarios de firmware bare metal en Ghidra

En esta publicación, analizaremos un binario de firmware STM32 en Ghidra. En particular, el firmware es para la placa de desarrollo STM32F103C de STMicroelectronics.

El archivo se puede descargar desde este enlace.

El análisis de archivos binarios de firmware suele ser diferente del análisis de un archivo PE o ELF. Un PE (ejecutable portátil) es el formato de archivo ejecutable estándar en Windows. Un .exe el archivo es un PE debajo. El formato de archivo PE está diseñado para sistemas Windows de 32 bits. Existe el formato de archivo PE64 que es similar a PE pero está diseñado para sistemas de 64 bits.

En consecuencia, en Linux tenemos el archivo ELF (formato ejecutable y enlazable) que tiene el mismo propósito. Ambos formatos de archivo tienen una estructura definida. Tienen un encabezado que describe cómo se colocará el archivo en la memoria cuando se ejecute. Las direcciones de las secciones de código y datos se proporcionan en el encabezado del archivo. Los desensambladores como Ghidra usan esta información para diferenciar automáticamente entre código y datos y cargar el archivo en la dirección correcta.

Un archivo de firmware plano, por otro lado, es solo un blob binario, un montón de bytes sin encabezado o metadatos que describen el diseño del archivo. Al examinar un archivo de este tipo, el propio analista debe proporcionar la información a Ghidra.

Analisis preliminar

Avancemos y carguemos el firmware en Ghidra. Dado que es un archivo binario sin procesar, Ghidra no sabe cómo procesarlo.

Cargando el firmware en Ghidra
Cargando el firmware en Ghidra

STM32F103 son una serie de microcontroladores alimentados por el procesador ARM Cortex-M3. Cortex-M3 es un procesador de 32 bits. Hagamos clic en el botón Opciones de idioma y establezcamos «ARM-Cortex-32-little» como idioma.

Especificación del idioma y la especificación del compilador
Especificación del idioma y la especificación del compilador

Dejando las otras opciones como están, ahora podemos continuar cargando el archivo y hacer doble clic para abrirlo en el desensamblador.

Solicitud de análisis de Ghidra
Solicitud de análisis de Ghidra

Ghidra le pedirá que analice el archivo y hacemos clic en sí manteniendo las opciones de análisis predeterminadas. Echemos un vistazo al código desensamblado después de que finalice el análisis.

Las direcciones no resueltas en Ghidra están marcadas en rojo
Las direcciones no resueltas en Ghidra están marcadas en rojo

Podemos notar que varias direcciones están marcadas con texto de color rojo. Las direcciones son de la forma 08000XXX. Ghidra marca una dirección en rojo cuando la dirección especificada no existe en el archivo. Hacer doble clic en la dirección no conduce a ninguna parte.

Del mismo modo, analicemos la lista de desmontaje de cualquier función (digamos FUN_000003e4) haciendo clic en él en el árbol de símbolos.

El árbol de los símbolos
El árbol de los símbolos

La lista de desmontaje tiene varias otras direcciones marcadas en rojo.

Direcciones no resueltas en la lista de desmontaje
Direcciones no resueltas en la lista de desmontaje

Hay referencias a direcciones de la forma e000xxxx y 20000xxxx que Ghidra no pudo resolver. Además, si buscamos cadenas, podemos ver que las cadenas no tienen ninguna referencia que las apunte.

Las cadenas no tienen referencias a ellas.
Las cadenas no tienen referencias a ellas.

Todo esto indica que no hemos cargado el archivo en la dirección correcta. Si hubiéramos especificado la dirección correcta al cargar el archivo en Ghidra, al menos algunas de las cadenas tendrían una referencia que las señalaría, si no todas.

Por lo tanto, nuestro próximo paso es encontrar la dirección de carga correcta en la memoria para un firmware STM32. Esta información a menudo se puede encontrar en la hoja de datos del dispositivo y en los archivos de encabezado del compilador.

Pasando por la hoja de datos

En el archivo de encabezado stm32f103x6.h podemos ver que la dirección base de flash es 0x08000000. Esto explica la presencia de 0x08000xxx dirección en el listado de desmontaje. Por lo tanto, la dirección de carga del firmware es 0x08000000.

SRAM comienza en 0x20000000 lo que explica el 20000xxxx direcciones.

#define FLASH_BASE            0x08000000UL /*!< FLASH base address in the alias region */
#define FLASH_BANK1_END       0x08007FFFUL /*!< FLASH END address of bank1 */
#define SRAM_BASE             0x20000000UL /*!< SRAM base address in the alias region */
#define PERIPH_BASE           0x40000000UL /*!< Peripheral base address in the alias region */
 
#define SRAM_BB_BASE          0x22000000UL /*!< SRAM base address in the bit-band region */
#define PERIPH_BB_BASE        0x42000000UL /*!< Peripheral base address in the bit-band region */
 
 
/*!< Peripheral memory map */
#define APB1PERIPH_BASE       PERIPH_BASE
 
[...]
 
#define FLASH_R_BASE          (AHBPERIPH_BASE + 0x00002000UL) /*!< Flash registers base address */
#define FLASHSIZE_BASE        0x1FFFF7E0UL    /*!< FLASH Size register base address */
#define UID_BASE              0x1FFFF7E8UL    /*!< Unique device ID register base address */
#define OB_BASE               0x1FFFF800UL    /*!< Flash Option Bytes base address */
 
#define DBGMCU_BASE          0xE0042000UL /*!< Debug MCU registers base address */

La misma información también se puede encontrar en el mapa de memoria del procesador de la hoja de datos del dispositivo.

Recreando el mapa de memoria en Ghidra

Volvamos a importar el archivo una vez más en Ghidra pero especificando la dirección base correcta esta vez. La dirección base se puede especificar haciendo clic en el botón de opciones en el cuadro de diálogo de importación.

Configuración de la dirección base
Configuración de la dirección base

Tenga en cuenta que la dirección base se ha establecido en 0x08000000. Siguiendo adelante con el análisis podemos ver el 0x08000xxx las direcciones ya no están marcadas en rojo. Esto se debe a que Ghidra puede ubicar la dirección dentro del archivo.

Las direcciones se resuelven correctamente
Las direcciones se resuelven correctamente

De manera similar podemos crear el segmento SRAM en 0x20000000. Según la hoja de datos que conocemos, los dispositivos STM32F103C8 cuentan con un tamaño de memoria flash de 64 KiB y SRAM de 20 KiB. Nuestro archivo de firmware ya tiene un tamaño de 64 KiB. 20 KiB es 0x5000 en hexadecimal.

La hoja de datos del dispositivo
La hoja de datos del dispositivo

A continuación, vaya a Ventana -> Mapa de memoria y haga clic en «+» para agregar un nuevo bloque de memoria. Especificar 0x20000000 como dirección base y 0x5000 como longitud.

Adición del bloque de memoria SRAM
Adición del bloque de memoria SRAM

Los periféricos se encuentran en la dirección 0xE0000000 por una longitud de 0x100000 bytes

La región de memoria de los periféricos
La región de memoria de los periféricos

En consecuencia, podemos crear el bloque de memoria en Ghidra.

Agregar el bloque de memoria de periféricos
Agregar el bloque de memoria de periféricos

Volviendo a FUN_080003e4 podemos notar que las direcciones ya no están marcadas en rojo.

La lista de desmontaje ya no tiene direcciones sin resolver
La lista de desmontaje ya no tiene direcciones sin resolver

De la misma manera, las cadenas tienen referencias que aparecen junto a ellas.

Las cadenas tienen referencias que las señalan.
Las cadenas tienen referencias que las señalan.

Esto implica que hemos cargado el archivo en la dirección correcta con la asignación de memoria adecuada.

Analizando el binario del firmware

La presencia de las cadenas «Ingrese la contraseña», «Autenticación exitosa» indica que el firmware tiene algún tipo de lógica de verificación de contraseña. La cadena «Ingrese la contraseña:» tiene una referencia de la función FUN_080007ee. El código descompilado parece

Listado de código descompilado de FUN_080007ee
Listado de código descompilado de FUN_080007ee

Al revisar el código descompilado, podemos inferir que el firmware lee una cadena byte por byte hasta que encuentra el r personaje. La contraseña se almacena en la variable. local_20. Luego se hace una llamada a la función FUN_080002e0 pasando el búfer de contraseña como argumento.

El código descompilado de FUN_080002e0 es simple.

Listado de código descompilado de FUN_080002e0
Listado de código descompilado de FUN_080002e0

El búfer de contraseña junto con un puntero a la cadena «attify» se pasa a la función FUN_08003910. Si la función devuelve cero, FUN_08000290 se llama. De lo contrario llama FUN_0800024c para un valor de retorno distinto de cero.

FUN_08000290 parece

Listado de código descompilado de FUN_08000290
Listado de código descompilado de FUN_08000290

La presencia de la cadena «Autenticación exitosa» indica que se llamará a esta función si la contraseña era correcta.

En la otra función, FUN_0800024c podemos ver la cadena «Autenticación fallida», lo que implica que se llamará si la contraseña es incorrecta.

Listado de código descompilado de FUN_0800024c
Listado de código descompilado de FUN_0800024c

Podemos deducir que la función FUN_08003910 es strcmphaciendo una comparación de cadenas de las dos cadenas pasadas. strcmp devuelve 0 cuando las dos cadenas coinciden (iguales). La contraseña correcta es, por lo tanto, la cadena «attify», pasada como segundo parámetro a strcmp.

Uso del cargador SVD

SVD-Loader de Leveldown Security es un complemento de Ghidra para automatizar la creación de segmentos de memoria y periféricos para firmware ARM bare metal. El complemento analiza los archivos SVD y crea automáticamente los segmentos de memoria. El campo SVD para varias plataformas ARM se puede obtener del repositorio cmsis-svd GitHub.

Instalar SVD-Loader en Ghidra es simple. Después de clonar el repositorio, la ruta al directorio se puede agregar a la lista de directorios de Script en el Administrador de Script. Se puede acceder al Administrador de secuencias de comandos desde Ventana -> Administrador de secuencias de comandos.

Antes de ejecutar SVD-Loader, debemos asegurarnos de que el archivo se cargue en la dirección base correcta 0x08000000 de la misma manera que lo hicimos antes. Una vez cargado el firmware en Ghidra, podemos ejecutar SVD-Loader.py script desde el administrador de scripts.

SVD-Loader en el administrador de secuencias de comandos Ghidra
SVD-Loader en script Ghidra gerente

El complemento solicitaría especificar la ruta a un archivo SVD para analizar. Podemos seleccionar el archivo STM32F103xx.svd descargado del repositorio cmsis-svd y pulsar en “Cargar archivo SVD”.

Elegir un archivo SVD para cargar
Elegir un archivo SVD para cargar

Después de que SVD-Loader haya terminado de importar, podemos ir a la ventana del mapa de memoria.

SVD-Loader creó bloques de memoria
SVD-Loader creó bloques de memoria

SVD-Loader ha creado automáticamente los bloques de memoria y los periféricos al analizar el SVD. Sin embargo, vale la pena señalar que el complemento no creó el bloque de memoria SRAM en 0x20000000. Podemos crear el bloque SRAM manualmente. En este punto, podemos ejecutar el análisis automático de Ghidra con las opciones predeterminadas de Análisis -> Análisis automático.

A continuación, podemos ir a 080007ee para verificar el código descompilado de la lógica de verificación de contraseña.

Ghdiar no pudo identificar la función en 080007ee
Ghdiar no pudo identificar la función en 080007ee

Desafortunadamente, Ghidra no pudo identificar la función por sí solo. En tales casos, podemos definir manualmente una función haciendo clic derecho en esa dirección -> Desmontar. Al hacerlo, Ghidra crea una función en esa dirección con el mismo código descompilado que antes.

Listado de código descompilado de FUN_080007ee después de crear un mapa de memoria con SVD-Loader
Listado de código descompilado de FUN_080007ee después de crear un mapa de memoria con SVD-Loader

Ultimas palabras

En este post hemos visto cómo analizar un firmware bare metal en Ghidra. Un firmware bare metal es solo una mancha binaria. Para analizar correctamente dicho archivo, necesitamos especificar la dirección de carga y crear los segmentos de memoria. La información sobre la dirección de carga y los segmentos de memoria se puede encontrar en la hoja de datos y en los archivos de encabezado del compilador. El mapa de memoria también se puede crear automáticamente usando SVD-Loader analizando archivos SVD. Sin embargo, es posible que SVD-Loader no cree todos los segmentos. SVD-Loader solo creará segmentos que están definidos en el archivo SVD. Por lo tanto, es importante verificar siempre con la hoja de datos para garantizar la exactitud.

Fuente del artículo

Deja un comentario