martes, 24 de febrero de 2015

El malware para Android, cada vez mas sofisticado

En una entrada anterior del blog hablamos de una técnica que consistía a usar una librería nativa como libAPKProtect para descifrar código Dalvik. Durante el análisis de un nuevo APK hemos encontrado un malware que usa un concepto similar al mostrado por libAPKProtect, pero que usa técnicas mas sofisticadas para ofuscar el código y hacer mas difícil la ingeniería inversa. La relevancia de las técnicas empleadas hace interesante su análisis.

En primer lugar, detectamos que el malware carga una librería denominada libprotectClass.so”. Esa librería como sugiere su nombre descifra una parte de la aplicación ofuscada. El malware carga esa librería desde la función “attachBaseContext” que en primera instancia determina la arquitectura del sistema (Intel o ARM), y en segunda instancia carga la librería nativa correspondiente (“libprotectClass.so” en nuestro caso) ejecutando el comando System.load (“libprotectClass.so”).


Analizando la librería nativa del malware, nos damos cuenta que no tiene un formato ELF valido. En efecto, la librería tiene más secciones “header” que secciones definidas. También tiene una sección más grande que el tamaño del malware, y por último tiene un tamaño de sección “header” invalido.

Cada sección es mapeada en la memoria del proceso y tiene una semántica definida. La sección “.text” contiene el código del programa, “.data” contiene las cadenas, etc. Resulta que, por ejemplo, no se puede cargar la librería con readelf (utilidad para mostrar información sobre archivos ELF). Sin embargo, con Android funciona perfectamente, es decir que no es tan riguroso como readelf.


Tras arreglar el tamaño de cada sección de la cabecera a 40 bytes, bajar la cantidad de secciones a 23, y ajustar la sección que excede el tamaño del archivo, se ha podido arreglar el problema. Abajo se muestra la lista de secciones presentes en el malware. 


Cuando una librería nativa se carga por Android desde la llamada System.load de la maquina virtual Dalvik (DVM) se llama a la funcion “JNI_OnLoad” de esa librería. Sin embargo, esa función parece estar cifrada:
JNI_OnLoad cifrada

Mirando a más bajo nivel, en la función “dvmLoadNativeCode” de la libreria “libdvm.so” de Android, vemos que esta función llama a “dlopen” para cargar la librería “libprotectClass.so” en el proceso. La función “dlopen” se encarga de cargar la librería en memoria y llama a los constructores definidos en la librería nativa. Es decir, a la función “init” de la librería nativa (si esta definida) y a las funciones donde las referencias están definidas en la sección “.init_array” del malware. Esas funciones permiten inicializar la librería. El malware usa esta funcionalidad para descifrar la función “JNI_OnLoad”:).

Después de haber inicializado la librería, Android recoge la dirección de la función “JNI_OnLoad” llamando a la función “dlsym”, y por fin llama a “JNI_OnLoad”. Entonces, sabemos que la función está descifrada cuando Android ha recogido la dirección de la función “JNI_OnLoad”, y así podemos poner un breakpoint:).

JNI_OnLoad descifrada

Comprobaciones anti ingeniería inversa

Al principio de la ejecución de la librería nativa, el malware hace cinco comprobaciones para detectar la presencia de un depurador, evitar la lectura de memoria, etc.

El malware realiza las siguientes comprobaciones:

1) Para evitar que un programa externo lea la memoria del malware, se monitorizan todos los accesos a los archivos “/proc/pid/mem” y “/proc/pid/pagemap” gracias a las funciones “inotify_init” y “inotify_add_watch”. Si detecta una lectura de su memoria por un programa externo, el malware termina su ejecución. En efecto, una manera muy rápida para extraer todos los archivos descomprimidos/descifrados es leer la memoria del malware después de descifrarlo y buscar, por ejemplo, con una herramienta como “volatility” los nuevos DEX creados (conteniendo el código Dalvik).

2) “Hookea” las funciones de Android “__android_log_write” y “__android_log_buf_write” de la librería “liblog.so”. Estas funciones, usadas para escribir en el log de Android, ayudan a realizar la ingeniería inversa porque revelan los archivos abiertos, los DEX cargados, etc. El sistema de explotación Android usa ese log tanto a nivel kernel como de usuario.

3) Igualmente tambien “hookea” las funciones usadas por el depurador de código Dalvik como “dvmDbgActive”. Con esto evita que la aplicación pueda estar depurada al nivel de la maquina virtual Dalvik (DVM).

4) Recorre todos los procesos activos y busca la cadena “android_server” que corresponde al depurador de IDA ejecutándose en el dispositivo. Por eso, abre la carpeta “/proc” que contiene todos los PID de los procesos activos. Forma la cadena “/proc/pid/cmdline” y abre el archivo correspondiente para recoger el nombre del proceso y lo compara con “android_server”.

