Cabecera blog ciberseguridad

OWASP FSTM etapa 6: emulación del firmware

La emulación del firmware cobra especial importancia en el hardware hacking

La emulación es una técnica que se utiliza en múltiples disciplinas y cobra especial importancia en el hardware hacking, en investigaciones por ejemplo en las que se lleva a cabo la emulación del firmware. Permite replicar el comportamiento de un dispositivo físico o de un programa de forma virtual. En el contexto de la ciberseguridad es especialmente interesante para realizar auditorías y encontrar vulnerabilidades.

Un caso de uso concreto de este tipo de técnicas lo encontramos por ejemplo en un servicio de autenticación que disponga de un número máximo de intentos. Podemos realizar infinitas pruebas si emulamos el dispositivo y lo reiniciamos cuando se nos agote el número máximo de intentos. Otro caso práctico de la emulación aplicada a la ciberseguridad la encontramos en las pruebas de penetración, podemos utilizar técnicas combinadas (fuzzing + emulación) para atacar a un servicio sin la necesidad de utilizar el dispositivo físico.

Principalmente, existen dos tipos de emulación:

  • Parcial, o en el espacio de usuario (userspace), cuando se emula un servicio o ejecutable concreto.
  • Total, o de sistema, cuando se emula el sistema completo o de manera global.

La complejidad del proceso de emulación total es casi siempre mayor que la parcial. No obstante, cuando realizamos una emulación parcial es importante asegurarse que contamos con todas las entradas necesarias para su correcta emulación. Por ejemplo, si ejecutamos un servicio que depende de un tercero o que precisa de un fichero o periférico concreto, será nuestra responsabilidad proporcionárselo durante la emulación.

La motivación de la emulación del firmware de los dispositivos IoT, durante un pentesting hardware, se asienta sobre dos principios: acelerar la realización de pruebas y ataques sobre el dispositivo y prescindir del dispositivo físico. Mediante la emulación del firmware se pueden abordar distintas estrategias en paralelo sin afectar a la integridad del dispositivo físico y evitando tener que adquirir más dispositivos.

Así, con la adquisición de un único dispositivo, se puede proceder a la extracción de su firmware, emularlo de forma total o parcial y atacarlo mediante distintas técnicas (fuzzing, frameworks de ataque…). Si se dispone de los recursos necesarios, se pueden paralelizar estos ataques obteniendo mejores resultados en un menor tiempo.

Actualmente coexisten distintas soluciones para la emulación de dispositivos. Algunas de las más populares son QEMU y Unicorn, pero existen muchas otras, algunas de las cuales se comentarán en este artículo.

1. Emulación del firmware, el camino

Gracias a la emulación del firmware y a estas herramientas, el investigador puede conocer con un mayor detalle el funcionamiento del dispositivo / ejecutable que quiere estudiar sin disponer del código fuente. Entre la información importante que el investigador debe conocer destaca la arquitectura del sistema a estudiar, su endianess y las regiones de memoria asociadas o referidas al ejecutable a emular.

A la hora de emular un dispositivo, durante un análisis hardware hacking, independientemente de que la emulación sea total o parcial, se encuentra un gran problema: replicar el contexto. En el mundo real existe una estrecha dependencia entre la dimensión hardware y software de un dispositivo. Así, si cambiamos la arquitectura del sistema, probablemente el software no se pueda ejecutar. Análogamente, si cambiamos el software del sistema por el de otro, el hardware es probable que no funcione correctamente.

En el plano de la emulación, necesitaremos replicar de una forma u otra el contexto, la arquitectura y, en ocasiones, los periféricos del dispositivo para conseguir una emulación exitosa. A lo largo de este artículo presentaremos un conjunto de herramientas y de buenas prácticas para realizar de forma exitosa el proceso de emulación.

El proceso de emulación del firmware tiene una estrecha dependencia con los pasos anteriores de la metodología OWASP-FSTM. En la etapa 2 de OWASP se detalla cómo se obtiene el firmware; en la etapa 4 de OWASP cómo se extrae… Además, es necesario, o muy recomendable, disponer de una idea clara de sobre qué hardware se está trabajando para poder emularlo acorde a sus características (en la etapa 1 de OWASP se describen recomendaciones y buenas prácticas para obtener este tipo de información).

