El lenguaje de programación Nim se ha vuelto cada vez más atractivo para los desarrolladores de malware debido a su robusto compilador y su capacidad para trabajar fácilmente con otros lenguajes. El compilador de Nim puede compilar Nim a JavaScript, C, C++ y Objective-C, y realizar compilaciones cruzadas para los principales sistemas operativos, como Windows, Linux, macOS, Android e iOS. Además, Nim admite la importación de funciones y símbolos de los lenguajes mencionados, así como la importación de bibliotecas enlazadas dinámicamente para Windows y bibliotecas compartidas para Linux. También hay disponibles módulos wrappers de Nim, como Winim, que facilitan la interacción con el sistema operativo. Todas estas capacidades permiten una fácil integración de Nim en las líneas de desarrollo que utilizan estos lenguajes e impulsan el desarrollo de nuevas herramientas, tanto benignas como maliciosas.

No es de extrañar, entonces, que ESET Research haya visto un uso continuo de malware desarrollado en Nim in the wild. Ya en 2019, Sednit fue descubierto utilizando un downloader malicioso escrito en Nim. Otro grupo notorio en el juego de Nim, y el impulso para desarrollar Nimfilt, es el grupo Mustang Panda APT. ESET Research registró a Mustang Panda utilizando Nim en su conjunto de herramientas por primera vez en una campaña contra una organización gubernamental en Eslovaquia en agosto de 2023. La DLL maliciosa detectada —y utilizada como parte del clásico loader trident Korplug del grupo— estaba escrita en Nim.

Para los investigadores encargados de la ingeniería inversa de este tipo de binarios, Nimfilt es una potente herramienta que acelera el análisis. Aunque Nimfilt puede ejecutarse como un script de Python tanto en la línea de comandos (con un subconjunto de su funcionalidad) como en el programa IDA de Hex-Rays, aquí se presentará principalmente como un plugin de Python para IDA.

Inicializando Nimfilt en IDA

Cuando IDA se abre por primera vez, carga e inicializa cualquier plugin en el directorio de plugins de IDA. Durante la inicialización de Nimfilt, el complemento usa heurística básica para determinar si el binario desensamblado fue compilado con el compilador Nim. Si se pasa una de las siguientes comprobaciones, Nimfilt determina que se usó este compilador:

  • El binario contiene las dos cadenas siguientes
    • fatal.nim
    • sysFatal
  • El binario contiene cualquiera de los siguientes nombres de función Nim bien conocidos
    • NimMain
    • NimMainInner
    • NimMainModule
  • El binario contiene al menos dos de las siguientes cadenas de mensaje de error
    • @value out of range
    • @division by zero
    • @over- or underflow
    • @index out of bounds

Junto con Nimfilt se proporcionan reglas YARA que realizan comprobaciones similares para determinar si un archivo ELF o PE ha sido compilado con Nim. Juntas, estas comprobaciones son mucho más robustas que el enfoque adoptado por otras herramientas, como Detect It Easy, que actualmente solo comprueba la sección .rdata de los archivos PE en busca de la cadena io .nim o fatal.nim.

Como paso final de la inicialización, si el indicador AUTO_RUN de Nimfilt está establecido en true, el complemento se ejecuta inmediatamente. De lo contrario, Nimfilt puede ejecutarse como siempre desde el menú de plugins de IDA, como se muestra en la Figura 1.

Figure 1. Initializing and running the Nimfilt plugin in IDA
Figura 1. Inicialización y ejecución del plugin Nimfilt en IDA

Demangling con Nimfilt

Nim usa un esquema personalizado de manipulación de nombres que Nimfilt puede decodificar. Durante una ejecución, Nimfilt itera a través de cada nombre de función en el binario, comprobando si el nombre es un paquete Nim o un nombre de función. Los nombres descubiertos son renombrados a sus formas demangled.

Curiosamente, estos nombres pueden filtrar información sobre el entorno del desarrollador, del mismo modo que las rutas PDB. Esto se debe a que el compilador Nim añade la ruta del archivo al nombre durante la descomposición - Nimfilt revela la ruta al descomponer.

Por ejemplo, los nombres de funciones de paquetes de terceros se almacenan como rutas absolutas durante el proceso de manipulación. La figura 2 muestra un nombre de función almacenado como ruta absoluta que revela la versión y la suma de comprobación del paquete nimSHA2 utilizado, junto con la ruta de instalación del desarrollador para nimble, el gestor de paquetes predeterminado de Nim.

