Explicaremos que es QILING y cómo este framework para emular binarios en distintos sistemas operativos puede llegar a ser de mucha utilidad en el proceso de análisis de malware.
QILING es un framework de código abierto que brinda la posibilidad de emular archivos binarios de diferentes formatos, por ejemplo, PE, ELF, COM, entre otros. Resulta muy útil a la hora de analizar una muestra de malware y comprender mejor una funcionalidad puntual del código malicioso sin recurrir a la ejecución completa de la muestra.
A su vez, QILING hace uso de otro framework conocido como Unicorn, el cual es un emulador de instrucciones de CPU multiplataforma basado en el emulador QEMU. De esta manera, mientras Unicorn se encarga de toda la emulación a más a bajo nivel, QILING se encarga de interpretar el sistema operativo al que pertenece el archivo a emular, permitiendo manejar las llamadas a API o llamadas de sistemas (syscalls) y manejar la memoria, entre otras cosas.
Gracias a esto, el framework logra engañar al archivo binario haciéndole creer que está siendo ejecutado sobre el sistema operativo para el que fue diseñado, cuando en realidad esto no es así y podemos terminar, por ejemplo, “ejecutando” un archivo binario de Windows sobre un sistema operativo Linux.
Otras características que presenta QILING son:
- Soporte multiplataforma: Windows, MacOS, Linux, BSD, UEFI, DOS, MBR, Ethereum Virtual Machine
- Soporte de distintas arquitecturas: x86, x86_64, ARM, ARM64, MIPS, 8086
- Plugin para integrarlo con la herramienta IDAPro
- Emulación de archivos en un ambiente aislado o sandbox
- Permite acceder a la memoria, registros y llamadas de APIs o syscalls
- Permite modificar la memoria o registros en tiempo de ejecución
- Permite debuggear un archivo utilizando la herramienta GDB
- Permite la emulación del shellcodes
ACLARACION: Este emulador no está pensado para que ejecutar los archivos de forma rápida, sino que se destaca por la posibilidad de poder analizarlos mientras se ejecutan.
Por último, al momento de realizar la emulación de un archivo binario, tenemos que proveerle al emulador una carpeta llamada “rootfs”, la cual va a contener distintas librerías o drivers acorde al sistema operativo que pertenece el archivo para poder funcionar. Mas adelante veremos un ejemplo de cómo utilizar este framework.
Capacidades de QILING al momento de emular un binario
Manipulando el emulador
Ahora que hemos mencionado a grandes rasgos para qué es y cómo funciona este framework, pasaremos a explicar algunas de todas las acciones que nos brinda al momento de emular un archivo binario.
Cuando ejecutamos un archivo binario, el mismo puede ejecutarse de forma total o parcial al proporcionarle una dirección de inicio y otra de fin. En la siguiente captura de pantalla se puede ver cómo se realiza una ejecución parcial de un archivo binario.
ACLARACION: Las direcciones de memoria pueden variar dependiendo del tipo de archivo que queremos ejecutar.
Por otro lado, podemos configurar el nivel de detalle (verbose) con el que queremos ver la información generada por el emulador mientras se ejecuta, e ir guardándola en un archivo de log o simplemente verla a través de la consola.
Los niveles de detalle que permite el framework pueden ser:
- OFF – Solo muestra mensajes de advertencia
- DEFAULT – Utilizado por defecto, muestra llamadas a API y errores
- DEBUG – Muestra más información que “DEFAULT”; por ejemplo, las direcciones donde son cargadas las DLL.
- DISASM – Muestra cada instrucción ejecutada
- DUMP – El modo más detallista, capaz de mostrar dumps de los registros, entre otras cosas.
Otra característica interesante que presenta este framework es la posibilidad de hookearse (engancharse) sobre distintas acciones, como puede ser:
- Código assembler del archivo binario
- Direcciones de memoria
De esta manera, le podemos indicar al emulador que cuando se llegue a cierta dirección de memoria, invoque y ejecute una función que hayamos desarrollado. Por otro lado, también podríamos indicarle que se ejecute una función por cada línea de código assembler que es ejecutada por el emulador.
En las siguientes imágenes pueden observarse ejemplos de cómo es posible hookearse sobre las acciones mencionadas anteriormente.
Para los casos puntuales de las llamadas a API o syscalls, QILING permite hacer un hijack (secuestro) de estas llamadas en distintos momentos; es decir, antes, durante, o después de la ejecución de la llamada en cuestión. De esta forma podríamos modificar los atributos a ser utilizados por la llamada a la API o modificar el código de la misma por algo desarrollado por nosotros.
En la siguiente captura de pantalla se puede ver cómo es posible realizar un hijack a algunas API de Windows.
Por último, mencionaremos otras acciones que podríamos realizar con este framework:
- Manipulación del stack:
- Cargar un valor: ql.stack_push(VALOR)
- Leer: ql.stack_read(OFFSET)
- Manipulación de registros:
- Lectura: ql.reg.read(REGISTRO)
- Escritura: ql.reg.write(REGISTRO, VALOR)
- Manipulación de la memoria:
- Lectura: ql.mem.read(DIRECCION, TAMAÑO)
- Escritura: ql.mem.write(DIRECCION, VALOR)
- Búsqueda de bytes: ql.mem.search(BYTES)
Ejemplo
A continuación, compartimos un ejemplo de un caso en el que QILING puede ser de utilidad para el análisis de malware.
Para ello, vamos a utilizar una muestra del ransomware Conti con el hash 8FBC27B26C4C582B5764EACF897A89FE74C0A88D, para el sistema operativo de Windows.
Esta muestra posee distintas strings que el malware utiliza como una “blacklist”, para detectar aquellas carpetas cuyo contenido no va a ser cifrado. Por ejemplo, no va a cifrar el contenido de la carpeta “Windows”.
Dado que esas strings se encuentran cifradas dentro de la muestra, nuestro objetivo va a ser emular la rutina de descifrado para poder obtener los nombres de esas carpetas.
En la siguiente imagen se observa un ejemplo de la carga de bytes que representan un string cifrado y la llamada a una función encargada de descifrarlos.
Para poder emular esta lógica, primero necesitamos saber la dirección de memoria donde empieza la función encargada de llamar a la función de descifrado (esta sería la dirección de entrada para el emulador). En este caso la dirección es 0x411AF0. Por otro lado, necesitamos una dirección para indicarle al emulador donde debe terminar. Revisando la ilustración 7 vemos que podemos utilizar la dirección 0x411B79.
Por último, como el resultado de la función de descifrado se guarda en el registro EAX y luego ese valor es cargado en memoria, vamos a hookearnos sobre la dirección de memoria 0x411B73,que es la encargada de ejecutar esa instrucción.
Habiendo obtenido estas direcciones de memoria procedemos a escribir un script que se encargue de emular el malware utilizando esas direcciones y que nos muestre el resultado por pantalla.
En la siguiente imagen puede observarse cómo sería el código del script.
Si bien el script que se observa en la ilustración 9 sirve para descifrar una string en particular, esta muestra contiene otros strings que utilizan una lógica similar a la vista en la ilustración 7.
Por ende, si nos detenemos a observar el código en esa ilustración, vemos que después de llamar a la función que descifra el string el resultado se carga en memoria mediante la ejecución de la instrucción “mov [ebp+var_158],eax”. Si observamos la Ilustración 11, vemos que esa instrucción se repite luego de cada llamado a las funciones que desencriptan los strings, modificando el offset donde se tiene que almacenar el resultado para no pisarlo con otros.
Por lo tanto, podemos modificar el script de la Ilustración 9 para que emule el malware hasta que termine de cargar todas las strings descifradas, y en vez de hookearnos a una dirección de memoria, esta vez vamos a ir leyendo cada instrucción que es ejecutada por el malware y cuando veamos algo parecido a “mov [ebp+var_158],eax” imprimiremos el valor por pantalla.
Como observamos en la ilustración anterior, hemos obtenido todas las strings que representan las carpetas cuyo contenido no será cifrado por esta muestra de Conti.
Por otro lado, es importante recordar que esta muestra es para un sistema operativo Windows y la emulación se realizó sobre un sistema operativo Linux.
Conclusión
QILING es un framework muy potente y versátil que nos brinda la oportunidad de poder ejecutar distintos binarios o shellcodes independientemente del sistema operativo que utilicemos.
Por la posibilidad de realizar distintos tipos de modificaciones de manera dinámica, como parchear un archivo binario mientras se está emulando, o el hecho de poder modificar llamadas a la API de Windows, entre otras cosas, resulta muy práctico e interesante el uso de QILING para ver el comportamiento del binario, ya sea un malware u otros.
Por otro lado, tener la posibilidad de emular una sección de un binario por medio de una ejecución parcial, resulta muy práctico para casos en los que uno está analizando una muestra de malware y solo se quiere tener un mejor conocimiento de una funcionalidad puntual sin recurrir a la ejecución completa de la muestra. O incluso también para automatizar el análisis dependiendo de cuanto conocimiento se tenga sobre un tipo de malware en particular.
Fuente info
Autor: Fernando Tavella