Tal y como se ha comentado previamente, existen dos tipos de emulación: parcial (o de espacio de usuario) y total (o de sistema). En ambos casos es de vital importancia conocer el contexto sobre el que se desea emular. La misma dependencia que existe entre el HW y el SW de un dispositivo físico la vamos a encontrar a la hora de realizar la emulación. Por ello, deberemos ser capaces de replicar el contexto (arquitectura, periféricos…) para tener éxito.

A la hora de emular un firmware completo o un proceso ha de tenerse en cuenta toda la información recopilada para elegir la técnica más fructífera ya que esta suele ser una tarea a la que se pueden dedicar ingentes horas de trabajo. Por ello, se ha de evaluar si es necesario hacer una emulación de sistema o si es posible reducir el esfuerzo dedicado a esta tarea emulando lo necesario para correr un solo proceso de todo el software presente en un dispositivo.

La emulación del firmware es de gran utilidad en las auditorías de ciberseguridad

2. Fórmulas para la emulación de un firmware

Una vez tomada esta decisión existen múltiples opciones para la emulación ordenadas en un orden creciente de esfuerzo:

  • Emulación en espacio de usuario para un ejecutable o un servicio. En este caso, el emulador sería el encargado de simular un kernel genérico que permitiese cargar ese ejecutable de otra arquitectura. Algunas de las herramientas que permiten esto son QEMU o Qiling.
  • Emulación en espacio de usuario con archivos simulados. En ocasiones, los ejecutables requieren acceso a dispositivos o archivos con comportamientos dependientes del hardware. En ocasiones podemos replicar estos dispositivos a los que se hace acceso mediante operaciones sobre un archivo con herramientas de software como CUSE (Character device in Userspace) o FUSE (Filesystem in Userspace).
  • Emulación de sistema sin bootloader. En ocasiones necesitamos emular la ejecución de un kernel para que los procesos o servicios funcionen de la manera adecuada. En este caso algunas herramientas tienen la capacidad para cargar el kernel en memoria y ejecutarlo directamente sin necesidad de un bootloader. Esta opción puede requerir que desarrollemos software para emular algunos dispositivos físicos que el kernel emulado necesita para arrancar. Esto puede ser una tarea relativamente sencilla o muy complicada dependiendo de la documentación del dispositivo disponible.
  • Emulación del sistema completo. Existen firmwares que usan métodos de compresión y/o cifrado no documentados y ofuscados. En estos casos, a veces es interesante emular el sistema completo desde su inicialización más temprana. Esto puede ser una ardua tarea ya que cuando se recurre a esta opción normalmente es por una falta de documentación. Cuando optamos por esta modalidad de emulación debemos ser conscientes de que va a requerir esfuerzos notables para lograr resultados.

3 Herramientas de emulación

Entre las principales herramientas actuales destacan QEMU, Unicorn, Renode, Qiling y Firmadyne.

3.1 QEMU

Logo de QUEMU

QEMU es un virtualizador y emulador de máquinas que dispone de distintos modos de funcionamiento. El más común es la emulación de sistemas, donde provee un modelo virtual de máquina completa (CPU, memoria y dispositivos virtuales) para ejecutar el sistema emulado. En este modo puede emular el sistema completo, siempre que el sistema emulado y el anfitrión sean el mismo. Puede trabajar, también, de supervisor como KVM, Xen, Hax, o Hypervisor.

Los entornos de desarrollo permiten al sistema huésped emular el sistema en la CPU del host. Adicionalmente, permite emular ejecutables (emulación en espacio de usuario) a través de los ejecutables qemu-arch, que solo emulan la arquitectura de la CPU origen del ejecutable y traducen las instrucciones a la arquitectura destino.

En ambos casos, puede ser útil compilar o instalar las versiones estáticas de los ejecutables de QEMU, habitualmente distinguidas con el sufijo -static, pues estas versiones, al contrario que las dinámicas, no dependen de las bibliotecas del sistema huésped. Esto es especialmente interesante para la construcción de un entorno virtual para la emulación de ejecutables, con herramientas como chroot, en el que no deberían instalarse las bibliotecas ni ejecutables de la arquitectura huésped.

3.2 Unicorn

Logo de Unicorn

Unicorn es una alternativa en QEMU, centrándose en la emulación de múltiples arquitecturas de CPU. Unicorn se define como un emulador de CPU y se abstrae del resto de sistema mediante un API simple con soporte para múltiples lenguajes.

