
Marco de emulación binaria scriptable que integra IDA Pro/Radare2 con el motor Unicorn para el análisis automatizado de malware, descifrado de cadenas y exploración de rutas de código en arquitecturas x86, ARM y ARM64.
flare-emu combina un marco de análisis binario compatible, como IDA Pro o Radare2, con el marco de emulación de Unicorn para proporcionar al usuario una interfaz fácil de usar y flexible para automatizar tareas de emulación. Está diseñado para manejar todo el mantenimiento de configurar un emulador flexible y robusto para las arquitecturas compatibles, de modo que puedas concentrarte en resolver tus problemas de análisis de código. Actualmente, flare-emu soporta las arquitecturas x86, x86_64, ARM y ARM64.
Actualmente ofrece cinco interfaces diferentes para cubrir tus necesidades de emulación, junto con una variedad de funciones auxiliares y de utilidad relacionadas.
emulateRange – Esta API se utiliza para emular un rango de instrucciones, o una función, dentro de un contexto especificado por el usuario. Proporciona opciones para hooks definidos por el usuario tanto para instrucciones individuales como para cuando se encuentran instrucciones "call". El usuario puede decidir si el emulador saltará o entrará en las llamadas a funciones. Esta interfaz ofrece una manera fácil para que el usuario especifique valores para registros y argumentos de pila determinados. Si se especifica una cadena de bytes, se escribe en la memoria del emulador y el puntero se escribe en el registro o variable de pila. Después de la emulación, el usuario puede hacer uso de las funciones de utilidad de para leer datos de la memoria o registros emulados, o usar el objeto de emulación de Unicorn que se devuelve para una inspección directa. Una pequeña función envolvente para , llamada , se puede usar para emular el rango de instrucciones actualmente resaltado en IDA Pro.
flare-emuemulateRangeemulateSelectioniterate - Esta API se utiliza para forzar la emulación a través de ramas específicas dentro de una función con el fin de alcanzar un objetivo determinado. El usuario puede especificar una lista de direcciones objetivo, o la dirección de una función desde la cual se usa una lista de referencias cruzadas a la función como objetivos, junto con una función de devolución de llamada para cuando se alcanza un objetivo. Los objetivos se alcanzarán, independientemente de las condiciones durante la emulación que puedan haber causado que se tomen diferentes ramas. Al igual que la API emulateRange, se proporcionan opciones para hooks definidos por el usuario tanto para instrucciones individuales como para cuando se encuentran instrucciones "call". Un ejemplo de uso de la API iterate es lograr algo similar a lo que hace nuestra herramienta argtracker.
iterateAllPaths - Esta API es muy similar a iterate, excepto que en lugar de proporcionar una o varias direcciones objetivo, proporcionas una función objetivo para la que intentará encontrar y emular todos los caminos posibles. Esto es útil cuando realizas análisis de código que desea alcanzar todos los bloques básicos de una función.
emulateBytes – Esta API proporciona una manera de simplemente emular un bloque de shellcode extraño. Los bytes proporcionados no se añaden al IDB y simplemente se emulan tal cual. Esto puede ser útil para preparar el entorno de emulación. Por ejemplo, el propio flare-emu usa esta API para manipular un Registro Específico de Modelo (MSR) para la CPU ARM64 que no está expuesto por Unicorn con el fin de habilitar las instrucciones de punto flotante vectorial (VFP) y el acceso a registros. El objeto de emulación de Unicorn se devuelve para una mayor inspección por parte del usuario.
emulateFrom - Esta API es útil en casos donde los límites de las funciones no están claramente definidos, como suele ocurrir con binarios ofuscados o shellcode. Proporcionas una dirección de inicio, y emulará hasta que no quede nada por emular o detengas la emulación en uno de tus hooks. Con IDA Pro, se puede llamar con el parámetro strict establecido en False para habilitar el descubrimiento dinámico de código; flare-emu hará que IDA Pro cree instrucciones a medida que se encuentren durante la emulación.
Para instalar flare-emu para IDA Pro, simplemente coloca flare_emu.py, flare_emu_ida.py y flare_emu_hooks.py en el directorio python de tu IDA Pro e impórtalo como módulo en tus scripts IDAPython.
Para instalar flare-emu para Rizin, simplemente asegúrate de que flare_emu.py, flare_emu_rizin.py y flare_emu_hooks.py estén en la ruta de búsqueda de Python para importar módulos. Al usar Rizin como componente de análisis binario para flare-emu, se requiere rzpipe.
Para instalar flare-emu para Radare2, simplemente asegúrate de que flare_emu.py, flare_emu_radare.py y flare_emu_hooks.py estén en la ruta de búsqueda de Python para importar módulos. Al usar Radare2 como componente de análisis binario para flare-emu, se requiere r2pipe.
En cualquier caso, flare-emu depende de Unicorn y sus enlaces de Python.
NOTA IMPORTANTE
flare-emu fue escrito usando la nueva API de IDA Pro 7x, no es compatible con versiones anteriores de IDA Pro.
Si bien flare-emu se puede usar para resolver muchos problemas diferentes de análisis de código, uno de sus usos más comunes es ayudar a descifrar cadenas en binarios de malware. FLOSS es una excelente herramienta que a menudo puede hacer esto automáticamente al intentar identificar la(s) función(es) de descifrado de cadenas y usar la emulación para descifrar las cadenas pasadas en cada referencia cruzada a ella. Sin embargo, no siempre es posible que FLOSS identifique estas funciones y las emule correctamente usando sus enfoques genéricos. A veces tienes que trabajar un poco más, y aquí es donde flare-emu puede ahorrarte mucho tiempo una vez que te sientas cómodo con él. Repasemos un escenario común que enfrenta un analista de malware al tratar con cadenas cifradas.
Has identificado la función para descifrar todas las cadenas en un binario x86_64. Esta función se llama en todas partes y descifra muchas cadenas diferentes. En IDA Pro, nombras esta función decryptString. Aquí está tu script flare-emu para descifrar todas estas cadenas y colocar comentarios con las cadenas descifradas en cada llamada de función, así como registrar cada cadena descifrada y la dirección en la que se descifra.```
from future import print_function
import flare_emu
def decrypt(argv): myEH = flare_emu.EmuHelper() myEH.emulateRange(myEH.analysisHelper.getNameAddr("decryptString"), registers = {"arg1":argv[0], "arg2":argv[1], "arg3":argv[2], "arg4":argv[3]}) return myEH.getEmuString(argv[0])
def iterateCallback(eh, address, argv, userData): s = decrypt(argv) print("%s: %s" % (eh.hexString(address), s)) eh.analysisHelper.setComment(address, s, False)
if name == 'main':
eh = flare_emu.EmuHelper()
eh.iterate(eh.analysisHelper.getNameAddr("decryptString"), iterateCallback)
En `__main__`, comenzamos creando una instancia de la clase `EmuHelper` de `flare-emu`. Esta es la clase que usamos para hacer todo con `flare-emu`. A continuación, utilizamos la API `iterate`, proporcionándole la dirección de nuestra función `decryptString` y el nombre de nuestra función de callback que `EmuHelper` llamará por cada referencia cruzada emulada.
La función `iterateCallback` recibe la instancia de `EmuHelper`, llamada `eh` aquí, junto con la dirección de la referencia cruzada, los argumentos pasados a esta llamada particular, y un diccionario especial llamado `userData` aquí. `userData` no se usa en este ejemplo simple, pero piensa en él como un contexto persistente para tu emulador donde puedes almacenar tus propios datos personalizados. Sin embargo, ten cuidado, porque `flare-emu` también usa este diccionario para almacenar información crítica que necesita para realizar sus tareas. Una de esas piezas de datos es la propia instancia de `EmuHelper`, almacenada en la clave "EmuHelper". Si estás interesado, busca en el código fuente para aprender más sobre este diccionario. Esta función callback simplemente llama a la función `decrypt`, imprime la cadena descifrada y crea un comentario para ella en la dirección de esa llamada a `decryptString`.
`decrypt` crea una segunda instancia de `EmuHelper` que se utiliza para emular la función `decryptString` en sí, la cual descifrará la cadena por nosotros. El prototipo de esta función `decryptString` es el siguiente: `char * decryptString(char *text, int textLength, char *key, int keyLength)`. Simplemente descifra la cadena en el lugar. Nuestra función `decrypt` pasa los argumentos tal como los recibe la función `iterateCallback` a nuestra llamada a la API `emulateRange` de `EmuHelper`. Dado que se trata de un binario `x86_64`, la convención de llamada utiliza registros para pasar argumentos y no la pila. `flare-emu` determina automáticamente qué registros representan qué argumentos según la arquitectura y el formato de archivo del binario según lo determinado por IDA Pro, permitiéndote escribir código al menos algo independiente de la arquitectura. Si esto fuera `x86` de 32 bits, usarías el argumento `stack` para pasar los argumentos, de la siguiente manera: `myEH.emulateRange(myEH.analysisHelper.getNameAddr("decryptString"), stack = [0, argv[0], argv[1], argv[2], argv[3]])`. El primer valor de la pila es la dirección de retorno en `x86`, así que simplemente usamos `0` como valor de marcador aquí. Una vez que la emulación está completa, llamamos a la API `getEmuString` para recuperar la cadena terminada en nulo almacenada en la ubicación de memoria apuntada por el primer argumento pasado a la función.
### flare-emu e idalib
* instalar IDA Pro
* instalar idalib según la guía de usuario de Hex-Rays
* (activar entorno virtual)
* pip install /path/to/IDA/installation/idalib/python
* python /path/to/IDA/installation/idalib/python/py-activate-idalib.py [-d /path/to/active/IDA/installation]
* importar idapro y escribir tu script
* ver tests/test_flare_emu_idalib.py para un ejemplo
### Escenario fácil de descifrado de cadenas con Rizin
Usando el mismo ejemplo anterior, no cambia mucho al trabajar con Rizin en lugar
de IDA Pro. Una diferencia es que `flare-emu` está actualmente diseñado para ejecutarse
como un script de línea de comandos o dentro de un shell de Python cuando se trabaja con Rizin. El
shell de Python es excelente para la resolución de problemas ad-hoc, mientras que el script de línea de comandos
es excelente para el procesamiento por lotes. La versión de Rizin del script anterior se ve
así (también puedes omitir la ruta de la muestra para ejecutarlo dentro de rizin):```
from __future__ import print_function
import sys
import flare_emu
def decrypt(argv, eh):
myEH = flare_emu.EmuHelper(samplePath=sys.argv[1], emuHelper=eh, isRizin=True)
myEH.emulateRange(
myEH.analysisHelper.getNameAddr("decryptString"),
registers={
"arg1": argv[0],
"arg2": argv[1],
"arg3": argv[2],
"arg4": argv[3],
},
)
return myEH.getEmuString(argv[0])
def iterateCallback(eh, address, argv, userData):
s = decrypt(argv, eh)
print("%s: %s" % (eh.hexString(address), s))
eh.analysisHelper.setComment(address, s, False)
if __name__ == "__main__":
eh = flare_emu.EmuHelper(samplePath=sys.argv[1], isRizin=True)
rz = eh.analysisHelper.r
eh.analysisHelper.setName(0x100000D60, "decryptString")
eh.iterate(eh.analysisHelper.getNameAddr("decryptString"), iterateCallback)
Usando el mismo ejemplo de arriba, no cambia mucho al trabajar con Radare2 en lugar de IDA Pro. Una diferencia es que flare-emu está diseñado actualmente para ejecutarse como un script de línea de comandos o dentro de un shell de Python cuando se trabaja con Radare2. El shell de Python es excelente para la resolución de problemas ad-hoc, mientras que el script de línea de comandos es excelente para el procesamiento por lotes. La versión de Radare2 del script anterior se ve así:```
from future import print_function
import flare_emu
def decrypt(argv, eh): myEH = flare_emu.EmuHelper(samplePath=sys.argv[1], emuHelper=eh) myEH.emulateRange(myEH.analysisHelper.getNameAddr("decryptString"), registers = {"arg1":argv[0], "arg2":argv[1], "arg3":argv[2], "arg4":argv[3]}) return myEH.getEmuString(argv[0])
def iterateCallback(eh, address, argv, userData): s = decrypt(argv, eh) print("%s: %s" % (eh.hexString(address), s)) eh.analysisHelper.setComment(address, s, False)
if name == 'main':
eh = flare_emu.EmuHelper(samplePath=sys.argv[1])
eh.analysisHelper.setName(, "decryptString")
eh.iterate(eh.analysisHelper.getNameAddr("decryptString"), iterateCallback)
Hay dos diferencias con este script. Primero, el constructor `EmuHelper` toma un parámetro aquí: `samplePath=sys.argv[1]`. Cuando se proporciona el parámetro `samplePath`, `flare-emu` usará Radare2 con `r2pipe` como su motor de análisis binario. También puede ver que se pasa un segundo parámetro a la segunda instancia de `EmuHelper` creada en la función `decrypt`. El parámetro `emuHelper` toma un objeto `EmuHelper` existente y clona su memoria al crear el nuevo objeto. Además, si está usando Radare2, la nueva instancia reutiliza la sesión existente de Radare2 en lugar de crear una nueva, lo que agregaría más sobrecarga. Segundo, `flare-emu` crea una nueva instancia de Radare2 usando `r2pipe.open`, por lo que probablemente no tendrá el nombre `decryptString` para la función que nos interesa. Puede establecer el nombre usted mismo usando el objeto `analysisHelper` de `EmuHelper` así: `eh.analysisHelper.setName(<alguna dirección>, "decryptString")`, o puede ingresar directamente la dirección para las llamadas a `iterate` y `emulateRange`.
## [Funciones de Emulación](#emulationfuncs)
`emulateRange(startAddr, endAddr=None, registers=None, stack=None, instructionHook=None, callHook=None, memAccessHook=None, hookData=None, skipCalls=True, hookApis=True, strict=True, count=0)` - Emula el rango de instrucciones comenzando en `startAddress` y terminando en `endAddress`, sin incluir la instrucción en `endAddress`. Si `endAddress` es `None`, la emulación se detiene cuando se encuentra una instrucción de tipo "return" dentro de la misma función donde comenzó la emulación.
* `registers` es un diccionario cuyas claves son nombres de registros y los valores son valores de registros. Algunos nombres de registros especiales son creados por `flare-emu` y se pueden usar aquí, como `arg1`, `arg2`, etc., `ret` y `pc`.
* `stack` es un arreglo de valores que se insertarán en la pila en orden inverso, de manera similar a los argumentos de una función en `x86`. En `x86`, recuerde tener en cuenta que el primer valor de este arreglo se usa como dirección de retorno para una llamada de función y no como el primer argumento de la función. `flare-emu` inicializará el contexto y la memoria del hilo emulado de acuerdo con los valores especificados en los argumentos `registers` y `stack`. Si se especifica una cadena para cualquiera de estos valores, se escribirá en una ubicación en la memoria y se escribirá un puntero a esa memoria en el registro especificado o en la ubicación de la pila.
* `instructionHook` puede ser una función que usted defina para que se llame antes de emular cada instrucción. Tiene el siguiente prototipo: `instructionHook(unicornObject, address, instructionSize, userData)`.
* `callHook` puede ser una función que usted defina para que se llame cada vez que se encuentre una instrucción de tipo "call" durante la emulación. Tiene el siguiente prototipo: `callHook(address, arguments, functionName, userData)`.
* `hookData` es un diccionario que contiene datos definidos por el usuario para que estén disponibles para sus funciones hook. Es un medio para conservar datos durante toda la emulación. `flare-emu` también usa este diccionario para sus propios fines, por lo que se debe tener cuidado de no definir una clave ya definida. Esta variable a menudo se nombra `userData` en las funciones hook definidas por el usuario debido a su nombre en Unicorn.
* `skipCalls` hará que el emulador omita las instrucciones de tipo "call" y ajuste la pila en consecuencia, el valor predeterminado es `True`.
* `hookApis` hace que `flare-emu` realice una implementación ingenua de algunas de las funciones más comunes de la biblioteca de tiempo de ejecución y del sistema operativo que encuentra durante la emulación. Esto le libera de tener que preocuparse por las llamadas a funciones como `memcpy`, `strcat`, `malloc`, etc., y el valor predeterminado es `True`.
* `memAccessHook` puede ser una función que usted defina para que se llame cada vez que se acceda a la memoria para lectura o escritura. Tiene el siguiente prototipo: `memAccessHook(unicornObject, accessType, memAccessAddress, memAccessSize, memValue, userData)`.
* `strict`, cuando se establece en `True` (predeterminado), verifica los destinos de las ramas para asegurarse de que el desensamblador espera instrucciones. De lo contrario, omite la instrucción de rama. Si se establece en `False` cuando se usa IDA Pro, `flare-emu` creará instrucciones en IDA Pro a medida que las emula **(DESACTIVAR CON PRECAUCIÓN)**.
* `count` es el número máximo de instrucciones a emular, el valor predeterminado es `0`, lo que significa que no hay límite.
`iterate(target, targetCallback, preEmuCallback=None, callHook=None, instructionHook=None, hookData=None, resetEmuMem=False, hookApis=True, memAccessHook=None)` - Para cada destino especificado por `target`, se realiza una emulación separada desde el inicio de la función contenedora hasta la dirección de destino. La emulación se forzará por las ramas necesarias para alcanzar cada destino. `target` puede ser la dirección de una función, en cuyo caso la lista de destinos se completa con todas las referencias cruzadas a la función especificada. O `target` puede ser una lista explícita de destinos.
* `targetCallback` es una función que usted crea y que será llamada por `flare-emu` para cada destino que se alcance durante la emulación. Tiene el siguiente prototipo: `targetHook(emuHelper, address, arguments, userData)`.
* `preEmuCallback` es una función que usted crea y que se llamará antes de que comience la emulación para cada destino. Puede implementar algún código de configuración aquí si es necesario.
* `resetEmuMem` hará que `flare-emu` restablezca la memoria de emulación antes de que comience la emulación de cada destino, el valor predeterminado es `False`.
`iterateAllPaths(target, targetCallback, preEmuCallback=None, callHook=None, instructionHook=None, hookData=None, resetEmuMem=False, hookApis=True, memAccessHook=None, maxPaths=MAXCODEPATHS, maxNodes=MAXNODESEARCH)` - Para la función que contiene la dirección `target`, se realiza una emulación separada para cada ruta descubierta a través de ella, hasta `maxPaths`.
* `maxPaths` - el número máximo de rutas a través de la función que se buscarán y emularán. Algunas de las funciones más complejas pueden hacer que la función de búsqueda de grafos tarde mucho tiempo o nunca termine; ajuste este parámetro para satisfacer sus necesidades en un tiempo razonable.
* `maxNodes` - el número máximo de bloques básicos que se buscarán al encontrar rutas a través de la función objetivo. Esta es una medida de seguridad para evitar tiempos de búsqueda irrazonables y bloqueos, y probablemente no necesite cambiarse.
`emulateBytes(bytes, registers=None, stack=None, baseAddress=0x400000, instructionHook=None, hookData=None)` - Escribe el código contenido en `bytes` en la memoria de emulación en `baseAddress` si es posible y emula las instrucciones desde el principio hasta el final de `bytes`.
`emulateFrom(startAddr, registers=None, stack=None, instructionHook=None, callHook=None, memAccessHook=None, hookData=None, skipCalls=True, hookApis=True, strict=True, count=0)` - Esta API es útil en casos donde los límites de las funciones no están claramente definidos, como suele ser el caso con binarios ofuscados o shellcode. Proporciona una dirección de inicio como `startAddr`, y emulará hasta que no quede nada por emular o usted detenga la emulación en uno de sus hooks. Esto se puede llamar con el parámetro `strict` establecido en `False` para habilitar el descubrimiento dinámico de código; `flare-emu` hará que IDA Pro cree instrucciones a medida que se encuentren durante la emulación.
## [Funciones de Utilidad](#utility)
La siguiente es una lista incompleta de algunas de las funciones de utilidad útiles proporcionadas por la clase `EmuHelper`.
* `hexString(value)` - Devuelve una cadena formateada en hexadecimal para el valor. Útil para registros y declaraciones de impresión.
* `skipInstruction(userData, useAnalysisHelper=False)` - Llame a esto desde un hook de emulación para omitir la instrucción actual, moviendo el contador de programa a la siguiente instrucción. La opción `useAnalysisHelper` se agregó para manejar casos donde el marco de análisis binario combina múltiples instrucciones en una pseudo instrucción y desea omitirlas todas. Esta función no se puede llamar varias veces desde un solo hook de instrucción para omitir varias instrucciones. Para omitir varias instrucciones, se recomienda no escribir directamente en el contador de programa si está emulando código ARM, ya que esto podría causar problemas con el modo thumb. En su lugar, intente la API `changeProgramCounter` de `EmuHelper` (descrita a continuación).
* `changeProgramCounter(userData, newAddress)` - Llame a esto desde un hook de emulación para cambiar el valor del registro del contador de programa. Esta API se encarga del seguimiento del modo thumb para la arquitectura ARM.
* `getRegVal(registerName)` - Recupera el valor del registro especificado, siendo sensible al direccionamiento de subregistros. Por ejemplo, "ax" devolverá los 16 bits inferiores del registro EAX/RAX en `x86`.
* `stopEmulation(userData)` - Llame a esto desde un hook de emulación para detener la emulación. Use esto en lugar de llamar a la API `emu_stop` de Unicorn para que el objeto `EmuHelper` pueda manejar la contabilidad relacionada con la función `iterate`.
* `getEmuString(address)` - Devuelve la cadena de caracteres ubicada en una dirección en la memoria emulada, hasta un terminador nulo. Los caracteres no son necesariamente imprimibles.
* `getEmuWideString(address)` - Devuelve la cadena de "caracteres anchos" ubicada en una dirección en la memoria emulada, hasta un terminador nulo. "Caracteres anchos" se entiende aquí en sentido amplio para referirse a cualquier serie de bytes que contenga un byte nulo cada dos bytes, como sería el caso de una cadena ASCII codificada en UTF-16 LE. Los caracteres no son necesariamente imprimibles.
* `getEmuBytes(address, length)` - Devuelve una cadena de bytes ubicada en una dirección en la memoria emulada.
* `getEmuPtr(address)` - Devuelve el valor del puntero ubicado en la dirección dada.
* `writeEmuPtr(address, value)` - Escribe el valor del puntero en la dirección dada en la memoria emulada.
* `loadBytes(bytes, address=None)` - Asigna memoria en el emulador y escribe los bytes en ella.
* `isValidEmuPtr(address)` - Devuelve `True` si la dirección proporcionada apunta a memoria emulada válida.
* `getEmuMemRegion(address)` - Devuelve una tupla que contiene la dirección de inicio y fin de la región de memoria que contiene la dirección proporcionada, o `None` si la dirección no es válida.
* `getArgv()` - Llame a esto desde un hook de emulación en una instrucción de tipo "call" para recibir un arreglo de los argumentos de la función.
* `addApiHook(apiName, hook)` - Agrega un nuevo hook de API para esta instancia de `EmuHelper`. Cada vez que se encuentre una instrucción de llamada a `apiName` durante la emulación, `EmuHelper` llamará a la función especificada por `hook`. Si `hook` es una cadena, se espera que sea el nombre de una API ya enganchada por `EmuHelper`, en cuyo caso llamará a su función hook existente. Si `hook` es una función, llamará a esa función.
* `allocEmuMem(size, addr=None)` - Asigna suficiente memoria del emulador para contener `size` bytes. Intenta respetar la `address` solicitada, pero si se superpone con una región de memoria existente, asignará en una región de memoria no utilizada y devolverá la nueva dirección. Si `address` no está alineada a página, devolverá una dirección que mantiene el mismo desplazamiento alineado a página dentro de la nueva región. Por ejemplo, solicitar la dirección `0x1234` cuando `0x1000` ya está asignado, puede hacer que asigne en `0x2000` y devuelva `0x2234`.
# [Aprende Más](#learn)
Para obtener más información sobre **flare-emu**, lea nuestro blog introductorio en https://www.fireeye.com/blog/threat-research/2018/12/automating-objective-c-code-analysis-with-emulation.html.