
Un desempaquetador y registrador automático para archivos dirigidos a DotNet Framework
¡Un desempaquetador y registrador automático para archivos orientados a DotNet Framework! Esta herramienta fue presentada en Black Hat USA 2022. En Black Hat Asia 2023, se publicaron DotDumperGUI y DotDumperNative, junto con la versión 1.1-estable de DotDumper. Estas tres herramientas están diseñadas para usarse juntas, donde DotDumper 1.1-estable requiere que las DLL de DotDumperNative estén presentes, mientras que DotDumperGUI está destinado a ser una interfaz gráfica de usuario para abrir y filtrar la salida JSON de las ejecuciones de DotDumper.
La detección y clasificación automática de cualquier archivo dado de manera confiable a menudo se considera el santo grial del análisis de malware. Las pruebas y tribulaciones para lograrlo son muchas, por lo que la creación de un sistema de este tipo es muy valorada. Cuando se trata de binarios orientados a DotNet, nuestra nueva herramienta de código abierto DotDumper tiene como objetivo asistir en varios pasos cruciales en el camino: registrar la actividad (en memoria), volcar segmentos de memoria interesantes y extraer características de la muestra dada.
En resumen, el desempaquetado manual es un proceso tedioso que consume una cantidad desproporcionada de tiempo para los analistas. Los binarios ofuscados aumentan aún más el tiempo que un analista debe dedicar para desempaquetar un archivo dado. Al escalar esto, las organizaciones necesitan numerosos analistas que diseccionen malware a diario, probablemente en combinación con un sandbox escalable. El valioso tiempo perdido podría usarse para profundizar en campañas o muestras interesantes para descubrir nuevas amenazas, en lugar del malware genérico mundano que está ampliamente extendido. Al fin y al cabo, los analistas buscan las pocas agujas en el pajar.
Entonces, ¿qué diferencia hace DotDumper? Ejecutar una muestra de malware basada en DotNet a través de DotDumper proporciona archivos de registro de llamadas a funciones cruciales, contextualizadoras y comunes en tres formatos (texto plano legible para humanos, JSON y XML), así como copias de segmentos de memoria útiles. De esta manera, un analista puede ojear el registro de llamadas a funciones. Además, los archivos volcados pueden escanearse para clasificarlos, proporcionando información adicional sobre la muestra de malware y los datos que contiene. Esto reduce el tiempo vital para los procesos de triaje y respuesta a incidentes, y libera tiempo de los analistas de SOC e investigadores para necesidades de análisis más sofisticadas.
Para registrar y volcar las llamadas a funciones contextualizadoras y sus resultados, DotDumper utiliza una combinación de reflexión y hooks administrados, todo escrito en C# puro. A continuación, se destacarán y explicarán las características clave, junto con extractos de los resultados de DotDumper de una muestra empaquetada del ladrón AgentTesla, cuyos hashes se muestran a continuación.
| Tipo de hash | Valor hash |
|---|---|
| SHA-256 | b7512e6b8e9517024afdecc9e97121319e7dad2539eb21a79428257401e5558d |
| SHA-1 | c10e48ee1f802f730f41f3d11ae9d7bcc649080c |
| MD-5 | 23541daadb154f1f59119952e7232d6b |
DotDumper es accesible a través de una interfaz de línea de comandos, con una variedad de argumentos. La imagen de abajo muestra el menú de ayuda. Tenga en cuenta que no se discutirán todos los argumentos, sino los más utilizados.

