Oh no, Cleo: análisis de malware del implante Malichus

Resumen - CVE-2024-55956

Huntress reportó anteriormente actividad maliciosa derivada de la explotación de una vulnerabilidad de día cero en el software Cleo. El malware entregado a través de esta explotación ya ha sido analizado, y este artículo incluye un desglose técnico de una nueva familia de malware que hemos llamado Malichus. El nombre es un juego de palabras con Cleopatra y proviene de Malichus I, quien, según se registra, quemó la flota naval de Cleopatra en venganza por las pérdidas sufridas durante una guerra que Cleopatra había iniciado.

Figura 1: Descripción general de la cadena de ataque

Análisis técnico

Etapa 1: Descargador de PowerShell

El malware utiliza un pequeño cargador de PowerShell que prepara el host para una explotación posterior. Se almacena como un blob en base64, se decodifica y se usa para ejecutar un archivo Java que se despliega en el sistema con el nombre cleo.[identificador-numérico].

El cargador crea una conexión TCP a una dirección IP de C2 para obtener una carga útil de segunda etapa. El cargador formateado se muestra a continuación:

Figura 2: Script del descargador de PowerShell de la etapa 1, con formato

El cargador también define una variable llamada Query que se usa para obtener la dirección de C2 utilizada en una puerta trasera Java posterior y el identificador de dirección IP de la víctima. La dirección IP de C2 y el dropper de segunda etapa son diferentes para cada carga útil identificada por Huntress.

Etapa 2: Descargador Java

El archivo de segunda etapa descargado es descifrado por el cargador usando una clave AES única por carga útil. Al descifrar y descompilar el descargador de segunda etapa, se encontró un archivo manifest que indicaba que ejecutaría la clase start al iniciarse.

Figura 3: Archivo MANIFEST.MF del descargador Java

A partir del método main, la puerta trasera obtiene la variable de entorno query y la repara para convertirla en una cadena base64 válida. Ese valor decodificado contiene la clave AES, un valor único necesario para descargar la etapa 3, y las IPs del servidor de C2 y del host víctima. Luego se envía una solicitud al C2 usando ese valor codificado en base64, añadido con TLS v3.

Figura 4: Descompilación anotada del método main en la etapa 2

Una receta de CyberChef que muestra el resultado de esto es la siguiente:

Loading Gist...
Figura 5: Vista de CyberChef con direcciones IP decodificadas (dirección de la víctima censurada)

Usando el espacio asignado que hemos renombrado como recvBuf, el dropper continúa aceptando bytes hasta que se ha descargado por completo la carga útil de la etapa 3. Luego, usando la clave AES y el IV fijo, descifra los datos descargados, lo que produce un zip intencionalmente corrupto. Luego reparan el encabezado cortando los primeros dos bytes (cC en el ejemplo que analizamos), y proceden a la extracción y carga.

Figura 6: Descompilación anotada de la rutina de descifrado

Una receta de CyberChef que muestra el resultado de esto es la siguiente:

Figura 7: Vista del archivo zip descifrado dentro de CyberChef

Finalmente, la puerta trasera descomprime el archivo descargado usando el método auxiliar proporcionado, y resuelve dinámicamente las clases que contiene.

Figura 8: Descompilación de los métodos start y findClass
Figura 9: Descompilación que muestra llamadas a start y carga dinámica de archivos de clase

Etapa 3: Puerta trasera Java / Marco de post-explotación

La etapa final es un marco de post-explotación modular basado en Java que contiene una cantidad significativa de funcionalidad. Se compone de 9 archivos de clase, siendo el controlador principal la clase Cli cargada por la etapa anterior. El marco es compatible con Linux y Windows, aunque Huntress solo observó su uso en Windows.

Cli

Esto comienza con una clase pública llamada Cli que recibe tres parámetros pasados desde el descargador de la etapa 2, almacenados en las variables host, cliid, y stage1fn.

Figura 10: Descompilación de la clase Cli asignando variables antes de ejecutar el método run

Los parámetros pasados desde la etapa 2 corresponden a:

  1. La dirección IP de C2 obtenida del parámetro query 
  2. La dirección IP del sistema explotado y el puerto asociado obtenido del parámetro query (usado como identificador único de víctima) 
  3. El nombre de archivo almacenado en la variable de entorno f del descargador (en este caso cleo.2607)

La clase tiene varias variables estáticas definidas que se usan a lo largo de la carga útil, incluyendo una que identifica si está operando en un sistema operativo Windows o no.

Figura 11: Descompilación de las variables de instancia de clase usadas por Cli
Figura 12: Descompilación de la clase Cli obteniendo el nombre de host y determinando si se ejecuta en Windows

Esto ejecuta el método principal run() y el posterior método runDelFileCmd() para eliminar del disco la carga útil de la primera etapa (el descargador) (cleo.2607) usando PowerShell o Bash según si el sistema fue identificado como Windows o no.

