Post

Hardware-Breakpoints-For-Hooking

Hardware-Breakpoints-For-Hooking

What is it?

A hooking library that uses hardware debug registers (Dr0–Dr3) and a Vectored Exception Handler to intercept function calls without patching any bytes. Instead of writing a JMP at the start of the target function like inline hooking, it sets a hardware breakpoint on the function’s address — the CPU raises an exception the moment execution hits it, which the VEH catches and routes to your detour function.

How it works

1
2
3
4
Globals:
  g_VectorHandler   → handle to the registered VEH
  g_CriticalSection → protects g_DetourFuncs from race conditions
  g_DetourFuncs[4]  → one detour function pointer per Dr0-Dr3

Step 1: Initialize

1
2
3
4
InitializeHardwareBPVariables()
  InitializeCriticalSection(&g_CriticalSection)
  AddVectoredExceptionHandler(1, VectorHandler)
  → VEH registered — any exception in the process now hits VectorHandler first

Step 2: Install a hook

1
2
3
4
5
6
7
SetHardwareBreakingPnt(MessageBoxA, Detour_MessageBoxA, Dr0)
  GetThreadContext(-2, CONTEXT_DEBUG_REGISTERS)
  ThreadCtx.Dr0 = MessageBoxA       ← write target address into debug register
  g_DetourFuncs[Dr0] = Detour_MessageBoxA
  ThreadCtx.Dr7 = SetDr7Bits(Dr7, 0, 1, 1)  ← enable G0 bit in Dr7
  SetThreadContext(-2, ThreadCtx)
  → CPU will raise EXCEPTION_SINGLE_STEP next time MessageBoxA executes

Step 3: Execution hits the breakpoint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CPU hits MessageBoxA entry
  → EXCEPTION_SINGLE_STEP raised
  → VectorHandler fires

VectorHandler:
  ExceptionCode == EXCEPTION_SINGLE_STEP?  ← check it's our type
  ExceptionAddress == Dr0/1/2/3?           ← check it's our breakpoint
  Identify which register triggered (Dr0 = MessageBoxA)

  RemoveHardwareBreakingPnt(Dr0)           ← lift temporarily so detour
                                              can call the real function
  fnHookFunc = g_DetourFuncs[Dr0]
  fnHookFunc(pCtx)                         ← call Detour_MessageBoxA

  SetHardwareBreakingPnt(addr, detour, Dr0) ← re-arm for future calls
  return EXCEPTION_CONTINUE_EXECUTION

Step 4: Inside the detour

1
2
3
4
5
6
7
Detour_MessageBoxA(PCONTEXT pCtx):
  GETPARM_2(pCtx)   → reads pCtx->Rdx  (lpText, x64 arg 2)
  GETPARM_3(pCtx)   → reads pCtx->R8   (lpCaption, x64 arg 3)
  RETURN_VALUE(pCtx, MessageBoxA(...))  → writes result to pCtx->Rax
  BLOCK_REAL(pCtx)  → sets pCtx->Rip = &ucRet (a single 0xC3 byte)
                        → original MessageBoxA body never executes
  CONTINUE_EXECUTION(pCtx) → sets trap flag so execution resumes cleanly

Step 5: Remove hook

1
2
3
4
5
RemoveHardwareBreakingPnt(Dr0)
  GetThreadContext → clear Dr0 = 0
  SetDr7Bits → clear G0 bit in Dr7
  SetThreadContext
  → Breakpoint gone, MessageBoxA runs normally again

Step 6: Cleanup

1
2
3
4
UnintializeHardwareBPVariables()
  RemoveHardwareBreakingPnt × 4   ← clear all registers
  DeleteCriticalSection
  RemoveVectoredExceptionHandler

The Dr7 register is the control register that enables or disables individual breakpoints. Each debug register has a corresponding “global enable” bit (G0 for Dr0, G1 for Dr1, etc.) at position Drx * 2 in Dr7. SetDr7Bits uses a bitmask to flip only that bit, leaving the rest of Dr7 untouched.

The BLOCK_REAL trick works because the RET stub (0xC3) is allocated in an executable .text section at compile time. Pointing RIP there makes the CPU “return” from the function immediately — from the caller’s perspective the hooked function simply returned without doing anything.

