DLL, Dynamic Link Library, é a forma que o Windows utiliza para compartilhar bibliotecas de funções entre múltiplas aplicações. Internamente uma DLL é bem semelhante a um EXE, utiliza o formato PE e somente uma simples flag diferencia que é uma DLL e não um EXE (essa flag está no IMAGE_FILE_HEADER no campo Characteristics do PE Header). Além disso DLLs geralmente têm mais Exports que os executáveis.
DLL não é um arquivo standalone, se clicarmos duas vezes nela ela não vai executar, necessita de um processo host. Esse processo é o responsável por carregar a DLL para seu espaço de memória através da função LoadLibrary.
Assim como os executáveis, as DLLs também possuem seu entry point (é chamado de DllMain ou DllEntryPoint), que na teoria é o endereço da primeira instrução que será executada pela DLL assim que ela for carregada na memória do processo host. Assim que a LoadLibrary carrega uma DLL na memória, automaticamente ela executa a DllMain.
Para os malwares, as DLLs são ótimos lugares de armazenamento de códigos maliciosos, inclusive por sua análise ser um pouco mais complicada que a de um executável comum. Imagine uma DLL com um packer, se pretendemos retirar a proteção através de debugging temos que carregá-la na memória de um processo e colocar um breakpoint bem no entry point da DLL. Como fazer isso sabendo que um processo pode ter inúmeras DLLs em seu contexto?
É o que veremos agora através de um passo a passo.
Ambiente de testes
Iremos utilizar o Immunity Debugger para injetar uma DLL em um processo e colocaremos um Breakpoint no entry point da DLL injetada para realizar o debugging desde a primeira instrução como se fosse um executável normal.
Apenas para título de demonstração utilizarei arquivos comuns do Windows, o processo host será o IEXPLORE.EXE e a DLL será a p2p.dll, localizada em “C:\windows\system32\”. O software utilizado é o Immunity Debugger que pode ser baixado aqui, e também é necessária a instalação do Python 2.7 que pode ser obtido aqui.
Injeção de DLL em um processo
Segue o passo a passo do procedimento:
1- Execute normalmente o programa que servirá como processo host da DLL, nesse caso irei utilizar o Internet Explorer.
2- Abra o Immunity Debugger e clique em File – Attach para selecionar o processo no qual o ImmDbg irá anexar para debugar. Será exibida uma janela com todos os processos em execução. Selecione o IEXPLORE e clique em Attach.
3- O ImmDbg irá abrir o processo IEXPLORE.exe em seu ambiente e irá pausar a execução. Queremos injetar uma DLL no processo e parar a execução exatamente no Entry Point da DLL.
Aqui temos um problema, a função que faz a injeção da DLL é a LoadLibrary() e sabemos que quando ela carregar a DLL na memória do processo ela automaticamente executará o entry point da DLL não dando tempo de colocar um breakpoint aí.
Para lidar com isso o ImmDbg possui uma opção, clique em Options – Debugging options e selecione a aba Events. Marque a opção “Break on new module (DLL)” e clique em OK. Assim o debugger irá parar a execução bem após ter carregado a DLL na memória e antes de executar o entry point.
4- Estamos prontos para injetar a DLL. Clique no segundo botão da barra de tarefas do ImmDbg para a abrir a Python Shell.
5- A shell que se abriu nos permite executar comando Python e ter acesso a API Python do debugger. Para obter a documentação da API há o menu ImmLib – Help no próprio ImmDbg ou ainda na pasta de instalação do programa: Immunity Debugger\Documentation\Ref\toc.html.
6- No momento que injetamos a DLL no processo é criada uma nova Thread para essa DLL, então vamos injetar nossa DLL e recuperar e exibir o número da Thread. Para isso digite os seguintes comandos na shell:
>>>thread = imm.injectDll("c:\\p2p.dll")
>>>
>>>print "Thread ID: 0x%X" % thread
Thread ID: 0x134
Injetamos a DLL e já recuperamos o número da nova Thread em um variável, depois exibimos esse número no formato hexadecimal.
7- A DLL foi injetada na memória do processo mas o módulo ainda não foi carregado pela LoadLibrary(). Precisamos executar o programa para que nosso módulo seja chamado. Pode ser que ele não seja o próximo módulo a ser carregado, talvez precisemos executar o programa (F9) mais de uma vez.
Quando pressionarmos F9 para a execução, teremos que ficar de olho na janela de módulos carregados (Window – 3 Executable modules) para descobrir se o nosso está lá. Às vezes o ImmDbg nos apresenta essa janela assim que o módulo é carregado e ele estará destacado em vermelho.
8- Pressione F9 e observe os resultados, caso não tenha carregado a DLL pressione novamente F9 até atingi-la. No momento que carregar o nosso módulo injetado será exibida na janela de módulos essa linha em vermelho:
9- Nosso módulo foi carregado e o entry point da DLL ainda não foi executado. Agora vamos voltar para a Python Shell para colocar um breakpoint no entry point da DLL carregada. Utilize os comandos abaixo.
>>>mod = imm.getModule("p2p.dll")
>>>
>>>print "Module ImageBase: 0x%X" % mod.getBase()
Module ImageBase: 0x4EFB0000
>>>
>>>print "Module EntryPoint: 0x%X" % mod.getEntry()
Module EntryPoint: 0x4EFC22E4
>>>
>>>imm.setBreakpoint(mod.getEntry())
0
>>>
Primeiro atribuímos para uma variável o módulo carregado, em seguida apenas por questões de estudo imprimimos os endereços do ImageBase e EntryPoint do módulo, com as funções getBase() e getEntry() respectivamente. Por fim colocamos o breakpoint exatamente no EntryPoint do módulo. Se olharmos na janela de breakpoint do ImmDbg ele estará lá.
10- Agora retire aquela opção de parar a execução nos módulos e execute o programa novamente com F9 ou com o comando imm.run() no Python Shell. A execução irá parar em nosso breakpoint e a partir daí é só debugar a DLL normalmente.
Conclusão
Essa injeção de DLL com o uso do ImmDebugger não é muito conhecida, é difícil encontrar documentação a respeito e até em uma fonte mais confiável como um livro [1] não apresenta o procedimento de forma precisa, são necessários vários testes até atingir os resultados esperados.