Figura 13: Descompilación del método runDelFileCmd mostrando la eliminación de la carga útil de la etapa 1 del disco

El método run se encarga de usar la clase SrvSlot para encolar conexiones a la dirección IP de C2 obtenida del parámetro query anterior en el puerto 443. Esta clase extiende la clase Slot, lo que le permite acceder a la variable estática st, usada para enviar comandos personalizados al implante desde el servidor de C2 conectado, y forma parte de un protocolo de C2 personalizado. Estas conexiones continuarán hasta que el número de conexiones sea 5 o más, y la variable st se establezca en algo distinto de 200 o 500.

Figura 14: Descompilación del método run mostrando los comandos ishell y prs-conf

Esta clase también contiene dos métodos auxiliares: l (registro) y dmp (volcado hexadecimal). El primero se usa en todo el implante para registrar varios datos en un búfer de registro. El segundo permite al malware crear un volcado hexadecimal válido a partir de un arreglo de bytes proporcionado, el cual luego se registra usando la función auxiliar de registro. La función de volcado hexadecimal probablemente sea para fines de depuración, ya que no se llama en ninguna parte del implante.

Figura 15: Descompilación de la función de registro
Figura 16: Descompilación anotada de la función de volcado hexadecimal, que nunca se usa.

Dwn

Una clase de gestión de archivos zip para subir, empaquetar y manejar archivos zip para que los operadores lean y recopilen archivos o directorios. Se usan varias variables en esta clase, además de un método llamado Dwn encargado de registrar cuánto tiempo ha pasado desde la última actualización y asignar una cola de archivos para subir al servidor de C2, almacenada en un arraylist llamado lvs.

Figura 17: Descompilación de la clase Dwn

La clase contiene un método tick usado para llevar el control del tiempo transcurrido y enviar una actualización de estado al servidor de C2 si han pasado más de 5 segundos desde la última actualización.

Figura 18: Descompilación anotada del método tick

El método setStat, llamado por tick, establece campos del paquete tick recién creado con información sobre el estado actual de la clase Dwn.

Figura 19: Descompilación anotada del método setStat

Curiosamente, esta clase también tiene una verificación llamada tick2 encargada de rastrear el estado de los archivos que se están exfiltrando, y de gestionar la cola de archivos a exfiltrar.

Figura 20: Descompilación del método tick2

El método addFile se usa para agregar archivos a la cola para exfiltración.

Figura 21: Descompilación del método addFile

Esto se complementa con los métodos readFile y write para recuperar el archivo del disco.

Figura 22: Descompilación del método readFile
Figura 23: Descompilación del método write

Otros métodos zipOpen, zipClose, y getCurrZipSize usan una nueva instancia de la clase Mos, pero aparte de eso son bastante autoexplicativos.

Figura 24: Descompilación del método zipOpen
Figura 25: Descompilación del método zipClose
Figura 26: Descompilación de getCurrZipSize

DwnLevel

Una clase simple para gestionar un arreglo de archivos en preparación para el archivado zip y la subida al atacante. Se usa principalmente en la clase Dwn y sirve solo para mantener el estado.

Figura 27: Descompilación de la clase DwnLevel

Mos

Una pequeña clase para manejar archivos zip de múltiples partes. Para optimizar el envío de archivos zip, estos se dividen cada 262154 bytes y luego se archivan con ZipIDs durante la exfiltración.

Esto parece equivaler a 2 de las longitudes de paquete de su protocolo personalizado, de 131072 bytes.

Figura 28: Clase Mos descompilada, incluyendo los métodos getSize, write y close

Proc

Proc contiene implementaciones de las tareas que el malware puede realizar, así como algunas capacidades de reconocimiento en la máquina de la víctima. Estos comandos se analizan en la clase SlotSrv.

La clase Proc permite a los atacantes remotos principalmente emitir comandos de ejecución y leer archivos de configuración usando la función run. El comando ishell (shell interactiva) utiliza la función pipeMode para manejar shells interactivas, lo que indica que el malware tiene la capacidad de permitir que un atacante actúe de forma manual («hands-on-keyboard») y ejecute comandos interactivamente a través de una terminal.

Esto coincide con la actividad identificada por el Huntress SOC cuando se observó que un actor de amenazas ejecutó interactivamente un comando nltest. La función run también es el método principal usado para obtener la configuración del software Cleo mediante el comando prs-conf.

Figura 29: Descompilación del método run

El método confParser recupera elementos de un archivo de configuración ubicado en conf/Top.xml en disco y obtiene todos los valores de host dentro de este. Para cada host identificado, intentará obtener el archivo xml correspondiente dentro de hosts/<hostname.xml> y analizarlo para usarlo en una función loadUserDirs.

