Image
Análisis del protocolo de comunicación del troyano para Android Xenomorph

Análisis del protocolo de comunicación del troyano para Android Xenomorph

¡Hola!

Hoy os traemos al blog una entrada que seguro que encontraréis muy interesante y de utilidad si os dedicáis al análisis de malware. Y es que, en este artículo os vamos a hablar del protocolo de comunicación del troyano para Android Xenomorph.

Pero antes, os ponemos en contexto. En febrero de 2022, el fabricante ThreatFabric se encontró con un nuevo troyano bancario para Android al que llamaron Xenomorph. El nombre proviene de sus claros vínculos con otro troyano bancario llamado Alien, del cual Xenomorph adopta algunas características como nombres de clases o cadenas de texto, pese a tratarse de una implementación independiente y no basada en este.

Según el informe publicado, este malware afectaría a los usuarios de hasta 56 bancos europeos diferentes y habría sido encontrado siendo distribuido en la tienda oficial de Google Play, con más de 50.000 instalaciones.

Este listado de aplicaciones a las que puede suplantar el malware es proporcionado por el servidor de mando y control (C2) que trae configurado la muestra. Sin embargo, un aspecto al que no entra muy en detalle el informe compartido por ThreatFabric es el protocolo específico utilizado por la aplicación para obtener este listado y que, mediante su estudio, permitiría la automatización de este con el objetivo de analizar futuras muestras o monitorizar cambios en los listados de aplicaciones afectadas por los C2 detectados.

A lo largo del artículo vamos a analizar el protocolo de comunicación de Xenomorph y comprobaremos cómo, gracias a esto, es posible implementar un Bot que emule las peticiones del troyano para interactuar con sus servidores C2.

Este estudio se basa en la muestra de Xenomorph compartida por ThreatFabric que, tras ser descifrada, corresponde con el siguiente hash sha256:

dae52bbee7f709fae9d91e06229c35b46d4559677f26152d4327fc1601d181be

La muestra contiene una serie de constantes que son utilizadas para personalizar dicha muestra y que podrían variar entre unas y otras. La mayor parte de esta configuración personalizada del malware se encuentra definida en un fichero llamado “Constants” donde se encuentran, entre otros, los dominios y direcciones de API de los C2, la clave de cifrado a utilizar o el tag del bot.

El malware contiene dos rutas principales con las que interactúa: /ping y /metrics. La ruta concreta para estas peticiones está también configurada en el fichero “Constants”, por lo que puede variar entre unas muestras y otras.

Con respecto a la comunicación con el servidor, Xenomorph destaca frente a otros troyanos por la utilización del proyecto de código abierto Retrofit2, un cliente REST para Android, Java y Kotlin desarrollado por la empresa Square y que se basa, a su vez, en OkHttp.

Las peticiones al servidor son cifradas mediante el algoritmo de cifrado AES. Inicialmente, se utiliza una clave que es establecida en el código, a lo que el servidor responde con una nueva clave utilizada para dicha sesión hasta que se realice otra petición que la reemplace por una nueva.

Petición de registro /ping

La petición inicial a la ruta /ping se construye de la siguiente forma:

{

    "hash": <hash de verificación de los datos de registro>, 

    "id": <datos de registro cifrados>,

    "iv": <vector de inicialización AES utilizado para cifrar los datos>,

    "type": "request_verify"

}

El valor del “hash” se calcula mediante la concatenación en bytes de la clave de cifrado AES  configurada en el código, la ruta donde se encuentra la petición inicial en el servidor y el IMEI del dispositivo. El siguiente código python emula los datos utilizados para construir los datos para calcular dicho hash. A esta información se le calcula su SHA256 y se codifica mediante base64.

def __ping_hash_bytes__(self):

        bytes_1 = bytes.fromhex(self.aes_key_init)

        bytes_2 = "{}/{}".format(self.active_c2, self.api_location_verify).encode("latin1")

        bytes_3 = bytes.fromhex(self.imei)

        return bytes_1 + bytes_2 + bytes_3

En el campo “id” se envía la información de registro cifrada con la clave AES establecida en el código. En el caso de la muestra analizada, esta clave es “5f9e4a92b1d8c8b98db9b7f8f8800d2e”. La estructura del campo “id” es la siguiente:

{

    "api": <Número del SDK del dispositivo>,

    "apps": [<listado de aplicaciones instaladas>],

    "imei": <IMEI>,

    "model": <modelo de dispositivo>,

    "numbers":[<listado de contactos>],

    "tag": <tag del bot configurado>,

    "uid": <IMEI>

}

