Cómo tratar el cifrado AES en el análisis de malware

Cómo tratar el cifrado AES en el análisis de malware

En la investigación de malware, existen básicamente dos tipos de análisis: estático y dinámico. En pocas palabras, el análisis estático comprende todas las formas de estudiar un software sin ejecutarlo, mientras que su homólogo dinámico se basa en observar el programa mientras se ejecuta. A los autores de malware les interesa dificultar ambas formas de análisis, dificultando así su detección.

En este boletín nos centraremos en el cifrado AES en el análisis de malware. Es decir, cómo se emplea para dificultar el estudio del software malicioso estático y cómo hacer frente a este tipo de técnicas.

Muestra elegida

Este boletín se basa en una muestra del Keylogger HawkEye de enero de 2022 (MD5:019A689DCC5128D85718BD043197B311). Se trata de un ejecutable .NET que emplea varias técnicas de ofuscación, incluido el uso de cifrado AES para ocultar cadenas importantes relacionadas con su funcionamiento
.

Al ser un .NET, su análisis estático involucra código C#. Entendemos que esta característica facilita la comprensión de los conceptos que presentaremos, ya que su lectura es más sencilla que ensamblador, lenguaje implicado en el análisis de malware en otros lenguajes.

Por último, esta muestra está a disposición del público en Internet. Esto permite a los interesados reproducir en sus propias máquinas todos los pasos de análisis aquí descritos.

Identificación

El empleo de la encriptación AES no siempre será obvio al analizar el propio contenido encriptado. Considere el siguiente ejemplo, una de las funciones clave de Hawkeye Keylogger:

Figura 1: Función con ofuscación de cadenas

A primera vista, las cadenas declaradas en la función anterior están codificadas con base64 (una pista crucial para llegar a esta conclusión es la presencia de "==" al final del texto). Nuestro primer impulso es descodificar este contenido y, con suerte, averiguar qué ha decidido ocultarnos el autor del malware. Tomemos como ejemplo la cadena gnfyANw8:

gnfyANw8 = "EcCYDcH+PxTAszyHU/StRg=="

Tras la descodificación, obtenemos el siguiente resultado:

.À.
Áþ?.À³<.Sô.F

Ok, no tenemos suerte. El resultado es ilegible, lo que no tiene sentido para una cadena. Si seguimos mirando el código después de estas declaraciones de variables, encontraremos una pista de que su de-fuzzing no sólo implica base64. Cada una de ellas se pasa individualmente como parámetro a la misma función. Este comportamiento se demuestra en el siguiente fragmento de código:

Path.Combine(this.njZ5r2Fw(this.bfuPrPbT), this.njZ5r2Fw(this.gnfyANw8)) 

La siguiente imagen muestra el contenido de esta función en su totalidad.

Figura 2: Función responsable del descifrado de variables

AES y Rjindael

Las siglas AES hacen referencia a un estándar decifrado (Advanced Encryption Standard) establecido por el NIST. Este estándar comprende tres miembros de la familia de cifradores Rjindael, caracterizados por el tamaño en bits de la clave utilizada. En concreto, forman parte de AES los cifrados Rjindael que utilizan claves de 128, 192 y 256 bits.

AES pertenece al grupo del cifrado simétrico, lo que significa que se utiliza la misma clave para cifrar y descifrar un contenido. El cifrado asimétrico (como RSA) requiere que se invierta la clave privada; por eso se utiliza a menudo en ransomwarepero rara vez se emplea para la ofuscación.

La criptografía es un campo muy complejo y no es la intención de este boletín explicar el funcionamiento paso a paso del estándar AES. Sólo explicaremos los conceptos necesarios para identificar su presencia en funciones (como la mostrada en la Figura 2) y descifrar elementos sin tener que ejecutar el malware (es decir, mantendremos todo el enfoque centrado en el análisis estático). El primer paso consiste en asociar las menciones de Rjindael con AES.

A continuación, el código en cuestión comienza a realizar operaciones con matrices. Las partes relevantes se destacan a continuación:

byte[] array = nuevo byte[32];
byte[] sourceArray =
md5CryptoServiceProvider.ComputeHash(Encoding.ASCII.GetBytes(s));
Array.Copy(sourceArray, 0, array, 0, 16);
Array.Copy(sourceArray, 0, array, 15, 16);
rijndaelManaged.Clave = array;
rijndaelManaged.Mode = CipherMode.ECB;