HardwareHooking.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
#include <Windows.h>
#include <stdio.h>
#include "HardwareHooking.h"

//---------------------------------------------------------------------------------------------------------------------------------------------------------
// RET stub — placed in an executable section so BLOCK_REAL() can point RIP/EIP here,
// causing the hooked function to return immediately without executing its real body

#pragma section(".text")
__declspec(allocate(".text")) const unsigned char ucRet[] = { 0xC3 };

VOID BLOCK_REAL(IN PCONTEXT pThreadCtx) {
#ifdef _WIN64
	pThreadCtx->Rip = (ULONG_PTR)&ucRet;
#elif _WIN32
	pThreadCtx->Eip = (DWORD)&ucRet;
#endif
}

//---------------------------------------------------------------------------------------------------------------------------------------------------------
// Global Variables

PVOID			g_VectorHandler		= NULL;
CRITICAL_SECTION	g_CriticalSection	= { 0 };
PVOID			g_DetourFuncs[4]	= { 0 };	// one slot per Dr0-Dr3

//---------------------------------------------------------------------------------------------------------------------------------------------------------

BOOL ReportError(IN PCWSTR szApiFuncName, IN OPTIONAL ULONGLONG uError) {
	if (uError)
		printf("[!] %ws Failed With Error: 0x%0.8llX \n", szApiFuncName, uError);
	else
		printf("[!] %ws Failed With Error: %lu \n", szApiFuncName, GetLastError());
	return FALSE;
}

//---------------------------------------------------------------------------------------------------------------------------------------------------------

/*
	Modify a run of bits inside the Dr7 control register

	Dr7 layout (per-breakpoint enable bits):
	  Bit 0 (L0), Bit 1 (G0) — Dr0
	  Bit 2 (L1), Bit 3 (G1) — Dr1
	  Bit 4 (L2), Bit 5 (G2) — Dr2
	  Bit 6 (L3), Bit 7 (G3) — Dr3

	StartingBitPosition -> First bit to modify
	NmbrOfBitsToModify  -> How many bits to overwrite
	NewBitValue         -> New value to write into those bits
*/
ULONG_PTR SetDr7Bits(ULONG_PTR CurrentDr7, int StartingBitPosition, int NmbrOfBitsToModify, ULONG_PTR NewBitValue) {
	ULONG_PTR mask = ((ULONG_PTR)1 << NmbrOfBitsToModify) - 1;
	return (CurrentDr7 & ~(mask << StartingBitPosition)) | (NewBitValue << StartingBitPosition);
}

//---------------------------------------------------------------------------------------------------------------------------------------------------------

/*
	Returns the hooked function's argument at dwParmIndex from the thread context
	Called through the GETPARM_X macros

	pThreadCtx  -> Thread context captured by the VEH
	dwParmIndex -> 1-based argument index
*/
PBYTE GetFunctionArgument(IN PCONTEXT pThreadCtx, IN DWORD dwParmIndex) {
#ifdef _WIN64
	switch (dwParmIndex) {
		case 0x1: return (PBYTE)pThreadCtx->Rcx;
		case 0x2: return (PBYTE)pThreadCtx->Rdx;
		case 0x3: return (PBYTE)pThreadCtx->R8;
		case 0x4: return (PBYTE)pThreadCtx->R9;
		default: break;
	}
	return (PBYTE)(*(ULONG_PTR*)(pThreadCtx->Rsp + dwParmIndex * sizeof(PVOID)));
#else
	return (PBYTE)(*(DWORD_PTR*)(pThreadCtx->Esp + dwParmIndex * sizeof(PVOID)));
#endif
}

//---------------------------------------------------------------------------------------------------------------------------------------------------------