Tras esto, se envía la petición de registro a la que el servidor da una respuesta.

Respuesta de registro /ping

El servidor responde a esta petición con una estructura similar a la enviada donde el campo “id” contiene ahora la nueva clave de cifrado AES que se utilizará en las peticiones posteriores. Este dato viene cifrado igualmente con AES utilizando la clave inicial y, como vector de inicialización IV, el que viene en la respuesta recibida. Este es un ejemplo de respuesta para la petición /ping:

{

    "type":"reponse_verify",

    "hash":"ON9Kk2P+wes7KgQFxJZzVMkZflh1o32k5YnFZP97ljY=",

    "iv":"/FIWarzQ200j1dAD9jwC0A==",

    "id":"BeUatLhbxruLSs/GMzWCvqVSIAKlNfmvBzPo7LrzPr8="

}

Donde el campo “id” se descifra del siguiente modo:

Tras esto, el bot queda registrado y puede realizar peticiones al servidor mediante el método /metrics.

Petición de oraciones /metrics

Estas peticiones tienen la siguiente estructura:

{

    "hash": <hash de verificación de la petición metrics>

    "id": <base64 del sha256 del IMEI>,

    "iv": <vector IV utilizado para cifrar la petición>,

    "metrics": <información solicitada o petición de comandos cifrada con AES y la clave de sesión obtenida del servidor>

}

El campo “id” se calcula mediante el hash SHA256 del IMEI del dispositivo codificado en base64. El siguiente código python emula dicho cálculo:

b64encode(hashlib.sha256(bytes.fromhex(self.imei)).digest()).decode("latin1")

El campo “hash” es ahora calculado mediante una concatenación de bytes diferente, uniendo la clave AES inicial, la nueva clave AES y el vector de inicialización IV utilizado para cifrar los datos de la petición a enviar. El siguiente código python emula dicha concatenación. Al igual que antes, a estos bytes se les calcula su hash SHA256 y se codifican en base64.

def __metrics_hash_bytes__(self, iv):

        bytes_1 = bytes.fromhex(self.aes_key_init)

        bytes_2 = bytes.fromhex(self.metrics_aes_key)

        bytes_3 = iv

        return bytes_1 + bytes_2 + bytes_3

Mediante la ruta /metrics el bot envía una petición periódica para consultar si tiene comandos por ejecutar o, si ha recibido una orden de ejecución, la información solicitada por alguno de los comandos. Para realizar esta petición se utiliza el campo “metrics” con la siguiente estructura que es cifrada mediante la nueva clave AES y un vector IV aleatorio.

{

    "permissions":["notification_manager","generic_permissions"],

    "rm_triggered":false,

    "user_present":true,

    "type":"get_coms"

}

Respuesta de operaciones  /metrics

A esta petición periódica de comandos, el servidor responde con la siguiente estructura:

{

    "metrics": <respuesta cifrada>,

    "hash": <hash de verificación>,

    "iv": <IV utilizado en el cifrado AES>,

    "id": <id enviado en la petición anterior>

}

El contenido de “metrics” es descifrado y contiene el siguiente diccionario:

{

    "type": 'get_coms', 

    "coms": []

}

El campo “coms” trae un listado con los posibles comandos a ejecutar o un array vacío, en caso de que no haya nada que ejecutar. Los diferentes comandos que pueden recibirse del C2 son los siguientes:

  • sms_log
  • self_kill
  • notif_ic_disable
  • fg_disable
  • inj_list
  • notif_ic_enable
  • self_cleanup
  • notif_ic_update
  • sms_ic_disable
  • fg_enable
  • inj_enable
  • app_kill
  • app_list
  • sms_ic_enable
  • inj_update
  • inj_disable
  • sms_ic_update
  • sms_ic_list
  • notif_ic_list

El comando “inj_update”

Mediante el comando “inj_update” es posible recuperar el listado completo de aplicaciones de las que contiene inyecciones el C2. Para ello, mediante la misma ruta de operaciones /metrics se envía un comando en el que en el campo “metrics” se envía cifrado el siguiente diccionario:

{

    "type":"inj_update"

}

A esto el servidor responde, tras descifrar su respuesta, con un listado de identificadores de apps y la URL desde la que descargar su inyección correspondiente. A continuación se puede observar el flujo completo de peticiones y respuestas descifradas que han sido emuladas para poder obtener dicho listado de forma automática.

Y con esto terminamos este análisis. Esperamos que os haya resultado interesante. Y si es así, ya sabéis, ¡compartid! :D

¡Hasta el próximo post!


Mariano Palomo