TypeRef Hasher— La solución de imphash para muestras en .NET

Joseliyo
7 min readFeb 10, 2021

--

TL;DR

Últimamente estoy analizando muchas muestras de malware de diferentes familias escritas en C#, C++ y otros lenguajes basados en el framework de .NET (.NET assembly).

Esto me ha llevado a encontrarme con un problema a la hora de poder correlar diferentes muestras mediante técnicas de hashing, y es que el imphash en un alto porcentaje siempre era el mismo, incluso con diferentes familias de malware, sin embargo, mediante otras técnicas de fuzzy hashing no encontraba ningún tipo de similitud.

La problemática se debe a que durante la compilación de los lenguajes de programación .NET, el código fuente es convertido en Lenguaje Intermedio de Microsoft (MSIL, en inglés, Microsoft Intermediate Language), lo que produce que siempre exista el mismo imphash, correspondiente en algunos casos a la importación de la DLL mscoree.dll y la función _CorExeMain.

Este problema lo he solucionado mediante otra herramienta de hashing llamada TypeRef Hasher desarrollada por la gente de G Data CyberDefense. Dicha herramienta, plantea una solución a imphash sólo para las muestras de malware en .NET.

Aprovechando el CLI que tienen disponible en GitHub, he desarrollado una pequeña solución que lo implementa y complementa.

Problemática

Durante los últimos meses, analizando muestras de malware que se encontraban compiladas por algún lenguaje de programación en .NET, siempre me chocaba con el mismo imphash, lo que en muchos casos me hacia creer que estaba loco.

Veía correlaciones donde no las había simplemente porque el imphash siempre era el mismo. Por esta misma razón, empecé a investigar para ver si imphash realmente podría tener algún tipo de fallo y podía desarrollar algo para solucionarlo, ya que imphash es uno de los indicadores que utilizo (de una larga lista) para correlar malware y que me ayude posteriormente en los procesos de atribución.

Mismo imphash para dos muestras de familias diferentes
Mismo imphash para dos muestras de diferentes familias

La parte superior de la imagen anterior se trata de una muestra de AsyncRAT la cual, como se puede apreciar, tiene el mismo imphash que la muestra de AgentTesla que está en la parte inferior (f34d5f2d4577ed6d9ceec516c1f5a744).

Recordando el funcionamiento de imphash, el hash que elabora está basado en las DLLs, funciones y el orden de las mismas en un PE. Este hecho fue el que me ayudo definitivamente a pensar que no estaba loco.

Mismas importaciones para ambas muestras
Mismas importaciones para ambas muestras

Ambas muestras de malware contienen la misma DLL importada (mscoree.dll) y la misma función (_CorExeMain), lo cual, debe de tener un motivo que explique este comportamiento.

Buscando información sobre la función _CorExeMain importada de la DLL en el Centro de Documentación de Microsoft indican que tiene la siguiente funcionalidad:

  1. Inicializa el CLR (Common Language Runtime)
  2. Busca el punto de entrada en el encabezado CLR del ejecutable
  3. Comienza la ejecución normal del PE
Funcionamiento de CorExeMain. Fuente: https://docs.microsoft.com/es-es/dotnet/framework/unmanaged-api/hosting/corexemain-function

Sintetizando, un PE que se ha compilado con .NET, tendrá su Entry Point a la función _CorExeMain() de la DLL mscoree.dll, la cual, asegurará que el CLR es cargado para ejecutar el resto de código.

Posteriormente, en tiempo de ejecución, bajo demanda, el PE irá cargando el resto de DLLs que vaya necesitando, por eso en primera instancia, las DLLs no son listadas en la IAT.

Flow de ejecución .NET PE

Solución

La gente de G Data CyberDefense se toparon con el mismo problema que yo recientemente y llevaron a cabo un desarrollo para solventar esta problemática muy interesante.

Han creado un hash llamado TypeRefHash que se basa en la tabla de referencias (TypeRef Table) de los PE en .NET.

Dicha tabla almacena referencias a los namespaces importados, teniendo un comportamiento muy similar al de las DLLs y sus funciones. Por ejemplo, si en un PE se importa la DLL Kernel32.dll para hacer uso de la función WriteFile, en un PE de .NET se puede utilizar la clase FileStream del namespace System.IO

Ejemplo del namespace y clase importado por una muestra de AsyncRAT

En la imagen anterior se puede apreciar parte de la tabla TypeRef de la misma muestra de AsyncRAT anterior, ilustrando a modo de ejemplo como realiza la importación del namespace System.IO y la clase FileStream.

Toda esa tabla sirve finalmente para construir un hash (TypeRefHash) con los names y namespaces que importan los PE, algo muy similar a como lo hace imphash con las DLLs.