/*
	Overwrites the hooked function's argument at dwParmIndex with uValue
	Called through the SETPARM_X macros

	pThreadCtx  -> Thread context captured by the VEH
	uValue      -> New argument value
	dwParmIndex -> 1-based argument index
*/
VOID SetFunctionArgument(IN PCONTEXT pThreadCtx, IN ULONG_PTR uValue, IN DWORD dwParmIndex) {
#ifdef _WIN64
	switch (dwParmIndex) {
		case 0x1: pThreadCtx->Rcx = uValue; return;
		case 0x2: pThreadCtx->Rdx = uValue; return;
		case 0x3: pThreadCtx->R8  = uValue; return;
		case 0x4: pThreadCtx->R9  = uValue; return;
		default: break;
	}
	*(ULONG_PTR*)(pThreadCtx->Rsp + dwParmIndex * sizeof(PVOID)) = uValue;
#else
	*(DWORD_PTR*)(pThreadCtx->Esp + dwParmIndex * sizeof(PVOID)) = uValue;
#endif
}

//---------------------------------------------------------------------------------------------------------------------------------------------------------

/*
	Install a hardware breakpoint on the current thread

	pAddress   -> Address to break on
	fnHookFunc -> Detour function to call when the breakpoint fires
	Drx        -> Debug register to use (Dr0-Dr3)
*/
BOOL SetHardwareBreakingPnt(IN PVOID pAddress, IN PVOID fnHookFunc, IN enum DRX Drx) {

	if (!g_VectorHandler || !pAddress || !fnHookFunc)
		return FALSE;

	CONTEXT ThreadCtx = { .ContextFlags = CONTEXT_DEBUG_REGISTERS };

	if (!GetThreadContext((HANDLE)-2, &ThreadCtx))
		return ReportError(L"GetThreadContext", 0);

	// Write the target address into the selected debug register
	switch (Drx) {
		case Dr0: if (!ThreadCtx.Dr0) ThreadCtx.Dr0 = (ULONG_PTR)pAddress; break;
		case Dr1: if (!ThreadCtx.Dr1) ThreadCtx.Dr1 = (ULONG_PTR)pAddress; break;
		case Dr2: if (!ThreadCtx.Dr2) ThreadCtx.Dr2 = (ULONG_PTR)pAddress; break;
		case Dr3: if (!ThreadCtx.Dr3) ThreadCtx.Dr3 = (ULONG_PTR)pAddress; break;
		default:  return FALSE;
	}

	EnterCriticalSection(&g_CriticalSection);
	g_DetourFuncs[Drx] = fnHookFunc;
	LeaveCriticalSection(&g_CriticalSection);

	// Enable the breakpoint — set G-bit for this register in Dr7 (Drx * 2 = bit position)
	ThreadCtx.Dr7 = SetDr7Bits(ThreadCtx.Dr7, (Drx * 2), 1, 1);

	if (!SetThreadContext((HANDLE)-2, &ThreadCtx))
		return ReportError(L"SetThreadContext", 0);

	return TRUE;
}

//---------------------------------------------------------------------------------------------------------------------------------------------------------

/*
	Remove a hardware breakpoint from the current thread

	Drx -> Debug register to clear (Dr0-Dr3)
*/
BOOL RemoveHardwareBreakingPnt(IN enum DRX Drx) {

	if (!g_VectorHandler)
		return FALSE;

	CONTEXT ThreadCtx = { .ContextFlags = CONTEXT_DEBUG_REGISTERS };

	if (!GetThreadContext((HANDLE)-2, &ThreadCtx))
		return ReportError(L"GetThreadContext", 0);

	// Clear the debug register
	switch (Drx) {
		case Dr0: ThreadCtx.Dr0 = 0x00; break;
		case Dr1: ThreadCtx.Dr1 = 0x00; break;
		case Dr2: ThreadCtx.Dr2 = 0x00; break;
		case Dr3: ThreadCtx.Dr3 = 0x00; break;
		default:  return FALSE;
	}

	// Clear the G-bit for this register in Dr7
	ThreadCtx.Dr7 = SetDr7Bits(ThreadCtx.Dr7, (Drx * 2), 1, 0);

	if (!SetThreadContext((HANDLE)-2, &ThreadCtx))
		return ReportError(L"SetThreadContext", 0);

	return TRUE;
}

//---------------------------------------------------------------------------------------------------------------------------------------------------------

