La instrumentación resulta una herramienta muy útil al momento de analizar malware. La posibilidad de alterar el flujo de ejecución de código de manera dinámica facilita el análisis de códigos maliciosos, especialmente cuando estos han sido ofuscados para entorpecer su estudio.
Anteriormente vimos cómo podíamos utilizar Pin sobre malware orientado a Android para incluir código entre las instrucciones de un archivo APK. No obstante, la instalación de este framework creado por Intel puede resultar trabajosa. Por ello, en este artículo haremos la revisión de una tentadora alternativa cuando de instrumentación de se trata.
Frida: instrumentación multiplataforma
Frida es una herramienta de instrumentación dinámica, asombrosamente flexible, creada por Ole André Vadla Ravnås y Karl Trygve Kalleberg. Esta potente aplicación puede inyectarse en procesos en ejecución a lo largo de múltiples plataformas: Android, iOS, Windows, Mac y QNX. Además de su flexibilidad y rápida instalación, Frida tiene la ventaja de no depender de otras herramientas, lo que ocurre con otras aplicaciones de instrumentación como Cydia Substrate.
Aunque el núcleo de la aplicación funciona en C, la instrumentación se da a través de JavaScript y la aplicación de hooking puede desarrollarse en diversos lenguajes: Python, Node.js, .NET o Qml, pudiendo extenderse a otros. Esto también plantea grandes beneficios en comparación con otras herramientas.
Este tipo de inyección nos permitirá inspeccionar el estado de diferentes objetos, variables e hilos de ejecución para, por ejemplo, registrar los parámetros que recibe un determinado método o modificar el valor devuelto por él.
Frida puede servir a propósitos de testeo de desarrollos o bien –esto es lo realmente interesante– a la búsqueda de vulnerabilidades y al análisis de códigos maliciosos. Un caso podría ser una aplicación ofuscada que codifique o cifre los datos antes de enviarlos a un servidor malicioso: con un rápido análisis estático de las fuentes podrían identificarse los métodos que hacen las llamadas a las librerías de cifrado del sistema, para posteriormente instrumentar código que registre la clave de cifrado o la información a enviarse antes de ser modificada.
En este sentido, el retorno de inversión de tiempo en el aprendizaje de estas herramientas vendrá de la mano de una reducción en el tiempo de análisis de futuras muestras maliciosas.
Instalación de Frida
El proceso de instalación es muy sencillo y se encuentra detallado en su documentación. La herramienta funciona bajo un modelo cliente servidor. Para instalar el cliente en un equipo anfitrión con alguna distribución de Linux podemos simplemente ejecutar este comando:
sudo pip install frida
Para instalar el servidor en el dispositivo móvil, una opción es descargar la herramienta en el computador y luego copiarla al sistema Android, como se muestra a continuación. También puede utilizarse el binario wget compilado para la versión de Android que se utilizará –tener en cuenta que la versión incluida en BusyBox no será útil para esto, pues no soporta HTTPS–.
curl -O https://build.frida.re/frida/android/arm/bin/frida-server
adb push frida-server /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "/data/local/tmp/frida-server &"
Llegado este momento, tanto el cliente como el servidor estarán en posición para comenzar a comunicarse. Para testear la comunicación puedes utilizar alguna de las variadas herramientas que Frida incluye listas para utilizar, como frida-ps o frida-trace.
Creando scripts para la instrumentación
En esta ocasión utilizaremos Python para crear una aplicación que instrumente procesos. Para ello, deberemos importar el módulo frida que fue descargado en el momento de instalación de la herramienta.
Primero deberemos crear el script en JavaScript a instrumentar. Debemos indicar a Dalvik que necesitamos que ejecute cierto código en nuestro favor. Para ello incluiremos la lógica del script dentro de la siguiente función:
[javascript]
Dalvik.perform(function () {
// Código
});
[/javascript]
Para obtener un objeto JavaScript capaz de manejar las interacciones con cada clase podemos utilizar:
[javascript]var miClase = Dalvik.use("nombre_de_clase");[/javascript]
Para crear una nueva instancia de una clase, el constructor debe invocarse de la siguiente manera:
[javascript]var miClaseInstancia = miClase.$new();[/javascript]
Este objeto nos permitirá acceder a los métodos y atributos de tal clase mediante la notación de punto.
[javascript]var resultado = miClaseInstancia.miMetodo(“Esto es un parámetro”);[/javascript]
Aún más, nos permitirá reemplazar la implementación de tal método reasignando una nueva función de implementación:
[javascript]
miClase.miMetodo.implementation = function(parametro) {
// Código
};
[/javascript]
Siempre que dos métodos de una clase posean el mismo nombre, esto se considerará una sobrecarga del método, sin importar si sus parámetros se diferencian en número o tipo. Para hacer referencia a métodos sobrecargados debe especificarse la cantidad y tipo de los parámetros.
Para ejemplificar lo anterior, supongamos que tenemos tres métodos llamados miMetodo: uno de ellos no recibe parámetros, otro recibe dos arrays de bytes y el tercero recibe el contexto de la aplicación y un valor booleano. En tal caso, para sobrescribir cada método se deberán realizar las siguientes llamadas:
[javascript]
miClase.miMetodo.overload().implementation = function() {
// Código
};
miClase.miMetodo.overload("[B", "[B").implementation = function(param1, param2) {
// Código
};
miClase.miMetodo.overload("android.content.Context", "boolean").implementation = function(param1, param2) {
// Código
};
[/javascript]
Para inyectar el código JavaScript deberemos identificar el dispositivo donde corre la aplicación y enlazar una sesión al proceso que deseamos instrumentar.
[python]session = frida.get_device_manager().enumerate_devices()[-1].attach("nombre_proceso")[/python]
Luego, necesitamos crear el script utilizando el método create_script de la sesión abierta y cargarlo.
[python]
script = session.create_script(javascript)
script.load()
[/python]
Para enviar mensajes por consola disponemos del método send(“mensaje”)
. Podemos asociar al script creado una función para el manejo de mensajes mediante la siguiente línea:
[python]script.on('message', on_message)[/python]
Donde on_message es una función en Python definida como se muestra a continuación:
[python]
def on_message(message, data):
if message['type'] == 'send':
print(message['payload'])
elif message['type'] == 'error':
print(message['stack'])
[/python]
La teoría cobra vida…
En numerosas ocasiones, al realizar un análisis dinámico de una muestra nos encontramos con paquetes de tráfico de red con contenido ofuscado. Un ejemplo de esto lo constituye la muestra detectada por las soluciones de seguridad de ESET como Android/TrojanSMS.Agent.ZS: un troyano SMS con alta incidencia en Brasil, donde aún hoy continúa propagándose.
Si observamos los paquetes capturados en el análisis de esta amenaza solo podremos observar texto ininteligible. Veamos cómo Frida sirve a descifrar este misterio.
Para definir qué métodos interceptar es necesario examinar el archivo DEX del malware. Una vez que se tienen las clases expuestas, se puede utilizar la herramienta Buscar del editor para localizar rápidamente los métodos que interesan mediante las llamadas a la API de la plataforma o buscando por nombres comunes. Por ejemplo, en este caso podemos buscar “Cipher”, “encrypt” o “encode”.
Siguiendo esta metodología encontramos la clase unitSecurity, la cual posee métodos como rsaEncode, rsaDecode, getCispher, encrypt o decrypt.
En este caso, la lógica de codificación resulta bastante sencilla y podría realizarse un script en Python para obtener el texto plano –como hemos hecho en otras publicaciones. No obstante, si quisiésemos aplicar Frida al análisis de estos textos para reducir el tiempo de análisis podríamos utilizar un script como el que se muestra a continuación.
[python]
#!/usr/bin/env python
import frida
import sys
def on_message(message, data):
if message['type'] == 'send':
print(message['payload'])
elif message['type'] == 'error':
print(message['stack'])</pre>
js = """ Dalvik.perform(function () {
send("[*] Instrumentando...");
var textoOfuscado = "6401061427999759705.6454024122387634229.58C2CB292355F79.10AF8E83DF91F4B2.2375487142FCED61.4039D9E2EAFCF911.58C2CB292355F79.4CFD73A05F7756AE.363715F8B6559D9D.C3496569D735D3C.771EBE95B86866.4039D9E2EAFCF911.10AF8E83DF91F4B2.C3496569D735D3C"; // Texto acortado
var unitSecurity = Dalvik.use("vn.units.unitSecurity");
var textoOriginal = unitSecurity.$new().rsaDecode(textoOfuscado);
send("[*] Texto original: " + textoOriginal);
});
"""
if __name__ == "__main__":
try:
session = frida.get_device_manager().enumerate_devices()[-1].attach("com.whatsapp.downloaders")
script = session.create_script(js)
script.on('message', on_message)
script.load()
sys.stdin.read()
except
KeyboardInterrupt as e: print '[*] Terminado'
sys.exit(0)
[/python]
El script anterior nos permitiría rápidamente tomar la información capturada en los paquetes de red y decodificarla utilizado los métodos del malware pertenecientes a una nueva instancia de la clase creada en el momento.
La salida producida por la instrumentación se muestra en las siguientes líneas:
denise@lab:~$ python frida_app.py
[*] Instrumentando...
[*] Texto original: dc4ed87a0ecae860|com.whatsapp.downloaders|com.example.android.apis|com.android.gesture.builder
Con lo que podemos ver que se están transmitiendo nombres de paquetes de aplicaciones.
También podríamos querer interceptar estos mensajes en tiempo real. Para ello deberíamos cambiar el JavaScript por el código que sigue:
[python]
js = """
Dalvik.perform(function () {
send("[*] Instrumentando...");
var unitSecurity = Dalvik.use("vn.units.unitSecurity");
unitSecurity.rsaEncode.implementation = function(textoOriginal) {
var textoOfuscado = this.rsaEncode(textoOriginal);
send("[*] Texto original: " + textoOriginal);
send("[*] Texto ofuscado: " + textoOfuscado);
return textoOfuscado;
};
});
"""
[/python]
Instrumentación al alcance de todos
Frida es una potente herramienta, sencilla de utilizar y muy útil para el análisis de malware sobre múltiples plataformas. Aprender a desarrollar rápidamente scripts e inyectarlos en procesos de manera flexible resultará un gran aliado en la reducción del tiempo de análisis.
Particularmente para Android, aún resta trabajar sobre ciertos aspectos a mejorar, como la posibilidad de definir los puntos de instrumentación antes de que la aplicación comience a ejecutarse o el soporte para ART.