Análise .NET: Do DnSpy ao IDA Pro

Análise .NET: Do DnSpy ao IDA Pro

Este artigo explora a evolução da análise de código .NET, desde a descompilação com DnSpy e ILSpy até a análise de código nativo com IDA Pro, abordando as complexidades de ReadyToRun e Native AOT. O artigo também discute as técnicas de ofuscação e as ferramentas necessárias para reverter engenharia em aplicações .NET modernas.

MundiX News·20 de maio de 2026·10 min de leitura·👁 14 views

O que normalmente imaginamos quando pensamos em análise de arquivos binários .NET? Geralmente, é simples: abrimos a assembly no DnSpy ou ILSpy, obtemos um código C# muito próximo do original (talvez não tão próximo, se estiver ofuscado) e então pensamos não em restaurar a lógica, mas em analisar o código-fonte - nem precisamos pressionar F5...

Em assemblies .NET padrão, o compilador salva os símbolos da aplicação na forma de metadados, necessários para o funcionamento do runtime e da reflexão. O DnSpy até suporta a exportação do conteúdo da assembly para um projeto do Visual Studio, o que borra a fronteira entre a análise do código-fonte e do arquivo binário.

Mas a plataforma da Microsoft está em desenvolvimento, e agora as aplicações .NET podem ser executadas não apenas através do CLR, mas também compiladas em código de máquina da plataforma alvo usando Ahead-Of-Time. Historicamente, a primeira solução desse tipo foi o NGEN (2002) - a pré-compilação de instalação para .NET Framework, no entanto, exigia execução manual, duplicava o código IL e não era atualizado automaticamente quando o runtime era alterado. Então, em 2015, surgiu o .NET Native - o primeiro AOT completo, mas exclusivamente em aplicações UWP para a Windows Store. Na ramificação moderna do .NET (Core/5+), o próximo passo foi o ReadyToRun (2019), com a capacidade de alternar para IL, e então para Native AOT, no qual a dependência da assembly do runtime .NET foi completamente removida.

Neste artigo, consideraremos o que um reverter pode encontrar ao analisar aplicações .NET compiladas usando Ahead-Of-Time em versões modernas do .NET.

Código de Teste

Para rastrear as mudanças na assembly de aplicações .NET, compararemos três maneiras diferentes de entregar o mesmo código. Compilaremos um pequeno projeto de teste em três variantes:

  • Código IL gerenciado normal sob CLR;
  • ReadyToRun;
  • Native AOT.

Ambiente do experimento: Windows x64, .NET SDK 8.0.419, runtime 8.0.25.

Para comparar as assemblies resultantes, usaremos um código um pouco mais complexo do que a saída Hello, world. Nele, descreveremos um pequeno gerador de hash não criptográfico

LicenseEngine

com os métodos estáticos

DeriveKey

,

MixBlock

e o ponto de entrada

Main

:

