El equipo del SOC de Huntress encontró e investigó una infección que involucraba un cargador de malware malicioso en un endpoint protegido por Huntress. Esta investigación se inició mediante el monitoreo de persistencia, que se activó por un script sospechoso de visual basic (.vbs) que persistía a través de una tarea programada.
El script estaba altamente ofuscado y requirió un análisis y decodificación manual para investigarlo. Hoy mostraremos nuestros métodos y proceso de pensamiento para decodificar manualmente el malware.
Usaremos principalmente CyberChef, junto con RegExper para validar expresiones regulares.
Si quieres seguir el ejercicio, aquí tienes un enlace a la muestra de malware.
(Si decides hacerlo, asegúrate de usar una máquina virtual segura y no tu propia computadora)
Empecemos
La investigación inicial fue por un archivo .vbs persistente que residía dentro del directorio de inicio de un usuario. Hay pocas razones legítimas para que un archivo .vbs sea persistente, así que obtuvimos de inmediato el archivo para un análisis e investigación más a fondo.
Dado que .vbs se basa en texto, transferimos el archivo a una máquina virtual de análisis y lo abrimos con un editor de texto. Al notar que el script estaba ofuscado, transferimos el contenido a CyberChef.
Analizando el archivo
El contenido ofuscado del script se puede ver a continuación.
Se usan numerosas formas de ofuscación: (Chr(45), StrReverse, Replace, etc.)
Simplificamos el script usando un resaltador de sintaxis configurado en "vbscript".
El resaltado de sintaxis es una forma simple y efectiva de mejorar la legibilidad de un script ofuscado, antes de hacer cualquier tipo de manipulación o análisis.
Consejo: dejar el idioma en "auto-detect" funciona, pero hemos descubierto que el resaltado es mucho más rápido si se especifica manualmente. Esto también resuelve el problema ocasional en el que Cyberchef identifica incorrectamente el idioma de un script ofuscado.
Ofuscación 1: valores codificados en decimal
Al profundizar en las primeras líneas de salida, hay numerosos valores numéricos dispersos por todas partes. Cada valor numérico está contenido dentro de una función "chr".
Una búsqueda rápida en Google revela que "chr" es una función incorporada de visual basic que convierte valores decimales en su representación en texto plano/ASCII.
Puedes encontrar una referencia a la función chr aquí y aquí. También puedes encontrar una lista completa de valores decimales y sus equivalentes ASCII aquí.
Aquí están los valores ofuscados con "chr" en su forma original ofuscada.
Estos valores numéricos se pueden decodificar de forma rudimentaria usando CyberChef, copiando manualmente cada valor y aplicando "From Decimal".
Copiar los valores manualmente es sencillo y funcionará la mayoría de las veces, pero consume mucho tiempo en un script grande y requiere que un analista copie manualmente los resultados de vuelta al script original.
Ahora mostraremos cómo automatizar este proceso usando CyberChef.
Ofuscación 1: automatizar el From Decimal usando CyberChef
Para automatizar la decodificación decimal, el equipo del SOC utilizó algunas tácticas avanzadas de regex y de CyberChef.
A un nivel general, esto consistió en:
- Desarrollar un regex que encontrara valores codificados en decimal (ubicar los datos codificados)
- Convertir este regex en una subsección (esto le indica a CyberChef que actúe SOLO sobre los datos codificados)
- Extraer los valores decimales (eliminar el "chr" y cualquier dato circundante)
- Decodificar los resultados (realizar la decodificación "From Decimal")
- Eliminar la basura circundante (limpiar cualquier residuo restante)
- Restaurar el script a su estado “normal”
Veamos eso en acción.
Primero implementamos un patrón de regex para resaltar y extraer automáticamente los valores codificados con “chr” del script original.
Como medio para probar nuestro regex inicial, utilizamos la opción “Regular Expression” y “Highlight Matches” en CyberChef.
Esto permitió observar en tiempo real la efectividad de nuestro regex.
Si algo no coincidía como se esperaba, podíamos ajustar fácilmente el regex y el resaltado se actualizaba en consecuencia.
El “Highlight Matches” ofrece una funcionalidad similar al popular sitio de pruebas de regex regex101.
Aquí puedes ver una representación visual del regex, cortesía de regexper.com.
(Regexper.com es un excelente sitio para aprender y probar regex de forma visual)
El regex coincidió exitosamente con el “chr” y los valores numéricos codificados, así que luego lo convertimos en una “subsección”.
Una subsección toma un regex como entrada, y obliga a que todas las operaciones futuras coincidan solo con valores que cumplan con el regex.
El proceso de "convertir a subsección" consiste simplemente en copiar y pegar el regex de "Regular Expression" a "Subsection".
¿Qué es una subsección?
En resumen: una subsección es una función de CyberChef que obliga a que todas las operaciones futuras se apliquen solo a los valores que coincidan con un regex proporcionado. (Por ejemplo, los valores resaltados de las capturas de pantalla anteriores)
Una subsección es una forma eficaz de “enfocarse” en contenido o valores particulares, permitiendo operaciones masivas sin estropear todo el script.
Esto fue útil para evitar decodificar accidentalmente valores numéricos que no están relacionados con las funciones y la codificación “chr”.
Para enfocarnos en nuestros valores, reemplazamos nuestro regex anterior con una subsección. (Asegurándonos de mantener el mismo regex)
A primera vista esto no parece emocionante, pero el verdadero poder aparece cuando se amplía la receta.
Por ejemplo, el "chr" ahora se puede quitar fácilmente, dejando solo los paréntesis () y los valores decimales.
Al aplicar la subsección antes de buscar y reemplazar, podemos usar el "chr" como marcador para enfocarnos en valores específicos.
Podríamos omitir la subsección e ir directo a buscar y reemplazar, pero esto podría hacer que actuemos por error sobre otros valores numéricos que no tienen relación con la decodificación actual.
Ahora se puede aplicar un segundo regex, que extraerá solo los valores numéricos de nuestro regex anterior.
En la siguiente captura de pantalla, observa cómo "chr(45)" se convierte en "45" y "chr(110)" se convierte en "110", y así sucesivamente.
Al enfocarnos en esos resultados, podemos ver que se eliminaron el "chr" y los "()". Esto deja solo los enteros/valores numéricos, así como el "& usado para la concatenación de cadenas. (Nos ocuparemos de esto más adelante.)
Luego se puede agregar un "from decimal", que convertirá esos valores numéricos de nuevo a ASCII.
De cerca, todavía se ve un poco desordenado, pero nos ocuparemos de eso en un momento.
Por ahora, podemos observar que las operaciones "chr" se reemplazaron por sus equivalentes en ASCII. (Aunque las concatenaciones de cadenas dificultan la lectura)
Para dejar todo limpio de forma definitiva, necesitábamos hacer dos cosas.
Primero, debíamos deshacer nuestra subsección. Esto nos permitiría eliminar las operaciones "&" que no estaban incluidas en nuestro regex inicial.
Esto se puede hacer con una operación de "merge". (Básicamente un botón de "deshacer" para subsecciones)
Luego usamos un Find/Replace para eliminar las comillas "" y la basura de "&".
La receta entonces se veía así. La parte más compleja es el regex `&?”&?\+?`.
Esto busca cualquier comilla precedida o seguida de un carácter &. El (?) indica que el "&" es opcional.
Una representación visual del regex, cortesía de regexper.com.
Después de esto, obtuvimos un valor decodificado limpio y ninguna operación "chr" restante en nuestro script.
Si tienes confianza con tu regex, podrías combinar los dos pasos anteriores en uno solo.
Esto finalmente nos deja algo así. Que es conceptualmente lo mismo, pero un poco más limpio que la receta original que teníamos antes, a costa de un regex un poco más complejo.
Para una explicación más profunda del regex utilizado, te recomendamos ampliamente regexper.com y regex101.com.
Si eres completamente nuevo en regex, también recomendamos ampliamente regexone.com.
Ofuscación 1: Conclusión
Resumen: cómo vencer la codificación decimal:
- Usa regex para localizar los valores codificados (localizar el chr)
- Usa una subsección para "actuar" sobre los valores codificados (enfocarse en el chr)
- Usa Find/Replace para eliminar la basura circundante (eliminar el chr)
- Realiza la decodificación (from decimal)
- Si es necesario, elimina cualquier basura adicional (eliminar la concatenación de cadenas)
- Hazlo lucir bien con un resaltador de sintaxis
Ofuscación 2: Cadenas invertidas
Un análisis más detallado determinó que había cadenas invertidas dispersas por todo el código. Esto se usa generalmente para evadir la detección y el análisis simples basados en cadenas.
Esto probablemente evadiría las firmas YARA que buscan cadenas sospechosas en archivos guardados en disco.
Abajo podemos ver el contenido invertido.
Esta codificación es simple y consiste literalmente en invertir el contenido de una cadena.
Podríamos realizar esta operación manualmente en CyberChef, pero, como antes, sabíamos que tomaría tiempo procesar todos los valores invertidos.
La especificación completa de StrReverse está aquí.
Decidimos realizar estas operaciones en lote usando CyberChef.
Nuestro enfoque…
- Usar regex para localizar los valores "invertidos"
- Usar Find/Replace o regex para eliminar la basura circundante (el nombre de la función StrReverse en este caso)
- Realizar la decodificación (usando "Reverse" + "by Character")
- Restaurar el estado original (usar un merge para deshacer la subsección)
Primero, desarrollamos el regex para localizar solo los valores invertidos.
Usamos el mismo método que antes, utilizando "regular expression" y "highlight matches" hasta que el resaltado coincidiera exactamente con lo que necesitábamos.
(Todos tenemos nuestro propio estilo de regex, puedes usar cualquier regex que resalte con éxito el contenido que te interesa).
Una descripción general de la regex, cortesía de regexper.com
Básicamente, esto dice
- Toma cualquier aparición de “StrReverse(“ incluyendo el paréntesis de apertura
- Toma todo lo que no sea una comilla doble
- Toma la comilla doble final y el paréntesis de cierre.
Luego convertimos la regex en una subsección y seguimos una metodología similar a la anterior.
- Subsección: extrae el contenido “general” de interés (en este caso, “StrReverse” y cualquier contenido entre comillas que le sigue)
- Expresión regular: extrae el contenido “exacto” de interés (extrae solo el contenido entre comillas)
- Reverse + By Character: realiza la operación inversa.
Luego observamos que las operaciones “StrReverse” se eliminaron y se limpiaron.
Con un antes y un después de una línea problemática.
Ofuscación 3: Replace
Con base en nuestro último resultado, ahora podíamos ver numerosas operaciones “replace” repartidas por todo el código.
Seguimos el mismo proceso que antes.
- Usar regex para “ubicar” los valores “codificados”
- Usar una subsección para “actuar” sobre los valores codificados
- Realizar la decodificación
- Restaurar el script a un estado limpio
Utilizamos regex para ubicar nuestros valores de interés.
Esto básicamente toma “Replace” seguido de los siguientes tres valores contenidos entre comillas dobles.
Después de confirmar que nuestra regex funcionaba como se esperaba, convertimos la regex en una subsección y aplicamos un registro.
Un registro nos permitiría extraer valores del script y almacenarlos en “registros”, que son el equivalente en CyberChef de las variables. Esto nos permitiría implementar mejor la operación de reemplazo de cadenas.
Para aplicar un registro, usamos la misma regex que antes, pero agregamos paréntesis alrededor de los valores que queríamos almacenar como variables.
Este concepto también se conoce como un “grupo de captura” si ya estás familiarizado con regex.
(Puedes encontrar un breve tutorial sobre grupos de captura en regexone.com)
Acortamos brevemente el script de malware para demostrar mejor este concepto. Observa cómo los distintos valores en la operación “replace” ahora se almacenan como variables $R0, $R1, $R2, etc.
Otra explicación gráfica cortesía de regexper.com.
Habíamos logrado extraer los valores de interés usando registros. Que luego aplicamos a una operación de buscar y reemplazar.
Esta operación pudo convertir esta línea original en la siguiente.
(De nuevo, el script de malware se acortó para demostrar el concepto)
Luego restauramos el script de malware completo y pudimos obtener el siguiente contenido decodificado. Observa que las operaciones Replace ya se eliminaron.
La receta completa se puede ver en la siguiente captura de pantalla.
(Nota la adición opcional de buscar/reemplazar para convertir las barras invertidas en guiones. Las barras invertidas extraídas inicialmente causaban problemas con la operación de buscar/reemplazar; esto no es necesario, pero da como resultado una salida un poco más limpia)
Ofuscación 4: concatenación de cadenas
Luego nos quedaba una última ofuscación. Es posiblemente la más simple hasta ahora e, irónicamente, la única que no se pudo resolver mediante CyberChef.
A lo largo del código hay cadenas concatenadas que el malware almacenaba previamente en variables.
Intentamos resolver esto usando subsecciones y registros, pero finalmente no pudimos encontrar una solución.
Luego encontramos una solución alternativa que no era CyberChef, pero que técnicamente no implicaba salir de la ventana de CyberChef, así que era lo suficientemente cercano.
Aquí está el script con las concatenaciones de cadenas originales "&"
Luego reemplazamos las concatenaciones de cadenas de visual basic (&) por un equivalente en javascript (+)
La consola de desarrollador de firefox para concatenar las cadenas de forma dinámica.
Las cadenas concatenadas se pueden ver a continuación. Esto revela la intención y el propósito final del script, que era utilizar Powershell para ejecutar un segundo payload (un script por lotes) almacenado en la máquina.
Para mayor claridad y completitud, reemplazamos manualmente los últimos valores decodificados, dejando este como el estado final del script.
Fotos de antes y después
Aquí puedes ver un antes y después completo de nuestra decodificación con CyberChef.
Aquí puedes ver un antes/después completo, con las concatenaciones de cadenas y las asignaciones eliminadas manualmente.
Conclusión
En este punto, consideramos que el script estaba completamente decodificado y procedimos a analizar el script .bat restante. Este script .bat estaba a su vez ofuscado, y se desenvolvía en otro script de PowerShell también ofuscado (como era de esperar). Este script de PowerShell contenía un cargador para el malware AsyncRat.
Si te interesa ver un análisis adicional de los payloads restantes, te recomendamos mucho las siguientes publicaciones.