21 octubre 2014

Evadiendo implementaciones de certificate pinning en Android

Pongamos la siguiente situación: estamos realizando el análisis de una aplicación en la que nos interesa ver las comunicaciones HTTP/S que realiza, para ello disponemos de un dispositivo Android rooteado en el que redirigimos el tráfico de estos protocolos a un web proxy del que tenemos instalado su certificado en el dispositivo.

Todo está configurado correctamente pero algo falla, la aplicación nos indica que no puede establecer comunicación con el servidor de destino y confirmamos que sin redirigir al web proxy la aplicación funciona perfectamente. Puede que nos hayamos encontrado con una implementación de certificate pinning, pero esto no nos va a detener.

Llevemos a la práctica un método de evasión sobre un ejemplo real cogido de Google Play.

La aplicación de ejemplo, eTools


Figura 1

eTools se presenta como una herramienta orientada a realizar búsquedas en la web utilizando los motores más conocidos de manera segura y totalmente privada, implementando entre otros mecanismos de seguridad un certificate pinning.

Preparamos el escenario que hemos descrito anteriormente (dispositivo rooteado + Proxydroid +  Burp + certificado Burp instalado en dispositivo), probamos a realizar una búsqueda utilizando la aplicación y confirmamos como el certificate pinning entra en juego y no nos permite realizar la búsqueda bajo estas condiciones:

Figura 2

Evasión por parcheo de código

Para saltarnos esta restricción el proceso que vamos a seguir se resume en descargar el APK y analizar su código, anular las llamadas que comprobarán el certificate pinning, re-empaquetar la aplicación y reinstalar.

Vamos con ello:

1. Descargamos el APK del dispositivo

Figura 3

2. Buscamos cadenas que apunten al uso de la clase TrustManager

Utilizando dex2jar podemos extraer el JAR del APK y utilizando JD-GUI podremos ver el código Java. Ya desde JD-GUI podemos comenzar nuestras búsqueda por el código:

Figura 4

Como vamos viendo, el código se encuentra ofuscado y puede llegar a complicarnos el análisis, aunque veremos que en este caso tenemos suerte ya que si abrimos la clase "a" encontraremos claramente que es la implementación de la interfaz X509TrustManager:

Figura 5

El código anterior nos deja ver la clave pública contra la que se validará cuando se realice la petición HTTPS. Tenéis información más detallada sobre esta implementación en la web de OWASP.

Por otro lado si miramos el otro resultado de la búsqueda en JD-GUI, la clase "c", encontraremos en ella el método que se encarga de realizar la petición por HTTPS:

Figura 6
He remarcado las partes más relevantes:
  • El primer bloque declara y asigna un array compuesto por la implementación de X509TrustManager. Este array se pasa como parámetro al objeto SSLContext que se utilizará para crear la conexión HTTPS.
  • En el segundo bloque se define que se utilizará la implementación X509TrustManager para la verificación.

En este punto la situación es clara, hay que anular en el método de la Figura 6 todo rastro de uso de la clase "a" si queremos evadir el certificate pinning implementado.

3. Modificamos el código

Tendremos que desensamblar el código Jasmin del APK para modificarlo, así que podéis seguir el paso a paso desde la documentación de dex2jar ModifyApkWithDexTool o utilizar el script modify.py que os comentaba en el artículo sobre inyección de Meterpreter en aplicaciones.

Si optáis por el script, saltaremos el primer paso ya que no necesitamos tocar nada en el AndroidManifest.xml y nos pararemos a modificar el código en el segundo paso:

Figura 7

Respecto a la modificación de código vamos atacar al punto más débil, la clase "c" en su método "a" encargado de gestionar la conexión por HTTPS.

Figura 8


Si recordamos la figura 6, en la línea de código "localSSLContext.init(...)" se pasaban dos valores nulos y el array de TrustManager. En nuestro caso para anularlo vamos a pasar en lugar del array otro valor null, de modo que sustituiremos el valor de la línea 469 por aconst_null .

También en la figura 6 veíamos como se definía el uso de la clase "a" para verificar el hostname, en este caso lo que haremos será eliminar las líneas 484, 485 y 486 para anular esa parte del código.

Finalmente nos debería quedar algo así:

Figura 9

Ya tenemos preparado el código modificado, sólo queda re-empaquetar y firmar el APK. Si estabais usando el script modify.py sólo tendréis que guardar el código modificar y dar a enter.

4. Probamos nuestra obra

Reinstalamos la aplicación:

Figura 10
Activamos Proxydroid, abrimos la aplicación y realizamos una búsqueda:

Figura 11

Perfecto, ya estamos capturando las comunicaciones:

Figura 12


Conclusiones

Realizar certificate pinning en nuestras aplicaciones puede aportarnos una capa más de seguridad, pero debemos entenderlo como una capa más y no delegar en esta técnica la seguridad de nuestras comunicaciones ya que como hemos visto no la hace inmune a su análisis.

Desde el punto de vista del ataque realizado, podría haberse complicado el escenario si hubiéramos encontrado una ofuscación más fuerte o si se hubiera incluido algún método de validación de código en tiempo de ejecución que debidamente ofuscado nos llevara a dedicar más tiempo de reversing (aunque incluso ante estas contra-medidas quedaría la posibilidad de aplicar otras técnicas de hooking y debugging).

Artículo cortesía de Miguel Ángel García