csharp
static class LicenseEngine
{
    [MethodImpl(MethodImplOptions.NoInlining)]
    public static int DeriveKey(string name, int salt)
    {
        var value = salt ^ 0x5A17;
        for (var i = 0; i < name.Length; i++)
        {
            value = (int)BitOperations.RotateLeft((uint)value, 5) ^ name[i];
            value += i * 17;
        }

        return value & 0x7FFFFFFF;
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    public static ulong MixBlock(ReadOnlySpan<byte> data, ulong seed)
    {
        var acc = seed ^ 0x9E3779B185EBCA87UL;
        for (var i = 0; i < data.Length; i++)
        {
            acc ^= data[i];
            acc *= 0x100000001B3UL;
            acc = BitOperations.RotateLeft(acc, 7);
        }

        return acc;
    }

    public static void Main(string[] args)
    {
        var key = DeriveKey("hello", 1337);
        Console.WriteLine($"Derived key = {key}");

        byte[] payload = System.Text.Encoding.UTF8.GetBytes("Hello, world!");
        ulong seed = (uint)key;
        seed = MixBlock(payload, seed);
        Console.WriteLine($"Mixed hash (ulong) = 0x{seed:X16}");
    }
}

.NET Common Language Runtime: IL

Normalmente, uma assembly .NET padrão contém apenas código gerenciado (Intermediate Language - IL, MSIL, CIL), e o CLR/CoreCLR já o JIT-compila em código de máquina no momento da execução:

Tal binário é compilado sem flags especiais:

bash
dotnet publish -c Release -o artifacts/coreclr

Após a compilação, temos um arquivo executável .NET clássico com cabeçalhos PE, DOS, CLI.

O DnSpy

restaurou sem problemas o teste

LicenseEngine

. Por exemplo, o método

DeriveKey

:

É importante notar que, ao usar os modos de compilação

CLR

ou

ReadyToRun

, dependendo do valor do flag

PublishSingleFile

, a compilação pode ser feita separadamente (apphost em EXE, e código .NET em DLL) ou em um único arquivo EXE (dependências e código IL). Quando

PublishSingleFile=true

, o DetetctItEasy não poderá nos informar sobre a presença de código IL na assembly - o código do usuário junto com as dependências será armazenado na sobreposição do arquivo binário.

Para clareza, os flags

PublishSingleFile

não serão usados nas assemblies de teste (exceto NativeAOT, onde este flag é sempre definido).

ReadyToRun: IL + ASM

Com o aumento da popularidade do .NET para criar os mais diversos tipos de aplicações, aumentou a necessidade de otimizar o lançamento lento, causado pelo JIT.

ReadyToRun foi introduzido pela primeira vez no .NET Core 3.0 (2019) como uma forma de acelerar a execução - aqui o código IL é usado apenas para compatibilidade e re-otimizações, enquanto o CLR usa instruções nativas, compiladas antes da execução (Ahead-Of-Time compilation).

Para compilar R2R, você precisa especificar

PublishReadyToRun=true

:

bash
dotnet publish -c Release -r win-x64 -p:PublishReadyToRun=true -o artifacts/r2r

No cabeçalho CLI, aparece

ManagedNativeHeader

, indicando o cabeçalho do diretório ReadyToRun -

READYTORUN_HEADER

com bytes mágicos

RTR\x00 (0x00525452):

ReadyToRun é frequentemente percebido como "quase Native AOT", mas para um reverter, isso não é totalmente verdade. Em um arquivo com R2R, você pode ver três coisas imediatamente.

Primeiro, o tamanho da DLL aumentou de 6 para 20 KB. Agora ele contém IL e código nativo.

Em segundo lugar, R2R já é dependente da plataforma. Se a assembly IL normal fosse arquiteturalmente neutra, então aqui a DLL está vinculada a

Amd64

:

Em terceiro lugar, o DnSpy ainda funciona. Ele ainda decompila bem o IL. Por exemplo, o método

MixBlock

:

IDA também define a assembly .NET:

Mas agora podemos encontrar a implementação de qualquer função sob

amd64

, por exemplo, a mesma

MixBlock

:

Como os métodos R2R são armazenados em dois formatos, surgem perguntas: o que exatamente será executado durante a operação do programa e como corrigi-lo, se necessário?

Ao iniciar uma assembly R2R, o runtime .NET executará preferencialmente a implementação nativa dos métodos, mas apenas até o momento em que o método chamado "aquecer" (mais de 30 chamadas). Nesses casos, entra em jogo a Tiered Compilation - o CLR usa JIT e recompila o IL em Tier 1 com a melhor otimização. Ou seja, saber com antecedência qual código será executado - nativo (Tier 0) ou IL (Tier 1) - nem sempre é possível.

Se quisermos alterar as instruções e rastrear todas as alterações no depurador, podemos usar com segurança o DnSpy. Ele executa apenas IL e todos os patches nele serão aplicados durante a depuração. Esse comportamento do DnSpy está relacionado ao fato de que suas configurações padrão não suportam otimizações IL e o depurador embutido força o JIT-compilação de todo o código intermediário, ignorando ReadyToRun. O problema é que, sem um depurador, o CLR executará código nativo, o que afetará diretamente o resultado.

Outra opção é forçar o binário alvo a sempre usar JIT através das variáveis de ambiente CLR:

bash
set COMPlus_ReadyToRun=0

Depois disso, podemos ver como o CLR JIT-compila e executa o bytecode usando

COMPlus_JitDisasm=

SomeFunc

. Nesse caso, você pode corrigir com segurança o código IL e executar o binário sem R2R.

Toda essa lógica incomum de execução de aplicações ReadyToRun sugere a ideia de ocultar o código em uma de suas formas de armazenamento. Essa técnica é chamada de

R2R stomping

, uma investigação detalhada deste método foi descrita no artigo

Checkpoint Research

.

Native AOT: ASM

No .NET 7+ (2022), surgiu a capacidade de compilar aplicações self-contained, pré-compiladas em código nativo e não usando JIT durante a execução. Após a publicação, este é um arquivo executável totalmente nativo, para o qual você já precisa de um depurador e desassemblador clássico de código de máquina da plataforma alvo.

Para compilar NAOT, você precisa especificar

PublishAot=true

:

bash
dotnet publish -c Release -r win-x64 -p:PublishAot=true -o artifacts/nativeaot

As seções do arquivo Native AOT de teste são assim:

Aqui está a resposta para a pergunta de por que a engenharia reversa de aplicações .NET pode ser associada ao IDA Pro. Estamos diante de um x64 PE: sem

CorHeader

e metadados abertos, com seções nativas e a exportação característica

DotNetRuntimeDebugHeader

:

NAOT permite complicar a engenharia reversa do arquivo imediatamente: agora o pesquisador não terá "códigos-fonte" do DnSpy, e sem símbolos de depuração ele não terá nada para análise estática. Ajudarão apenas as assinaturas FLIRT ou BinDiff.

Mas ainda assim, este programa não foi escrito em C++ (embora se assemelhe muito pela abundância de tabelas de funções virtuais), e, portanto, o .NET salva algumas informações nos metadados.

No arquivo Native AOT DLL de teste, você pode encontrar as strings:

LicenseEngine

,

DeriveKey

,

MixBlock

. Esses nomes são salvos para suportar reflexão, rastreamento de pilha, etc. Mas os literais de string estáticos de

Main

, como

"hello"

estão ausentes no arquivo e se referem a dados não inicializados na seção

hydrated

:

O fato é que, no NAOT, parte das estruturas de runtime é armazenada na seção

.rdata

em uma forma compacta e desserializada durante a execução do processo (data-rehydration). No diretório

ReadyToRun

já conhecido, existem links para esses dados -

DEHYDRATED_DATA

e

FROZEN_OBJECT_REGION

. Essas estruturas incluem:

Tabelas de métodos - contêm métodos de classe, como C++ Vftable, mas com metadados adicionais (tipo, tamanho, código hash, número de interfaces implementadas, etc.). Como em C#, cada classe é um descendente de

System.Object