5) Comprueba si el nombre del proceso padre es “strace”, “ltrace”, “gdb” o “android_server”. “Strace” y “ltrace” permiten de listar las llamadas al sistema y así ver los archivos leídos/escritos, memoria leída/escrita, etc. En cuanto a “gdb” y “android_server”, son programas que permiten depurar un proceso.

Cifrado interno

Merece la pena comentar que todas las cadenas usadas por el malware están cifradas. Cada cadena es codificada en base64 antes de pasar por un XOR.


Tras las comprobaciones el malware carga una nueva librería nativa, localiza la función “JNI_OnLoad” y procede a descifrarla.


En la nueva función “JNI_OnLoad”, el malware hace llamadas a métodos Dalvik, definidos en la maquina virtual (DVM). Usa el JNI, que se puede ver como un puente entre la maquina virtual y la librería nativa, para comunicar con la DVM desde la librería nativa. Por ejemplo, se puede usar ésta técnica para “hookear” los métodos Dalvik. Se muestra un ejemplo de uso de la API de Android para recoger la clase “com/qihoo/util/StubApplication”.

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
        JNIEnv *env;
        jvm->GetEnv((void **)&env, JNI_VERSION_1_6);
        jvm->AttachCurrentThread(&env, NULL);
        gJClass = env->FindClass("com/qihoo/util/StubApplication");

        return JNI_VERSION_1_6;
}
En esta función, el malware recoge el hash de la firma del APK llamando a métodos Dalvik. Pensamos que el malware comprueba si el APK fue modificado. En efecto, cada APK necesita estar firmado antes de ser instalado en el móvil. Si queremos modificar el APK, necesitamos firmarlo otra vez, con un certificado diferente.

Comprueba la versión del SDK actual del sistema (“ro.build.version.sdk”) y del Android runtime (Dalvik o ART). Pensamos que hace esas comprobaciones para asegurarse que los métodos Dalvik necesarios para la ejecución del malware existen en la versión del sistema.

Finalmente, reserva memoria en el proceso mapeando el dev “/dev/zero”, copia el DEX cifrado en esta zona de memoria, y lo descifra. Abajo se puede ver el principio del nuevo DEX descifrado:)


Ahora, podemos hacer un volcado del nuevo DEX y abrirlo con un desensamblador de código dalvik. Se muestra una parte de una función desensamblada del nuevo DEX.


Por fin, el malware registra todas las clases del nuevo DEX como “Lcom/astep/pay/theActivity”, “Lcom/astep/pay/rd/Rd1Activity”, “Lcom/common/as/activity/DlActivity”, etc, y cambia la variable strEntryApplication de la clase “com/qihoo/util/StubApplication” a “com.ly.tcmy.application.PeibanApplication” para que la nueva aplicación sea cargada por el malware.


Este malware usa algunos de los métodos de ofuscaciones y anti-debugging mas sofisticados de los que hemos visto, como por ejemplo los incluidos en APKProtect. Estas ofuscaciones están presentes tanto al nivel del DVM como de la librería nativa. Ademas, el malware llama a funciones métodos Dalvik desde la librería nativa y así manipula objetos de la DVM que hace la depuración mas difícil. Por encima, el malware se aprovecha del poco rigor de Android para manipular la librera nativa.

Más información:

SHA256 de la muestra analizada:
1987a8b5858762247b77b62dd7ce8494c5442a19e0c883a5359332e09342b614

Introduction to Dynamic Dalvik Instrumentation,




Laurent Delosières

viernes, 24 de octubre de 2014

Análisis de un phishing

El método de propagación de los phishings, en su gran mayoría y casi por definición, sigue siendo el mismo desde hace bastantes años, a través de email. Hace unos días recibimos uno del Banco Santander Chile en el correo del laboratorio. El email, en su conjunto, estaba bien construido, con un lenguaje correcto y sencillo, que nos indicaba que nuestra "Clave 3.0" estaba bloqueada porque aún no se había verificado y nos invitaba a entrar para verificar los datos a través de un enlace.

El phishing que nos mostraba al pulsar en el enlace del correo se encontraba en un sitio vulnerado, cuyo tiempo de carga era un poco largo, por lo tanto esto nos hizo sospechar que el contenido no estaba alojado realmente en el mismo servidor, o que algo raro sucedía, como explicaremos más adelante.

A través de una escalada de directorios, nos encontramos que el servidor, por defecto, listaba el contenido de sus carpetas, y además, nos encontramos un archivo zip llamado "www.santander.cl.zip". Los atacantes suelen subir el kit de phishing comprimido a la página vulnerada y posteriormente extraerlo para tener el phishing listo. En este caso, olvidaron borrar el archivo zip después de extraerlo.