El requisito mínimo para ejecutar una muestra dada es proporcionar el argumento -file, junto con un nombre de archivo o ruta de archivo. Si se proporciona una ruta completa, se utiliza. Si se proporciona un nombre de archivo, se verifica el directorio de trabajo actual, así como la carpeta de la ubicación del ejecutable de DotDumper.
A menos que se proporcione un nombre de directorio, el nombre de la carpeta -log se establece igual al nombre del archivo de la muestra sin la extensión (si la hay). La carpeta se ubica en la misma carpeta donde reside DotDumper, que es donde se guardarán los registros y los archivos volcados.
En el caso de una biblioteca, o un punto de entrada alternativo a un binario, se debe sobrescribir el punto de entrada usando -overrideEntry true. Además, se debe proporcionar la clase completamente calificada, que incluye el espacio de nombres, usando -fqcn Mi.NameSpace.MiClase. Esto le dice a DotDumper qué clase seleccionar, que es donde se recupera el nombre de la función proporcionada (usando -functionName MiFunción).
Si la función seleccionada requiere argumentos, se debe proporcionar el número de argumentos usando -argc y la cantidad de argumentos requeridos. Los tipos y valores de los argumentos deben proporcionarse como string|miValor int|9. Tenga en cuenta que cuando se usan espacios en los valores, el argumento en la interfaz de línea de comandos debe encapsularse entre comillas para garantizar que se pase como un solo argumento.
Otras opciones de uso menos frecuente, como -raceTime o -deprecated, son seguras en su configuración predeterminada, pero podrían requerir ajustes en el futuro debido a cambios en el marco DotNet. Actualmente están expuestas en la interfaz de línea de comandos para permitir cambios fácilmente, si es necesario, incluso si se está utilizando una versión anterior de DotDumper cuando llegue el momento.
El registro y el volcado son las dos características principales de DotDumper. Para minimizar la cantidad de tiempo que lleva el análisis, el registro debe proporcionar contexto al analista. Esto se hace proporcionando al analista la siguiente información para cada llamada a función registrada:
Tenga en cuenta que para cada archivo volcado, el nombre del archivo es igual al hash SHA-256 del archivo.
Para aclarar lo anterior, a continuación se muestra un extracto de un registro. El extracto muestra los detalles de la muestra AgentTesla mencionada anteriormente, donde carga la segunda etapa usando la función Assembly.Load de DotNet.
![El registro de una llamada a función Assembly.Load(byte[] rawAssembly) interceptada](images/assembly_load.png)
Primero, se proporciona la hora del sistema local, junto con el tipo de retorno, el nombre y los argumentos de la función original. Segundo, se proporciona el seguimiento de pila, donde muestra que la función principal de la muestra conduce a un constructor, inicializa los componentes y llama a dos funciones personalizadas. La función Assembly.Load fue llamada desde NavigationLib.TaskEightBestOil.GGGGGGGGGGGGGGGGGGGG(String str). Esto proporciona contexto al analista para encontrar el código alrededor de esta llamada si es de interés.
Luego, se proporciona información sobre el orden de llamada del ensamblado. Cuantas más etapas se cargan, más complejo se vuelve ver a través de qué etapas se originó la llamada. Normalmente se espera que una etapa cargue la siguiente, pero en algunos casos las etapas posteriores utilizan etapas anteriores en un orden no lineal. Además, se proporciona información sobre el ensamblado de origen para enriquecer aún más los datos para el analista.
A continuación, se proporciona el hash padre. El padre de una etapa es la etapa anterior, que en este ejemplo aún no está presente. La etapa recién cargada tendrá esta etapa como su padre. Esto permite al analista correlacionar eventos más fácilmente.
Finalmente, se almacenan el tipo de retorno y el valor de la función, junto con el tipo, nombre y valor de cada argumento que se pasa a la función hookeada. Si alguna variable tiene más de 100 bytes de tamaño, se almacena en el disco en su lugar. Luego se inserta una referencia en el registro para hacer referencia al archivo, en lugar de mostrar el valor. El umbral se ha establecido para evitar problemas en la impresión del registro, ya que algunos arreglos tienen miles de índices de tamaño.
Según la documentación de Microsoft, la reflexión se resume como “[…] proporciona objetos que encapsulan ensamblados, módulos y tipos”. En resumen, esto permite la creación e invocación dinámica de clases y funciones de DotNet a partir de la muestra de malware. DotDumper contiene un cargador reflexivo que permite a un analista cargar y analizar tanto ejecutables como bibliotecas, siempre que estén basados en DotNet Framework.
Para utilizar el cargador, se debe optar por sobrescribir el punto de entrada en la interfaz de línea de comandos, especificar la clase (incluido el espacio de nombres en el que reside) y el nombre de la función dentro de un archivo dado. Opcionalmente, se pueden proporcionar argumentos a la función especificada, para todos los tipos nativos y sus arreglos. Ejemplos de tipos nativos son int, string, char, y arreglos como int[], string[], y char[]. Todos los argumentos deben proporcionarse a través de la interfaz de línea de comandos, donde se deben especificar tanto el tipo como el valor.
No sobrescribir el punto de entrada hace que se utilice el punto de entrada predeterminado. Por defecto, se pasa un arreglo de cadenas vacío a la función principal de la muestra, como si la muestra se ejecutara sin argumentos.
Además, los cargadores a menudo utilizan reflexión para invocar una función dada en una clase dada en la siguiente etapa. A veces, también se pasan argumentos, que se utilizan más tarde para descifrar un recurso. En la muestra AgentTesla mencionada anteriormente, ocurre exactamente este escenario. Los hooks relacionados con la invocación de DotDumper registran estas ocurrencias, como se puede ver a continuación.

