Driver de Hook em Kernel Mode + Aplicação User Mode
Driver de Hook em Kernel Mode + Aplicação User Mode
Este é um sistema completo de comunicação kernel ↔ usermode que demonstra a técnica de Inline Hooking.
Especificamente, ele:
- Driver Kernel: Intercepta a função
NtOpenCompositionSurfaceSectionInfodo driverdxgkrnl.sys - Aplicação Usermode: Comunica-se com o driver através da função hookada
- Permite ler/escrever memória de outros processos
- Retorna endereços base de DLLs carregadas em processos
- Modifica memória protegida (read-only) usando MDL (Memory Descriptor List)
- Usa shellcode em assembly x64 para criar o redirecionamento
Aplicações educacionais:
- Entender como anti-cheats detectam hooks
- Aprender sobre proteção de memória no kernel
- Estudar arquitetura de drivers Windows
- Praticar engenharia reversa e análise de código
- Aprender comunicação kernel ↔ usermode
📚 Guias Disponíveis
Este projeto contém guias completos e detalhados:
| Guia | Descrição |
|---|---|
| COMO_CONSTRUIR.md | Tutorial completo para criar o driver kernel do zero |
| COMO_CONSTRUIR_USERMODE.md | Tutorial completo para criar a aplicação usermode |
| readme_estudo_de_hooks_em_driver_win_dbg_kdmapper.md | Notas de debugging com WinDbg e kdmapper |
Recomendação: Se você é iniciante, siga os guias nesta ordem:
- Leia este README para entender o conceito
- Siga COMO_CONSTRUIR.md para criar o driver
- Siga COMO_CONSTRUIR_USERMODE.md para criar a aplicação cliente
- Use o guia de debugging para troubleshooting
Como funciona um Hook Inline?
Um hook inline substitui os primeiros bytes de uma função com um jump para sua própria função.
Exemplo Visual
Antes do Hook:
NtOpenCompositionSurfaceSectionInfo:
xor eax, eax ; Código original
ret ; Retorna
Depois do Hook:
NtOpenCompositionSurfaceSectionInfo:
mov rax, 0x1234567890ABCDEF ; 48 B8 [8 bytes de endereço]
jmp rax ; FF E0
; (os bytes restantes são sobrescritos)
Agora, quando alguém chamar NtOpenCompositionSurfaceSectionInfo, ele vai executar sua função ao invés da original!
O Shellcode (12 bytes)
48 B8 [endereço de 64 bits] FF E0
│ │ └─────────┬─────────┘ │ │
│ │ │ │ └─> JMP RAX (pula para o endereço)
│ └─────────────┴─────────────┴───> MOV RAX, <endereço>
Este código:
- Carrega o endereço da sua função em
RAX - Pula para esse endereço
Estrutura do Projeto
Driver/
│
├── Driver # Driver Kernel
│ ├── main.cpp # Ponto de entrada do driver (DriverEntry)
│ ├── hook.cpp/h # Implementação do hook inline
│ ├── memory.cpp/h # Funções para manipulação de memória
│ ├── definitions.h # Definições de estruturas não documentadas
│ ├── Driver.inf # Arquivo de informações do driver
│ └── Driver.vcxproj # Projeto do Visual Studio
│
├── user_mode/ # Aplicação Usermode
│ ├── main.cpp # Cliente que se comunica com o driver
│ └── user_mode.vcxproj # Projeto do Visual Studio
│
├── COMO_CONSTRUIR.md # Guia completo do driver
├── COMO_CONSTRUIR_USERMODE.md # Guia completo do usermode
├── README.md # Este arquivo
├── .gitignore # Arquivos ignorados pelo Git
│
└── x64/Release/ # Binários compilados
├── Driver.sys # Driver final
└── user_mode.exe # Aplicação final
Arquivos Principais
| Arquivo | Descrição |
|---|---|
| Driver Kernel | |
main.cpp | Ponto de entrada - chama a função de hook |
hook.cpp | Contém a lógica de hook e comunicação com usermode |
memory.cpp | Funções para manipular memória e processos |
definitions.h | Estruturas e declarações de APIs não documentadas |
| Aplicação Usermode | |
user_mode/main.cpp | Cliente que se comunica com o driver hookado |
Pré-requisitos
Software Necessário
- Windows 10/11 (de preferência em uma VM)
- Visual Studio 2019 ou 2022 com:
- Desenvolvimento para Desktop com C++
- Windows Driver Kit (WDK)
- SDK do Windows (Windows 10/11 SDK)
- kdmapper (para carregar o driver sem assinatura digital)
- Disponível em: https://github.com/TheCruZ/kdmapper
Conhecimento Recomendado
- Programação em C/C++
- Conceitos básicos de Assembly x64
- Estrutura de drivers Windows (WDM/KMDF)
- Como funciona o kernel do Windows
Como Compilar
Passo 1: Abrir o Projeto
- Clone o repositório ou baixe os arquivos
- Abra o arquivo
KernelCheatYT.slnno Visual Studio
Passo 2: Configurar o Build
- Selecione a configuração: Release
- Selecione a plataforma: x64
- Verifique se o WDK está instalado corretamente
Passo 3: Compilar
- No Visual Studio:
Build→Build Solution(Ctrl+Shift+B) - O driver será gerado em:
x64\Release\KernelCheatYT\KernelCheatYT.sys
Possíveis Erros de Compilação
Erro: WDK não encontrado
- Instale o Windows Driver Kit (WDK) compatível com sua versão do Visual Studio
Erro: Target platform version
- Clique com botão direito no projeto → Properties → General → Windows SDK Version
- Selecione a versão instalada no seu sistema
3. Verificar o Hook no WinDbg
O driver será carregado e o hook será aplicado automaticamente. Para verificar:
# No WinDbg (modo kernel):
lm m dxgkrnl # Lista o módulo dxgkrnl
x dxgkrnl!NtOpenCompositionSurfaceSectionInfo # Mostra o endereço
u dxgkrnl!NtOpenCompositionSurfaceSectionInfo # Disassembly - você verá o shellcode!
Você deverá ver algo como:
dxgkrnl!NtOpenCompositionSurfaceSectionInfo:
fffff807`12345678 48b8 mov rax, 0xFFFFF8071234ABCD ; Nosso shellcode!
fffff807`12345682 ffe0 jmp rax
4. Testar a Comunicação Usermode ↔ Kernel
Após carregar o driver, execute a aplicação usermode:
user_mode.exe
Saída esperada (sucesso):
[*] Loading user32.dll...
[+] user32.dll loaded!
=== TESTE COM NOTEPAD ===
[+] Found notepad.exe with PID: 12345
[DEBUG] Requesting base for: ntdll.dll (PID: 12345)
[DEBUG] Calling hook at: 0x7FFB...
[DEBUG] Hook returned: 0x0
[DEBUG] Returned base: 0x7ffb95870000
[+] ntdll.dll base address: 0x7ffb95870000
✅ Se aparecer endereços hexadecimais reais: ESTÁ FUNCIONANDO!
Entendendo o Código Passo a Passo
1. main.cpp - Ponto de Entrada
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING reg_path)
{
// DriverEntry é chamado quando o driver é carregado
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(reg_path);
// Chama a função que vai instalar o hook
nullhook::call_kernel_function(&nullhook::hook_handle);
return STATUS_SUCCESS; // Retorna sucesso
}
O que acontece:
- Windows carrega o driver
DriverEntryé executado- Chamamos nossa função de hook
- Driver retorna sucesso e permanece na memória
2. hook.cpp - O Coração do Hook
Função: call_kernel_function
bool nullhook::call_kernel_function(void* kernel_function_address)
{
// Verifica se o endereço é válido
if (!kernel_function_address)
return false;
// PASSO 1: Encontrar a função alvo
PVOID* function = reinterpret_cast<PVOID*>(
get_system_module_export(
"\\SystemRoot\\System32\\drivers\\dxgkrnl.sys",
"NtOpenCompositionSurfaceSectionInfo"
)
);
if (!function)
return false;
// PASSO 2: Criar o shellcode
BYTE orig[12] = {0}; // Buffer para os 12 bytes
// MOV RAX, <endereço> (48 B8)
BYTE shell_code[] = { 0x48, 0xB8 };
// JMP RAX (FF E0)
BYTE shell_code_end[] = { 0xFF, 0xE0 };
// PASSO 3: Montar o shellcode completo
RtlSecureZeroMemory(orig, sizeof(orig));
// Copia: 48 B8
memcpy((PVOID)((ULONG_PTR)orig), &shell_code, sizeof(shell_code));
// Copia: [8 bytes do endereço]
uintptr_t hook_address = reinterpret_cast<uintptr_t>(kernel_function_address);
memcpy((PVOID)((ULONG_PTR)orig + sizeof(shell_code)), &hook_address, sizeof(void*));
// Copia: FF E0
memcpy((PVOID)((ULONG_PTR)orig + sizeof(shell_code) + sizeof(void*)), &shell_code_end, sizeof(shell_code_end));
// PASSO 4: Escrever na memória protegida
write_to_readonly_memory(function, &orig, sizeof(orig));
return true;
}
Detalhamento:
| Etapa | O que faz | Por quê? |
|---|---|---|
| 1 | Encontra o endereço da função alvo | Precisamos saber ONDE escrever |
| 2 | Cria os opcodes do shellcode | Instruções assembly que fazem o jump |
| 3 | Monta o shellcode com o endereço | Combina tudo em 12 bytes |
| 4 | Escreve na memória (protegida!) | Substitui o código original |
Função: hook_handle
NTSTATUS nullhook::hook_handle(PVOID called_param)
{
// Esta função será chamada quando alguém chamar NtOpenCompositionSurfaceSectionInfo
if (!called_param)
return STATUS_INVALID_PARAMETER;
NULL_MEMORY* instructions = (NULL_MEMORY*)called_param;
// Processar requisições do usermode:
// 1. req_base = TRUE → Retornar endereço base de uma DLL
if (instructions->req_base == TRUE)
{
DbgPrint("[HOOK] req_base for: %s (PID: %d)\n",
instructions->module_name, instructions->pid);
// Converter string, pegar processo, buscar DLL
// Retornar em instructions->base_adress
return STATUS_SUCCESS;
}
// 2. write = TRUE → Escrever memória em outro processo
if (instructions->write == TRUE)
{
// Alocar buffer, copiar dados, escrever no processo alvo
}
// 3. read = TRUE → Ler memória de outro processo
if (instructions->read == TRUE)
{
// Ler memória e retornar em instructions->output
}
return STATUS_SUCCESS;
}
3. memory.cpp - Manipulação de Memória
Função: get_system_module_base
PVOID get_system_module_base(const char* module_name)
{
ULONG bytes = 0;
// 1. Pergunta ao sistema quantos bytes são necessários
ZwQuerySystemInformation(SystemModuleInformation, NULL, bytes, &bytes);
if (!bytes)
return NULL;
// 2. Aloca memória para a lista de módulos
PRTL_PROCESS_MODULES modules = (PRTL_PROCESS_MODULES)
ExAllocatePoolWithTag(NonPagedPool, bytes, 0x636e7474);
// 3. Obtém a lista de módulos carregados
ZwQuerySystemInformation(SystemModuleInformation, modules, bytes, &bytes);
// 4. Procura pelo módulo específico
PVOID module_base = 0;
for (ULONG i = 0; i < modules->NumberOfModules; i++)
{
if (_stricmp((char*)modules->Modules[i].FullPathName, module_name) == NULL)
{
module_base = modules->Modules[i].ImageBase;
break;
}
}
// 5. Libera a memória e retorna
ExFreePoolWithTag(modules, NULL);
return module_base;
}
O que faz:
- Consulta o sistema sobre módulos carregados
- Aloca memória para armazenar a lista
- Itera pelos módulos procurando pelo nome
- Retorna o endereço base do módulo
Função: get_system_module_export
PVOID get_system_module_export(const char* module_name, LPCSTR routine_name)
{
// 1. Primeiro encontra o módulo
PVOID lpModule = get_system_module_base(module_name);
if (lpModule <= NULL)
return NULL;
// 2. Usa a função do Windows para encontrar a export
return RtlFindExportedRoutineByName(lpModule, routine_name);
}
Como funciona:
- Todo módulo PE (Portable Executable) tem uma tabela de exports
RtlFindExportedRoutineByNameparseia essa tabela- Retorna o endereço virtual da função
Função: write_to_readonly_memory
bool write_to_readonly_memory(void* address, void* buffer, size_t size)
{
// 1. Aloca um MDL (Memory Descriptor List)
PMDL Mdl = IoAllocateMdl(address, size, FALSE, FALSE, NULL);
if(!Mdl)
return false;
// 2. Faz lock das páginas de memória
MmProbeAndLockPages(Mdl, KernelMode, IoReadAccess);
// 3. Mapeia as páginas em um novo endereço virtual
PVOID Mapping = MmMapLockedPagesSpecifyCache(Mdl, KernelMode, MmNonCached, NULL, FALSE, NormalPagePriority);
// 4. Altera as permissões para RWX (Read-Write-Execute)
MmProtectMdlSystemAddress(Mdl, PAGE_EXECUTE_READWRITE);
// 5. Escreve os dados
write_memory(Mapping, buffer, size);
// 6. Limpeza: desmapeia, unlock, libera
MmUnmapLockedPages(Mapping, Mdl);
MmUnlockPages(Mdl);
IoFreeMdl(Mdl);
return true;
}
Por que é necessário?
As páginas de código do kernel são read-only por segurança. Para modificá-las:
- MDL (Memory Descriptor List): Estrutura que descreve páginas físicas
- Lock: Previne que as páginas sejam paginadas
- Mapeamento: Cria um novo endereço virtual para as mesmas páginas físicas
- Permissões: Altera para RWX temporariamente
- Escrita: Modifica o conteúdo
- Limpeza: Libera todos os recursos
Conceitos Importantes
1. O que é um Driver Kernel?
Um driver kernel é um programa que roda em Ring 0 (modo kernel), com acesso total ao sistema:
Ring 3 (User Mode) Ring 0 (Kernel Mode)
┌───────────────┐ ┌───────────────┐
│ Aplicativos │ │ Kernel │
│ (você aqui) │ ──────> │ Drivers │
└───────────────┘ └───────────────┘
Limitado Acesso Total
2. Por que Hookar no Kernel?
Vantagens:
- Mais difícil de detectar
- Pode interceptar qualquer chamada do sistema
- Bypassa proteções de user-mode
Desvantagens:
- Um erro = BSOD (tela azul)
- Mais complexo de desenvolver
- Difícil de debugar
3. Por que NtOpenCompositionSurfaceSectionInfo?
Esta função foi escolhida porque:
- Está no
dxgkrnl.sys(DirectX Graphics Kernel) - Raramente é chamada (menos chance de crash)
- É exportada (podemos encontrá-la facilmente)
- Funciona bem para comunicação usermode ↔ kernel
- Boa para fins educacionais
Outras funções que funcionam:
NtOpen*(maioria das funções NtOpen)- Funções com “Composition” no nome
- Lista completa: https://j00ru.vexillium.org/syscalls/win32k/64/
Funções para evitar:
- Funções com “SecureCookie” (causam BSOD)
- Funções em regiões críticas do sistema
4. O que é kdmapper?
kdmapper é uma ferramenta que usa uma vulnerabilidade no driver iqvw64e.sys da Intel para:
- Carregar drivers não assinados
- Executar código no kernel
- Mapear o driver na memória sem registro
Aviso: Só funciona em alguns sistemas e só deve ser usado para aprendizado!
5. Assembly x64 - Referência Rápida
| Opcode | Instrução | Descrição |
|---|---|---|
48 B8 | MOV RAX, imm64 | Move um valor de 64 bits para RAX |
FF E0 | JMP RAX | Pula para o endereço em RAX |
90 | NOP | No Operation (não faz nada) |
C3 | RET | Retorna da função |
6. MDL (Memory Descriptor List)
Um MDL é uma estrutura que descreve páginas de memória física:
Virtual Address ──MDL──> Physical Pages
┌─────────────┐ ┌──────┐
│ 0x12345000 │ ───────> │ Page │
│ 0x12346000 │ │ Page │
│ 0x12347000 │ │ Page │
└─────────────┘ └──────┘
Usado para:
- Transferências DMA
- Modificar memória protegida
- Mapear memória entre processos
Fluxo Completo: Usermode ↔ Kernel
┌─────────────────────────────────────────────────────────────────┐
│ APLICAÇÃO USERMODE │
│ (user_mode.exe) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. LoadLibraryA("user32.dll"); ← CRÍTICO! │
│ └─> Inicializa KernelCallbackTable │
│ │
│ 2. process_id = get_process_id("cs2.exe"); │
│ └─> Encontra PID do processo alvo │
│ │
│ 3. base = get_module_base_address("client.dll"); │
│ │ │
│ ├─> Cria struct NULL_MEMORY │
│ │ └─> pid = process_id │
│ │ └─> req_base = TRUE │
│ │ └─> module_name = "client.dll" │
│ │ │
│ ├─> call_hook(&instructions); │
│ │ │ │
│ │ ├─> LoadLibraryA("win32u.dll") │
│ │ ├─> GetProcAddress(hWin32u, "NtOpen...") │
│ │ └─> func(&instructions); ← Chama função hookada! │
│ │ │ │
└─────┼───────┼───────────────────────────────────────────────────┘
│ │
│ ↓
┌───────┼───────────────────────────────────────────────────────────┐
│ │ WIN32U.DLL (usermode) │
│ │ │
│ └─────> NtOpenCompositionSurfaceSectionInfo() │
│ │ │
│ │ (syscall para kernel) │
└───────────────────────┼────────────────────────────────────────────┘
│
↓
┌───────────────────────┼────────────────────────────────────────────┐
│ │ KERNEL MODE │
│ │ │
│ ┌───────────────▼─────────────────┐ │
│ │ dxgkrnl!NtOpen... (hookado) │ │
│ ├─────────────────────────────────┤ │
│ │ 48 B8 [addr] ; mov rax, addr │ ← NOSSO SHELLCODE! │
│ │ FF E0 ; jmp rax │ │
│ └───────────────┬─────────────────┘ │
│ │ │
│ ↓ │
│ ┌───────────────────────────────────────┐ │
│ │ hook_handle(instructions) │ │
│ ├───────────────────────────────────────┤ │
│ │ │ │
│ │ if (req_base == TRUE) │ │
│ │ { │ │
│ │ 1. PsLookupProcessByProcessId() │ │
│ │ 2. get_module_base_x64() │ │
│ │ └─> Itera PEB/LDR do proc │ │
│ │ └─> Compara nome das DLLs │ │
│ │ 3. instructions->base_adress = X │ │
│ │ 4. ObDereferenceObject(process) │ │
│ │ } │ │
│ │ │ │
│ │ return STATUS_SUCCESS; │ │
│ └───────────────┬───────────────────────┘ │
│ │ │
└───────────────────────┼────────────────────────────────────────────┘
│
↓ (retorna para usermode)
┌───────────────────────┼────────────────────────────────────────────┐
│ │ APLICAÇÃO USERMODE │
│ ▼ │
│ │
│ 4. base = instructions.base_adress; ← RECEBE O RESULTADO! │
│ └─> 0x7FF612340000 │
│ │
│ 5. int hp = Read<int>(base + 0x1234); ← Usar o endereço! │
│ │
└────────────────────────────────────────────────────────────────────┘
Resumo:
- Usermode chama função hookada através de
win32u.dll - Win32u faz syscall para o kernel (
dxgkrnl.sys) - Kernel executa nosso shellcode (hook inline)
- hook_handle processa a requisição
- Resultado retorna para usermode através da estrutura
NULL_MEMORY
⚠️ AVISO IMPORTANTE:
- Este projeto é apenas para fins educacionais
- Um erro no kernel pode causar tela azul (BSOD)