jueves, 4 de septiembre de 2014

APKProtect: desempaquetador de bytecode Dalvik dentro de una libreria nativa

En nuestro departamento de antifraude hemos analizado el APK de un troyano Android para envío de SMS, en él hemos encontrado una técnica de desempaquetado muy interesante. El malware usa una librería nativa integrada (libAPKProtect.so) en el APK para desempaquetar el bytecode Dalvik. Además de eso, implementa técnicas para hacer mas difícil el volcado de la memoria del proceso y extraer el bytecode Dalvik también implementa un control para terminar la ejecución en caso de que el APK haya sido reempaquetado. Entendemos que el lector está familiarizado con el entorno Android y con las herramientas para depurar las aplicaciones Android como ndk-gdb de la NDK de Android.



Al abrir el archivo "classes.dex" del APK que contiene el bytecode Dalvik, vemos que hay instrucciones que no fueron interpretadas porque los byte codes están cifrados. El cifrado dificulta la ingeniería inversa, evita que los sistemas automatizados inserten "hooks", etc.

Ejemplo de la funcion "onCreate" cifrada:












Recorriendo el archivo, vemos el constructor de la clase "APKMainAPP12D0C" que carga una librería llamada "APKProtect". El desempaquetador seguramente está dentro de la librería nativa. ¿Cómo? Sí, un desempaquetador de bytecode Dalvik está contenido en una librería nativa compilada para procesadores ARM. Necesitamos de depurar la librería nativa. Por eso, nos hemos inspirado en el blog "The Cobra Den" para configurar el entorno de depuración y la instalación de ndk-gdb de Google.


Cuando el malware carga la librería nativa con la función "loadLibrary", el sistema operativo llama a la funcion "On_JNILoad" de esa misma librería nativa. Depurando la librería, hemos puesto un breakpoint a esa función "On_JNILoad".

En esa función, la primera parte se encarga de  llamar a la función "ptrace" con el parámetro PTRACE_TRACEME. Evita el funcionamiento de todo tipo de depuradores como GDB al igual que tampoco funcionará cualquier tentativa de volcado de memoria.

En la segunda parte, el malware abre el archivo "/proc/self/maps" que contiene el mapeado de su memoria. Itera cada línea hasta encontrar la cadena "classes.dex", es decir la zona de memoria que contiene el mapa del archivo "classes.dex". Analizando esa misma línea, el malware conoce la localización en memoria del mapa del archivo "classes.dex".

Ejemplo del mapeado del codigo Dalvik en el proceso donde vemos que el archivo “classes.dex” esta mapeado entre la dirección 0x47c46000 y 0x47c53000.

47c46000-47c53000 r--p 00000000 1f:01 525 /data/dalvik-cache/data@app@google.service-1.apk@classes.dex

Luego el malware comprueba si es un DEX o un DEX optimizado (DEY) buscando la cadena "dex" o "dey". Si es un DEX, recoge 20 bytes y compara esos 20 bytes con bytes de su librería. Si no hay correspondencias, el malware lanza un hilo que termina la ejecución del proceso en un tiempo aleatorio. ¿Pero…? un momento, ¿20 bytes no es el tamaño de una firma SHA1? En la documentación de Google, vemos que cada archivo DEX tiene una firma SHA1 en su cabecera excepto los códigos optimizados (DEY). Sirve para asegurarse de la integridad del archivo, es decir que el archivo no fue modificado. Si el archivo fue modificado, vuelve a matar su proceso de la misma manera. Pensamos, que el malware comprueba eso para evitar todos los tipos de reempaquetado que consisten en desempaquetar, insertar hooks, y re-empaquetar.

Esta es la firma SHA1 del archivo "classes.dex" que hemos encontrado:
0x73 0xAD 0x50 0x7F 0x1C 0xC6 0x8A 0x4D 0x2E 0x8C 0xEE 0xF5 0xDA 0xF8 0xE7 0x7C 0x27 0x4D 0x97 0xE7