El nombre de la función en la primera línea no es una función interna del marco DotNet, sino una llamada a una función específica en la segunda etapa. Los tipos y nombres de los tres argumentos se enumeran en la firma de la función. Sus valores se pueden encontrar en la sección de información de argumentos de la función. Esto permitiría a un analista cargar la segunda etapa en un cargador personalizado con los valores dados para los argumentos, o incluso hacerlo usando DotDumper cargando la etapa previamente volcada y proporcionando los argumentos.
Antes de entrar en los hooks administrados, es necesario entender cómo funcionan los hooks. Hay dos variables principales a considerar aquí: la función objetivo y una función controlada que se denomina hook. En pocas palabras, la memoria en la función objetivo (por ejemplo, Assembly.Load) se altera para que, en su lugar, salte al hook. De esta manera, el flujo de ejecución del programa se desvía. El hook puede entonces realizar acciones arbitrarias, opcionalmente llamar a la función original, después de lo cual devuelve la ejecución al llamante junto con un valor de retorno si es necesario. El diagrama a continuación ilustra este proceso.

Saber qué son los hooks es esencial para entender qué son los hooks administrados. El código administrado se ejecuta en un entorno virtual y administrado, como el tiempo de ejecución de DotNet o la máquina virtual de Java. Obtener la dirección de memoria donde reside la función administrada difiere de un lenguaje no administrado como C. Una vez que se han obtenido las direcciones de memoria correctas para ambas funciones, el hook se puede establecer accediendo directamente a la memoria usando C# no seguro, junto con el servicio de interoperabilidad de DotNet para llamar a la funcionalidad nativa de la API de Windows.
Desde DotDumper v1.1-estable, DotDumper también puede hookear funciones no administradas (o nativas, si se prefiere). La redirección de funciones puede ser cualquier combinación de funciones administradas a no administradas y viceversa, con una advertencia importante. Cualquier función no administrada que use un hook administrado no podrá leer correctamente los valores de la pila. Como tal, se requiere un componente nativo (llamado DotDumperNative). Este componente se comunica a través de una tubería con nombre con el propio DotDumper, utilizando así su sistema de registro centralizado, mientras intercepta llamadas no administradas con acceso a los argumentos de la función que se encuentran en la pila.
Dado que DotDumper está escrito en C# puro sin dependencias externas, se puede extender fácilmente el marco utilizando Visual Studio. El código está documentado en este blog, en GitHub, y en clases, funciones y en línea en el código fuente. Esto, en combinación con el esquema de nombres claro, permite a cualquier persona modificar la herramienta según lo considere necesario, minimizando el tiempo y el esfuerzo que uno debe dedicar para entender la herramienta. En cambio, permite que tanto desarrolladores como analistas centren sus esfuerzos en la mejora de la herramienta.
Si bien la función de soporte de tiempo de espera del sandbox no ha sido cambiada ni modificada, no se había documentado antes. Dado que DotDumper ejecuta el archivo que se le proporciona, su ejecución continuará hasta que la muestra termine. El malware a menudo entra en un estado de "espera", donde se debe cumplir una cierta condición antes de que se reactive, o el malware continúa la ejecución en un proceso diferente (con la ayuda de inyección de procesos). Por ejemplo, un hilo recién creado en un proceso hueco solo regresa una vez que ha terminado.
Para evitar el estancamiento, DotDumper tiene un manejador de estancamiento. Cada vez que se registran los resultados de un hook, se incrementa un contador. Si este contador no cambia después de tres intervalos consecutivos de 20 segundos, DotDumper asume que se ha encontrado un estado estancado. En ese caso, la hora del sistema se establece en 30-12-2200 12:00 para forzar el tiempo de espera del sandbox, después de lo cual notifica al analista a través del registrador y se termina a sí mismo. El tiempo de espera del sandbox ocurre cuando el tiempo transcurrido desde la fecha actual hasta la fecha recién establecida supera los 5 o 10 minutos habituales. Incluso si el tiempo de ejecución del sandbox está configurado en horas o días, se supera.
La razón para acortar el análisis es ahorrar tiempo y recursos, ya que un envío con un tiempo de espera de 10 minutos podría tardar solo 2 minutos en ejecutarse. De esta manera, se podrían ejecutar múltiples muestras en el mismo período de tiempo con el manejador de estancamiento. A continuación se muestra una descripción general de la lógica del manejador de estancamiento.

