DLL Hijacking – injetando DLLs para evasão e persistência

Há uma sub-técnica do MITRE para injeção de DLLs, chamada de DLL Hijacking. E neste post, você vai encontrar uma explicação teórica e uma prova de conceito simples, com uma DLL programada em C++ que abre a calculadora do Windows no caso de uma injeção bem sucedida.

Boletins e relatórios como este são divulgados toda semana no Twitter do grupo de Threat Intelligence da ISH, o Heimdall. Acompanhe-nos por lá!

Dynamic-link library, ou DLL, é a implementação feita pela Microsoft para o conceito de bibliotecas compartilhadas. Tratam-se de módulos que contêm funções que podem ser utilizadas por outros executáveis. A ideia é que os desenvolvedores não precisem reinventar a roda para utilizar funções elementares do sistema operacional, como ler arquivos, criar sockets para conexão com a internet etc. As DLLs de sistema fornecem o código pronto para essas funções (APIs), cabendo ao executável em questão apenas importar aquelas que são relevantes.

Carregando módulos em um processo

Todo programa a ser executado recebe uma série de recursos em memória necessários ao seu funcionamento. Chamamos esse conjunto de recursos de processo. Uma parte crucial da inicialização de qualquer processo é a importação de todas as DLLs especificadas no executável.

A imagem abaixo fornece um exemplo das informações de importação contidas em um arquivo dessa natureza.

Figura 1: lista de módulos a serem importados para o bloco de notas (notepad.exe)

A importação de todos esses módulos é responsabilidade do Image Loader, um componente da ntdll.dll (esse módulo é essencial para a inicialização de todos os processos em um ambiente Windows).

O foco desse relatório não é explicar detalhadamente o funcionamento do Windows na criação de um processo. Entretanto, forneceremos ao longo dele nomes de funções e módulos envolvidos nessa criação, junto a referências que o leitor pode consultar caso deseje se aprofundar no assunto.

O funcionamento do Image Loader envolve uma série de funções da ntdll.dll, com destaque especial para LdrpLoadDll, LdrpCheckForLoadedDll e LdrpMapDll. Para cada um dos módulos listados, LdrpLoadDll iniciará o processo de importação. Parte desse processo é checar se a DLL desejada já foi importada; é isso que a função LdrpCheckForLoadedDll faz. Caso o módulo ainda não faça parte do processo, cabe a LdrpMapDll encontrar o módulo em disco e carregá-lo em memória. Entender como a busca em disco acontece é crucial para compreender como DLL hijacking funciona.

Ordem de busca

Há uma ordem específica de diretórios onde qualquer módulo a ser importado será buscado. A ordem padrão para o Windows é a seguinte:

  1. O diretório do qual a aplicação foi lançada
  2. O diretório de sistema (C:\Windows\System32)
  3. A versão 16-bit do diretório de sistema
  4. O diretório do Windows (C:\Windows\)
  5. O diretório atual

Chamamos essa sequência de padrão pois ela é definida por uma chave no registro, HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode, que é habilitada por padrão. Caso ela seja desabilitada, a sequência muda para:

  1. O diretório do qual a aplicação foi lançada
  2. O diretório atual
  3. O diretório de sistema (C:\Windows\System32)
  4. A versão 16-bit do diretório de sistema
  5. O diretório do Windows (C:\Windows\)

Um jeito de observarmos essa busca em ação é usando o Process Monitor da SysInternals. A imagem abaixo demonstra essa rotina com a execução do processo explorer.exe.

Figura 2: explorer buscando por DLLs no diretório de onde ele foi executado

As operações de CreateFile são ações de busca pelos módulos especificados na tabela de importações do executável do explorer. Importante: as buscas em C:\Windows\ demonstradas acima não são resultado do quarto passo da ordem de pesquisa, mas sim do primeiro. O caminho completo deste arquivo é C:\Windows\explorer.exe, logo C:\Windows\ é o diretório do qual a aplicação foi lançada.

Figura 3: resultado da ação CreateFile para cscapi.dll

Destacamos apenas operações que tiveram “NAME NOT FOUND” como resultado, o que significa que o módulo não foi encontrado naquele diretório. LdrpMapDll seguirá então para o próximo diretório, até encontrar o módulo desejado. No caso em destaque, por exemplo, cscapi.dll está localizada em C:\Windows\System32. Logo, será encontrada no segundo passo da busca e mapeada para a memória do processo.

Mas o que aconteceria se houvesse uma DLL de mesmo nome em C:\Windows?

DLL hijacking

DLL hijacking é uma tática para carregar módulos maliciosos em um processo se aproveitando da ordem de busca utilizada pelo Windows. Respondendo à pergunta feita no item anterior: caso houvesse uma biblioteca chamada cscapi.dll no mesmo diretório de explorer.exe, ela seria carregada no lugar do módulo legítimo localizado em C:\Windows\System32.

