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