iOS Hacking: Introducción al análisis dinámico de aplicaciones con Frida
En este artículo pretende ser una breve introducción a LA HERRAMIENTA por excelencia en iOS usada en las auditorias de aplicaciones para el análisis dinámico. Como ejemplo básico, usare la aplicación de una conocida franquicia de comida rápida y de paso obtendremos algún que otro descuento “by the face”.
¿Qué es Frida?
Frida es un poderoso y flexible set de herramientas de instrumentación dinámica creada por Ole André V. Ravnås (@oleavr). Este set de herramientas permite, entre otras cosas, visualizar y manipular “on-the-fly” procesos en ejecución en múltiples plataformas (Windows, macOS, GNU/Linux, iOS, Android y QNX).
El núcleo de Frida funciona en C, pero la instrumentación se da a través del motor JavaScript que inyecta en el proceso. Inicialmente fue el conocido V8 de Google pero en la actualidad se usa por defecto Duktape. Básicamente Frida nos da una interfaz JavaScript para interactuar con la memoria del proceso en ejecución interpretándolo y permitiéndonos inspeccionar el estado de los objetos, variables e hilos que nos permite, por ejemplo, capturar un determinado parámetro pasado a una función o editar el valor devuelto por ella. Esta interacción la podemos desarrollar en diversos lenguajes (Python, Node.js, .NET o Qml, pudiendo extenderse a otros) y nos permite automatizar todo el proceso con unos simples scripts. ¡Casi ná!
Instalación
Para instalar Frida en iOS, el dispositivo deberá de estar previamente con Jailbreak, aunque es posible inyectarlo en una aplicación sin realizar este proceso, por comodidad, es la opción que recomiendo y ese proceso daría para otro artículo.
Simplemente deberemos agregar la fuente https://build.frida.re a Cydia y buscar Frida para instalarlo como cualquier otra app de Cydia.
Podemos interactuar con el demonio de Frida (frida-server) conectándonos a él por USB o túnel SSH para trabajar cómodamente desde nuestro equipo. Por defecto está a la escucha en el puerto 27042 de forma local únicamente.
Para conectarnos al demonio desde nuestro equipo, también deberemos instalar Frida. La instalación de Frida en Windows, macOS o GNU/Linux se realiza de forma sencilla mediante pip. Personalmente os recomiendo usar macOS ya que tiene más compatibilidad con la mayoría de las herramientas utilizadas en el proceso de pentesting en iOS. Si no disponéis de un Mac, siempre podéis optar por tenerlo virtualizado con VMWare o VirtualBox.
En consola escribimos:
sudo pip install frida
La instalación de todas las herramientas y dependencias se realizará de forma automática. ¡Ya tenemos todo lo necesario para trabajar con Frida! ¡Así de simple!
Primera toma de contacto
Como he comentado antes, Frida permite conectarse al demonio de distintas formas. La forma más simple es conectando nuestro iDevice por USB y utilizando el parámetro –U que en la versión actual debería de funcionar todo correctamente. Para realizar la conexión mediante túnel SSH tenemos que mapear el puerto 27042 de nuestro iDevice en local:
ssh -L 27042:localhost:27042 root@IP_DISPOSITIVO
Y utilizar el parámetro –R.
En primer lugar, vamos a ver que procesos están activos en el dispositivo con:
$ frida-ps -U -a
PID Name Identifier
—- ———- ————————-
841 Calendario com.apple.mobilecal
839 Mail com.apple.mobilemail
4496 ACME’s es.acme.restaurantes
Para este ejemplo nos interesa ACME’s, una aplicación de una franquicia de hamburguesas bastante conocida que llamaremos ACME’s, copiamos su nombre para usarlo en los siguientes comandos. Prefiero usar el nombre y no el PID ya que con las pruebas se suele cerrar el proceso y su PID cambia, el nombre no.
Como primer ejemplo, vamos a tracear el uso que hace la aplicación de la función Open usando Frida-trace. Con el comando:
frida-trace -U -i «open*» «ACME’s»
Donde –i es para especificar un nombre de una función (se pueden agregar tantos –i u otro identificador como se quieran) y agregando el asterisco al nombre de la función valdrá como comodín para tracear todas las funciones que comiencen por open. Frida-trace automáticamente buscará todas y cada una de ellas y nos creará en el mismo directorio de trabajo un nuevo directorio llamado “__handlers__” y en su interior autogenerará por cada función encontrada, un fichero JavaScript con el código por defecto autogenerado por Frida.
Si todo ha ido bien, usamos la app y podemos ver lo siguiente en la consola:
Y, ¡Magia! Con dos simples comandos podemos ver en qué momento se usa la función que nos interese. Para ir más allá con este ejemplo, vamos a editar el fichero autogenerado por Frida para que nos muestre que parámetros está pasando a la función open. Para ello editaremos con nuestro editor de texto preferido el fichero “__handlers__/libsystem_kernel.dylib/open.js”. En este fichero nos encontramos definidas dos funciones, onEnter y onLeave. Con ellas podremos hookear la función cuando se accede a ella para capturar por ejemplo los parámetros (args) o cuando finaliza su ejecución para capturar los valores devueltos (retval).
Para el ejemplo, nos interesa conocer los parámetros pasados a la función para conocer que ficheros intenta abrir. Así que, utilizaremos onEnter dejándola así:
onEnter: function (log, args, state) {
path = Memory.readUtf8String(args[0]);
log(«open(» + path + «)»);
},
Guardamos, cerramos Frida-trace y volvemos a ejecutarlo:
Ya sabemos que parámetros está recibiendo esta función 🙂
Dependiendo del tipo de valor que reciba la función, deberemos usar una función u otra en JavaScript para que no nos pete y poder ver el texto en claro que recibe. En este caso es un string y usamos Memory.readUtf8String. Tened en cuenta que estamos trabajando siempre directamente sobre la memoria del proceso y en ella pueden hacerse referencias a las múltiples abstracciones que nos permita el lenguaje. Disponéis de toda la documentación en el sitio web de Frida https://www.frida.re/docs/javascript-api/
Pero bueno, no os he soltado toda esta parrafada para simplemente ver que carga una app cualquiera, vamos a ver qué más podemos hacer.
Consiguiendo descuentos gratis
Esta app incluye una zona de Ofertas donde nos dan cupones descuentos en base a un ranking que obtenemos escaneando los tickets de compras anteriores. Como no estoy registrado y suelo tirarlos, la oferta que me interesa está en “Nivel Oro” y no puedo acceder a este nivel ¿o quizás sí?
Tras ver por encima la app interceptando el tráfico y demás. Se intuye que no han puesto muchos impedimentos para que cualquiera pueda acceder a estos descuentos de “Nivel Oro”.
Para seguir con los ejemplos simples de Frida, vamos a ver cómo podríamos llegar a hookear la función encargada de permitir acceder a estos descuentos para que nos los muestre sin ser nivel Oro.
En primer lugar, tenemos que localizar dicha función. Para ello tenemos que saber que clases son las involucradas y esto lo podemos averiguar mediante classdump-z y el binario descifrado o también directamente con Frida.
Para ello, vamos a servirnos de un script que encontré en github find-classes.js (https://github.com/interference-security/frida-scripts), aunque hay mejores implementaciones, esta nos valdrá para el ejemplo.
Escribimos en consola:
git clone https://github.com/interference-security/frida-scripts.git
Para tenerlo en local. Ahora vamos a utilizar la funcionalidad de Frida de ejecutar scripts en JavaScript para automatizar tareas con los procesos.
El siguiente comando ejecuta el script y almacena los valores en un fichero de texto:
frida -U -l frida-scripts/iOS/find-classes.js «ACME’s» > clases
Tras un rato ejecutándolo, nos saltará un error de Python. Esto quiere decir que ya ha terminado de dumpearnos todas las clases y el error es debido a que salta la consola interactiva de Frida y se está redireccionando toda la salida a un fichero de texto. Cerramos Frida y haremos grep al fichero para ver que posible clase podría ser la que nos interesa.
El lenguaje de programación usado en iOS es objetive-c. Este, está basado puramente en objetos. Sabiendo esto, las ofertas posiblemente estén abstraídas en objetos. Viendo el fichero por encima, vemos que se usan nombres en inglés, así que probemos con Offer para ver si salta algo.
$ grep Offer clases
¡Bingo!
Tenemos una buena candidata. Usemos Frida-trace para ver si es así.
$ frida-trace -U -m «*[Offer *]» «ACME’s»
Con esto, traceamos todas las funciones de la clase Offer (segundo asterisco), sean privadas o públicas (el primer asterisco). Si quisiéramos tracear otra clase, únicamente tenemos que agregar otro parámetro –m con su correspondiente clase.
Ahora, si intentaremos acceder a una de las ofertas restringidas para ver si nos devuelve algo.
¡Pues parece que vamos por buen camino!
loyaltyLevel, o nivel de lealtad. Parece ser que se comprueba el nivel de lealtad con esta función para intentar acceder a una oferta. Vamos a ver los parámetros devueltos por la función para ver si realmente es así.
Editamos el fichero __Offer_loyaltyLevel_.js en su declaración onLeave con el siguiente contenido:
onLeave: function (log, retval, state) {
console.log(«loyaltyLevel -> » + retval.toString(16));
}
Ejecutamos Frida nuevamente e intentamos acceder a ofertas de distintos niveles.
¡Parece ser que estábamos en lo correcto!. Dependiendo del nivel, la función devuelve un valor u otro, 0 para el Bronce, 1 plata y 2 Oro. ¿Qué pasará si reemplazamos el valor devuelto por 0 siempre? Agregamos a la función otra línea más:
onLeave: function (log, retval, state) {
console.log(» loyaltyLevel ->» + retval.toString(16));
retval.replace(0);
}
Guardamos el script, cargamos de nuevo la pantalla y ¡Ya tenemos todos los cupones descuentos “by the face”! 😀
Las ofertas que antes salían como Nivel Oro o Plata, ahora salen en bronce y podemos usarlas sin problemas.
A hincharnos de hamburguesas con patatas por 2,9€ XD
Hasta aquí este mini tutorial de introducción a Frida. La implementación para automatizar todo desde el propio dispositivo os la dejo a vosotros.
Espero que os haya gustado y para cualquier duda, podéis contactar conmigo por twitter en @4r4nL
¡Sed buenos!
Arán Lora.