En Huntress, trabajamos para entender las actividades nefastas de los hackers y analizamos mucho malware.
Y hablo de mucho malware.
Al momento de escribir esto, el equipo del SOC de Huntress ha respondido y enviado informes de incidentes para 103.872 investigaciones. ¡Eso es más de cien mil! 😱
Esta historia explora una investigación particularmente interesante porque, por todo lo que descubrimos, realmente parece un ataque dirigido.
Sigue leyendo para un análisis detallado—¡con videos!—de la persistencia en acción.
¿Qué encontró Huntress?
En un momento nos adentraremos en el análisis técnico y recorreremos la muestra de malware, pero antes déjame contarte por qué este caso es tan interesante. 😈
Bueno, al menos si crees que el malware y las tácticas de los hackers pueden ser "interesantes" de una forma extraña:
- Una técnica interesante para distribuir un payload grande de PowerShell en varios fragmentos
- El uso de un sitio web con dominio typosquatted para parecerse a la organización real
- Se hace pasar por la solución legítima de un proveedor, un producto que la organización objetivo realmente usa
Después de desentrañar cada etapa del malware y unir las piezas del rompecabezas, descubrimos que esto prepara una funcionalidad de comando y control con un beacon de Empire—Empire es un framework ofensivo de PowerShell muy común.
¿Cómo encontró esto Huntress?
Huntress busca puntos de apoyo persistentes—lo que suelo explicarle a la gente como un implante, o el medio por el cual un hacker mantiene acceso a la computadora de una víctima. Normalmente uso el término "puerta trasera" porque creo que es la palabra más fácil de entender. No es la representación más precisa de lo que queremos decir con persistencia, pero sienta las bases.
La persistencia es establecida intencionalmente por los malos. Saben que no pueden hacer phishing a la misma víctima dos veces, ni explotar la misma vulnerabilidad porque eso es ruidoso y evidente—así que instalan un pequeño código sigiloso que les da acceso a distancia.
Esto les da a los hackers una ventaja porque pueden acechar en el entorno el tiempo que quieran y luego volver fácilmente a comprometer el objetivo. Pero, en realidad, esto es un arma de doble filo… los mecanismos de persistencia, por naturaleza, tienen que ser persistentes. Eso significa que se quedan en la computadora de alguna manera, y los malos dejan rastros.
Eso nos da a nosotros, los buenos, una ventaja igual de importante. Si localizamos los implantes persistentes que usan los hackers, podemos estar seguros de que hay actividad maliciosa. Los defensores pueden entonces actuar como detectives para entender qué han estado haciendo los hackers. Finalmente, podemos eliminar la persistencia y bloquear a los hackers.
La persistencia es la pistola humeante en la escena del crimen.
Encontrar la persistencia significa que podemos obligar a los hackers a ganarse cada centímetro de su acceso.
¿Dónde estaba la persistencia?
En este artículo, nos enfocaremos en el ataque dirigido y la muestra discutida anteriormente. (Para una visión más amplia sobre la persistencia y otras técnicas astutas usadas por actores maliciosos, consulta nuestro Kit de conocimiento sobre persistencia.)
Encontramos esta peculiar tarea programada, llamada "LTSvc", presente en esta ubicación de archivo:
C:\WINDOWS\system32\tasks\Microsoft\Windows\EnterpriseMgmt\LTSvc
La tarea programada parecía invocar PowerShell. Cuando vemos que un autorun inicia PowerShell, tomamos precauciones extra porque a veces el código podría ser un script benigno desarrollado por un administrador de sistemas, o una herramienta que la organización usa legítimamente.
En este caso, sin embargo, fue fácil notar un comportamiento extraño que ningún administrador de sistemas pondría en su entorno.
Puedes encontrar el script completo en línea en este gist.
Un ojo observador notará de inmediato que esto parece código de PowerShell. Podemos ver que esto invoca PowerShell con un estilo de ventana oculto, evitando la política de ejecución, ejecutándose sin un perfil y pasando un comando extenso como argumento.
Antes de entrar de lleno a entender qué hace este código, retrocedamos un poco y facilitemos la lectura.
Tal como está ahora, este código es muy difícil de leer para un analista humano. La sintaxis está amontonada en una sola línea, pero no parece haber ningún truco usado para "ofuscar" u ocultar segmentos del código… solo necesita limpiarse.
El proceso de "limpiar" para que se vea más legible a menudo se llama "embellecer" o "formatear". Hacer esto manualmente puede tomar tiempo y ser tedioso, pero a veces es el mejor método.
Muchas veces existen utilidades en línea que pueden automatizar este proceso, pero, personalmente, no he encontrado una opción confiable específicamente para código de PowerShell. Sin embargo, puedes intentar embellecer la sintaxis de PowerShell dentro de Visual Studio Code con una extensión de formato (a veces esta se comporta mal conmigo) o la herramienta local Edit-DTWBeautifyScript creada por Dan Ward.
Según mis pruebas, no vi que ni Visual Studio Code ni Edit-DTWBeautifyScript lograran limpiar este script específico con toda la sintaxis amontonada en una línea. Limpié el script manualmente así:
- Agregando un salto de línea después de cada punto y coma (;)
- Agregando un salto de línea y sangría de tabulación después de abrir bloques lógicos (es decir, una llave { )
- Agregando espacios en blanco y saltos de línea para facilitar la lectura visual
Con eso completo, ahora tenemos un script de PowerShell mucho más legible que podemos empezar a entender.
Lo primero que hace el código de PowerShell es definir un arreglo o lista grande de "Models", que parecen ser distintos tipos de procesadores de computadora. Esta variable, "$Models", no se usa en ningún otro lugar de este código, pero tal vez se use más adelante… así que la tendremos en cuenta.
Después de esa larga lista de nombres de CPU, vemos algunas "declaraciones if" o condicionales que son la sintaxis habitual para preparar la comunicación entre sitios web HTTPS. Esto tiene sentido, ya que el siguiente segmento de código busca solicitar distintas páginas web en línea una y otra vez.
La llamada a la función (New-Object Net.WebClient).DownloadString() se comunicará con una página web externa y devolverá todo su contenido. Vemos estas llamadas canalizadas hacia Invoke-Expression y, posteriormente, hacia Out-Null.
Invoke-Expression ejecutará y evaluará los datos pasados como código real de PowerShell. Es el equivalente de una declaración "eval" en otros lenguajes, donde la cadena sin procesar suministrada se invocará y ejecutará como una nueva parte del programa. Invoke-Expression a menudo se ve en su forma de alias, "IEX", que es solo un apodo abreviado para el mismo cmdlet.
Esta técnica de uso repetido de DownloadString e Invoke-Expression inicialmente parecía un método de "fuerza bruta" interesante para obtener el siguiente payload, pero cuando empecé a descargar cada nuevo fragmento para analizarlo, quedó claro que estas eran solo una pequeña pieza del rompecabezas.
¿Por qué consideras esto "dirigido"?
Ten en cuenta que el dominio typosquatted-random-nonsense-productweb-com.lookalike.net que ves presente en este código es algo que colocamos intencionalmente para proteger la información de los clientes. Originalmente, este era de hecho un sitio web falso que se hacía pasar por la organización y el negocio original.
Este dominio, y el código malicioso en sí, hacía referencia repetida a un producto específico o solución de software legítima que esta empresa realmente usaba en su stack tecnológico. Dado que esto parecía ser información interna de la organización, queda claro que los hackers hicieron su tarea. Querían que este malware se mezclara y se camuflara con las operaciones reales de ese negocio específico.
¿Cuál fue el siguiente código de PowerShell?
Después de descargar todos los endpoints de DownloadString referenciados repetidamente para obtener nuevo código .ps1, salió a la luz esta sintaxis:
Este es el código completo que se dividió en diferentes partes y se descargó junto con la etapa original de LTSvc. Podemos empezar a embellecerlo y desofuscarlo como de costumbre.
Este nuevo código de PowerShell usa codificación Base64 repetida para intentar ocultar los indicadores del uso de llamadas a funciones de la API de Win32, concretamente:
- LoadLibrary (como variable $XTFEJEOHNBE)
- GetProcAddress (como variable $TTSHYJRVOKWITW)
- VirtualProtect(como variable $MPNHYEJNTLNMCF)
A continuación, vemos un bloque muy grande de datos codificados en Base64. Hay una pista reveladora aquí, ya que los caracteres iniciales son "TVqQ"... ¡esta es una de las formas en que un binario ejecutable de Windows podría verse cuando se codifica en Base64!
Al extraer y decodificar ese primer bloque de datos en Base64, podemos determinar con certeza que se trata de un ejecutable de Windows. De hecho, ¡es una DLL... y un ensamblado .NET!
Al ser un ensamblado .NET, podemos abrir este archivo en herramientas como dotPeek, dnSpy o ILSpy para ver cuál era el código fuente original. Por suerte no necesitamos abrir un depurador o un desensamblador como GHIDRA o IDA, porque, debido a la naturaleza de un ensamblado .NET, podemos extraer el código fuente directamente.
En la distribución REMnux, puedes extraerlo con la versión de línea de comandos preinstalada de ILSpy, ilspycmd.
Este código en C# parece ser solo un pequeño stub, que carga e importa esas mismas tres funciones desde kernel32.dll. Esta clase se llama ASBBAPI, y conviene tenerlo en cuenta, ya que probablemente lo volvamos a ver mientras avanzamos por el código de PowerShell.
Esto significa que el primer bloque en Base64 es esta DLL compilada, que luego se cargará de forma reflectiva en el contexto de PowerShell en ejecución. Vemos esto aquí, en la línea 5:
[Reflection.Assembly]::Load([Convert]::FromBase64String($SAVNNBL))
En este punto, el código ya preparó el uso de estas tres funciones de la API de Win32.
¿Qué hace el código de PowerShell con estas llamadas a la API?
En las siguientes líneas del código de PowerShell, podemos ver que crea hábilmente un objeto a partir de esta clase ASBBAPI definida en el código fuente en C#. Con eso definido, puede llamar a esas funciones LoadLibrary, GetProcAddress y VirtualProtect con argumentos que proporciona e intenta ofuscar con conversiones de tipo [char] repetidas.
Al observar cómo se desarrolla esto, vemos que el código...
- Carga la biblioteca amsi.dll en una variable con LoadLibrary
- Encuentra la función AmsiScanBuffer con GetProcessAddress
- Hace que el espacio de memoria de AmsiScanBuffer sea modificable con VirtualProtect
Finalmente, se escriben algunos bytes nuevos únicos (0xB8, 0x57, 0x00, 0x07, 0x80 y 0xC3) en el espacio de memoria y básicamente sobrescriben lo que antes era la función AmsiScanBuffer. Esto se hace con una de las últimas líneas de ese código de PowerShell:
[System.Runtime.InteropServices.Marshal]::Copy($LHLCD, 0, $WUGAGU, 6)
Esos nuevos bytes son las operaciones de código máquina para "retornar", es decir, no hacer nada y dejar que la ejecución continúe sin problemas.
Este proceso finalmente rompe AMSI.
¿Qué es AMSI?
Si no estás familiarizado con el término, AMSI es la “Antimalware Scan Interface”. Es un mecanismo de seguridad integrado en versiones modernas del sistema operativo Windows que busca código nefasto o malicioso dentro de ensamblados .NET, código de PowerShell u otros lenguajes nativos.
Básicamente, antes de que se ejecute una línea de código, se verifica con tu antivirus para determinar si es “buena o mala”. Si el motor determina que parece ser código malicioso, se bloquea la ejecución.
La funcionalidad de AMSI reside en la biblioteca nativa amsi.dll, precisamente por eso fue cargada por nuestra muestra de PowerShell. El núcleo de AMSI y lo que realmente hace que funcione es la función AmsiScanBuffer, precisamente por eso la muestra de PowerShell selecciona esa función y la sobrescribe.
Esta no es una técnica nueva ni novedosa. Hay mucha investigación previa e incluso utilidades en línea para generar rápidamente un payload que rompa AMSI justo así. Puedes experimentar en tu propia máquina virtual si quieres, probando la sintaxis de https://amsi.fail. Mientras hacía algunas pruebas, descubrí que muchos payloads de PowerShell generados no lograban evadir AMSI, pero esta técnica específica (usar un stub de C# para cargar las llamadas a la API de Win32) sí tuvo éxito.
Puedes ver el efecto de esto en el video de demostración en vivo a continuación.
¿Qué sucede después?
Hasta ahora, el código de PowerShell que hemos examinado ha evadido AMSI en este contexto de ejecución. Eso significa que cualquier código malicioso o nefasto en PowerShell podría ejecutarse sin ser detectado, sin las miradas indiscretas del antivirus o las soluciones de seguridad preventivas.
Finalmente, llegamos a la última línea de esta muestra de PowerShell. Como podrías haber adivinado, esto incluye otro gran bloque de datos codificados en Base64, llamado en una variable denominada $a. Estos datos luego se decodifican, se descomprimen como un archivo GZIP y finalmente se pasan a IEX… ese alias y apodo abreviado para el dudoso Invoke-Expression.
Una vez más, tenemos otra “etapa” o capa de esta muestra de malware a medida que pelamos la cebolla para descubrir qué hace realmente este código de PowerShell.
Esta siguiente etapa es nuevamente una línea minificada y comprimida en una sola línea, con mayúsculas aleatorias y aún más codificación Base64. Una vez más la embellecí/desofusqué manualmente, obteniendo esto:
La primera línea, $ErrorActionPreference = "SilentlyContinue";, simplemente le indica a PowerShell que ignore y continúe ante cualquier error en este script.
Luego el código comienza a construir funcionalidad para hacer aún más solicitudes web. Define un nuevo objeto WebClient y proporciona un User-Agent personalizado. Cada vez que accedes a un sitio web con tu navegador como Firefox o Chrome, inherentemente incluyes en tu solicitud un encabezado configurable “User-Agent” que identifica qué navegador estás usando. Por defecto, las solicitudes web de PowerShell incluirían su propio User-Agent de facto… pero cambiar esto para que parezca un navegador web más estándar y típico nuevamente intentaría mezclarse con el tráfico normal.
He renombrado las variables aquí, así que $c2_server originalmente se llamaba $ser, pero esto nuevamente hace referencia al dominio similar. Vemos un nuevo endpoint, “/login/product.php”.... sin embargo, el nombre “product” era el nombre de la solución de software real que esta organización realmente usaba. Nuevamente, esta información se redacta por confidencialidad del cliente.
La segunda mitad del código de PowerShell busca preparar alguna forma de comunicación cifrada con este endpoint. Descarga los datos de la página, los desenreda con el uso de las variables $K (clave), $R, $D, $S y $J, y finalmente las pasa de nuevo a IEX para ejecutar aún más PowerShell.
Cuando encontramos esta muestra de malware por primera vez, el sitio web de C2 aún estaba activo y accesible. Neutralizamos la llamada maliciosa a IEX, reemplazándola esencialmente con un simple Write-Host, para poder dejar que PowerShell ejecutara este código por nosotros y luego mostrara qué ejecutaría esta siguiente etapa de PowerShell.
¿Cuál es la etapa final del código de PowerShell?
Aunque vemos otra llamada a IEX presente en este código, lamentablemente no pudimos recuperar más payloads para un análisis posterior. Parece que el beacon de escucha ya no estaba activo, y ahora podemos explorar por qué decimos eso.
Esta etapa final del código de PowerShell es un poco extensa, así que para abreviar está disponible en este gist y compartiremos un fragmento en la captura de pantalla a continuación.
La mitad superior del código prepara funciones llamadas Start-Negotiate, ConvertTo-RC4ByteStream y Decrypt-Bytes, y una vez más prepara la funcionalidad para hacer comunicaciones web cifradas. Realiza un reconocimiento ligero para recopilar el sistema operativo de la computadora de la víctima, la cuenta de usuario y la dirección IP.
Cerca del final del código, puedes ver una pieza de información reveladora. El PowerShell malicioso ejecuta el cmdlet Invoke-Empire.
Justo encima de eso, vemos otra llamada a IEX... pero este es el segmento del que no pudimos recuperar más datos. Es fácil suponer que lo que se descargó y ejecutó en esa llamada prepararía y construiría las funciones para Empire, para que el cmdlet "Invoke-Empire" se ejecutara con éxito.