Figura 30: Descompilación del método confParser

La función loadUserDirs se usa para analizar cualquier nodo Host o Mailbox dentro del archivo xml del host y extraer los alias definidos en este archivo. Además, también analiza el archivo en busca de referencias a varios directorios, que se envían junto con el alias con el que están emparejados.

  • Directorio de inicio predeterminado
  • Ruta raíz de FTP
  • Bandeja de entrada
  • Bandeja de enviados
  • Bandeja de salida
Figura 31: Descompilación del método loadUserDirs
Figura 32: Descompilación de la parte de análisis de buzones del método loadUserEnv

Esto incluye un método para reemplazar cualquier variable personalizada llamada Homedirectory y Homedirectorytype que pudiera haberse definido en la instancia de la aplicación, con su valor correspondiente.

Figura 33: Descompilación del método replCustomVars

Esto demuestra que el autor del malware tenía conocimiento de los archivos de configuración específicos usados dentro del software Cleo, y estaba interesado en entender qué tipos de relaciones comerciales se habían configurado en una instancia de Cleo Harmony, VLTrader o LexiCom, además de dónde residían en disco los archivos de carga útil, como archivos enviados o recibidos entre estas empresas.

Curiosamente, esto contenía un método llamado checkDir que parece verificar si un directorio es legible o no; sin embargo, nunca se llamó en el código, por lo que pudo haberse usado con fines de depuración o se trata de una característica aún no implementada.

Figura 34: Descompilación del método checkDir

SFile

Esta clase permite a los atacantes realizar operaciones básicas de lectura y escritura en el sistema de archivos usando las clases FileOutputStream y FileInputStream. Esto se referencia en los procedimientos de manejo de archivos dentro de la clase SvrSlot.

Figura 35: Descompilación de la clase SFile

También hay varias funciones auxiliares que ayudan a SFile a leer, escribir y gestionar archivos:

Figura 36: Descompilación de los métodos auxiliares de SFile

ScSlot

Este es un enlace de comunicación de múltiples canales, con el canal usado para manejar comunicación asíncrona; cada ScSlot actúa como una canalización única de datos usando la clase SvrSlot.

Figura 37: Descompilación de la clase ScSlot con los métodos evConnect, evRead y tick

Slot

Esta clase se encarga de establecer y gestionar la conexión con el servidor de C2. Inicializa dos búferes para enviar y recibir datos:

Figura 38: Descompilación de la clase Slot con el método connect

SrvSlot

Esta clase se ocupa principalmente de recibir y enviar respuestas del servidor de C2. Al conectarse a un servidor de C2, esta clase registra el número de paquete usando el método l dentro de la clase Cli con fines de depuración, antes de establecer st en 2, indicando que la conexión se estableció correctamente y estaba lista para recibir un paquete hello. Esto sobrescribe un método con el mismo nombre en Slot usado para determinar si la conexión se ha establecido o no.

Figura 39: Descompilación del método evConnect

El paquete hello enviado al C2 tiene un encabezado de 16 bytes, cuya estructura es la siguiente:

Desplazamiento de byteValor
0 - 9Math.random() * 256.0D
102
11119
1233
13-1
141
15Cli.fIsWin ? 0 : 1

Un detalle interesante a notar es los 10 bytes basura que se añaden al frente del paquete. Esto probablemente sea un intento de evadir técnicas de huella digital de red, aunque los demás bytes del paquete son estáticos, lo que hace que eso sea relativamente trivial.

El primer paquete enviado al C2 por el malware es un paquete “hello” que contiene un identificador emparejado con un nombre de host si este se obtuvo previamente en Cli (cliid + “\t” + hostname), además de la sincronización requerida para cifrar paquetes posteriores.

Figura 40: Método pktHello descompilado y anotado

Después de enviar ese paquete hello, el implante espera una respuesta hello del servidor, que se analiza en el método prsHelloPkt.

Figura 41: Descompilación anotada del método prsHelloPkt

SvrSlot también contiene una opción para registrar estadísticas de cada endpoint comprometido, que parece usarse para el seguimiento de salud de su propio C2 y endpoint.

Hay tres opciones de depuración usadas para recopilar información; el comando principal es #dbg#, que recopila información sobre cuántos errores de archivos zip ha habido, cuántas veces se encontró un estado de cola del servidor, y la disponibilidad de memoria libre en el host.

Figura 42: Comando #dbg# descompilado

El comando #ll# muestra cuántos archivos se han añadido a los zips que referencian la clase Dwn mencionada anteriormente.

Figura 43: Comando #ll# descompilado

El comando #lsz# recopila información sobre los archivos zip, detallando el tamaño del último archivo zip, el ID del zip, el número de archivo zip y el desplazamiento del archivo zip; esto probablemente se usa para depurar el proceso de archivado multi-zip realizado dentro de la clase Mos mencionada anteriormente.