El primer punto de atención es el tamaño de la matriz creada: 32 bytes, o 256 bits. Al final del fragmento resaltado, vemos que esta matriz se utilizará como clave para el cifrado Rjindael, es decir, el cifrado AES-256. Con esta clave, podemos hacer el trabajo de esta función sin ejecutarla realmente, frustrando el esfuerzo del autor del malware por cifrar. Con esta clave, podemos hacer el trabajo de esta función sin ejecutarla realmente, frustrando el esfuerzo del autor del malware para que ciertas cadenas sólo se descifren durante la ejecución. A continuación, necesitamos obtener manualmente el array llamado array.

Creación de una clave AES

Los pasos para crear una clave AES varían poco entre las diversas aplicaciones. En resumen, siempre se espera como resultado una matriz de 8, 24 o 32 bytes (AES-128, 192 y 256, respectivamente).

En el código que analizamos este array se crea con las siguientes instrucciones:

byte[] array = nuevo byte[32];
byte[] sourceArray =
md5CryptoServiceProvider.ComputeHash(Encoding.ASCII.GetBytes(s));
Array.Copy(sourceArray, 0, array, 0, 16);
Array.Copy(sourceArray, 0, array, 15, 16);

Ya hemos hablado de crear un array vacío de 32 bytes de tamaño para recibir la clave. A continuación vemos un método para calcular el hash de la variable s utilizando el algoritmo MD5. Esta variable fue declarada al principio de la función que analizamos y tiene la cadena hlNCwqLIdBYdaloFZajsnThCVnhYFN. ¿Y por qué generar un hash MD5? Porque independientemente de la entrada, este hash siempre tiene 16 bytes como salida. Este hash se almacena en sourceArray.

A continuación vemos dos líneas que emplean el método Array.Copy. Este método se utiliza en el código presentado con las siguientes entradas:

Copiar(Matriz, Int32, Matriz, Int32, Int32)

Translating: la función copiará elementos de un array(sourceArray) que empieza en la posición Int32(0) a otro array(array) que empieza en la posición Int32(0). Esta copia será de Int32(16) elementos. Recordando las características de las dos matrices, entendemos que se copiarán 16 bytes de la matriz que contiene el hash MD5 (es decir, se copiarán todos los bytes) a la matriz vacía de 32 bytes. Así que tenemos 16 bytes llenos y otro array
de 16 bytes vacío.

La siguiente línea utiliza el mismo método para copiar todos los bytes de sourceArray de nuevo, esta vez a la posición 15 del array. Esto significa sobrescribir el último byte de la operación anterior con el primer byte de sourceArray y rellenar el resto. Ahora tenemos 31 bytes rellenados y un byte a cero. Para facilitar la comprensión, demostraremos a continuación las operaciones descritas en los dos últimos párrafos.

sourceArray = [6E 13 31 95 5E AE 65 A4 13 8D 96 26 FC E2 CC E5] (hash MD5 de la variable s)
array = [00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00]
(matriz vacía de 32 bytes)

Primera operación de copia:

array = [6E 13 31 95 5E AE 65 A4 13 8D 96 26 FC E2 CC E5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00]

Segunda operación de copia:

array = [6E 13 31 95 5E AE 65 A4 13 8D 96 26 FC E2 CC 6E 13 31 95 5E AE 65 A4 13 8D 96 26 FC E2 CC E5 00]

Aquí está nuestra clave de 32 bytes.

Descifrar variables

En posesión de la clave, sólo nos queda entender cómo se manejan las entradas de la función que deshace el cifrado. Esto se demuestra en las tres últimas líneas de la función:

ICryptoTransform cryptoTransform = rijndaelManaged.CreateDecryptor();
byte[] array2 = Convert.FromBase64String(input);
result = Encoding.ASCII.GetString(cryptoTransform.TransformFinalBlock(array2,
0, array2.Length));

