Image
Desempaquetando BadPack Packer

Desempaquetando BadPack Packer

¡Hola lectores!

En el artículo de hoy os traemos un análisis de un nuevo empaquetado que actualmente está siendo empleado en diferentes familias de malware bancario dirigidas a dispositivos Android como Anatsa, Hydra o Cerberus. El hecho de que este packer haya sido observado en diferentes familias, hace pensar que es muy probable que éste se trate de un nuevo MaaS (malware as service). 

El empaquetado de malware es comúnmente empleado para ocultar la carga maliciosa real de la muestra analizada, normalmente cifrando la misma con cualquier algoritmo criptográfico. De esta manera, se dificulta la ingeniería inversa de la muestra original, ya que, como se puede ver en la siguiente imagen, el fichero final se divide en dos partes. Por una parte, éste contiene la carga maliciosa real cifrada, y por otra parte contiene la porción de código encargada del descifrado y ejecución en memoria del malware.

En este artículo se abordará principalmente el proceso empleado para el desempaquetado de BadPack Packer (bautizado de esta manera debido a la clasificación que hacen los motores antivirus en VirusTotal) a través de un script de Python, el cual nos proporcionará la carga maliciosa real de la muestra analizada. Esa carga maliciosa es un fichero con extensión .DEX, cuyas siglas vienen de Dalvik Executable file (archivo ejecutable Dalvik) y se denominan así porque son interpretados por la máquina virtual Dalvik. Dalvik forma parte del sistema operativo Android de Google y se utiliza para ejecutar aplicaciones compatibles. 

Análisis inicial de las muestras:

La muestra analizada ha sido obtenida de Virus Total y su hash SHA256 es el siguiente:

12f09fe5abe2cee128bcc551d39cddfd0bd2302fc9ded52c1489c92846ea8ce8

La muestra analizada posee una clase llamada StubApp, la cual se encarga de iniciar el descifrado y ejecución en memoria de la carga maliciosa a través de la función renombrada a “load_malicious_dex”.

Esta función se encarga de buscar la carpeta “assets” dentro del código para, posteriormente, buscar en ella el fichero DEX cifrado. Acto seguido, lo descifra y lo carga en memoria a través de la función “Invoke_dex_class_loader”.

Inicialmente se va a intentar localizar el fichero DEX cifrado, el cual va a ser nuestro objeto de estudio. Para ello, se busca el contenido de la variable “dex_folder” y “dex_extension”, las cuales se encuentran cifradas en la siguiente clase:

La función de descifrado de las mismas se trata de un simple algoritmo XOR:

Las cadenas descifradas obtenidas son las siguientes:

Efectivamente, dentro de la carpeta ‘qonig’ se puede localizar un fichero con extensión ‘wdj’, el cual se trata de la carga maliciosa cifrada.

Una vez localizado el fichero a DEX cifrado, hay que estudiar la función de descifrado empleada por el packer. En este caso, el algoritmo empleado no se trata de ningún algoritmo convencional de criptografía simétrica como pueden ser RC4, AES o DES, sino que en este caso el algoritmo implementado es propio.

Inicialmente, el algoritmo realiza una descompresión a través de la librería ZLib del fichero.

Posteriormente, se llama a la función “Init_Transformation”, la cual inicialmente realiza una serie de operaciones sobre la variable “key”, la cual se trata de la clave de descifrado del DEX. Como se puede ver en la siguiente imagen, va cogiendo diferentes valores de la clave y realiza diferentes operaciones de OR y desplazamiento de 16 bits hacia la izquierda sobre ellos.

El código Python equivalente es el siguiente. 

Como se puede ver en la imagen, a través de ord se obtiene el valor entero que representa ese valor unicode. Finalmente, con la función c_int32 de la librería ctypes se obtiene el valor representado como entero de 32 bits, ya que Java y Python interpretan y representan los números con signo de manera diferente.

Como se ve en la siguiente imagen, en Python esa misma operación devuelve el número 3197199208 (cuando en Java se devuelve -1097768088), el cual se trata del número BE916368 en hexadecimal. 

Observando una calculadora, se puede ver que la representación del número hexadecimal BE916368 es 3197199208 en decimal y -1097768088 en complemento a 2 con signo. Y este es el motivo por el cual hay que emplear la función c_int32 de la librería ctypes.

En la siguiente parte del código hace una transformación (función Transformation1) sobre el array de enteros obtenidos anteriormente:

La transformación realizada es la siguiente:

El código Python equivalente sería el que se puede ver en la siguiente imagen.

Debido a que Python no posee el operador desplazamiento a la derecha sin signo, ésta se ha de implementar a parte en una función diferente, la cual se llama “unsigned_right_shift”. Por otra parte, también se ha implementado la función lsb32 (desplazamiento hacia la izquierda), ya que como Python es capaz de manejar enteros grandes, el valor obtenido será un número negativo más pequeño en valor absoluto, que en este caso es -26. Sin embargo, como en Java los números tienen un tamaño fijo de 32 bits, los bits más significativos se mantendrán iguales, y los bits restantes serán desplazados hacia la derecha. Por lo tanto, el resultado será un entero con signo, que en este caso es 230, siendo este el valor que estamos buscando. 

Posteriormente, y como se puede ver en la imagen 13, el packer se encarga de leer el fichero DEX cifrado en porciones de 8192 bytes para ir descifrándolo poco a poco. Posteriormente, también emplea una función la cual se ha renombrado a “Transformation2”, a la cual le pasa dos arrays de enteros calculados anteriormente para seguir realizando operaciones sobre ellos. En este caso, las operaciones realizadas son desplazamientos a la derecha sin signo, operaciones OR, una suma y finalmente un XOR. 

Después de todas las operaciones realizadas, guarda en iArr2[0] y en iArr[1] un par de valores, los cuales serán empleados en la fase final del descifrado:

Para acabar el desencriptar el fichero, hace un XOR del resultado obtenido entre la operación “opp1” y el byte del array sobre el que esté iterando. Finalmente, en la “opp3” lo que se está haciendo es pasar el valor obtenido a byte.

Una vez se ha obtenido el array de bytes que conforma el fichero completo, se vuelve a realizar un Zlib inflate sobre el mismo, obteniendo el fichero DEX descifrado, el cual se trata de la carga útil real del malware.

Como se puede ver en la siguiente imagen, los bytes obtenidos poseen la cabecera “dex”, lo cual nos indica que el descifrado se ha realizado correctamente.

Por tanto, este es un caso en el cual el algoritmo de descifrado ha sido implementado por los propios desarrolladores del malware, dificultando así el descifrado del mismo.

Y hasta aquí la entrada de hoy. Espero que os haya sido útil y que os haya gustado, ¡hasta la próxima! 

Referencias:


Santiago González Ocaña, Malware Analyst en Entelgy Innotec Security