Ir para conteúdo

DLL Hijacking / DLL MITM (Man in The Middle)


Pimptech

Posts Recomendados

Fala, galera!

Espero que todos estejam bem. 

Vou trazer aqui para vocês hoje algo que eu desenvolvi para aprendizado, para melhorar minha análise de Windows Internals no geral. Acredito que pode ser válido para o aprendizado da galera aqui também. 

Eu não vou fazer algo detalhado, portanto para compreender totalmente esse post é requerido um mínimo de conhecimento em:

  • Arquivos PE
  • PEB - Portable Environment Block
  • Linguagem C
  • Assembly

--EDIT--
Esqueci de postar o projeto
 

Projeto Github

 

Eu escrevi sobre os dois primeiros tópicos, mas ambos estão em inglês. Contudo o @Leandro Fróes fez uma série de tópicos sobre arquivos PE muito mais completo do que o que eu escrevi. E o @Fernando Mercês fez uma série de vídeos sobre a linguagem C.

 

Bom, como eu disse antes eu fiz esse PoC para aprendizado se você também gosta de aprender irá aproveitar bem. Não é algo muito difícil de ser feito, porém as áreas que são necessárias para entender como se faz são importantes em engenharia reversa / análise de malware em geral.

Do que se trata ?

Eu tenho estudado mais afundo sobre PEB hooking ultimamente, que já é uma técnica antiga no ramo de DLL injection. PEB Hooking basicamente tem o objetivo de se infiltrar em um módulo já carregado por um processo. Depois de se infiltrar em um módulo o processo acessará as funções do módulo injetado FAKE e não o original. Porém para isso acontecer é necessário que a DLL injetada exporte as mesmas funções que a DLL original. 

Com isso eu desenvolvi uma tool que eu chamei de "DLL Exporter" (sou péssimo em nomear haha), essa tool acessa o Export Directory para exportar as funçoes que uma determinada DLL exporta. Com essas funções a ferramenta vai gerar dois arquivos para  NASM, um com a extensão ".ext" e outro com a extensão ".subs".  Cada um dos arquivos foram gerados por essa tool para ser embutidos no código principal.

O exemplo que eu estou trazendo faz algo bem trivial e comum nos dias de hoje. O exemplo é um protótipo de troca de mensagem (mesmo não havendo nenhuma troca real) com "encoding" da mensagem para uma troca "segura" de tais mensagens. O exemplo em questão acessa uma função da DLL chamada 'send' para encodar e enviar a mensagem. 

Para fazer essa DLL hijacking/MITM a ideia é codar uma DLL que exporte a mesma função e que dentro dessa função ela chame a DLL original após nosso código customizado ser executado. Não há alteração no processo que faz a chamada da DLL e também não qualquer alteração na DLL original. A DLL fake irá fazer o load manual da DLL original e chamar a função original dentro da funão fake.

Para fazer isso eu codei a DLL fake com o NASM e linkei a DLL com o GoLink. GoLink se encarregou de linkar o objeto gerado com as libs Kernel32, msvcrt e a dllsend. O nome de DLL que eu utilizei no exemplo foi "dllsend.dll".

Problemas ? 

Como tive que renomear a DLL fake com o mesmo nome da DLL original, quando o Windows Loader vai tentar fazer o load da DLL original (mesmo nome da DLL fake) ele já acha a DLL carregada no espaço de endereços de memória do processo (a fake DLL), com isso a IAT (Import Address Table) é carregada com os endereços da própria DLL fake. No fim isso acaba gerando um Deadlock, a fake DLL quando chamada pelo processo e tenta executar a função da DLL original acaba chamando a si mesma infinitamente.

Solução!

Para resolver esse problema eu tive que fazer o Load da DLL original com a API LoadLibraryA, acessar o "module" da DLL fake para guardar o endereço de IAT da DLL. Acessar o "module" da DLL original para acessar e guardar o endereço do campo AddressOfFunctions localizado dentro da Export Directory. A inteção aqui é iterar sobre a AddressOfFunctions  e sobrescrever a IAT com os endereços da função original. Contudo as páginas de memória onde a IAT está localizada está protegida contra gravação e para resolver isso precisei utilizar outra API VirtualProtectEx para alterar a proteção do espaço de memória onde a IAT está localizada.

 

Divisão do projeto

Cada pasta do projeto possui um make.ps1 para compilar o objeto de cada pasta.

  • dll
    • Esta pasta contém o código da dllsend.dll
  • dll-consume
    • Esta pasta contém o código do programa que consome a dllsend.dll.
  • dll-exporter
    • Esta pasta contém a tool "DLL Exporter" que exporta as funções de uma determinada dll. A ferramenta gera dois arquivos {NOME_DLL}.DLL.EXT E {NOME_DLL}.DLL.SUBS.
      • .EXT: Este arquivo contém o código NASM para indicar uma função importada com a diretiva "extern".
      • .SUBS: Este arquivo contem a declaração NASM da função exportada.
  • dll-fake-asm
    • Esta pasta contém a DLL fake codificada. É totalmente codada em NASM com uma section especial chamada drectve. Essa section especial é lida pelo GoLink para importar as libs informadas lá.
    • make.ps1: Tem toda a lógica para compilar e linkar a DLL.

 

