ALCARED, Centro Autorizado de Servicios de FLUKE Networks en Perú
Análisis profundo de un malware descargado sin encabezado PE
29 de mayo de 2025
Fondo
Este análisis es parte de una investigación de incidentes dirigida por el Equipo de Respuesta a Incidentes de FortiGuard.
Descubrimos malware que llevaba varias semanas ejecutándose en un equipo comprometido. El actor de amenazas había ejecutado un lote de scripts y PowerShell para ejecutar el malware en un proceso de Windows. Aunque obtener el ejecutable original del malware fue difícil, se obtuvo con éxito un volcado de memoria del proceso del malware en ejecución y un volcado de memoria completo del equipo comprometido (el archivo «fullout», de 33 GB).
Figura 1: Archivo de volcado de memoria completo de la máquina comprometida.
La Figura 1 proporciona información detallada sobre el archivo de memoria volcado, “fullout”, que escaneamos para crear un entorno de prueba local para analizar el malware.
El archivo de malware volcado
El malware se ejecutaba en un proceso dllhost.exe con PID 8200. El archivo volcado se llama pid.8200.vad.0x1c3eefb0000-0x1c3ef029fff.dmp. El nombre del archivo revela que el malware se cargó e implementó en memoria en el rango de direcciones 0x1c3eefb0000 a 0x1c3ef029fff.
Figura 2: Vista del archivo de malware descargado.
El archivo volcado es un archivo PE (ejecutable portátil) de 64 bits implementado. Durante la ejecución, Windows Loader lee y analiza sus encabezados DOS y PE para cargar e implementar el archivo PE. Una vez implementado, estos encabezados ya no son necesarios. Para evitar volcar el malware en un archivo para su análisis por parte de los investigadores, algunos programas maliciosos suelen corromper estas regiones de encabezado sobrescribiéndolas con ceros (como este) o datos aleatorios. Como se muestra en la Figura 2, tanto los encabezados DOS como los PE están dañados, lo que dificulta la reconstrucción completa del ejecutable desde la memoria.
Implementación local del malware descargado
Para analizar dinámicamente el malware, necesitábamos replicar localmente el entorno del sistema comprometido. Esto requirió ejecutar el proceso dllhost.exe en un depurador como proceso de destino para la implementación del malware descargado. Esto nos permitiría analizar el malware en un entorno de análisis local.
Preparar el malware para que se ejecute correctamente en este entorno controlado implica varios pasos complicados.
- Localizar el punto de entrada
El primer paso es localizar la función de punto de entrada (la función de inicio), que es el código inicial que se ejecuta cuando el cargador de Windows carga el malware en la memoria.
Aunque el desplazamiento de la función de punto de entrada suele almacenarse en el encabezado PE, este no era el caso. En su lugar, tuvimos que localizar manualmente el punto de entrada (la función de inicio). Según nuestra experiencia, la primera instrucción de la función de entrada suele compilarse como «sub rsp, 28h», pero otras funciones también pueden contener esta instrucción. Sin embargo, al volcar el malware en IDA Pro, pudimos buscar todas las apariciones de esta instrucción en la base de datos de IDA Pro.
Afortunadamente, solo ocho instancias de la instrucción aparecieron en el malware (Figura 3). Tras el análisis, confirmamos que la cuarta función (en 0x1C3EEFEE0A8) es el punto de entrada.
Figura 3: Ubicación de la función de punto de entrada.
- Asignación de la memoria principal
En un proceso dllhost.exe recién iniciado, ejecutamos manualmente algunas instrucciones para asignar memoria para implementar el malware descargado, como se ve en la Figura 4. Llama a una API VirtualAlloc() relevante con la misma dirección base (0x1C3EEB70000) que la que se ve en el sistema comprometido.
Figura 4: Memoria recién asignada en un dllhost.exe.
Una vez asignado, el malware descargado se copió en la memoria recién creada.
- Resolución de la tabla de importación
La tabla de importación de un archivo PE enumera las API de Windows de las que depende. Estas direcciones de carga de API difieren en los distintos sistemas Windows. Para ejecutar y analizar el malware descargado en el sistema local, estas direcciones debían reubicarse en las que estaban cargadas en el sistema local.
Figura 5: Vista parcial de la tabla de importación del malware.
La Figura 5 muestra parte de las direcciones API de Windows de la Tabla de Importación. Con base en nuestro análisis, la dirección API final se puede calcular a partir de esta información.
Por ejemplo, la dirección API en 0x1C3EF0240D0 es 0x1C3EEEE1CE0, como se muestra en la Figura 5. Calcula la dirección API como 0x7FFD74224630 ejecutando el siguiente código ASM en la dirección 0x1C3EEEE1CE0h:
001C3EEEE1CE0 movimiento r10, 0E528F49552F112B4h
001C3EEEE1CEA movimiento r11, 0E5288B6826D35484h
001C3EEEE1CF4 xor r11, r10
001C3EEEE1CF7 jmp r11 ; 0x7FFD74224630
Usando la herramienta Volatility, listamos los módulos cargados en el proceso dllhost.exe (PID 8200) desde el archivo «fullout». Como puede ver en la Figura 6, la API en 0x7FFD74224630 se exporta desde el módulo GDI32.dll.
Figura 6: Lista de módulos cargados en dllhost.exe.
Al volcar el GDI32.dll del archivo “fullout” y analizarlo, determinamos que la API en la dirección 0x7FFD74224630 corresponde a GetObjectW() en el sistema comprometido.
En nuestro entorno de prueba local, esta misma API reside en la dirección 0x07FFFF77CB870. Para esta API, su dirección original se reemplazó por la dirección local.
Este malware tiene 257 API de Windows que requieren reubicación en 16 módulos, incluidos:
- kernel32.dll
- ws2_32.dll
- ntdll.dll
- gdi32.dll
- shlwapi.dll
- sspicli.dll
- usuario32.dll
- shell32.dll
- msvcrt.dll
- advapi32.dll,
- comctl32.dll
- crypt32.dll
- gdiplus.dll
- ole32.dll
- rpcrt4.dll
- userenv.dll
Para cada API en la Tabla de Importación, su dirección original debe ser reemplazada con su dirección local correspondiente utilizando el mismo método utilizado para API GetObjectW().
Además, necesitábamos cargar todos los módulos necesarios que dllhost.exe no cargaba automáticamente. Para ello, se debía llamar a la API LoadLibraryA() o LoadLibraryW() con el nombre del módulo para cargarlos en la memoria del malware.
La Figura 7 muestra el malware ejecutándose en un depurador. El registro RIP apunta a la dirección del punto de entrada 0x1C3EEFEE0A8. El depurador también muestra algunas de las funciones de la API de Windows reubicadas en la parte inferior.
Figura 7: Función de interrupción en el punto de entrada con la tabla API fija.
- Asignar más memoria
Según nuestro análisis, el malware también requería algunos datos variables globales ubicados en la dirección 0x1C3EEB7000 con un tamaño de 0x5A000 bytes.
Extrajimos los datos globales necesarios del archivo «fullout» utilizando la herramienta Volatility y el comando dd. Volvimos a llamar a la API VirtualAlloc() para asignar una nueva región de memoria dentro del proceso dllhost.exe con la dirección y el tamaño deseados. Tras la asignación correcta, los datos extraídos se copiaron en el nuevo espacio de memoria asignado, como se muestra en la Figura 8.
Figura 8: Datos de variables globales copiados a partir de la dirección 0x1C3EEB7000.
- Fijación de los parámetros al punto de entrada y a la pila
El análisis estático de la función del punto de entrada del malware revela que la función requiere tres parámetros.
- El primer parámetro (RCX) es la dirección base del malware cargado, que en este caso es 0x1C3EEFB0000.
- El valor del segundo parámetro (RDX) es 0x1.
- El tercer parámetro (R8) es un puntero a un búfer de 0x30 bytes, que también se puede extraer del archivo “fullout”.
Preparamos los tres parámetros en consecuencia y asignamos memoria adicional para almacenar los datos de 30H para el tercer parámetro, como se ilustra en la Figura 9.
Figura 9: Se prepararon los tres parámetros y el registro RSP ajustado.
El último punto crítico es la correcta alineación del registro RSP. Al interrumpirse el punto de entrada, los cuatro bits más bajos del valor RSP deben ser 0x8, como se muestra en la Figura 9. Una alineación incorrecta del RSP puede generar una excepción EXCEPTION_ACCESS_VIOLATION (código 0xC0000005) al iniciarse el malware.
Esto se debe a un error de alineación, especialmente al ejecutar instrucciones como «movdqa», que requiere una alineación de 16 bytes. En el modo de código de 64 bits, antes de llamar a la función de punto de entrada, el valor de la RSP está alineado a 16 bytes (los cuatro bits más bajos son 0x0). Inserta la dirección de retorno en la pila, y el valor de la RSP es -8. Para corregir esto, ajustamos el valor de 0x09CAD11D850 a 0x09CAD11D848.
Analizando el malware
Después de múltiples pruebas, errores y repetidas correcciones, finalmente logramos ejecutar el malware en el entorno local.
Tras su ejecución, el malware ejecuta una función para descifrar la información del dominio de su servidor C2 almacenada en la memoria. Como se muestra en la Figura 10, la función de descifrado se muestra junto con los detalles del dominio recién descifrado, incluyendo el dominio («rushpapers.com») y el número de puerto («443»).
Figura 10: Recién descifrada la información del servidor C2.
El malware establece comunicación con su servidor C2 mediante la creación de un hilo. Como se muestra en la Figura 11, se prepara para invocar la API CreateThread() con la función de hilo en 0x1C3EEFDE300.
Figura 11: Un hilo a punto de crearse para la comunicación C2.
El hilo recién creado se encarga de gestionar la comunicación con su servidor C2. Parte del código del hilo se muestra a la derecha de la Figura 11. Tras iniciar el hilo, el hilo principal entra en estado de suspensión hasta que el hilo de comunicación finaliza su ejecución.
Comunicación con el servidor C2
Como indica el puerto de dominio descifrado «443», el malware se comunica con el servidor C2 mediante el protocolo TLS. Utiliza la API getaddrinfo() para obtener la dirección IP del dominio «rushpapers.com» mediante una consulta DNS.
La figura 12 es una captura de Wireshark del tráfico de red generado por el malware mientras se comunica con su servidor C2.
Figura 12: Los paquetes de red intercambiados entre el malware y el servidor C2.
Dado que el paquete TLS está cifrado, es necesario inspeccionar los datos antes o después del cifrado para ver el texto sin formato. Esto se puede lograr estableciendo puntos de interrupción en un depurador en las rutinas de cifrado y descifrado que utiliza este malware.
El malware utiliza dos funciones de la API, SealMessage() y DecryptMessage(), para cifrar y descifrar los datos del tráfico TLS. Como se puede ver en la Figura 13, el malware se prepara para cifrar una solicitud HTTP GET mediante la API SealMessage().
Figura 13: Preparación para cifrar la solicitud GET usando SealMessage().
A continuación se muestran dos ejemplos de paquetes de texto sin formato (uno enviado y otro recibido):
- Paquete de solicitud (antes del cifrado TLS):
OBTENER /ws/ HTTP/1.1
Anfitrión: rushpapers[.]com
Conexión: Actualización
Actualización: websocket
Versión de Sec-WebSocket: 13
Clave de Sec-WebSocket: OCnq155rYct3ykkkdLrjvQ==
- Paquete de respuesta (después del descifrado TLS):
HTTP/1.1 101 Protocolos de conmutación
Servidor: nginx/1.18.0
Fecha: viernes, 28 de marzo de 2025 06:13:24 GMT
Conexión: actualización
Actualización: websocket
Aceptación de Sec-WebSocket: Bzr0K1o6RJ4bYvvm4AM5AAG172Y=
Estos dos paquetes de texto sin formato se utilizan para completar un proceso similar a un protocolo de enlace. Posteriormente, se cambia
a un algoritmo de cifrado personalizado para cifrar los datos del paquete antes de aplicar el cifrado TLS.
A continuación se muestra un ejemplo de los datos que se han cifrado mediante el algoritmo personalizado.
00000000 82 A6 16 98 5C 75 59 CB 66 55 41 F1 32 11 79 EF ‚¦ ˜\uYËfUAñ2 yï
00000010 2F 55 27 A8 7C 5A 36 AE 68 58 74 F1 28 55 3E A9 /U’¨|Z6®hXtñ(U>©
00000020 6C 5B 26 B6 6D 4C 26 CA 69 5C 1B 92 l[&¶mL&¬i\ ‘
Un desglose más claro de los datos se muestra en la siguiente tabla:
| Compensar | Longitud | Descripción |
| 00 | 01 | Etiqueta mágica. 0x82 |
| 01 | Variable | Longitud extendida variable. 0xA6 para este caso. |
| 02 | 04 | Clave de cifrado. 16 98 5C 75 |
| 06 | 26 horas | Datos cifrados. 59 CB 66 … 1B 92 |
De acuerdo con la regla de longitud extendida variable, el tamaño de los datos se calcula como 0xA6-0x80=0x26.
La clave de cifrado (por ejemplo, 0x755C9816) es un número generado aleatoriamente. El algoritmo de cifrado personalizado realiza una operación XOR repetida entre cada byte de la clave y los bytes de datos cifrados.
Como resultado, los datos descifrados son los siguientes:
00000000 4F 53 3A 20 57 69 6E 64 6F 77 73 20 31 30 20 2F Sistema operativo: Windows 10 /
00000010 20 36 34 2D 62 69 74 20 28 31 30 2E 30 2E 31 39 64 bits (10.0.19
00000020 30 34 35 29 0D 0A 045)
Los datos descifrados revelan claramente la información del sistema de nuestro entorno de prueba local: «SO: Windows 10 de 64 bits (10.0.19045)». Esta información se recopila y se envía al servidor C2 cuando este la solicita.
A continuación se muestra un fragmento de código que demuestra el algoritmo personalizado utilizado para cifrar y descifrar los datos.
001C3EF00CED3 loc_1C3EF00CED3: ; CÓDIGO XREF: sub_1C3EF00CE08+FD ↓ j
001C3EF00CED3 movimiento eax, r9d
001C3EF00CED6 y eax, 80000003h
001C3EF00CEDB jge corto loc_1C3EF00CEE4
001C3EF00CEDD dec eax
001C3EF00CEDF o eax, 0FFFFFFFCh
001C3EF00CEE2 incluye eax
001C3EF00CEE4
001C3EF00CEE4 loc_1C3EF00CEE4: ; CÓDIGO XREF: sub_1C3EF00CE08+D3 ↑ j
001C3EF00CEE4 movimiento ecx, [rbx+30h]
001C3EF00CEE7 agregar ecx, r9d
001C3EF00CEEA cdqe
001C3EF00CEEC incluye r9d
001C3EF00CEEF mov r8b, byte ptr [rsp+rax+28h+arg_0] ;; la clave aleatoria
001C3EF00CEF4 xor r8b, [r10] ;;;;;;; cifrar/descifrar los datos con una clave aleatoria.
001C3EF00CEF7 incluido r10
001C3EF00CEFA movimiento rax, [rbx+20h]
001C3EF00CEFE movimiento [rcx+rax], r8b
001C3EF00CF02 cmp r9d, edi ; edi es el tamaño de los datos
001C3EF00CF05 jl corto loc_1C3EF00CED3
001C3EF00CF07
001C3EF00CF07 loc_1C3EF00CF07: ; CÓDIGO XREF: sub_1C3EF00CE08+C6 ↑ j
001C3EF00CF07 agregar [rbx+30h], edi
001C3EF00CF0A jmp loc_1C3EF00CE55
[…]Análisis de características
Mediante un análisis exhaustivo de sus llamadas a la API y su flujo de ejecución, hemos confirmado que este malware es un RAT (troyano de acceso remoto). Esta sección detalla las capacidades del malware para controlar el sistema comprometido.
- Captura de pantalla
El malware tiene una función que captura la pantalla de la víctima como imágenes JPEG y las exfiltra a su servidor C2. También recopila el título del programa activo actual (el más importante) para proporcionar contexto sobre lo que el usuario está haciendo en el momento de la captura.
Para ello, llama a una secuencia de API, incluidas CreateStreamOnHGlobal(), GdiplusStartup(), GetSystemMetrics(), CreateCompatibleDC(), CreateCompatibleBitmap(), BitBlt(), GdipCreateBitmapFromHBITMAP(), GdipSaveImageToStream() y GdipDisposeImage().
La figura 14 muestra cómo el malware captura la captura de pantalla llamando a estas API.
Figura 14: El pseudocódigo de la función que captura una captura de pantalla.
- Actuar como servidor
El malware incluye una función de subproceso diseñada para actuar como servidor, escuchando en un puerto TCP especificado por el servidor C2. Una vez activada, esta función permite al malware esperar las conexiones entrantes del atacante.
Implementa una arquitectura de sockets multihilo: cada vez que un nuevo cliente (atacante) se conecta, el malware genera un nuevo hilo para gestionar la comunicación. Este diseño permite sesiones concurrentes y admite interacciones más complejas.
Al operar en este modo, el malware convierte efectivamente el sistema comprometido en una plataforma de acceso remoto, lo que permite al atacante lanzar más ataques o realizar diversas acciones en nombre de la víctima.
- Servicios del sistema de control
El malware puede enumerar y manipular los servicios del sistema en el equipo infectado. Para ello, utiliza varias API del Administrador de Control de Servicios (SCM) de Windows, como OpenSCManagerW(), EnumServicesStatusExW(), ControlService() y otras.
Conclusión
Este análisis demostró con éxito la implementación y el análisis dinámico de malware con encabezados DOS y PE dañados en un entorno local controlado.
El proceso detallado (desde la preparación del malware para su ejecución, incluida la asignación de memoria y la resolución de API, hasta la corrección de los parámetros de ejecución) garantizó una emulación precisa del comportamiento del malware.
Nuestra investigación sobre la carga útil reveló su sofisticada comunicación con el servidor C2, incluidos mecanismos seguros de cifrado y descifrado utilizando las API SealMessage() y DecryptMessage().
Finalmente, confirmamos las importantes capacidades del malware en el sistema comprometido, como la captura de pantalla, la funcionalidad de servidor remoto y la manipulación de los servicios del sistema a través de las API del Administrador de control de servicio.
Fortinet protege contra estos ataques y el equipo de FortiGuard IR está disponible para ayudarlo cuando sea necesario.
Fuente:
