Es muy común hoy en día que los equipos de desarrollo se apresuren para cumplir con los plazos. Una de las primeras cosas que se dejan de lado es la calidad del código en general, pero un punto que está entre los más importantes, y después de algunos años en el mercado puedo afirmar claramente que es el primero que se deja de lado, es la seguridad.

El objetivo de este artículo es concienciar a nuestra comunidad de que un proceso de desarrollo es algo que debe hacerse a su ritmo y con los desarrolladores concentrados en lo que realmente implica construir software seguro. Intentaré explicar algunos de los procesos implicados en un eventual ataque para mostrar a los desarrolladores algunas ideas de cómo su código podría ser utilizado en beneficio de otra persona.

 

Ingeniería inversa de una aplicación iOS

[Exención de Responsabilidad: sólo describiré los procesos y mencionaré las herramientas y los detalles, pero esto no pretende ser una guía sobre cómo hacer nada de esto y no entraré en detalles sobre la configuración del entorno.]

El objetivo

Pongamos las manos como si fuéramos el atacante, lo primero que hay que hacer es encontrar el objetivo y empezar a buscar vulnerabilidades. El objetivo sería una aplicación real, el archivo .ipa bundle, pero no es suficiente con sólo descargarlo de la App Store. Apple encripta todas las aplicaciones, así que el primer paso sería desencriptar el archivo bundle. En el iOS 12 por ejemplo esto se puede hacer con frida, el resultado será un archivo .ipa desencriptado. Básicamente lo que puedes encontrar dentro es el archivo binario del código real, y todos los archivos incluidos en el bundle. Por ejemplo, activos, archivos de lista de propiedades (.plist), archivos JSON y cualquier otro archivo que el desarrollador haya agregado. Ahora, estamos listos para comenzar lo que se llama Análisis Estático.

Análisis Estático

Este es el proceso de inspeccionar los archivos que obtuvimos en el paso anterior y buscar posibles vulnerabilidades. Permítanme primero hacer una línea entre “bundle files” y el propio binario de la aplicación (donde está el código), primero veamos los archivos bundle.

En la siguiente imagen verás cómo podrían ser los archivos bundle de una simple aplicación:

Como puede ver, ya tenemos algunos archivos que nos resultan familiares: Info.plist, GoogleService-Info.plist, un archivo .json aparentemente relacionado con la configuración de firebase, una carpeta de marcos… ¿Alguna idea ya? Lo primero que hay que tener en cuenta es que el acceso a estos archivos es una tarea realmente fácil, así que cualquier información almacenada aquí está en manos de cualquiera. Esperemos que no haya ninguna clave privada almacenada allí o claves de configuración del SDK. Tampoco sería raro encontrar otros archivos de configuración que puedan darte muchas pistas sobre los diferentes comportamientos de la aplicación, por ejemplo, banderas de características.

¿Y si encontramos esto?

 

 

Podemos adivinar que hay algún código que se supone que no debe ser ejecutado todavía y que hay una comprobación de eso en el lado del cliente en un caso particular tal vez.

La Info.plist es también una gran fuente de información, algunas capacidades habilitadas en la aplicación están aquí, por ejemplo NSAllowsArbitraryLoads (habilita configuraciones débiles de TLS en algunos dominios) y CFBundleURLTypes que describirá las Scheme URLs comúnmente usadas para permitir que otras aplicaciones lancen la suya a través de una URL (que casi con seguridad está analizada en el cliente 👀).

Mirando de nuevo a nuestros Bundle Files, podemos ver una carpeta de Frameworks, allí puedes encontrar archivos de paquetes para frameworks incluidos en el destino, y obtener fácilmente qué versiones está usando. Por ejemplo, hubo un caso de una vulnerabilidad que afectó a muchos proyectos en una versión de Alamofire, más info sobre esto aquí.

Ahora veamos qué podemos obtener del binario de la aplicación, primero usaremos una herramienta llamada class_dump_z. Como su nombre lo indica, podemos volcar todas las clases a un archivo y comenzar a indagar en los nombres que llaman nuestra atención. Lo siguiente, para profundizar un poco más en el código, podría ser desensamblar y descompilar el binario, para ello podemos utilizar dos herramientas: hopper o ghidra. Ambas son similares y lo que harán es dejarte ver el código descompilado de diferentes maneras (ensamblado, pseudocódigo, hexadecimal, gráfico de control de flujo).

Por ejemplo, aquí puedes ver el pseudo código (en hopper) de un método viewDidAppear:

Como puedes ver, r4 es una referencia a NSUserDefaults, y el if en la siguiente línea obtiene el valor de isProVersion en NSUserDefaults y ejecuta un código específico dependiendo de él.

En esta etapa debemos reunir toda la información que podamos para tener algo que hacer en lo que se llama Análisis Dinámico.

Análisis Dinámico

 

Este es el último proceso que describiré porque aquí es donde ya tenemos el control real de lo que podemos hacer con la aplicación en sí, y finalmente podemos escribir código y ver si la aplicación es vulnerable.

Imagina que encontramos que el objetivo utilizaScheme URLs (vimos los CFBundleURLTypes en el Info.plist), ahora que tenemos acceso al código, podemos buscar el código que maneja las Scheme URLs, este es el método application:openURL:options: método de la clase AppDelegate:


Asumiendo que r19 contiene la URL que viene de “el exterior” (podría ser un navegador de tu iPhone), esta url debería ser algo como “tuApp://<algo>”. Si analizamos este código, obtenemos que si la URL contiene “noticias” entonces eliminará las ocurrencias de “news/”, instanciará un WebViewController y lo configurará con la URL, que por el nombre del método configureWithHTMLString sabemos que espera HTML. Así que hay una pista de cómo un script HTML podría ser ejecutado en esta aplicación con sólo pegar algún URL codificado en su navegador, sí, ahora cualquier código javascript podría ser ejecutado dentro de este WebViewController. Por ejemplo, si encuentro una base de datos encriptada en el paquete, y encuentro que la clave para descifrar la clave está en el archivo Info.plist, podría escribir un script HTML con una función Javascript para abrir ese archivo de base de datos y publicarlo en un servidor remoto o lo que quiera hacer con él.

Vamos ahora con el último fallo, ¿recuerdas ese código con una comprobación del valor bool de “isProVersion” almacenado en NSUserDefaults que encontramos durante el análisis estático? Bueno, veamos cómo podemos ser Pro sin serlo realmente, es bastante simple. Haremos uso de frida de nuevo, y simplemente escribiendo el código que necesitamos en un archivo .js y lanzando la herramienta, hemos terminado.

Siendo este nuestro setProVersion.js:

Podemos ejecutar frida -U -l setProVersion.js VulnerableApp y ahí está su verdadero valor para la clave de isProVersion inyectada en la aplicación.

Conclusión

 

Debo decir que da miedo ver cómo la mayoría de las aplicaciones de la AppStore tienen al menos una de estas vulnerabilidades, tal vez sin mucho que aprovechar, pero como dije al principio esto tiene como objetivo aumentar la conciencia.

Es muy común introducir involuntariamente fallos de seguridad, y no sólo los desarrolladores, sino equipos enteros deben ser conscientes de esto, porque algunos de los fallos implican el diseño de toda la infraestructura de un sistema.