En la tercera parte, el malware recoge el bytecode Dalvik (dentro del map del archivo classes.dex), llama a la funcion mprotect para cambiar la proteccion de las paginas de memoria a escritura. Por cada parte del bytecode Dalvik cifrado, recoge cada byte (LDRB), aplica un Exclusive OR (EORS) con bytes de su libreria, y guarda el resultado (STRB) a la misma ubicacion. Cuando todas las partes estan decifradas, el malware llama de nuevo a la funcion “mprotect” para cambiar las paginas de memoria al modo lectura. Solo en modo lectura, porque es la maquina Dalvik quien va a leer los byte codes, ejecutarlos y no el procesador ARM :=).



Ahora que sabemos donde descifra el malware los bytecode, solo es necesario poner un breakpoint cuando ha terminado, es decir a la funcion "mprotect". Con el comando "dump memory /home/ubuntu/dump.bin 0x47c46000 0x47c53000" dentro de la consola de gdb, conseguimos un volcado de la memoria.  Solo falta abrir el volcado con un intérprete de bytecode Dalvik como IDA para ver el código descifrado.

La misma función "onCreate" pero descifrada: 

El desempaquetador APKProtect no fue diseñado por el autor de malware. En efecto, APKProtect es un producto que se encarga de proteger los APKs ante ingeniería inversa. Desafortunadamente, ayuda tanto a los buenos como a los malos.

Más información:

SHA256 de la muestra analizada  3aee81db24540fb6b3666a38683259fd32713187ec6e0b421da9b91bd216205f

NDK,

Debugging Apps with Native Code - part 1

Dalvik Executable Format

ApkProtect,



  
Laurent Delosières

jueves, 28 de agosto de 2014

Estudio del Algoritmo de Generación de Dominios de Murofet, una variante de Zeus

Cada vez es más frecuente que los troyanos empleen un gran número de dominios para tratar de ocultar sus acciones: desde puntos de descarga, puntos de envió de instrucciones, etc. Para el malware es importante dificultar a los investigadores la localización de los sitios desde donde operan, de forma que cuanto más diversificado esté, mejor. Para generar un gran número de dominios de forma controlada se emplean los algoritmos de generación de dominios o Domain Generation Algorithm (DGA).

Para el atacante es importante controlar los dominios que se generan cada día para poder registrarlos previamente. De ahí la importancia que tiene para los investigadores conocer y controlar de que forma funcionan estos algoritmos. El Domain Generation Algorithm (DGA) fue usado por las variantes de Zeus como Murofet. Otras familias han usado DGAs como Conficker, BankPatch, etc. En el caso del Murofet (descrito como variante de Zeus), el DGA se usa para contactar con un sistema infectado y enviar  un ejecutable destinado a robar datos bancarios.

En esta entrada, analizamos el algoritmo DGA usado por un troyano de la familia Murofet (SHA256 99370d5162c2d9e165892af3bde7c6de8c44ec5945ed0a1ddb6b827b876931d0).

Cuando el malware se lanza empieza a generar los dominios. Para ilustrar el funcionamiento, tomamos el ejemplo de 3 dominios generados "whvqwshpulytfvx.biz", "rtqispmsdmrqcn.info", y "rtqispmsdmrqcn.org". Cuando los dominios resuelven a una dirección IP, el malware hace una petición HTTP al dominio generado. Aquí, el malware recoge un ejecutable desde el último ordenador, lo comprueba y lo ejecuta. Estos dominios fueron generados el 27 de Agosto 2014, y aquí la fecha toma especial importancia.



El algoritmo DGA se compone de dos partes. La primera parte va a inicializar el algoritmo mientras que la segunda parte va a construir el dominio. La fecha sirve para inicializar el algoritmo. En otras palabras, el algoritmo genera dominios diferentes de un día para otro. Cada día, Murofet genera 800 dominios diferentes. La ventaja principal de usar este sistema es la de aumentar la dificultad de recoger las infraestructuras con un sistema automatizado, de esta forma el proceso de obtener el ejecutable puede tardar horas, días, o meses. La otra ventaja que vemos es que a priori no se puede ver que el ordenador de la victima esta infectado por un troyano bancario, ya que la parte responsable de robar los datos bancarios todavía no fue descargada.

El algoritmo DGA genera un nuevo dominio a cada iteracion llamando la funcion "get_domain". El dominio se genera en base a dos parámetros que son la semilla (seed) y el contador (counter).

def DGA ():
 seed = GetSystemTime()
        for counter in range(0x320):
             domain = get_domain(seed[0:7], counter)

