Hooking de funciones VBScript con Frida

El trabajo de análisis requiere de una lucha continua contra código desconocido, ofuscado y cifrado. Recientemente me encontraba peleándome con un VBScript que, cómo no, se encontraba ofuscado (al menos dos capas de protección). Tras superar la primera protección y empezar con la segunda, comprobé que el método de descifrado en este punto no era trivial. Esto se sumaba a una aversión, por cuestiones meramente personales, por este lenguaje. Un análisis dinámico me podía dar la secuencia de comandos que finalmente se ejecutaban, pero perdía entonces visibilidad de todo cuanto había ocurrido por mitad. Y… ¿quién sabe? A lo mejor hay alguna técnica antianálisis por mitad que modifica el comportamiento del script para contactar contra otra infraestructura diferente. Siempre con el modo paranoico encendido.

Decidí que iba a atajar el asunto de raíz. Tanto el primer script como el segundo descifrado, después de desprotegerse, hacían uso de la función ‘Execute’ de VBScript para dar vida a su código antes protegido. Internamente, el proceso que ejecuta el script (‘wscript.exe’ en este caso), tendría que manejar el buffer descifrado y pasarlo a otra función que se encargue de ejecutarlo. Pero… ¿Dónde se encontraba susodicha función?

Con fines didácticos, en lugar de analizar la aplicación usando el modo rápido, voy a suponer que desconocemos la estructura del propio ‘wscript.exe’ y de las funciones de las librerías que carga. En primer lugar, ¿Qué hace el script y qué espero ver al analizar ‘wscript.exe’?

Ilustración 1: Script inicial cifrado

Ilustración 1: Script inicial cifrado

La primera protección era trivial, así que creé un script rápido para descifrarlo (también podría haber sustituido el ‘Execute’ por una escritura de fichero, pero entonces la entrada del blog terminaría aquí). Se recorre una lista obteniendo todos los enteros separados por el carácter ‘#’, les resta 21, obtiene el carácter ASCII correspondiente, y lo concatena para ejecutarlo a continuación. Una vez descifrada, ejecuta la cadena de texto con la función ‘Execute’. Incluyo la parte inicial del script, sin ningún valor a la hora de su ejecución, porque ahora nos será de utilidad.

Ilustración 2: Script descifrado

Ilustración 2: Script descifrado

Para comprobar qué operaciones realiza ‘wscript.exe’ internamente, lo ejecuto desde API Monitor pasándole como parámetro el script que venimos analizando. La aplicación API Monitor, a grandes rasgos, se encarga de monitorizar el uso de las APIs exportadas de las DLLs de Windows. Las APIs de nuestro interés no se encuentran en la sección de ‘exports’, así que toca buscarlas “a mano”.

Ilustración 3: API Monitor

Ilustración 3: API Monitor

Sin entrar en muchos detalles, vemos que hay muchos movimientos de memoria (función ‘memcpy’), entre ellos el de una cadena que se encuentra al principio del fichero que he descifrado justo antes: Dim jvDYk…. Todo indica que me encuentro cerca del punto de descifrado y, por tanto, de ejecución de la cadena. Algo más adelante, se mueve una gran zona de memoria cuyo tamaño coincide con el del script descifrado.

Ilustración 4: API Monitor

Ilustración 4: API Monitor

Si nos fijamos en la pila de llamadas, vemos que la DLL responsable de la invocación de ‘memcpy’ es ‘vbscript.dll’. Tiene sentido que una DLL con este nombre sea la encargada de gestionar la ejecución del los scripts VBS. Si en este punto se está manejando un bloque de memoria de tal tamaño, tengo que estar cerca de la función de ejecución del código. Echando un ojo a los ‘offsets’ de las funciones cuya cadena de ejecución ha llevado a este ‘memcpy’, puedo empezar a localizarme dentro de la memoria de la DLL.

Una vez analizando la DLL, tras estudiar el flujo de la ejecución, encuentro varias funciones que reciben como uno de sus parámetros una cadena que contiene el código descifrado. Coloco un punto de interrupción en todas ellas para comprobar si solamente se utilizan cuando se invoca ‘Execute’ o si tienen otro propósito más general, como la asignación de variables, copia de memoria, etc. Volviendo a ejecutar ‘wscript.exe’ desde el principio veo que varias de ellas se llaman en otras situaciones que nada tienen que ver con la función ‘Execute’ y reciben como parámetro otras cadenas en las que no estoy interesado. Finalmente me quedo con una función (a la que llamo a partir de ahora ‘ExecuteString’) que sólo es llamada una vez por cada invocación de ‘Execute’. ¿Tengo ya una víctima sobre la que centrarme? Usando el “libro de soluciones” (archivo .PDB de ‘vbscript.dll’) y viendo qué funciones invocan a ésta seleccionada por mi, obtengo las siguientes referencias cruzadas (XREFS):

Ilustración 5: Desensamblado de 'ExecuteString'

Ilustración 5: Desensamblado de ‘ExecuteString’