Teniendo ya el zip en nuestro poder nos permite analizarlo al detalle. Este tipo de oportunidades no se deben dejar pasar, pues suponen una valiosa fuente de información para nuestro trabajo de antifraude en el laboratorio.

Al descargarlo y descomprimirlo, observamos que como suponíamos, eran todos los archivos del phishing, así que procedimos a reproducirlo en local para estudiar su funcionamiento, que por su interés mostramos a continuación.

Lo que ve el usuario 

En primer lugar vamos a mostrar el funcionamiento desde el punto de vista del usuario/cliente afectado y posteriormente cómo el phishing almacena y utiliza los datos que le proporcionamos.

Al acceder a la página, se carga una copia falsa de la portada principal del banco. En ella, como buenos usuarios confiados, procedemos a introducir el RUT (número de identificación nacional para los habitantes de Chile) y la clave:


En este punto el phishing hace tres comprobaciones. En primer lugar el RUT y la clave deben tener un formato válido. Además, comprueba contra la página real del banco (por supuesto, en segundo plano), que las credenciales son correctas. En caso de que el RUT y clave tengan el "formato" adecuado pero no seamos clientes del banco (o nos hayamos equivocado en alguno de ellos), nos mostrará la siguiente pantalla de error, pidiéndonos que accedamos 15 minutos después, achacando un error en sus sistemas (y además con faltas ortográficas).


En caso contrario, es decir, somos clientes y nuestro RUT y clave son correctos, pasa a una pantalla en la que solicita la tarjeta de coordenadas completa, que además plantea de manera desorganizada, para parecer más real. Hay dos pequeños detalles en esta pantalla, y es que el phishing ha conseguido ya obtener el nombre y apellido del cliente y el número identificativo de la tarjeta de coordenadas (señalados en verde).


Una vez se rellenan todos los datos, reenvía al usuario a la página real del banco para levantar menos sospechas. En caso de que no se rellenaran todas las casillas o alguna de ellas no tuviera el formato correcto (2 dígitos numéricos), avisaría con un error y se quedaría a la espera para que se corrija.



Este es el proceso "visible" para el usuario, pero vamos a estudiar el código que hay detrás de esto.

Como se realiza el trabajo "sucio"

En primer lugar, el atacante hace uso en todo momento de la librería "cURL" de PHP. Esta facilita la realización de conexión HTTP sobre otro servidor, siendo capaz de mantener las sesiones y cookies, para facilitar bastante el trabajo.

En la primera pantalla, carga el contenido de la página original del banco "http://www.santander.cl/" haciendo uso de cURL. Esto hace que el tiempo de carga aumente, que fue una de las cuestiones que nos llamó la atención.


En el primer bloque de código hace la petición a la página real del banco y descarga el contenido en la variable "$html". En el segundo bloque reemplaza las rutas relativas por rutas absolutas, para que se carguen correctamente las imágenes y demás recursos de la web (JS, CSS) y que además los enlaces sean correctos. En el último bloque modifica la dirección de envío del formulario de identificación, escribiendo una dirección que mantiene bajo su control "?STP=login".

Posteriormente, para comprobar si el RUT y la clave introducidas por el usuario son correctas, vuelve a hacer una petición con cURL sobre la dirección real "https://www.santander.cl/transa/cruce.asp". En caso afirmativo continúa haciendo sendas peticiones sobre
"https://www.santander.cl/transa/segmentos/Menu/views/sections/vsTS.asp?sectionCode=TC1" y "https://www.santander.cl/transa/mensajeria2/pivote_superclave.asp" para obtener el nombre y apellido del cliente y número identificativo de la tarjeta de coordenadas respectivamente.

Toda esta información la mantiene guardada en la sesión de PHP para posteriormente usarla en el proceso de solicitud de la tarjeta de coordenadas, como pudimos ver en la tercera imagen. Todo con objeto de darle más credibilidad al phishing.

Pasamos al momento de generar la página en la que nos solicita la tarjeta de coordenadas.


Con la función GenerateCard() genera un array asociativo con todas las coordenadas y posteriormente, con el código remarcado, extrae coordenadas pseudo-aletorias del array y genera la tabla HTML para la petición de coordenadas.

Como hemos mostrado en la imagen anterior, las solicita de manera desordenada y además tiene 2 métodos de control para asegurarse, en cierto modo, que el usuario no se equivoca al introducirlas. El primero de los métodos es que el usuario debe introducir todas las coordenadas, no puede dejar ninguna en blanco. El segundo es que cada una de las coordenadas deben ser numéricas y tener al menos 2 dígitos. LONGCOORDS es una constante que está fijada a 2 en la cabecera del archivo (no se aprecia en la imagen).