Las cadenas son decodificadas desde base64 y almacenadas en hexadecimal en un nuevo array, array2. Este array será descifrado con el método cryptoTransform.TransformFinalBlock, convertido a ASCII y almacenado en la variable result. Usando esta información junto con la clave que calculamos en el punto anterior, creamos una receta en CyberChef para deshacer el cifrado de variables empleado por Hawkeye Keylogger. Está disponible en este enlace. Veamos qué contiene cada variable del punto 3 después de ejecutar la función de descifrado:

gnfyANw8 = " JavaUpdater.exe
bfuPrPbT = "C:\Users\Ebecco\AppData\Local\Temp".
hXkfmZ = "Software Microsoft Windows
QaP8HK = "Cargar".

Esto nos permite identificar el fichero con la ruta completa donde se copiará el malware ("C:\Users\Ebecco\AppData\Local\Temp\JavaUpdater.exe") y una nueva clave (Load) que se creará en el registro ("Software\Microsoft\Windows NT\CurrentVersion\Windows").

También podemos buscar otras funciones que llamen a la función njZ5r2Fw para encontrar más cadenas cifradas en el código. La imagen de abajo trae un ejemplo:

Figura 3: Ejemplo de una cadena cifrada en otra función

Tras utilizar nuestra receta CyberChef, descubrimos que "iqfiKzfW5rUJpUrUzDCUhA==" significa "img". Se trata de una imagen presente en los recursos del malware, que se utilizará en esteganografía para obtener un nuevo ejecutable, que sólo existe en memoria. Pero ese es un tema para un futuro boletín.

Figura 4: Imagen que se convertirá en ejecutable durante la ejecución del malware

Conclusión

El cifrado es un método eficaz para proteger contenidos, ya sean malignos o benignos. Ser capaz de identificar su presencia y revertirlo es una herramienta importante en el arsenal de un analista de malware y amplía enormemente el alcance de un análisis estático.

Como se ha mencionado a lo largo de este informe, el código que implementa un algoritmo de cifrado se suele reutilizar, no se escribe desde cero para cada implementación. Por lo tanto, los conceptos presentados aquí no sólo se aplican a la muestra elegida para el análisis; pueden emplearse en el análisis de otro malware que también utilice cifrado AES. Esto también es cierto para la receta que proporcionamos de CyberChef: siempre que se posea la clave, otros aspectos de la misma (como la clave) pueden adaptarse para deshacer el cifrado utilizado en otras muestras.

Si te sientes cómodo programando en C#, también puedes aprovechar la función presente en el código del malware para que trabaje a tu favor. Para ello, proporcionamos una función genérica para revertir el cifrado, cortesía de Saskia Hiltemann en GitHub.

using System;
using System.Security.Cryptography;
using System.Text;

public class Program
{
     public static void Main()
     {


               string pass = "[STRING CRIPTOGRAFADA]";
               string target= "[STRING DA CHAVE]";
               byte[] keyarray = new byte[32];

               // Base64 decrypt target string
               byte[] target2 = Convert.FromBase64String(target);

               // Set our encryption/decryption key
               MD5CryptoServiceProvider mD5CryptoServiceProvider = new 
MD5CryptoServiceProvider();
               byte[] sourceArray = 
mD5CryptoServiceProvider.ComputeHash(Encoding.ASCII.GetBytes(pass));
               Array.Copy(sourceArray, 0, keyarray, 0, 16);
               Array.Copy(sourceArray, 0, keyarray, 15, 16);

               // Set up Rijndael Decrypt
               RijndaelManaged rijndaelManaged = new RijndaelManaged();
               rijndaelManaged.Key = keyarray;
               rijndaelManaged.Mode = CipherMode.ECB;
               ICryptoTransform cryptoTransformDecrypt = rijndaelManaged.CreateDecryptor();

               // Do it
               byte[] result = cryptoTransformDecrypt.TransformFinalBlock(target2,0, 
target2.Length);
               System.Text.Encoding encoding = new System.Text.ASCIIEncoding();

               // print result
               Console.WriteLine(encoding.GetString(result));

     }
}

Referencias

  1. https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
  2. https://github.com/shiltemann/
  3. VX-UNDERGROUND
  4. https://docs.microsoft.com/en-us/dotnet/api/?view=net-6.0

Por Alexandre Siviero

One Reply to "Cómo tratar el cifrado AES en el análisis de malware"

  1. Alexandre,
    Excelente artículo, ¡felicidades! 👋

Dejar un comentario

Su dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *.