Serpientes en un dominio: un análisis de un cargador de malware en Python

Hackers y serpientes, ¡qué miedo! ¿Qué tienen en común? Ambos son personajes turbios que pueden esconderse a plena vista, esperando el momento justo para atacar.

Pero ¿cómo sabes si tienes plagas indeseadas cerca? A menudo, solo tienes que salir a buscarlas, y eso es exactamente lo que hicimos. En el camino, encontramos un Python muy turbio (y, casualmente, un RAT amistoso) esperando para atacar.

Acompáñanos en este recorrido mientras te mostramos lo importante que es mantener tu jardín limpio y ordenado, tanto el real con pasto verde como el virtual con bytes y binarios. De lo contrario, nunca sabes qué tipo de criaturas turbias pueden estar acechando en las sombras.

¿Qué pasó?

Hace poco investigamos un archivo de acceso directo sospechoso que persistía en la carpeta de inicio de un usuario. El archivo se llamaba “sysmon.lnk” y se veía un poco raro. Tras una rápida investigación inicial, descubrimos que el acceso directo ejecutaba un script de Python malicioso que se usaba para inyectar un troyano de acceso remoto (RAT) en el sistema.

En el camino, encontramos un total de seis payloads consecutivos y algunas herramientas ofensivas nuevas que nos parecieron bastante interesantes. Hacia el final, también experimentamos con algunos scripts personalizados para desofuscar datos y extraer la configuración del RAT final, lo que nos dio unos jugosos indicadores de compromiso (IOC) con 0 detecciones en VirusTotal (a junio de 2021).

Manos a la obra

Antes de avanzar mucho más, aquí tienes una representación visual del malware que encontramos.

Blog_Python-RAT-Infographics_P1-01

Nos topamos con un archivo sospechoso (sysmon.lnk) que parecía residir en el directorio de inicio de un usuario. La función del directorio de inicio es guardar archivos que se ejecutan automáticamente cuando un usuario inicia sesión en la computadora. Como parece una carpeta normal, basta con copiar y pegar un archivo en ella y, listo, puedes persistir, o quedarte, entre reinicios.

Esto les da a los programas legítimos una forma fácil de quedarse y seguir ejecutándose. Por su simplicidad y sigilo, es un lugar común donde los atacantes colocan malware y archivos maliciosos que quieren mantener en el sistema.

¿Quieres más información sobre la persistencia? Descarga nuestro eBook Persistencia: la clave del sigilo, la estrategia y el éxito de los ciberdelincuentes.

Este es un fragmento de lo que vimos:

c:\\users\<username>\appdata\roaming\microsoft\windows\startmenu\programs\startup\sysmon.lnk

Este es un archivo .lnk (también conocido como acceso directo), que redirige a otro archivo o comando del sistema. Inspeccionar el archivo .lnk nos puede decir a dónde apunta.

Cuando inspeccionamos sysmon.lnk, descubrimos que redirigía a un sospechoso “ctfmon.exe” con “update.py” pasado como argumento. Ambos residían en un directorio de aspecto sospechoso:

c:\users\<username>\appdata\roaming\PpvcbBQh\ctfmon.exe

c:\Users\<username>\AppData\Roaming\PpvcbBQh\update.py

Así que recuperamos los archivos e hicimos algo de análisis.

Análisis de archivos

Primero, notamos que el hash de ctfmon.exe tenía 0 detecciones en VirusTotal, lo que al principio nos pareció interesante, pero pudimos entenderlo después de revisar la información del archivo. (Normalmente no podemos confiar en la información de versión de un archivo sin una firma válida, pero en este caso, la información tenía sentido).

La información sugería que ctfmon.exe es un intérprete de Python renombrado, específicamente un intérprete de IronPython, que utiliza una rama de Python con acceso a bibliotecas .NET. Esto permite que el código Python acceda a funciones profundas del sistema operativo Windows normalmente reservadas para .NET o PowerShell. Esto era interesante y nos dio suficiente información para pasar con confianza al archivo de Python.

virustotal1

Podemos ver que el archivo original ctfmon.exe tenía 0 detecciones en VirusTotal, ya que técnicamente es un intérprete legítimo y no un archivo malicioso.

Abajo podemos ver la descripción del archivo, que indica que era un intérprete de IronPython renombrado. También podríamos haber descubierto esta información con PeStudio o una herramienta similar.

SigVerif
IronPython

Esto bastó para determinar el propósito del archivo ctfmon.exe, así que pasamos al archivo Update.py, al que llamaremos stage1.py.

Stage1.py

Primero movimos el archivo de Python a un editor de texto dentro de una máquina virtual por si era malicioso y, alerta de spoiler: lo era. 😅

Esto nos llevó a un script relativamente pequeño con una cadena ofuscada grande y algunos nombres de variables ofuscados. Podemos ver el script completo en esta captura de pantalla:

mal1

No era muy agradable de leer, así que lo limpiamos un poco y le agregamos comentarios, lo que dejó este script:

mal2

Si lo inspeccionamos más de cerca, vemos que el script logra cuatro cosas principales:

  • Decodifica en Base64 una cadena ofuscada
  • Convierte la cadena decodificada en Base64 en un bytearray de valores hexadecimales
  • Luego, reduce el valor de cada byte en 12 (decimal)
  • Por último, ejecuta los datos resultantes

Al copiar la cadena ofuscada y recrear la lógica en CyberChef, pudimos recuperar otro script de Python, que guardamos con el nombre stage2.py. La lógica de decodificación se puede ver abajo:

Stage2

Stage2.py

Copiamos el script resultante de CyberChef y abrimos stage2 en un editor de texto, donde rápidamente notamos otra cadena ofuscada, además de algunas bibliotecas importadas relacionadas con la reflexión. (Por si no la conoces, la reflexión es una técnica común que se usa para ejecutar código desde la memoria sin necesidad de guardarlo en disco; en este caso, ese “algo” sería la cadena ofuscada que contiene malware).

Con base en esta información, supusimos que el script decodificaba la cadena y cargaba los resultados en memoria para ejecutarlos.

Stage2_2

En el centro de la captura de pantalla anterior, podemos observar dos operaciones principales que se usan para decodificar la cadena:

  • Reemplazar todos los signos de exclamación “!” por la letra “A”
  • Decodificar en Base64 los resultados

No parecía muy complicado, así que volvimos a CyberChef y recreamos la lógica de decodificación. Esto hizo aparecer un encabezado MZ, lo que indicaba que habíamos decodificado los datos con éxito y recuperado un archivo ejecutable. Guardamos este archivo con el nombre stage3.bin.

Stage2_3

Stage3.bin

Al guardar stage3 como archivo ejecutable, pudimos hacer una inspección básica con PeStudio y Detect-It-Easy (DIE). Esto nos llevó rápidamente a concluir que era un archivo .NET y probablemente otro stager (por la presencia de una ruta que hacía referencia a injector.pdb).

Abajo podemos ver que DIE reconoció el archivo como un ejecutable .NET, lo que significaba que podíamos usar Dnspy o ILspy para analizarlo.

DIE1

Abajo también podemos ver la ruta PDB con referencias a “injector.pdb”, lo que indica que probablemente se trata de otro stager que realiza algún tipo de inyección:

injector

Como ya sabíamos que era un archivo .NET, pasamos a Dnspy, donde pudimos ver el código fuente del archivo. Se puede ver abajo.

DNSpy

Con solo ver los nombres de las funciones, tuvimos una idea clara de lo que iba a hacer el archivo. Vemos funciones que indican inyección (VirtualAlloc, WriteProcessMemory, etc.), carga dinámica de bibliotecas y funciones (GetProcAddress, LoadLibrary) y decodificación (compress, decompress, base64_encode). Sin ver el código en detalle, ya podíamos suponer su función principal: un payload ofuscado se va a decodificar e inyectar en un proceso.

Al navegar a la función principal, encontramos rápidamente el payload codificado. Junto con las llamadas a funciones anteriores (Load, Decompress, Base64), podemos suponer que los datos se decodifican en Base64, luego se descomprimen y se cargan en memoria.

Abajo podemos ver la cadena codificada y las llamadas a funciones relacionadas:

DNSpy2

Hacia el final de los datos codificados, también observamos una referencia a msbuild.exe. Esto cobró importancia más adelante, ya que resultó ser el segundo argumento pasado al método Mandark.Load.

DNSpy3

Luego, navegamos al método Mandark.Load para averiguar qué más estaba pasando y determinar la importancia de ese argumento msbuild.exe.

Esto nos llevó a concluir que el segundo argumento pasado al método load se convierte en el proceso objetivo de la inyección. También notamos el uso de ZwUnmapViewOfSection, lo que indica que este tipo de inyección es process hollowing. MITRE ATT&CK define el process hollowing así:

“Los adversarios pueden inyectar código malicioso en procesos suspendidos y vaciados para evadir las defensas basadas en procesos. El process hollowing es un método para ejecutar código arbitrario en el espacio de direcciones de un proceso activo separado”.

Creemos que probablemente se eligió MSBuild porque las herramientas de listas de permitidos de aplicaciones, incluido AppLocker de Microsoft, suelen permitir su ejecución de forma predeterminada.

Argument

Con este nuevo conocimiento, decidimos volver a la función principal e intentar decodificar el payload inyectado. Ya habíamos notado que se usaba codificación Base64 y compresión.

Argument2

Inspeccionamos rápidamente el método decompress para confirmar el tipo de compresión; en este caso, era Gzip.

Argument3

Al combinar toda la información anterior, pudimos decodificar el siguiente payload con CyberChef. Esto produjo otro encabezado MZ de un archivo ejecutable. Guardamos este archivo con el nombre stage4.bin. Ten en cuenta que este payload probablemente se habría inyectado en el proceso msbuild.exe.

Argument4

Stage4.bin

Al cargar stage4.bin, hicimos un análisis estático básico y determinamos que no era otro archivo .NET, así que no pudimos usar Dnspy.

Abajo podemos ver el compilador detectado con DIE, que sugería que estaba escrito en C++/C y no en .NET.

DIEc1

Con PeStudio, notamos esta función exportada, que nos llamó la atención porque indicaba que probablemente se trataba de otro loader (lo delataba el término “ReflectiveLoader”).

DIEc2

Lo anotamos y seguimos adelante.

Al seguir explorando, notamos esta referencia en la sección de depuración del archivo. Contenía otra ruta PDB y una estructura de carpetas muy parecida a la de git.

DIEc3

Al buscar en Google algunas palabras clave de la ruta PDB, llegamos a la conclusión de que el archivo probablemente era un loader execute-assembly, una reimplementación de código abierto del módulo execute-assembly de Cobalt Strike:

DIEc4

A juzgar por el repositorio de GitHub, es un loader muy completo e interesante que incorpora tácticas de evasión realmente ingeniosas. Casi podríamos dedicarle un Blog entero a las capacidades de este loader, pero hoy nos limitaremos a sus capacidades de carga e intentaremos centrarnos en encontrar el siguiente payload.

En el resto de la documentación del repositorio de GitHub, había un detalle en particular (ver abajo) que nos llamó mucho la atención. Indicaba la estructura de los payloads incrustados, que debían tener el formato “0|0|0|0|1|sizeofpayload.b64_encoded_compressed_payload”.  (Nota: el payload estará comprimido con Gzip y codificado en Base64).

DIEc5

Esto fue muy interesante porque había una cadena muy grande dentro del archivo que coincidía exactamente con esa descripción (y medía 64983 bytes, espacio más que suficiente para otro payload).

DIEc6

Copiamos esa cadena en CyberChef y reimplementamos la rutina de decodificación (Base64 y descompresión Gzip), lo que dio como resultado otro archivo ejecutable más.

DIEc7

Ya conoces la rutina: guardamos este archivo con el nombre stage5.bin.

Stage5.bin

Al hacer nuestro análisis estático habitual del archivo más reciente, pronto nos dimos cuenta de que era otro .NET (¡bien!). Por suerte, pudimos volver a Dnspy y ver el código fuente.

stage5_1

Al pasar a Dnspy, notamos que esta vez no había muchas funciones, solo seis en total:

stage5_2

Al navegar a la función principal, notamos dos cadenas ofuscadas grandes:

stage5_3

La primera solo estaba codificada en Base64 y resultó ser un script de parcheo de la interfaz de análisis anti-malware (AMSI). Implementada por Microsoft, AMSI ofrece un marco para que las herramientas de seguridad monitoreen la actividad de los scripts de PowerShell. El objetivo de un parche de AMSI es eludir este marco y reducir las probabilidades de que un antivirus o EDR detecte actividad maliciosa de PowerShell. (Más adelante veremos que el malware sí usa scripts de PowerShell, así que este parche probablemente les permite ejecutarse sin ser detectados).

Abajo podemos ver el script completo de parcheo de AMSI, que estaba ligeramente ofuscado.

stage5_4

Pudimos decodificar el script, que a grandes rasgos se traduce en lo siguiente.

stage5_5

La segunda cadena era mucho más interesante, ya que incorporaba una rutina de codificación personalizada junto con el Base64 y la compresión a los que ya estábamos acostumbrados. Esto indicaba que íbamos a necesitar algo más que CyberChef para decodificar el siguiente payload.

stage5_6

Para entender mejor la ofuscación, inspeccionamos el método Cipher y encontramos la rutina de codificación. No parecía estándar y claramente era algo hecho a medida, aunque no muy complicado de decodificar. Rutinas como esta se usan a menudo para evadir el análisis automatizado, ya que su naturaleza no estándar entorpece algunas herramientas automatizadas y suele requerir intervención y análisis manual para decodificarla correctamente.

Abajo podemos ver la rutina personalizada completa, que recibe una cadena codificada, una clave y un indicador de cifrado.

stage5_8

Al volver a la función principal, encontramos rápidamente la clave “avyhk” y el indicador de cifrado, que estaba configurado en false.

stage5_7

Decidimos no usar CyberChef para esto. Tras una inspección y un análisis cuidadosos, pudimos reimplementar la rutina con el código Python equivalente que incluimos abajo.

stage5_9

Con nuestro nuevo script de Python, escribimos un wrapper alrededor de la función de cifrado y pudimos volcar el contenido decodificado en un archivo nuevo. Así obtuvimos otro archivo ejecutable: stage6.bin.

Stage6.bin

Guardamos y cargamos el archivo stage6.bin en PeStudio y DIE para hacer algo de análisis estático y vimos que teníamos otro archivo .NET. (¡Otra vez Dnspy, bien!)

stage6_2

En general, no encontramos nada particularmente útil en PeStudio, así que pasamos a Dnspy. Pudimos determinar que el archivo era un troyano de acceso remoto (RAT), probablemente de la familia de malware URSU.

Este malware tenía todas las funciones típicas de un RAT, incluida la capacidad de recopilar y enumerar información del sistema, así como descargar archivos y comandos desde un servidor remoto de comando y control.

Análisis del RAT

Abajo podemos ver una descripción gráfica de las funciones del payload final del RAT.

Blog_Python-RAT-Infographics_P1-02

Descifrado de la configuración

Tras determinar que este malware probablemente era un RAT, decidimos buscar indicadores del servidor C2 y cualquier ajuste de configuración que pudiéramos usar como indicadores de compromiso. Al analizar el código del RAT en Dnspy, encontramos un método “InitializeSettings” que cargaba datos de configuración a partir de valores cifrados con AES256 y luego codificados en Base64.

Este es el código para descifrar los datos de configuración dentro del método InitializeSettings:

decrypt_1

Abajo podemos ver cómo se cargan los valores cifrados con AES256 y codificados en Base64.

decrypt_2

Después de experimentar con el código de descifrado, pudimos descifrar la configuración y extraer los siguientes valores, incluidos un número de puerto, un nombre de mutex, números de versión y de agrupación, y tres dominios de servidores C2.

decrypt_3

Enumeración de la máquina

Mediante una combinación de consultas al sistema operativo, principalmente a través de consultas WMI, el malware recopilaba la siguiente información para enviarla al servidor C2:

  • Antivirus y productos de seguridad en ejecución
  • Privilegios del usuario
  • Si la víctima estaba conectada a un dominio
  • IP externa de la máquina actual
  • Nombres de las ventanas abiertas y los procesos activos

Comprobaciones antianálisis

Después de enumerar la información del sistema, el malware ejecutaba algunas comprobaciones antianálisis para ver si se estaba ejecutando dentro de una máquina virtual o un entorno de análisis.

El malware contenía varios métodos y funciones para detectarlo. Eran relativamente simples y consistían en cinco comprobaciones principales:

  • DetectManufacturer: busca VMware o VirtualBox en las descripciones del hardware
  • DetectDebugger: revisa el indicador “Debugger.IsAttached” y también comprueba si hay un archivo  dnspy.xml en el directorio %appdata%
  • DetectSandboxie: busca controladores de Sandboxie (sbiedll.dll)
  • IsSmallDisk: comprueba si el tamaño del disco es menor de 61 GB
  • IsXP: comprueba si el sistema operativo actual es Windows XP

Si alguna de estas comprobaciones es verdadera, el malware hace limpieza y se termina a sí mismo con el método “failFast”.

Abajo podemos ver los nombres de las funciones antianálisis que se llaman.

anti_1

Ninguna era especialmente interesante ni compleja, y todas seguían una estructura similar a la de la captura de pantalla de abajo.

anti_2

Persistencia final: claves Run y tareas programadas

Una vez completadas las comprobaciones antianálisis, el malware establecía más persistencia mediante tareas programadas y claves Run, según el nivel de privilegios actual.

Si había privilegios de administrador disponibles, se creaba una tarea programada elevada. Esto le permitiría al malware persistir con privilegios de administrador entre reinicios, sin necesidad de avisos de UAC cada vez.

