A linguagem de programação Nim tem se tornado cada vez mais interessante para os desenvolvedores de malware devido ao seu compilador robusto e à sua capacidade de trabalhar facilmente com outras linguagens. O compilador Nim pode compilar para JavaScript, C, C++ e Objective-C, e fazer a compilação cruzada para os principais sistemas operacionais, como Windows, Linux, macOS, Android e iOS. Além disso, a Nim suporta a importação de funções e símbolos das linguagens que citamos, bem como a importação de bibliotecas vinculadas dinamicamente para Windows e bibliotecas compartilhadas para Linux. Os módulos de wrapper da Nim, como o Winim, também estão disponíveis para facilitar a interação com o sistema operacional. Todos esses recursos permitem a fácil integração da Nim aos pipelines de desenvolvimento que usam essas linguagens e impulsionam o desenvolvimento de novas ferramentas, tanto benignas quanto maliciosas.
Não é de se admirar, portanto, que a equipe de pesquisa da ESET tenha observado o uso contínuo de malware desenvolvido em Nim na natureza. Em 2019, o Sednit foi descoberto usando um downloader malicioso escrito em Nim. Outro grupo notório no jogo Nim, e o ímpeto para o desenvolvimento da Nimfilt, é o grupo APT Mustang Panda. A equipe de pesquisa da ESET registrou o Mustang Panda usando a Nim em seu kit de ferramentas pela primeira vez em uma campanha contra uma organização governamental na Eslováquia em agosto de 2023. A DLL maliciosa detectada - e usada como parte do clássico loader trident do Korplug do grupo- foi escrita em Nim.
Para a equipe de pesquisa encarregada de fazer a engenharia reversa de tais binários, a Nimfilt é uma ferramenta poderosa para acelerar a análise. Embora a Nimfilt possa ser executado como um script Python na linha de comando (com um subconjunto de sua funcionalidade) e no programa IDA do Hex-Rays, ele será apresentado aqui principalmente como um plug-in Python para o IDA.
Inicialização da Nimfilt no IDA
Quando o IDA é aberto pela primeira vez, ele carrega e inicializa todos os plug-ins no diretório de plug-ins do IDA. Durante a inicialização da Nimfilt, o plug-in usa heurística básica para determinar se o binário desmontado foi compilado com a Nim. Se uma das verificações a seguir for aprovada, a Nimfilt determinará que esse compilador foi usado:
- O binário contém as duas cadeias de caracteres a seguir:
- fatal.nim
- sysFatal
- O binário contém qualquer um dos seguintes nomes de função Nim conhecidos:
- NimMain
- NimMainInner
- NimMainModule
- O binário contém pelo menos duas das seguintes sequências de mensagens de erro:
- @value out of range
- @division by zero
- @over- or underflow
- @index out of bounds
Juntamente com a Nimfilt, são fornecidas regras YARA que realizam verificações semelhantes para determinar se um arquivo ELF ou PE foi compilado com a linguagem Nim. Juntas, essas verificações são muito mais robustas do que a abordagem adotada por outras ferramentas, como o Detect It Easy, que atualmente verifica apenas a seção .rdata dos arquivos PE a procura da cadeia string io .nim ou fatal.nim.
Como uma etapa final de inicialização, se o sinalizador AUTO_RUN da Nimfilt estiver definido como verdadeiro, o plug-in será executado imediatamente. Caso contrário, a Nimfilt pode ser executado como de costume no menu de plug-ins do IDA, conforme mostrado na Figura 1.
Demangling com a Nimfilt
A linguagem Nim usa um esquema personalizado de manipulação que a Nimfilt pode decodificar. Durante uma execução, a Nimfilt itera através de cada nome de função no binário, verificando se o nome é um pacote Nim ou um nome de função. Os nomes descobertos são renomeados para suas formas demangled.
É interessante notar que esses nomes podem vazar informações sobre o ambiente do desenvolvedor, da mesma forma que os caminhos do PDB. Isso ocorre porque o compilador Nim anexa o caminho do arquivo ao nome durante a decomposição - a Nimfilt revela o caminho durante a decomposição.
Por exemplo, os nomes de funções de pacotes de terceiros são armazenados como caminhos absolutos durante o processo de manipulação. A Figura 2 mostra um nome de função armazenado como um caminho absoluto que revela a versão e a soma de verificação do pacote nimSHA2 usado, juntamente com o caminho de instalação do desenvolvedor para o nimble, o gerenciador de pacotes padrão da Nim.
Figura 2. Descriptografia (demangling) de um nome de função de um pacote de terceiros.
Em contrapartida, a Figura 3 mostra o nome de uma função de um pacote padrão da Nim armazenado como um caminho relativo (ou seja, relativo ao caminho de instalação da Nim).
Figura 3: Decodificando (demangling) o nome de uma função de um pacote Nim padrão.
Entretanto, os nomes nem sempre são tratados da mesma forma. A Figura 4 mostra que o mesmo nome de função do pacote nimSHA2 acima é armazenado no Linux como um caminho relativo.
As funções de inicialização do pacote são codificadas de uma maneira completamente diferente: o nome do pacote é armazenado como um caminho de arquivo (incluindo a extensão do arquivo) antes do nome da função e um esquema de escape é usado para representar determinados caracteres, como barras, hífens e pontos. Após o demangling, a Nimfilt limpa o nome do pacote, removendo a extensão de arquivo .nim, conforme mostrado na Figura 5.
A Figura 6 mostra como os nomes das funções de inicialização de pacotes nativos são armazenados como caminhos absolutos.
Figura 6. Demangling de um nome de função de inicialização de pacote nativo.
No IDA, o processo de demangling da Nimfilt é seguido pela criação de diretórios na janela Functions para organizar as funções de acordo com o nome ou caminho do pacote, conforme mostrado na Figura 7.
Aplicação de structs a cadeias da linguagem Nim
A última ação realizada durante a execução da Nimfilt é aplicar structs no estilo C às strings da linguagem Nim. Assim como em outras linguagens de programação, as cadeias de caracteres são objetos em vez de sequências de bytes com terminação zero, na Nim elas também são. A Figura 8 mostra como a string ABCDEF aparece no IDA antes e depois da execução da Nimfilt. Observe que, na forma desmontada, um binário compilado com a Nim usa o prefixo _TM como parte do nome temporário de algumas variáveis; essas são geralmente strings da Nim.
A Nimfilt itera por todos os endereços no segmento .rdata ou .rodata e em qualquer outro segmento de dados somente leitura, procurando por cadeias Nim. As estruturas são aplicadas às cadeias descobertas; a estrutura contém um campo de comprimento e um ponteiro para a carga útil que consiste nos caracteres da cadeia.
Recapitulação
Em seu caminho para a compilação como um executável, o código-fonte da Nim geralmente é traduzido para C ou C++; no entanto, esse processo não remove completamente todos os traços da Nim. Ao percorrermos o código-fonte do compilador Nim, desvendamos alguns dos caminhos percorridos no processo de compilação e, portanto, conseguimos criar a Nimfilt como uma ferramenta Python e um plug-in IDA para ajudar nesse desvendamento.
Em resumo, quer você conheça a linguagem Nim ou não, usar a Nimfilt tornará seu trabalho de engenharia reversa com binários compilados pela Nim quase instantaneamente mais fácil e mais focado. No entanto, o desenvolvimento da Nimfilt não está paralisado; estamos trabalhando em recursos adicionais para lidar com a doble mangling e melhorar a formatação de nomes desemaranhados e o agrupamento de nomes de pacotes.