Figura 44: Comando #lsz# descompilado

Análisis del protocolo C2

Malichus utiliza un protocolo de C2 totalmente personalizado. Cada paquete entrante y saliente es procesado por la función en SrvSlot llamada pkt0. Esta toma un paquete completo y calcula su valor CRC32 para confirmar la integridad del paquete. Luego, los bytes 3 y 4 se establecen con los bytes altos y bajos de ese valor CRC32 antes de cifrarlo.

Figura 45: Descompilación anotada del método pkt0

Después de que se actualiza el CRC32 del paquete, pkt0 llama a addEncr, encargada de cifrar el paquete. Hay algunas variables globales usadas para gestionar tanto el estado de cifrado como el de descifrado:

Loading Gist...

El C2 y el cliente deben mantenerse sincronizados para que el cifrado/descifrado funcione, ya que la mutación de las variables de estado e índice se actualiza según el valor del byte previamente cifrado/descifrado.

Figura 46: Descompilación de la rutina de cifrado

Existe un método similar para descifrar paquetes, pero usa un conjunto diferente de variables para llevar el control del estado:

Figura 47: Descompilación de la rutina de descifrado

Como mencionamos antes, el método pkt0 es el constructor de paquetes final usado por los otros 6 tipos de paquetes, la mayoría de los cuales son métodos sobrecargados del nombre pkt. Cada uno tiene usos ligeramente distintos pero comparten que el primer byte es el identificador de paquete. Hay 4 tipos de paquete genéricos, y 2 especializados. De los paquetes genéricos:

Figura 48: Descompilación anotada de pkt1
Figura 49: Descompilación anotada de pkt
Figura 50: Descompilación anotada de pkt
Figura 51: Descompilación del auxiliar pkt

Los dos paquetes especiales utilizados son helloPkt y zipPkt, con el helloPkt ya detallado anteriormente:

Figura 52: Descompilación del paquete especial zipdata

Después de que pkt0 crea los paquetes, se colocan en la cola outbuf y se envían al C2 usando el método evWrite de la clase Slot:

Figura 53: Descompilación del método evWrite usado para enviar paquetes al C2

Conclusión

Si bien el alcance completo de quién creó este malware y por qué sigue siendo objeto de especulación, este blog busca arrojar luz sobre una nueva familia de malware que apunta al software Cleo, su funcionalidad, y ofrece una posible visión de lo que el autor del malware esperaba lograr con esta pieza de malware personalizada.

Este artículo de blog se creó de forma independiente junto con otros proveedores de la industria, y coincide en gran medida con el análisis realizado también de forma independiente por:

Identificadores de paquete C2 válidos

Número de paqueteValorExplicación
1HelloPaquete hello para inicializar conexiones
2ChannelObtener identificador de canal de C2
3DataPaquete de datos
4Prepare FilePrepara un archivo para la subida
5Close ChannelCierra el canal de C2 y cualquier identificador abierto
6EchoDevuelve los datos recibidos al C2
7HaltReinicia los búferes de entrada
8CommandEjecuta el comando recibido
9StopDetiene el proceso, canal o transferencia de archivo
10ResumeReinicia el proceso, canal o transferencia de archivo
11AckConfirma la sincronización de los comandos procesados
12File I/O StatusConfirma que el implante puede recibir o procesar datos zip
13Zipdata ReassemblyReensambla los fragmentos del zip y los elimina de la cola

IOCs

Nombre de archivoSHA256
cleo.26076705eea898ef1155417361fa71b1078b7aaab61e7597d2a080aa38df4ad87b1c
Cli0c57b317b572d071afd8ccdb844dd6f117e20f818c6031d7ba8adcbd32be0617
Dwn429d24e3f30c7e999033c91f32b108db48d669fde1c3fa62eff9da2697ed078e
DwnLevelf80634ce187ad4834d8f68ac7c93500d9da69ee0a7c964df1ffc8db1b6fff5a9
Mos0b7b1b24f85a0107829781b10d08432db260421a7727230f1d3caa854370cb81
Proc1ba95af21bac45db43ebf02f87ecedde802c7de4d472f33e74ee0a5b5015a726
SFile57ec6d8891c95a259636380f7d8b8f4f8ac209bc245d602bfa9014a4efd2c740
ScSlot87f7627e98c27620dd947e8dd60e5a124fdd3bb7c0f5957f0d8f7da6d0f90dee
Slot1e351bb7f6e105a3eaa1a0840140ae397e0e79c2bdc69d5e1197393fbeefc29b
SrvSlotf4e5a6027b25ede93b10e132d5f861ed7cca1df7e36402978936019930e52a16

Reglas YARA

https://github.com/huntresslabs/threat-intel/tree/main/2024/2024-12/Cleo

También te podría interesar