Resulta que no sólo ‘VbsExecute’ llama a mi función localizada en 0x7ff7513dad0, sino que ‘VbsExecuteGlobal’ y ‘VbsEval’ también lo hacen. De esta manera, y monitorizando sólo una función seré capaz de controlar el código ejecutado mediante cualquiera de estos tres métodos. Por curiosidad, analizo el comportamiento de ‘VbsExecute’ y puedo ver que éste recibe un objeto de VBScript del cual se extrae finalmente la cadena a ejecutar que es pasada a mi ‘ExecuteString’. De esta forma me ahorro el estudiar y tener que navegar a través de la estructura interna del objeto y consigo abstraerme a sólo una cadena que apenas necesitaré tratar.

Una vez terminada la primera parte del estudio, conozco el punto en común por donde pasarán todas las cadenas descifradas. Es el momento de ponerse manos a la obra para inyectarme en el proceso que ejecuta los scripts y volcar todo lo que pase por este lugar. Para la inyección utilizaré Frida, que es un software multiplataforma de inyección en procesos que tenía ganas de probar.
La idea es inyectarme en mi función bautizada como ‘ExecuteString’ que se encuentra en ‘vbscript.dll’ y volcar el primer parámetro que contiene el buffer a ejecutar. Los primeros intentos de interceptar la llamada me hacen descubrir dos problemas a superar:

  1. La inyección de código la realizo justo antes de que comience la ejecución de ‘wscript.exe’ y la DLL ‘vbscript.dll’ no está cargada desde el principio.
  2. ‘ExecuteString’ no recibe por parámetro el tamaño de la memoria a ejecutar.

Para solventar el primer problema tendré que buscar qué API del sistema es la encargada de la carga de ‘vbscript.dll’ y habrá también que interceptarla. Un análisis dinámico me revela que tengo que interceptar las llamadas a ‘LoadLibraryExW’. Después de cargar la librería, buscaré la función ‘ExecuteString’ y la interceptaré para volcar el primer parámetro (pasado a través del registro RCX en la arquitectura de 64 bits).

Con respecto al segundo problema, una opción es buscar en la cadena a ejecutar alguna ocurrencia del byte 0x00, pero este carácter es perfectamente válido en un buffer ejecutado por la función ‘Execute’, así que queda descartada. De alguna forma, el programa ha de conocer el tamaño de la cadena a ejecutar. Sé que en varios lenguajes de programación, el tamaño de una cadena o buffer se encuentra unos bytes antes de la propia memoria referenciada… y en este caso el análisis dinámico del código corroboró mi hipótesis.

Ilustración 6: Análisis dinámico de 'ExecuteString'

Ilustración 6: Análisis dinámico de ‘ExecuteString’

En los 4 bytes justo anteriores a la cadena a ejecutar, se encuentra el valor 0x1D6CE, 120526 en decimal, que es el tamaño de esta cadena descifrada.

Con todos los problemas surgidos solventados, sólo queda implementar el código a inyectar en ‘wscript.exe’. Frida hace uso del lenguaje de programación JavaScript a la hora de trabajar dentro del ejecutable inyectado. Se muestra a continuación cómo, con un puñado de líneas de código puede implementarse la idea desarrollada a lo largo de esta entrada:

Ilustración 7: Código inyectado

Ilustración 7: Código inyectado

La primera función es la encargada de ‘hookear’ la llamada a la API ‘LoadLibraryExW’, responsable de cargar distintas librerías. Ahora, además de la funcionalidad propia de la API, cuando se detecte que la DLL ‘vbscript.dll’ ha sido cargada, se buscará la función ‘ExecuteString’ para modificarla.

La segunda parte, es el código encargado de la “búsqueda” de la función ‘ExecuteString’ y su intercepción. Cabe decir que la “búsqueda” de la función utilizando un ‘offset’ para acceder a ella es únicamente válida para la versión concreta de la DLL que tengo en mi máquina. El código inyectado obtiene el puntero a la cadena que va a ser ejecutada, que llega a través de primer parámetro (args[0]). El tamaño del buffer, que recordemos que se encuentra en los 4 bytes anteriores, lo extraigo en base a la dirección que acabo de obtener. Con estos dos valores podemos proceder al volcado de la memoria con la llamada a ‘send’.

Al otro lado de la función ‘send’ se encuentra el script encargado del lanzamiento de ‘wscript.exe’, de inyectar el código JS en él y de interactuar con el código inyectado. En el caso de ‘send’, simplemente se vuelcan en un fichero las cadenas recibidas, que son las ejecutadas tras su descifrado.

Una vez me cercioro de que las inyecciones son realizadas de forma correcta, puedo proceder a probar su funcionamiento con muestras reales. En el caso concreto de la muestra inicialmente analizada, resultó que ésta contenía, “solamente” en Visual Basic Script, 3 capas de ofuscación diferentes, que pasaban finalmente la pelota a un PowerShell.

De esta forma poseo ya, para futuros análisis de scripts VisualBasic, una herramienta que me permite ahorrarme el analizar el propio fichero “a ojo”. Evitamos así tener que pelearnos con código ofuscado con variables de nombres aleatorios, operaciones matemáticas interminables dentro de bucles con código basura y un largo etcétera de “perrerías” de código que no hacen más que dificultar nuestra tarea de análisis.