/*
	VEH callback — fires on every exception in the process

	Only handles EXCEPTION_SINGLE_STEP raised by our hardware breakpoints:
	1. Identify which Dr register triggered
	2. Temporarily remove the breakpoint so the detour can call the real function
	3. Call the detour function with the captured thread context
	4. Re-install the breakpoint
	5. Resume execution
*/
LONG WINAPI VectorHandler(PEXCEPTION_POINTERS pExceptionInfo) {

	if (pExceptionInfo->ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP)
		return EXCEPTION_CONTINUE_SEARCH;

	PVOID		pExAddr		= pExceptionInfo->ExceptionRecord->ExceptionAddress;
	PCONTEXT	pCtx		= pExceptionInfo->ContextRecord;

	// Only handle breakpoints we installed
	if (pExAddr != (PVOID)pCtx->Dr0 && pExAddr != (PVOID)pCtx->Dr1 &&
		pExAddr != (PVOID)pCtx->Dr2 && pExAddr != (PVOID)pCtx->Dr3)
		return EXCEPTION_CONTINUE_SEARCH;

	// Identify which register fired
	enum DRX dwDrx = -1;
	if (pExAddr == (PVOID)pCtx->Dr0) dwDrx = Dr0;
	if (pExAddr == (PVOID)pCtx->Dr1) dwDrx = Dr1;
	if (pExAddr == (PVOID)pCtx->Dr2) dwDrx = Dr2;
	if (pExAddr == (PVOID)pCtx->Dr3) dwDrx = Dr3;

	VOID (*fnHookFunc)(PCONTEXT) = NULL;

	EnterCriticalSection(&g_CriticalSection);

	// Temporarily remove the breakpoint so the detour can call the real function
	RemoveHardwareBreakingPnt(dwDrx);

	fnHookFunc = g_DetourFuncs[dwDrx];
	fnHookFunc(pCtx);

	// Re-install the breakpoint for future calls
	SetHardwareBreakingPnt(pExceptionInfo->ExceptionRecord->ExceptionAddress, g_DetourFuncs[dwDrx], dwDrx);

	LeaveCriticalSection(&g_CriticalSection);

	return EXCEPTION_CONTINUE_EXECUTION;
}

//---------------------------------------------------------------------------------------------------------------------------------------------------------

BOOL InitializeHardwareBPVariables() {

	RtlSecureZeroMemory(&g_CriticalSection, sizeof(CRITICAL_SECTION));
	RtlSecureZeroMemory(g_DetourFuncs, sizeof(g_DetourFuncs));

	if (g_CriticalSection.DebugInfo == NULL)
		InitializeCriticalSection(&g_CriticalSection);

	if (!g_VectorHandler) {
		if ((g_VectorHandler = AddVectoredExceptionHandler(1, (PVECTORED_EXCEPTION_HANDLER)&VectorHandler)) == NULL)
			return ReportError(L"AddVectoredExceptionHandler", 0);
	}

	return (g_VectorHandler && g_CriticalSection.DebugInfo);
}

//---------------------------------------------------------------------------------------------------------------------------------------------------------

VOID UnintializeHardwareBPVariables() {

	for (int i = 0; i < 4; i++)
		RemoveHardwareBreakingPnt(i);

	if (g_CriticalSection.DebugInfo)
		DeleteCriticalSection(&g_CriticalSection);

	if (g_VectorHandler)
		RemoveVectoredExceptionHandler(g_VectorHandler);

	RtlSecureZeroMemory(&g_CriticalSection, sizeof(CRITICAL_SECTION));
	RtlSecureZeroMemory(g_DetourFuncs, sizeof(g_DetourFuncs));
	g_VectorHandler = NULL;
}

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include <Windows.h>
#include <stdio.h>
#include "HardwareHooking.h"

//---------------------------------------------------------------------------------------------------------------------------------------------------------
// Detour Functions
//
// Each detour receives the full thread context of the hooked function.
// Use GETPARM_X to read args, SETPARM_X to modify them,
// RETURN_VALUE to set a custom return value, BLOCK_REAL to skip the original.
//---------------------------------------------------------------------------------------------------------------------------------------------------------