La idea detrás de Unicorn es proveer un emulador extremadamente flexible y de alto desempeño en cuanto a velocidad de emulación. Esto convierte a Unicorn en una de las opciones a las que recurrir cuando necesitamos se necesita realizar la emulación de un sistema que requiere una carga computacional relativamente intensa ya que reduce los tiempos de ejecución con respecto a sus alternativas significativamente.

También resulta útil para simular trozos aislados de código con un contexto limitado ya que su flexibilidad permite emular el contexto y limitar la ejecución a secciones de código arbitrarias.

Esta flexibilidad nos permite también hacer un análisis de código de manera dinámica a muy bajo nivel. Unicorn, a través de su API expone todos los estados y contextos de la emulación en cada momento.

Si bien es cierto que este emulador aporta ventajas considerables (como su potencia) frente al resto de alternativas, precisa de una gran inversión de tiempo. Así, para poder hacer una emulación se requieren conocimientos avanzados de la plataforma que se desea emular y de una gran inversión de tiempo para escribir el código que nos permita lanzar sus ejecuciones (en numerosas ocasiones se deberán emular a bajo nivel periféricos y otros componentes para que el sistema funcione adecuadamente).

3.3 Renode

Logo de Renode

Renode es otra de las herramientas basadas en QEMU destinadas a la emulación de firmware, y otros sistemas. Sus funcionalidades incluyen la interacción entre múltiples procesadores virtuales, con la memoria virtualizada y con múltiples tipos de dispositivos como sensores, pantallas y otras entradas y salidas del sistema. También permite conectar el entorno emulado con hardware implementado en sistemas FPGA.

A diferencia de QEMU, Renode ha sido principalmente diseñado para la emulación de dispositivos IoT y empotrados con sistemas operativos de tiempo real, aunque también es capaz de emular sistemas más potentes, como Linux.

Para emular un sistema con Renode, se requiere conocer cómo se encuentran organizadas las comunicaciones con los dispositivos, que, en caso de utilizarse el habitual método de comunicación Memory Mapped I/O (MMIO), se trata de encontrar qué dispositivos están asignados a cada región de memoria. Aunque este tipo de información se puede obtener a partir de los manuales del fabricante del dispositivo IoT que se analiza, también puede encontrarse en los bloques de datos tipo Device Tree Blob (DTB) que suelen estar presentes en firmware.

El modo de configurar Renode para la emulación de un sistema a menudo consiste en empezar con un conjunto mínimo de dispositivos y ejecutar el sistema hasta que se produzcan fallos, para luego investigar los fallos y completar la lista de dispositivos.

En su repositorio oficial se pueden encontrar ejemplos de aplicación de Renode en diferentes escenarios, mientras que la documentación contiene listas de dispositivos y placas de desarrollo para los que se ofrece soporte inmediato.

3.4 Qiling

Logo de Quiling

El framework de emulación Qiling tiene el objetivo de emular cualquier tipo de ejecutable. Soporta múltiples arquitecturas y la emulación de diferentes sistemas operativos y formatos de ejecutable.

Se trata de un framework escrito en Python como lenguaje base que se apoya sobre el motor de emulación Unicorn para ofrecer emulación de sistemas de ficheros, bibliotecas dinámicas, carga de ejecutables y muchas otras características de alto nivel propias de un sistema operativo.

Para preparar la emulación con Qiling se requiere el sistema de ficheros del firmware extraído con el ejecutable que se desea emular, de forma que se encuentren disponibles los ejecutables y ficheros originales. Las llamadas al sistema y las salidas se dirigen a stdout y, en general, se parece al uso de Unicorn, con extensiones para integrar la ejecución con múltiples herramientas.

Una de esas herramientas es el framework de fuzzing AFL++, que permite realizar pruebas de fuzzing sobre los ejecutables que están siendo emulados con Qiling. Esta integración puede ser de gran utilidad para agilizar el proceso de emulación y análisis de un ejecutable y resulta una gran herramienta en esta fase del estudio.

3.5 Firmadyne

Firmadyne es un conjunto de herramientas que tratan de automatizar y simplificar el trabajo de emulación de una imagen de firmware. Está escrito en Python y consiste en un conjunto de módulos que se utilizan para la extracción del sistema de ficheros, la identificación de la arquitectura del firmware y la creación de una máquina virtual ejecutable específica para el firmware analizado.