python nimfilt.py GET_UINT32_BE__6758Z85sersZ85serOnameZOnimbleZpkgs50Znim837265504548O49O494554555453d57a4852c515056c5452eb5354b51fa5748f5253545748505752cc56fdZnim83726550_u68
C:/Users/User.name/.nimble/pkgs2/nimSHA2-0.1.1-6765d9a04c328c64eb56b3fa90f45690294cc8fd/nimSHA2::GET_UINT32_BE u68

Figura 2. Descodificación (demangling) del nombre de una función de un paquete de terceros

En cambio, la figura 3 muestra el nombre de una función de un paquete Nim estándar almacenado como ruta relativa (es decir, relativa a la ruta de instalación de Nim).

python nimfilt.py toHex__pureZstrutils_u2067
pure/strutils::toHex u2067

Figura 3. Descodificación (demangling) del nombre de una función de un paquete Nim estándar

Sin embargo, los nombres no siempre se manipulan de la misma manera. La figura 4 muestra que el mismo nombre de función anterior del paquete nimSHA2 se almacena en Linux como una ruta relativa.

Figura 4. Demangling del nombre de una función de un paquete de terceros en Linux

Las funciones de inicialización de paquetes se codifican de una forma completamente diferente: el nombre del paquete se almacena como una ruta de archivo (incluida la extensión del archivo) situada antes del nombre de la función y se utiliza un esquema de escape para representar ciertos caracteres como barras inclinadas, guiones y puntos. Tras el demangling, Nimfilt limpia el nombre del paquete, eliminando la extensión de archivo .nim, como se muestra en la figura 5.
 
Figure 5 – Demangling name of an initialization function from third-party package

Figura 5. Demangling del nombre de una función de inicialización de un paquete de terceros

La Figura 6 muestra cómo los nombres de las funciones de inicialización de paquetes nativos se almacenan como rutas absolutas.

python nimfilt.py atmCatcatstoolsatsNimatsnimminus2dot0dot0atslibatssystemdotnim_Init000
C:/tools/Nim/nim-2.0.0/lib/system::Init000

Figura 6. Demangling del nombre de una función de inicialización de un paquete nativo

En IDA, el proceso de demangling de Nimfilt es seguido por la creación de directorios en la ventana Functions para organizar las funciones de acuerdo al nombre de su paquete o ruta, como se muestra en la Figura 7.

Figure 7. The IDA Functions window before (left) and after (right) Nimfilt organizes function names by package or path
Figura 7. La ventana Functions de IDA antes (izquierda) y después (derecha) de que Nimfilt organice los nombres de las funciones por paquete o ruta

Aplicando structs a cadenas Nim

La última acción realizada durante una ejecución de Nimfilt es aplicar structs estilo C a cadenas Nim. Al igual que en otros lenguajes de programación las cadenas son objetos en lugar de secuencias de bytes terminadas en cero, en Nim también lo son. La figura 8 muestra cómo aparece la cadena ABCDEF en IDA antes y después de ejecutar Nimfilt. Nota que en forma desensamblada, un binario compilado con Nim usa el prefijo _TM como parte del nombre temporal de algunas variables; estas son a menudo cadenas Nim.

Figure 8. A Nim string before (left) and after (right) running Nimfilt
Figura 8. Una cadena Nim antes (izquierda) y después (derecha) de ejecutar Nimfilt

Nimfilt itera a través de cada dirección en el segmento .rdata o .rodata, y en cualquier otro segmento de datos de solo lectura, buscando cadenas Nim. Se aplican estructuras a las cadenas descubiertas; la estructura contiene un campo de longitud y un puntero a la carga útil formada por los caracteres de la cadena.

Recapitulación

En su camino hacia la compilación como ejecutable, el código fuente de Nim se traduce normalmente a C o C++; sin embargo, este proceso no elimina por completo todos los rastros de Nim. Haciendo un viaje a través del código fuente del compilador de Nim, hemos desentrañado algunos de los caminos tomados en el proceso de compilación y, por lo tanto, hemos sido capaces de construir Nimfilt como una herramienta Python, y un plugin IDA, para ayudar en este desenredo.

En resumen, tanto si eres nuevo en Nim como si no, recurrir a Nimfilt hará que tu trabajo de ingeniería inversa con binarios compilados con Nim sea casi instantáneamente más fácil y centrado. Sin embargo, el desarrollo de Nimfilt no está estancado; estamos trabajando en características adicionales para manejar el doble mangling, y mejorar el formato de los nombres demangled y la agrupación de nombres de paquetes.