/*
	Detour for MessageBoxA
	Logs the original parameters, replaces the call with our own MessageBox
*/
VOID Detour_MessageBoxA(PCONTEXT pThreadCtx) {

	printf("[i] MessageBoxA Intercepted! \n");
	printf("\t> lpText:    %s \n", (char*)GETPARM_2(pThreadCtx));
	printf("\t> lpCaption: %s \n", (char*)GETPARM_3(pThreadCtx));

	// Replace original call with our own
	RETURN_VALUE(pThreadCtx, MessageBoxA(NULL, "This Is The Hook", "Detour_MessageBoxA", MB_OK | MB_ICONEXCLAMATION));

	// Skip the real MessageBoxA body
	BLOCK_REAL(pThreadCtx);

	CONTINUE_EXECUTION(pThreadCtx);
}

//---------------------------------------------------------------------------------------------------------------------------------------------------------

/*
	Detour for Sleep
	Logs the original sleep duration and suppresses the call entirely
*/
VOID Detour_Sleep(PCONTEXT pThreadCtx) {

	printf("[i] Sleep Intercepted! \n");
	printf("\t> dwMilliseconds: %lu ms \n", (DWORD)GETPARM_1(pThreadCtx));
	printf("\t> Skipping sleep... \n");

	// Skip sleep — return immediately
	BLOCK_REAL(pThreadCtx);

	CONTINUE_EXECUTION(pThreadCtx);
}

//---------------------------------------------------------------------------------------------------------------------------------------------------------

int main() {

	printf("[i] Initializing Hardware Breakpoint Engine ... \n");
	if (!InitializeHardwareBPVariables()) {
		printf("[!] InitializeHardwareBPVariables Failed \n");
		return -1;
	}
	printf("[+] VEH Handler Registered \n\n");


	// Not hooked
	printf("[i] Calling MessageBoxA Before Hooking ... \n");
	MessageBoxA(NULL, "Original Call - Not Hooked", "Before Hook", MB_OK);


	// Install hooks
	printf("\n[i] Installing Hooks ... \n");

	if (!SetHardwareBreakingPnt(MessageBoxA, Detour_MessageBoxA, Dr0)) {
		printf("[!] Failed To Hook MessageBoxA \n");
		return -1;
	}
	printf("\t[+] MessageBoxA -> Dr0 \n");

	if (!SetHardwareBreakingPnt(Sleep, Detour_Sleep, Dr1)) {
		printf("[!] Failed To Hook Sleep \n");
		return -1;
	}
	printf("\t[+] Sleep       -> Dr1 \n\n");


	// Hooked calls
	printf("[i] Calling MessageBoxA While Hooked ... \n");
	MessageBoxA(NULL, "This Should Not Appear", "Hooked?", MB_OK);

	printf("\n[i] Calling Sleep(-1) While Hooked ... \n");
	Sleep(-1);
	printf("[+] Sleep Returned Immediately \n\n");


	// Remove MessageBoxA hook only
	printf("[i] Removing Hook On Dr0 (MessageBoxA) ... \n");
	if (!RemoveHardwareBreakingPnt(Dr0)) {
		printf("[!] RemoveHardwareBreakingPnt Failed \n");
		return -1;
	}
	printf("[+] Dr0 Cleared \n\n");


	// Post-unhook
	printf("[i] Calling MessageBoxA After Unhooking ... \n");
	MessageBoxA(NULL, "Back To The Original MessageBoxA", "After Unhook", MB_OK);

	printf("\n[i] Calling Sleep(-1) - Still Hooked On Dr1 ... \n");
	Sleep(-1);
	printf("[+] Sleep Returned Immediately \n\n");


	// Cleanup
	printf("[i] Cleaning Up ... \n");
	UnintializeHardwareBPVariables();
	printf("[+] All Hooks Removed, VEH Deregistered \n\n");

	printf("[#] Press <Enter> To Quit ... \n");
	getchar();
	return 0;
}

HardwareHooking.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#pragma once

#include <Windows.h>

#ifndef HARDWAREBP_H
#define HARDWAREBP_H

//---------------------------------------------------------------------------------------------------------------------------------------------------------
// Hardware Breakpoint Hooking Library
// Uses hardware debug registers (Dr0-Dr3) and VEH to intercept function calls without patching.
// Maximum 4 concurrent hooks — one per debug register.
//---------------------------------------------------------------------------------------------------------------------------------------------------------

// Debug register selector
typedef enum _DRX {
	Dr0 = 0,
	Dr1 = 1,
	Dr2 = 2,
	Dr3 = 3
} DRX, *PDRX;