Para terminar, guarda todo esto en un fichero de logs ("Logsdb.txt"), definido en la constante DB_FILE) en el servidor y además, lo manda por correo usando el servidor SMTP configurado en el servidor vulnerado.

La manera de guardar este archivo de logs también es bastante curiosa, pues si lo guardara en texto plano, cualquiera que conociera la ruta del archivo podría abrir el archivo "Logsdb.txt" y ya tendría el "trabajo hecho". Para ello, antes de guardar la información en el registro, la cifra.


Para comenzar la función de cifrado utiliza la variable $string que son los datos robados y la clave $key, que está definida en el código.

  • La línea 5 lo único que hace es extraer cada uno de los caracteres. 
  • La linea 6 extrae un carácter de la clave siguiendo este patrón: si el bucle sólo contuviera esta línea de código y la clave fuera "1234", nos generaría como resultado la cadena "412341234...", con la misma longitud que el texto de entrada. 
  • La línea 7 es la que en realidad hace el cifrado. Realiza una SUMA del valor en decimal de ambos caracteres (el del texto plano y el correspondiente de la clave) y después lo vuelve a transformar en carácter. 
  • La línea 8 concatena todos estos caracteres en la variable "$result". 
  • Para finalizar la función, devuelve el contenido de la variable "result", pero antes lo codifica en base64, ya que los caracteres generados pueden no quedar en el rango ASCII y generar problemas al almacenarlos en disco.

El cifrado es bastante débil, con un pequeño análisis estadístico se podría extraer el texto en claro. Basa la fortaleza del cifrado en lo que se conoce como "seguridad por oscuridad", es decir, se supone que nadie conoce el método excepto él.

Por comodidad, el atacante ha creado la página "index.php?STP=logers" en la que introduciendo un usuario y contraseña, que tiene previamente definidos en el código, se muestra todo el contenido de los logs en texto plano. Para ello utiliza el siguiente código, junto con una captura de los logs (obviamente no son datos reales):


En ella comprueba si el usuario ha introducido el usuario y la contraseña definidas en el código, si es así, muestra el contenido de todos los logs que tiene en el archivo, no sin antes descifrarlos con el método "Desofuscar()". Si no se introduce el usuario y la contraseña o son incorrectos, devuelve un formulario para hacer login.

Y para finalizar, vamos a explicar cómo funciona el método "Desofuscar()", aunque en realidad hace lo mismo que "Ofuscar()", pero a la inversa.



  • En la línea 4 decodifica el texto que tenía almacenado en base64. 
  • En la línea 6 extrae carácter a carácter de la cadena cifrada. 
  • En la línea 7 extrae el carácter correspondiente de la clave, exactamente igual que el método de cifrado. 
  • La línea 8 es la que se encarga realmente de descifrar. Cuando se cifraba se realizaba una suma de los valores en decimal de los caracteres, pues ahora se tiene que realizar la resta del valor almacenado menos la clave. Con ello obtenemos el carácter original. 
  • En la línea 10 concatena los resultados que tenía y cuando termina la cadena completa, la devuelve.

En resumen, el flujo de trabajo del phishing se resume en la siguiente imagen.


Conclusiones

Después de este pequeño estudio podemos sacar algunas conclusiones:
El objetivo final es claro, recoger la máxima información del cliente para posteriormente poder realizar transacciones ilegítimas. Las novedades que han llamado la atención a nuestro departamento antifraude son, en primer lugar, la validación contra la página web del banco, si bien es cierto que no es el primer caso, este banco lo hace un poco más fácil para el atacante, ya que no usa sistemas como el "CSRF token" (por ejemplo), lo cuál no elimina el riesgo, pero complicaría un poco el proceso.

Por otro lado, la forma de engañar al usuario utilizando su nombre real y el número exacto de su tarjeta de coordenadas. La mayoría de phishings que tratamos en nuestro departamento antifraude a diario no se molestan en extraer tanta información para simplemente mostrarla al usuario. En algunas ocasiones simplemente, en lugar del nombre, usan algo genérico como "Cargando...".

Y para terminar, la forma de guardar la información de los usuarios (cifrada) es de lo más interesante. No es un cifrado robusto, como ya hemos dicho anteriormente, pero también es cierto que pocos "phishers" se molestan en hacer esto, simplemente lo guardan en texto plano en algún archivo con nombre extraño.

El código también tiene referencias al sistema de autenticación por SMS, pero no hace uso de él. No sabemos si es por un mal desarrollo o simplemente porque el atacante todavía no han tenido usuarios reales para probarlo. Bien es cierto, que si finalmente se implanta este método (a través de SMS), la recogida de datos del phishing sería más compleja, lo que esperamos que alerte al usuario, porque se haría la transferencia fraudulenta en el mismo momento en que el usuario está introduciendo los datos en el phishing.
  

Antonio Sánchez