Con el objetivo y las características de DotDumper claros, podría parecer que hay superposición con herramientas públicas conocidas como ILSpy, dnSpyEx, de4dot o pe-sieve. Tenga en cuenta que no hay intención de proclamar que una herramienta es mejor que otra, sino más bien cómo difieren las herramientas.
El objetivo de DotDumper es registrar y volcar llamadas a funciones cruciales, contextualizadoras y comunes de muestras orientadas a DotNet. ILSpy es un desensamblador y descompilador de DotNet, pero no permite la ejecución del archivo. dnSpyEx (y su predecesor dnSpy) utilizan ILSpy como componente desensamblador y descompilador, mientras añaden un depurador. Esto permite inspeccionar y manipular la memoria manualmente. de4dot se utiliza únicamente para desofuscar binarios DotNet, mejorando la legibilidad del código para los ojos humanos. La última herramienta en esta comparación, pe-sieve, está diseñada para detectar y volcar malware de procesos en ejecución, sin importar el lenguaje de programación utilizado. La tabla a continuación proporciona una descripción gráfica de las herramientas mencionadas.

DotDumper está bajo revisión y desarrollo constantes, todo lo cual se centra en dos áreas principales de interés: corrección de errores y adición de nuevas características. Durante el desarrollo, se probó el código, pero debido a la inyección de hooks en las funciones del marco DotNet que pueden estar sujetas a cambios, es muy posible que haya errores en el código. Se insta a cualquiera que encuentre un error a abrir un problema en el repositorio de GitHub, que luego se examinará. También es posible sugerir nuevas características a través del repositorio de GitHub. Para aquellos con una cuenta de GitHub, o para aquellos que prefieran no interactuar públicamente, siéntase libre de enviarme un mensaje privado en mi Twitter.
No hace falta decir que, si ha utilizado DotDumper durante un análisis, o lo ha utilizado de manera creativa, ¡siéntase libre de contactar en público o en privado! ¡No hay nada como escuchar sobre el uso de una herramienta casera!
¡Hay más por venir para DotDumper, y se enviará una actualización a la comunidad una vez que esté disponible!