Si solo había privilegios de usuario estándar, se colocaba un script .bat en la clave Run del usuario actual, lo que brindaba persistencia con privilegios de usuario estándar.

Con estos indicadores, pudimos encontrar otros artefactos que dejó el malware y desarrollar detecciones para alertar sobre actividad similar.

Puedes buscar persistencia similar mediante tareas programadas y claves Run revisando con regularidad las siguientes ubicaciones de claves Run y tareas programadas:

  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run
  • c:\windows\system32\tasks

(O bien, regístrate para una prueba gratuita y lo revisamos por ti).

persistence_1

Comandos y funcionalidad de C2

Una vez establecida la persistencia, el malware contactaba a los servidores de comando y control para recibir más comandos. Estos comandos podían ser...

  • Update: descarga malware nuevo mediante PowerShell, lo inicia y luego termina el proceso actual
  • SavePlugin: descarga y carga una DLL remota
  • Unload: envía un comando de terminación a través de una canalización con nombre
  • Restart: termina el proceso actual y fuerza un reinicio mediante una tarea programada
  • Self-delete: elimina toda la persistencia y termina el proceso actual

Algunos fragmentos breves de esta funcionalidad aparecen en las capturas de pantalla de abajo:

c2_1
c2_2
c2_3

Verificación de dominios en VirusTotal: 0/3

Al momento del análisis inicial (mayo de 2021), todos los dominios tenían 0/85 detecciones en VirusTotal, aunque uno de ellos estaba marcado como sospechoso por un proveedor.

VT_1
VT_2

Recomendaciones y comentarios finales

Con esto concluye nuestro análisis de este malware. Esperamos que lo hayas disfrutado tanto como nosotros. Ojalá hayas aprendido algo nuevo y pronto puedas aplicar algunas de estas técnicas de análisis por tu cuenta.

Como vimos, incluso un payload relativamente simple (como un RAT) puede implementarse de una forma muy compleja y difícil de detectar, sobre todo al usar archivos y dominios personalizados o únicos que se escapan de las herramientas de seguridad automatizadas. Aunque las herramientas automatizadas tienen su lugar, ya pasaron los días en que podías depender solo de ellas.

Asegúrate de que tu stack de seguridad incluya métodos de caza de amenazas proactivos e impulsados por personas, junto con herramientas en capas que dificulten y reduzcan la probabilidad de un compromiso exitoso.

Para cerrar, queremos hacer algunas recomendaciones para lidiar con este tipo de malware:

  • Evita depender de firmas estáticas para detectar actividad maliciosa. Esto aplica tanto a los indicadores de compromiso de red como a los basados en archivos. Todos los ejecutables en ejecución y los dominios de esta investigación eran “legítimos” y probablemente no se habrían bloqueado solo por su hash.
  • Monitorea y revisa manualmente los archivos sospechosos que se ejecutan desde claves Run, tareas programadas y carpetas de inicio persistentes.  
  • Monitorea los eventos de creación de procesos en los que se pasa un archivo de Python a un ejecutable que no es Python ni un editor de texto.
  • Inspecciona cualquier evento de creación de procesos sospechoso o no estándar. Define una línea base de los procesos que se espera que inicien msbuild.exe y genera alertas ante cualquier cosa fuera de esa línea base.
  • Al analizar archivos y dominios sospechosos, asegúrate de incorporar el análisis y la decodificación manual en tu proceso. Evita depender únicamente de herramientas automatizadas como VirusTotal o sandboxes en línea.

Indicadores de compromiso

  • Dominios:

windowsupdatecdn[.]cn

gjghvga7ffgb[.]xyz

huugbbvuay4[.]cn

  • Hashes:

ctfmon.exe: 3e442cda613415aedf80b8a1cfa4181bf4b85c548c043b88334e4067dd6600a6

Update.py: dd1fa3398a9cb727677501fd740d47e03f982621101cc7e6ab8dac457dca9125

stage2: 2CCADFC32DB49E67E80089F30C81F91DFFF4B20B8FC61714DF9E2348542007FD

stage3: 4591EDA045E3587A714BB11062EB258F82EE6F0637E6AA4D90F2D0B447A48EF7

stage4: 4417298524182564AED69261B6C556BDCE1E5B812EDC8A2ADDFC21998447D3C6

stage5: 9B775DFC58B5F82645A3C3165294D51C18F82EC1B19AC8A41BB320BEE92484ED

stage6: 169F5DBCD664C0B4FD65233E553FF605B30E974B6B16C90A1FB03404F1B01980

También te podría interesar