//---------------------------------------------------------------------------------------------------------------------------------------------------------
// MACROS — call from within detour functions to read/write hooked function arguments
// x64: first 4 args in RCX/RDX/R8/R9, rest on stack
// x86: all args on stack

// Read parameters
#define GETPARM_1(CTX)			( GetFunctionArgument(CTX, 0x1) )
#define GETPARM_2(CTX)			( GetFunctionArgument(CTX, 0x2) )
#define GETPARM_3(CTX)			( GetFunctionArgument(CTX, 0x3) )
#define GETPARM_4(CTX)			( GetFunctionArgument(CTX, 0x4) )
#define GETPARM_5(CTX)			( GetFunctionArgument(CTX, 0x5) )
#define GETPARM_6(CTX)			( GetFunctionArgument(CTX, 0x6) )
#define GETPARM_7(CTX)			( GetFunctionArgument(CTX, 0x7) )
#define GETPARM_8(CTX)			( GetFunctionArgument(CTX, 0x8) )

// Write parameters
#define SETPARM_1(CTX, VAL)		( SetFunctionArgument(CTX, (ULONG_PTR)(VAL), 0x1) )
#define SETPARM_2(CTX, VAL)		( SetFunctionArgument(CTX, (ULONG_PTR)(VAL), 0x2) )
#define SETPARM_3(CTX, VAL)		( SetFunctionArgument(CTX, (ULONG_PTR)(VAL), 0x3) )
#define SETPARM_4(CTX, VAL)		( SetFunctionArgument(CTX, (ULONG_PTR)(VAL), 0x4) )
#define SETPARM_5(CTX, VAL)		( SetFunctionArgument(CTX, (ULONG_PTR)(VAL), 0x5) )
#define SETPARM_6(CTX, VAL)		( SetFunctionArgument(CTX, (ULONG_PTR)(VAL), 0x6) )
#define SETPARM_7(CTX, VAL)		( SetFunctionArgument(CTX, (ULONG_PTR)(VAL), 0x7) )
#define SETPARM_8(CTX, VAL)		( SetFunctionArgument(CTX, (ULONG_PTR)(VAL), 0x8) )

// Set return value (RAX/EAX)
#ifdef _WIN64
#define RETURN_VALUE(CTX, VAL)	( (ULONG_PTR)(CTX)->Rax = (ULONG_PTR)(VAL) )
#else
#define RETURN_VALUE(CTX, VAL)	( (ULONG_PTR)(CTX)->Eax = (ULONG_PTR)(VAL) )
#endif

// Resume normal execution after the detour
#define CONTINUE_EXECUTION(CTX)	( (CTX)->EFlags |= (1 << 16) )

//---------------------------------------------------------------------------------------------------------------------------------------------------------
// PUBLIC

// Initialize VEH handler and critical section - call once before installing any hooks
BOOL InitializeHardwareBPVariables();

// Remove all breakpoints and deregister the VEH handler
VOID UnintializeHardwareBPVariables();

/*
	Install a hardware breakpoint
	pAddress   -> Address to break on (where to hook)
	fnHookFunc -> Detour function called when the breakpoint fires
	Drx        -> Debug register to use (Dr0-Dr3)
*/
BOOL SetHardwareBreakingPnt(IN PVOID pAddress, IN PVOID fnHookFunc, IN enum DRX Drx);

/*
	Remove a hardware breakpoint
	Drx -> Debug register to clear (Dr0-Dr3)
*/
BOOL RemoveHardwareBreakingPnt(IN enum DRX Drx);

// Skip the real function body — redirects RIP/EIP to a single RET stub
VOID BLOCK_REAL(IN PCONTEXT pThreadCtx);

//---------------------------------------------------------------------------------------------------------------------------------------------------------
// PRIVATE — used internally, do not call directly

PBYTE GetFunctionArgument(IN PCONTEXT pThreadCtx, IN DWORD dwParmIndex);
VOID  SetFunctionArgument(IN PCONTEXT pThreadCtx, IN ULONG_PTR uValue, IN DWORD dwParmIndex);

#endif // !HARDWAREBP_H

This post is licensed under CC BY 4.0 by the author.