Mas qual a utilidade de carregar uma DLL falsa em um processo? Afinal de contas, ela não vai conter as funções que o processo deseja importar, então em tese nenhum código seu seria executado. Para isso, precisamos entender outro componente do Image Loader, LdrpRunInitializeRoutines, que é responsável por invocar o entry point de executáveis e bibliotecas.

Essa rotina, assim como as outras funções de ntdll.dll que mencionamos em itens anteriores, não são oficialmente documentadas pela Microsoft. Entretanto, existe uma API que é documentada e que chama LdrLoadDLL​: LoadLibraryA.

A documentação oficial detalha que após o processo de mapeamento de uma biblioteca, essa API invoca a função DLLMain do módulo, com o valor DLL_PROCESS_ATTACH. Na realidade, essa é a invocação do entry point realizada por LdrpRunInitializeRoutines que mencionamos no começo deste item. Vamos traduzir para termos menos técnicos o que essa invocação implica.

Segundo a documentação de DLLMain, o valor DLL_PROCESS_ATTACH significa que a DLL em questão está sendo carregada na memória de um novo processo. O que LdrpRunInitializeRoutines vai fazer é executar o código que está atrelado a esse valor no momento que esse módulo for importado.

Isso responde à pergunta do começo deste item: não precisamos que uma função exportada de uma DLL seja chamada para que algum código nela execute. Precisamos apenas que o código em questão esteja associado ao valor DLL_PROCESS_ATTACH. Criamos uma POC simples para demonstrar melhor esses conceitos. Seu código segue abaixo:

Figura 4: código da POC para demonstrar DLL Hijacking

Observe o que vem logo após case DLL_PROCESS_ATTACH: WinExec(“calc.exe”, 0). Traduzindo: assim que nossa biblioteca for mapeada para a memória de um processo, ela abrirá a calculadora. Para testar a teoria que apresentamos até agora, precisamos compilar nosso código, chamá-lo de cscapi.dll e colocar o arquivo no mesmo diretório que explorer.exe

Figura 5: DLL da POC salva no mesmo diretório que explorer.exe

Agora resta apenas encerrar o processo do explorer.exe e reiniciá-lo.

Figura 6: encerrando e reiniciando explorer.exe

Caso tudo tenha corrido com sucesso, o novo processo deve encontrar cscapi.dll no diretório C:\Windows. O log do Process Monitor abaixo demonstra essa ação:

Figura 7: nosso cscapi.dll foi encontrado no diretório C:\Windows

Após encontrar o arquivo, o módulo em questão será importado pelo processo.

Figura 8: módulo carregado com sucesso no processo explorer.exe

Caso nosso código em DLL_ PROCESS_ATTACH seja executado, devemos ver o explorer.exe criando um processo da calculadora do Windows (calc.exe).

Figura 9: explorer.exe criando um processo da calculadora do Windows

Nossa prova de conceito de DLL hijacking funcionou perfeitamente!

Conclusão

DLL hijacking é uma técnica muito útil para obter persistência em um sistema, ao mesmo tempo que evita artifícios já conhecidos como chaves de registro e diretórios que executam programas durante a fase de inicialização do Windows.

Esse método também possui seus pontos fracos. Em primeiro lugar, por usar métodos legítimos do Windows para carregar uma biblioteca em memória, esse método popular regiões da memória do processo com o caminho completo para o módulo malicioso. Nas mãos de um analista experiente, DLLs sendo carregadas de locais incorretos são um claro sinal de presença de malware.

Outro problema reside no fato dessa técnica substituir uma DLL legítima. Isso significa que qualquer função presente no módulo que foi substituído não está mais à disposição do processo alvo. Tentativas malsucedidas de hijacking podem eliminar funções importantes para uma aplicação, causando instabilidade ou mesmo crash.

Por fim, há o problema de permissão. O explorer.exe, escolhido por nós para demonstrar essa técnica, pode parecer um ótimo alvo. Trata-se de um executável presente em qualquer versão do Windows e cuja execução é garantida. Mas ele reside em um diretório de sistema. Isso significa que só conseguimos colocar nossa DLL maliciosa na mesma pasta que ele porque possuímos permissões de administração na máquina. Para um atacante que acabou de obter o primeiro acesso a um ativo, utilizar o explorer para essa técnica é impossível.

Referências

  1. https://www.youtube.com/watch?v=3eROsG_WNpE&ab_channel=IppSec
  2. https://docs.microsoft.com/en-us/windows/win32/dlls/dllmain
  3. https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order#standard-search-order-for-desktop-applications
  4. https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya
  5. https://docs.microsoft.com/en-us/sysinternals/downloads/procmon
  6. https://web.archive.org/web/20010618170125/http://www.microsoft.com:80/msj/0999/hood/hood0999top.htm
  7. https://docs.microsoft.com/en-us/archive/msdn-magazine/2002/march/windows-2000-loader-what-goes-on-inside-windows-2000-solving-the-mysteries-of-the-loader
  8. https://guidedhacking.com/threads/dll-injection-methods.14569/
  9. https://docs.microsoft.com/en-us/sysinternals/resources/windows-internals

Por Alexandre Siviero, Laura Cardillo e Átila Altoé