Análise do processo

A ordem de compilação é indiferente, porém pode fazer dll-consume, dll e dll-fake-asm. Vou assumir que você tenha compilado tudo certo. The program dll-consume é bastante simples e bem intuitivo. Você digita a mensagem e o programa faz o "encoding" da mensagem e faz o envio (não de verdade).

1.jpg.b02601ebfe6652824f6f0ff37521e214.jpg

Para fazer  processo funcionar eu renomeei a DLL original com o final "2". Como a DLL fake foi codada em NASM o tamanho é muito diferente, pois não tem todos os apetrechos da GCC. Vamos ver como fica a lista de arquivos na pasta.

2.jpg.db14b712a4b91ae9fdeae7ef797c9527.jpg

Talvez alguns de vocês ainda não estjam cientes, porém o entry point dos programas compilados com o GCC não são diretamente da main. O entry point criado pelo GCC é um "pré-código" de inicialização de todo o ambiente para então executar o OEP (original entry point). Você pode debugar linha a linha e achar o entry point ou pode ir direto ao assunto e procurar pelas "inter-modular calls"  feitas pelo módulo m.exe para o módulo dllsend.dll. Esta é a main function do programa:

3.jpg.64d84ce900e3a10e7467ca60d9c8a584.jpg

Se você não esta muito acostumado com assembly, minha dica seria treinar. Resolva crackmes, codifique em C e debug seu próprio programa, mas não em IDE, ao invés disso direto no assembly. É necessário treinar os olhos para ler assembly e idenficar padrões de chamada/instruções. Eu coloquei o mouse na linha que faz a chamada para a função send e o x64dbg gerou um preview do destino da chamada. Esse jmp faz um salto para o endereço da função send gravado na IAT do módulo m.exe.

Nesse ponto de execução a DLL fake já foi inicializada e já inicializou a DLL original também. Essas informações podem ser chacadas na aba de Memory Map

4.jpg.b60ac05ff6bed91b6182b53940947003.jpg

Se você quiser debugar todo o processo de inicialização (recomendo MUITO) da DLL original e do processo de reescrita da IAT você vai precisar debugar a entry function da dllsend.dll (fake dll). Para fazer isso é necessário habilitar no x64dbg a opção DLL Entry.

5.jpg.b65ccf9279515d43b483feadd5dad8dc.jpg

Ele vai pausar a execução no início de cada DLL, só ir dando F9 até chegar na dllsend.dll. Eu achei função send da dllsend.dll e coloquei um break point lá para debugar. Dentro da execução podemos ver o código que loga todas as menssagens enviadas pelo programa.

7.jpg.95c12fc73e92016a26c3ed07a1eba6f3.jpg

Como você pode ver no código assembly gerado, estou utilizando fopen, fwrite e fclose. Com esse padrão de chamada fica visível que eu estou logando as informações em algum arquivo antes de efetuar o JMP (salto) para a função send original. Usei pushad e popad para salvar o contexto de registradores antes de executar minha função custom. E não podemos esquecer que a lib msvcrt é __cdecl(calling convention) o chamador (caller) que limpa a stack.

Eu recomendo a debugar o processo de escrita da IAT para aprender (se quiser). Abre a DLL no CFF Explorer e vai debugando ao mesmo tempo. :)

Como evitar isso ?

Até existem algumas medidas para tentar bloquear esse tipo de coisa. Podemos checar a MD5 da DLL antes de efetuarmos a chamada para ela, se for diferente da MD5 armazenada, então seu programa foi comprometido. Podemos também checar na PEB (LdrModules) quantos módulos existem dentro do espaço de endereços do processo, se tiver qualquer módulo a mais podemos concluir que houve uma dll injection ou algo parecido, como esse hijacking/MITM.

Mas a gente sabe que é mais complicado do que isso. O que o programadores realmente podem fazer é deixar a engenharia reversa muito mais difícil de ser realizada. E para isso o programador tem que saber usar as técnicas de engenharia reversa.

 

Bom galera, sei que não é muita coisa, mas acho que talvez ajude alguém. Qualquer dúvida, crítica, sugestão ou correção fique à vontade para comentar. Estamos ai para aprender. Abraço!

Post Original

Link para o comentário
Compartilhar em outros sites

18 horas atrás, Leandro Fróes disse:

Legal pra caramba esse post mano, cai justamente no que estou estudando.

Obrigado pela referência, não tenho tido tanto tempo pra finalizar o tutorial, mas assim que o fizer será justamente sobre directories e sections, será um prazer ter sua leitura.

Abraços!!

Bacana, mano!

Essas partes são bem interessantes de forma geral. Com certeza irei ler quando postar.
Fico aguardando!

Abraço!

Link para o comentário
Compartilhar em outros sites

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

  • Quem Está Navegando   0 membros estão online

    • Nenhum usuário registrado visualizando esta página.
×
×
  • Criar Novo...