El cálculo del hash SHA256 que hacen se basa en los siguientes tres pasos que indican en su post.

  1. Ordenan las entradas por TypeNameSpace y luego por TypeName. Esto es muy importa, ya que de esta manera si una nueva variante de una familia de malware cambia el orden de importaciones, no afectará en el resultado final del cálculo de hash. Los TypeNameSpace vacíos serán los primeros en ordenarse.
  2. Concatenan los TypeNamespace y TypeName con un guión. Si el TypeNameSpace está vacío, la cadena concatenada comienza con el guión.
  3. Se unen todas las cadenas separadas por comas y se calcula entonces el SHA256 de la cadena de bytes UTF8 resultante.

Un ejemplo podría ser el siguiente que he querido ilustrar en una imagen a modo resumen.

Ejemplo de TypeRefHash calculado

Desarrollo de Neossins

Basado en la herramienta de G Data CyberDefense, he desarrollado una pequeña aplicación llamada Neossins en su versión 0.1 Alfa, que iré mejorando e implementando nuevas funcionalidades a lo largo del tiempo.

El objetivo que persigue Neossins es en primer lugar tener un dataset estructurado y amplio de TypeRef Hashes sobre las principales familias de malware .NET, teniendo hasta el momento contempladas tres:

  1. AsyncRAT
  2. Blackshades
  3. AgentTesla

Una vez estructurada la información y almacenada en una base de datos, el siguiente paso es poder comparar muestras de malware que se vayan recibiendo de diferentes fuentes con el dataset para identificar si existe match con algún TypeRef Hash, y en caso de ser así, correlar otras muestras que tengan el mismo hash y que estuviesen almacenadas en la base de datos previamente.

En último lugar, lo óptimo es que las nuevas muestras que hagan match se vayan almacenando en el dataset, ya que la idea es hacer crecer el conjunto de datos para poder conocer el máximo de TypeRef Hashes posibles de una familia de malware.

Flujo a alto nivel de Neossins

La herramienta la he desarrollado en python3 y tiene algunas dependencias como por ejemplo Cytoscape para la generación del grafo que es mostrado en una aplicación web que levanta mediante Dash.

En una serie de pruebas que he estado realizando mediante unas muestras de malware descargadas y relacionadas con AsyncRAT y AgentTesla, el resultado fue el siguiente.

Relaciones obtenidas durante las pruebas de Neossins

Los nodos rectangulares de color rojo son todas las muestras que deposité en el directorio (el punto de partida) que se debe de establecer en el archivo de configuración de la aplicación.

Empezando por la parte de la izquierda, se puede ver como existen cinco nodos que comparten todos entre si el mismo TypeRef Hash, lo que podría significar que se tratan de variantes pertenecientes a la misma familia.

En la parte central (zona roja), existen tres nodos rectangulares de color rojo que son las muestras seleccionadas para realizar una comparativa con la familia de AsyncRAT, una de ellas, es la que se ha visto en este post al principio del todo. Dos de las tres muestras, puede evidenciarse que tienen una gran cantidad de matches almacenados previamente en el dataset, cuyos nodos son representados mediante círculos de color rosa. Todas esas relaciones entre nodos significa que comparten el mismo TypeRef Hash.

Finalmente, en la parte derecha del todo, se pueden ver siete nodos huérfanos que no tienen ningún match con el dataset, los cuales, podrían ser objeto de estudio para verificar si pudiesen tratarse de las familias mencionadas mediante otro tipo de técnicas diferente a TypeRef Hash, y en caso de ser así, convendría almacenar las muestras en el dataset.

Conclusión

La solución TypeRef Hash es una característica más que puede servir a la hora de identificar variantes de malware sobre familias conocidas.

Como bien digo y reitero, no es una solución definitiva y única que debe de emplearse para el cometido mencionado, ya que perfectamente puede ser utilizada en paralelo junto a otras técnicas como ssdeep, vhash de VT, imphash para archivos no .NET, etc…

El uso de imphash para las muestras compiladas mediante .NET y que incorporan únicamente la DLL mscoree.dll y su correspondiente función _CorExeMain, no tiene ningún valor ya que en la mayor parte de los casos dará un falso positivo en posibles relaciones de muestras de malware.

Poco a poco iré actualizando Neossins hasta que tenga una versión estable e incorpore nuevas funcionalidades que quiero implementar en el futuro.

Twitter: @Joseliyo_Jstnk

Github: https://github.com/jstnk9/neossins

LinkedIn: https://linkedin.com/in/joseluissm/

Referencias

https://github.com/GDATASoftwareAG/TypeRefHasher
https://www.gdatasoftware.com/blog/2020/06/36164-introducing-the-typerefhash-trh
https://gist.github.com/secana/42974cf3536590def7bf09582efca7a8

--

--

Joseliyo
Joseliyo

Written by Joseliyo

IT Security - Cyber Threat Intelligence

No responses yet