Firmadyne también intenta automatizar parte del proceso de análisis y contiene módulos para la inspección de servicios Web en el dispositivo, recolección de información de servicios SNMP y comprobación de vulnerabilidades con Metasploit.

Sin embargo, la instalación de esta suite puede ser más complicada que en otros casos y la integración con depuradores como GDB requiere la compilación estática del servidor gdbserver para incluirlo en el sistema de ficheros del firmware.

En general, la emulación del firmware con Firmadyne puede resultar mucho más sencilla que con el resto de las herramientas, pero es también más limitada en cuanto al control del flujo de ejecución y es más propensa a errores.

3.6 ARMX Firmware Emulation Framework

ARMX es una colección de scripts, ejecutables, kernels y sistemas de archivos

ARMX es una colección de scripts, ejecutables, kernels y sistemas de archivos que junto con QEMU tratan de emular dispositivos ARM IoT. El proyecto trata de ser lo más similar a una máquina virtual IoT en la actualidad.

El proyecto cuenta con soporte para emular once modelos de dispositivos ARM de manera completa.

Si ARMX no cuenta con soporte para el dispositivo que nosotros estamos emulando, cuenta con una sección que explica como crear nuestros propios dispositivos y plantillas para facilitarnos el trabajo de implementación.

4 Ejemplo de emulación del firmware con QEMU

En algunos sistemas empotrados se necesita disponer de capacidades que permitan mostrar datos a través de una web. Si bien es cierto que existen múltiples alternativas para desplegar este tipo de capacidades, los servicios utilizados para ello dependen mucho de la potencia de procesamiento y del tipo de dispositivo que se utilice.

Cuando se utilizan sistemas bastante potentes o con alimentación constante es común usar servicios generalistas como Apache, nginx, etcétera. Sin embargo, cuando el dispositivo tiene unos recursos limitados o se quiere prolongar la duración de la batería se utilizan otros servicios más sencillos como devd. Este será el ejecutable que utilizaremos como caso práctico para ilustrar el proceso de emulación del firmware con QEMU.