La semilla ("seed") resulta de la llamada a la función API de Windows "GetSystemTime" para inicializarse. La API de Windows lo guarda en una estructura SYSTEMTIME mientras que nosotros para más comodidad, lo guardamos en una lista que  llamamos "seed". Los dos primeros índices corresponden al año (2014 =  0x07*0x100 + 0xDE), el tercero al mes, el quinto al día de la semana, el séptimo al día, y el resto a los segundos y milisegundos. En la API de Windows, enero corresponde a 1, febrero a 2, etc. El domingo corresponde a 0, lunes a 1, etc. El día depende del mes y varia entre 1 y 28, 30 o 31. Abajo es el ejemplo de un seed:

Seed = [0xDE, 0x07, wMonth, 0x00, wDayOfWeek, 0x00, wDay, 0x00, 0x0F, 0x00, 0x31,
0x00, 0x33, 0x00, 0xD1, 0x03]
En la función "get_domain", la primera parte define una lista "data_to_hash" que esta inicializada con la semilla y el contador y luego se hace un XOR con los  bytes 0xB1, 0xA4, 0xD7, etc. Finalmente, se realiza el "hash" de esta lista con el algoritmo MD5 y el "hash" resultante se usa para generar el dominio.

def get_domain(seed, counter):
    //Primera parte
    data_to_hash = [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]

    data_to_hash[0] = (seed[0] + 0x30) & 0xff
    data_to_hash[1] = seed[2] & 0xff
    data_to_hash[2] = seed[6] & 0xff
    data_to_hash[3] = 0x00
    data_to_hash[4] = counter & 0xfe
    data_to_hash[5] = (counter >> 8) & 0xff

    data_to_hash[0] = (data_to_hash[0] ^ 0xB1) & 0xff
    data_to_hash[1] = (data_to_hash[1] ^ 0xA4) & 0xff
    data_to_hash[2] = (data_to_hash[2] ^ 0xD7) & 0xff
    data_to_hash[3] = (data_to_hash[3] ^ 0xD6) & 0xff
    data_to_hash[4] = (data_to_hash[4] ^ 0xB1) & 0xff
    data_to_hash[5] = (data_to_hash[5] ^ 0xA4) & 0xff
    data_to_hash[6] = (data_to_hash[6] ^ 0xD7) & 0xff
    data_to_hash[7] = (data_to_hash[7] ^ 0xD6) & 0xff

    hash_md5 = hashlib.md5(array.array('B', data_to_hash).tostring()).hexdigest()
    bytes_array = array.array('B', hash_md5.decode("hex"))

En la segunda parte, el algoritmo itera todos los bytes del "hash" con los cuales construye el nombre del dominio. Pone el límite de 0x7a que corresponde a la ultima letra del alfabeto (z)  y añade un offset 0x 61 (letra a) para solo generar letras. La extensión del dominio esta basada sobre el valor del contador. El DGA distingue 5 casos: la extensión “.org”, “.com”, “.net”, “.info”, o “.biz”. Concatenando el nombre y la extensión, conseguimos el dominio.

//Segunda parte
    #Generate the name
    for byte in bytes_array:
        al = (byte & 0xF) + (byte>>4) + 0x61
        if al <= 0x7A:
            name = "%s%c" % (name, chr(al))

    #Generate the extension
    if (counter % 5 != 0):
        if (counter & 0x3 != 0):
            if (counter % 3 == 0):
                extension = ".org"
            else:
                if (counter & 0x1 == 0x1):
                    extension = ".com"
                else:
                    extension = ".net"
        else:
            extension = ".info"
    else:
        extension = ".biz"

    domain = "%s%s" % (name, extension)
    return domain
Ejecutando el script Python, sobre el año 2014 entero, hemos notado que un dominio (epmmxkoszqyown.org) aloja un servidor nginx/1.6.0 activo, pero no pudimos recoger ningún ejecutable. Se trata de la maquina de una victima, es decir sin instrumentación del algoritmo, que podría haberse infectado el día 14 de Agosto 2014.

Más información:

Domain Name Generator for Murofet,

ZeuS Gets More Sophisticated Using P2P Techniques,

W32/Murofet-A,