  • um conjunto mínimo de métodos:

ToString()

,

Equals()

,

GetHashCode()

;

Objetos - consistem em informações para o coletor de lixo, campos de classe e um ponteiro para a tabela de métodos;

Objetos de string e arrays (objetos congelados).

Native AOT .NET 7 ainda não suportava data-rehydration, no .NET 8 as estruturas são desserializadas em uma seção especial

hydrated

, no .NET 9 não há seção adicional - a descompactação ocorre em

.data

, e na versão 10 do .NET, essa funcionalidade

parou de ser usada por padrão em assemblies do Windows

(usada apenas em

OptimizationPreference=Size

).

A serialização de estruturas, um grande número de tabelas de funções virtuais, a diferença nas implementações do NAOT para as versões 7-10 da plataforma dificultam significativamente a engenharia reversa estática de assemblies Ahead-of-Time. Para simplificar a análise de aplicações Native AOT, desenvolvemos um plugin para IDA Pro

ida-nativeaot

. O projeto foi inspirado nas ideias do complemento

ghidra-nativeaot

para Ghidra.

O plugin desserializa as estruturas dehydrated .NET NativeAOT (strings e arrays) na seção

hydrated

/

.data

, restaura as tabelas de métodos com base na pesquisa

System.Object

e seus descendentes, e também aplica assinaturas FLIRT NAOT para versões PE/ELF .NET de 7 a 10.

Após usar o plugin, o pseudocódigo do programa alvo se torna muito mais claro para análise:

Mundo Paralelo: Unity, Mono e IL2CPP

Se olharmos um pouco mais amplamente, uma mudança semelhante de IL para código de máquina ocorreu há muito tempo no mundo do Unity. O motor de jogo, com o crescimento da popularidade, também enfrentou problemas de velocidade e multiplataforma do ambiente .NET. E começou a resolvê-los à sua maneira, superando a AOT da própria Microsoft e apresentando o IL2CPP em 2015.

IL2CPP não é um modo de publicação CLR nem uma variante do Native AOT no ecossistema .NET. A Unity tem seu próprio pipeline: o código é primeiro compilado em IL normal, e então o pipeline da Unity o traduz para C++, do qual o binário nativo é obtido.

Por causa disso, a diferença prática para a engenharia reversa é a seguinte:

  • Nas assemblies Unity Mono, basta abrir Assembly-CSharp.dll no dnSpy/ILSpy;
  • Nas assemblies Unity IL2CPP, não há bytecode familiar para análise. Os principais artefatos são um módulo nativo como GameAssembly.dll e o arquivo global-metadata.dat .

Então, a engenharia reversa .NET não é apenas sobre DnSpy e IL. Na prática, o pesquisador pode encontrar tanto a análise do ReadyToRun híbrido quanto o Native AOT do binário nativo com especificidades .NET.

🛡️⚡

Pare de pesquisar. Comece a hackear.

O MundiX é seu copiloto de pentest com IA: comandos exatos, análise de outputs e próximo passo na kill chain — em segundos.

Testar grátis por 7 dias →

Sem cartão para começar · Planos a partir de R$49/mês

📤 Compartilhar & Baixar

🧰 Ferramentas recomendadas

Divulgação: alguns links são patrocinados. Podemos receber comissão se você comprar — sem custo extra para você. Só indicamos o que faz sentido para a comunidade.

Aprendendo Kali Linux: Teste de segurança, pentest e hacking ético

Aprendendo Kali Linux: Teste de segurança, pentest e hacking ético

Com centenas de ferramentas pré-instaladas, a distribuição Kali Linux facilita o trabalho de os profissionais de segurança começarem a fazer testes de segurança rapidamente. No entanto, com mais de 600 ferramentas em seu arsenal, o Kali Linux também pode ser desafiador. A nova edição deste prático livro abrange as atualizações nas ferramentas e inclui uma melhor abordagem da análise forense e da engenharia reversa. Ric Messier, autor, não fica apenas no teste de segurança, mas também faz uma abordagem sobre a execução de análise forense, incluindo a análise em disco e na memória, assim como alguma análise básica de malware. • Explore as diversas ferramentas disponíveis no Kali Linux • Entenda o valor do teste de segurança e examine os tipos de teste disponíveis • Aprenda os aspectos básicos do pentest em todo o ciclo de vida do ataque • Instale o Kali Linux em vários sistemas, tanto físicos quanto virtuais • Descubra como usar diferentes ferramentas destinadas à segurança • Estruture um teste de segurança baseado nas ferramentas do Kali Linux • Estenda as ferramentas do Kali para criar técnicas de ataque avançadas • Use o Kali Linux para ajudar a criar relatórios quando o teste terminar “A abordagem concisa, clara e baseada na experiência adotada por Ric Messier para a introdução do Kali Linux e dos testes de cibersegurança é incomparável. Este livro é uma leitura excelente e acessível para iniciantes e um recurso valioso para qualquer pessoa.” —Alexander Arlt, Consultor sênior de segurança, Google

Ver na Amazon
Gshield 2 em 1 Hub Extensor Conector USB-C + USB-A e Adaptador de Rede Ethernet LAN RJ45 com 3 Entradas USB 3.0 até 5 Gbps em Liga de Alumínio para Computador e Notebook, Cinza

Gshield 2 em 1 Hub Extensor Conector USB-C + USB-A e Adaptador de Rede Ethernet LAN RJ45 com 3 Entradas USB 3.0 até 5 Gbps em Liga de Alumínio para Computador e Notebook, Cinza

Compatível com portas USB-C e USB-A, ideal para ampliar a conectividade de dispositivos como MacBook Pro e outros com portas USB-C. Inclui um adaptador USB-A extra, proporcionando uma conexão Ethernet estável e veloz de até 1 Gbps, perfeita para filmes, jogos online e videoconferências. Oferece três portas USB 3.0 com velocidades de transferência de até 5 Gbps, permitindo conectar mouse, teclado, discos rígidos e outros periféricos. Fabricado em alumínio durável, garantindo longa vida útil e resistência ao uso diário. Design compacto e leve, ideal para viagens de negócios e uso diário, facilitando o transporte e armazenamento. Funciona com Windows 10/8.1/8, Mac OS e Chrome OS, oferecendo versatilidade incomparável para diversas necessidades de conectividade. Assegura uma conectividade estável e rápida, perfeita para tarefas exigentes como transferência de dados, streaming e mais.

Ver na Amazon
Hacking APIs: Breaking Web Application Programming Interfaces

Hacking APIs: Breaking Web Application Programming Interfaces

Hacking APIs is a crash course on web API security testing that will prepare you to penetration-test APIs, reap high rewards on bug bounty programs, and make your own APIs more secure. You'll learn how REST and GraphQL APIs work in the wild and set up a streamlined API testing lab with Burp Suite and Postman. Then you'll master tools useful for reconnaissance, endpoint analysis, and fuzzing, such as Kiterunner and OWASP Amass. Next, you'll learn to perform common attacks, like those targeting an API's authentication mechanisms and the injection vulnerabilities commonly found in web applications. You'll also learn techniques for bypassing protections against these attacks. In the book's nine guided labs, which target intentionally vulnerable APIs, you'll practice: Enumerating APIs users and endpoints using fuzzing techniques Using Postman to discover an excessive data exposure vulnerability Performing a JSON Web Token attack against an API authentication process Combining multiple API attack techniques to perform a NoSQL injection Attacking a GraphQL API to uncover a broken object level authorization vulnerability

Ver oferta
Gray Hat Hacking: The Ethical Hacker's Handbook, Sixth Edition

Gray Hat Hacking: The Ethical Hacker's Handbook, Sixth Edition

Up-to-date strategies for thwarting the latest, most insidious network attacks This fully updated, industry-standard security resource shows, step by step, how to fortify computer networks by learning and applying effective ethical hacking techniques. Based on curricula developed by the authors at major security conferences and colleges, the book features actionable planning and analysis methods as well as practical steps for identifying and combating both targeted and opportunistic attacks. Gray Hat Hacking: The Ethical Hacker's Handbook, Sixth Edition clearly explains the enemy's devious weapons, skills, and tactics and offers field-tested remedies, case studies, and testing labs. You will get complete coverage of Internet of Things, mobile, and Cloud security along with penetration testing, malware analysis, and reverse engineering techniques. State-of-the-art malware, ransomware, and system exploits are thoroughly explained. Fully revised content includes 7 new chapters covering the latest threats Includes proof-of-concept code stored on the GitHub repository Authors train attendees at major security conferences, including RSA, Black Hat, Defcon, and B-Sides

Ver na Amazon
Bloqueador USB de privacidade de porta USB para PC, notebook, bloco de laptop,

Bloqueador USB de privacidade de porta USB para PC, notebook, bloco de laptop,

Proteção de privacidade aprimorada: protege o link de transmissão de dados para evitar roubo de informações, fornecendo proteção de segurança robusta que protege a privacidade do usuário durante transferências de arquivos e garante uma conexão segura para interações de dispositivos sem preocupações em vários ambientes Uso a longo prazo: a camada protetora resistente ao desgaste, combinada com um corpo de metal resistente, oferece gerenciamento de calor confiável e qualidade duradoura durante o uso diário Entrega eficiente de energia: a tecnologia de chip inteligente garante a identificação automática dos requisitos de energia, fornecendo carregamento eficiente alinhando-se com vários protocolos de carregamento rápido para maior conveniência Proteção contra sobrecarga: evitando riscos de sobrecarga, este bloqueador de dados USB protege a vida útil da bateria e garante um desempenho estável, mantendo um fluxo estável de energia para melhorar a longevidade do dispositivo de forma eficaz Prático de transportar: com atenção à portabilidade, este bloqueador de dados USB oferece um design compacto que é leve e fácil de transportar, melhorando a conveniência do usuário e operação eficiente

Ver na Amazon

📩 Newsletter MundiX

Receba novidades de cibersegurança + um checklist de pentest grátis. Sem spam.

Ao assinar você concorda em receber e-mails. Cancele quando quiser.