Nota: La herramienta QEMU está presente en los repositorios de diferentes distribuciones de Linux o en el repositorio oficial de GitHub (https://github.com/qemu/QEMU). Su ubicación por defecto tras la instalación suele ser una de estas dos:

/usr/local/bin/qemu-arch
/usr/bin/qemu-arch

4.1 Identificación de la arquitectura

Antes de emular un ejecutable con QEMU es necesario conocer el endianness y la arquitectura adecuada. Para ello, podemos hacer uso de herramientas como binwalk o readelf. En nuestro ejemplo, la arquitectura es x86-64 y el endianness: little endian.

Nota: cuando no se indica el endianness, QEMU utiliza el definido por defecto: Little endian (EL).

$ readelf -h devd
ELF Header:
Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class:                             ELF64
Data:                              2’s complement, little endian
Version:                           1 (current)
OS/ABI:                            UNIX – System V
ABI Version:                       0
Type:                              EXEC (Executable file)
Machine:                           Advanced Micro Devices X86-64
Version:                           0x1
Entry point address:               0x458e40
Start of program headers:          64 (bytes into file)
Start of section headers:          456 (bytes into file)
Flags:                             0x0
Size of this header:               64 (bytes)
Size of program headers:           56 (bytes)
Number of program headers:         7
Size of section headers:           64 (bytes)
Number of section headers:         24
Section header string table index: 3

4.2 Emulación y buenas prácticas

Conocida la arquitectura y el endianness (EL (Little Endian) o EB (Big Endian)), se identifica el ejecutable de QEMU apropiado para realizar la emulación del firmware en espacio de usuario (existen distintos dentro de la utilidad QEMU dependiendo del propósito de la emulación). En nuestro caso será qemu-arch-static, donde arch se refiere a la arquitectura del ejecutable (x86_64).

Una buena práctica cuando se está haciendo la emulación es copiar el ejecutable de QEMU al directorio de trabajo (el que contiene el sistema descomprimido y los ejecutables extraídos que queremos emular). En este caso, se recomienda utilizar las versiones de QEMU que terminan en “-static”.

$ qemu-arch-static [options] <filename>

Si, por el contrario, el archivo es dependiente del resto del sistema se deberá crear un entorno virtual (por ejemplo, con chroot) que contenga el sistema de archivos requerido. Una vez creado, se procederá a la ejecución.

$ sudo chroot . ./qemu-arch-static [options] <filename>

Otra recomendación interesante es utilizar la opción “-strace” de QEMU durante la ejecución. De esta forma, podremos visualizar las llamadas al kernel que están ocurriendo durante la ejecución en tiempo real.

El resultado obtenido al ejecutar la utilidad devd de forma nativa es el siguiente:

$ ./devd . -ol
12:13:12: Route / -> reads files from .
12:13:12: Listening on https://devd.io:8000 (127.0.0.1:8000)
12:13:15: GET /
<- 200 OK 1.6 kB

4.3 Resultado de la emulación

Haciendo uso de las recomendaciones presentadas en el apartado anterior, se procede a la emulación del ejecutable. Se puede comprobar que la emulación es exitosa y que devuelve el mismo resultado que al ejecutarlo de forma nativa.

$ ./qemu-x86_64-static ./devd . -ol
12:14:18: Route / -> reads files from .
12:14:18: Listening on https://devd.io:8000 (127.0.0.1:8000)
12:14:20: GET /
<- 200 OK 1.6 kB

A modo ilustrativo, se emula también el ejecutable con la opción strace para estudiar las llamadas que realiza. Debido a la extensión del resultado obtenido, se presentan únicamente las primeras llamadas.

$ qemu-x86_64-static -strace devd . -ol
80379 arch_prctl(4098,13492464,126614525,0,0,0) = 0
80379 sched_getaffinity(0,8192,274886286920,0,274886296000,0) = 40
80379 mmap(NULL,262144,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0) = 0x0000004000803000
80379 mmap(0x000000c000000000,67108864,PROT_NONE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0) = 0x000000c000000000
80379 mmap(0x000000c000000000,67108864,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED,-1,0) = 0x000000c000000000


15:46:30: GET /


<- 200 OK 2.8 kB

Tras la emulación del firmware, se procede a comprobar que el funcionamiento de la aplicación es correcto (y comprobar así que el proceso de emulación ha sido exitoso). En la siguiente captura se puede observar la web emulada.

Captura de pantalla que muestra la emulación del firmware y de una web

5. Conclusiones

En este artículo se ha presentado la importancia de la emulación en el contexto de la ciberseguridad durante un pentesting hardware. Emular permite paralelizar el proceso de búsqueda de vulnerabilidades sin afectar ni alterar a los dispositivos físicos. De esta forma se ahorran tiempo y recursos.

No obstante, tal y como se ha explicado, este proceso de emulación no es sencillo y requiere de un elevado nivel de conocimiento por parte del investigador. Además, precisa de una serie de datos que son necesarios para una correcta emulación como la arquitectura del sistema, los periféricos que existen o el endianness.

Es importante resaltar también que las vulnerabilidades encontradas durante la emulación pueden no ser aplicables al dispositivo real (físico). A veces una PoC vulnera un sistema emulado, pero no lo hace en un sistema real porque depende de algún parámetro que no podemos controlar o que no se ha emulado correctamente. Por ello, estos fallos son siempre un buen indicativo, pero no son determinantes y requerirán que una comprobación detallada por parte del investigador.

Por último, hay que comentar que existen numerosas herramientas como QEMU y Unicorn que nos ayudan a realizar estos procesos de emulación. No obstante, cada día están surgiendo nuevas alternativas más maduras que probablemente ayudarán a acelerar y facilitar este tipo de trabajos de investigación.

Más artículos de la serie OWASP

Este artículo forma parte de una serie de articulos sobre OWASP

  1. Metodología OWASP, el faro que ilumina los cíber riesgos
  2. OWASP: Top 10 de vulnerabilidades en aplicaciones web
  3. Análisis de seguridad en IoT y embebidos siguiendo OWASP
  4. OWASP FSTM, etapa 1: Reconocimiento y búsqueda de información
  5. OWASP FSTM, etapa 2: Obtención del firmware de dispositivos IoT
  6. OWASP FSTM, etapa 3: Análisis del firmware
  7. OWASP FSTM, etapa 4: Extracción del sistema de ficheros
  8. OWASP FSTM, etapa 5: Análisis del sistema de ficheros
  9. OWASP FSTM etapa 6: emulación del firmware
  10. OWASP FSTM, etapa 7: Análisis dinámico
  11. OWASP FSTM, etapa 8: Análisis en tiempo de ejecución