1
+ #[
2
+ Author: m4ul3r (@m4ul3r_0x00)
3
+ Reference: Maldev Academy
4
+ License: BSD 3-Clause
5
+
6
+ Description:
7
+ Utilize Hardware Breakpoints to hook functions.
8
+ See "EXMAPLE USAGE" on how to use hardware breakpoints.
9
+ ]#
10
+
11
+ import winim/ lean
12
+
13
+ type
14
+ DRX = enum
15
+ Dr0, Dr1, Dr2, Dr3
16
+ HookFuncType = proc (pContext:PCONTEXT){.stdcall.}
17
+
18
+ var
19
+ g_VectorHandler: PVOID # Vectored Exception Handler
20
+ g_DetourFuncs: array [4 , PVOID] # Array of 4 Hook functions
21
+ g_CriticalSection: CRITICAL_SECTION
22
+
23
+ proc ucRet() {.asmNoStackFrame.} =
24
+ # # Ret used to terminate original function execution
25
+ asm """ .byte 0xc3 """
26
+
27
+ proc BLOCK_REAL(pThreadCtx: PCONTEXT) =
28
+ # # Used in detour function to block execution of original hooked function
29
+ pThreadCtx.Rip = cast [int ](ucRet)
30
+
31
+ proc setDr7Bits(currentDr7Register, startingBitPosition, nmbrOfBitsToModify, newBitValue: int ): int =
32
+ # # Enable or disable an installed breakpoint
33
+ var
34
+ mask: int = ((1 shl nmbrOfBitsToModify) - 1 )
35
+ newDr7Register: int = (currentDr7Register and not (mask shl startingBitPosition)) or (newBitValue shl startingBitPosition)
36
+ return newDr7Register
37
+
38
+ proc setHardwareBreakpoint(pAddress: PVOID, fnHookFunc: PVOID, drx: DRX): bool =
39
+ var threadCtx: CONTEXT
40
+ threadCtx.ContextFlags = CONTEXT_DEBUG_REGISTERS
41
+
42
+ if GetThreadContext(cast [HANDLE](-2), threadCtx.addr) == 0:
43
+ echo "[ ! ] GetThreadContext Failed: " , GetLastError()
44
+ return false
45
+
46
+ case drx:
47
+ of Dr0:
48
+ if (threadCtx.Dr0 == 0 ):
49
+ threadCtx.Dr0 = cast [int ](pAddress)
50
+ of Dr1:
51
+ if (threadCtx.Dr1 == 0 ):
52
+ threadCtx.Dr1 = cast [int ](pAddress)
53
+ of Dr2:
54
+ if (threadCtx.Dr2 == 0 ):
55
+ threadCtx.Dr2 = cast [int ](pAddress)
56
+ of Dr3:
57
+ if (threadCtx.Dr3 == 0 ):
58
+ threadCtx.Dr3 = cast [int ](pAddress)
59
+
60
+ # Save the hooked function at index 'drx' in global array
61
+ EnterCriticalSection(g_CriticalSection.addr )
62
+ g_DetourFuncs[cast [int ](drx)] = fnHookFunc
63
+ LeaveCriticalSection(g_CriticalSection.addr )
64
+
65
+ # Enable the breakpoint
66
+ threadCtx.Dr7 = setDr7Bits(threadCtx.Dr7, (cast [int ](drx) * 2 ), 1 , 1 )
67
+
68
+ if SetThreadContext(cast [HANDLE](-2), threadCtx.addr) == 0:
69
+ echo "[ ! ] SetThreadContext Failed" , GetLastError()
70
+ return false
71
+
72
+ return true
73
+
74
+ proc removeHardwareBreakpoint(drx: DRX): bool =
75
+ var threadCtx: CONTEXT
76
+ threadCtx.ContextFlags = CONTEXT_DEBUG_REGISTERS
77
+
78
+ if GetThreadContext(cast [HANDLE](-2), threadCtx.addr) == 0:
79
+ echo "[ ! ] GetThreadContext Failed: " , GetLastError()
80
+ return false
81
+
82
+ # Remove the address of the hooked function from the thread context
83
+ case drx:
84
+ of Dr0:
85
+ threadCtx.Dr0 = cast [int ](0 )
86
+ of Dr1:
87
+ threadCtx.Dr1 = cast [int ](0 )
88
+ of Dr2:
89
+ threadCtx.Dr2 = cast [int ](0 )
90
+ of Dr3:
91
+ threadCtx.Dr3 = cast [int ](0 )
92
+
93
+ # Disabling the breakpoint
94
+ threadCtx.Dr7 = setDr7Bits(threadCtx.Dr7, (cast [int ](drx) * 2 ), 1 , 0 )
95
+
96
+ if SetThreadContext(cast [HANDLE](-2), threadCtx.addr) == 0:
97
+ echo "[ ! ] SetThreadContext Failed" , GetLastError()
98
+ return false
99
+
100
+ return true
101
+
102
+ proc vectorHandler(pExceptionInfo: ptr EXCEPTION_POINTERS): int =
103
+ # If the exception is 'EXCEPTION_SINGLE_STEP' then its caused by a bp
104
+ if (pExceptionInfo.ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP):
105
+ if (cast [int ](pExceptionInfo.ExceptionRecord.ExceptionAddress) == pExceptionInfo.ContextRecord.Dr0) or
106
+ (cast [int ](pExceptionInfo.ExceptionRecord.ExceptionAddress) == pExceptionInfo.ContextRecord.Dr1) or
107
+ (cast [int ](pExceptionInfo.ExceptionRecord.ExceptionAddress) == pExceptionInfo.ContextRecord.Dr2) or
108
+ (cast [int ](pExceptionInfo.ExceptionRecord.ExceptionAddress) == pExceptionInfo.ContextRecord.Dr3):
109
+ var
110
+ dwDrx: DRX
111
+ fnHookFunc = cast [HookFuncType](0)
112
+
113
+ EnterCriticalSection(g_CriticalSection.addr)
114
+
115
+ if (cast[int ](pExceptionInfo.ExceptionRecord.ExceptionAddress) == pExceptionInfo.ContextRecord.Dr0):
116
+ dwDrx = Dr0
117
+ if (cast[int ](pExceptionInfo.ExceptionRecord.ExceptionAddress) == pExceptionInfo.ContextRecord.Dr1):
118
+ dwDrx = Dr1
119
+ if (cast[int ](pExceptionInfo.ExceptionRecord.ExceptionAddress) == pExceptionInfo.ContextRecord.Dr2):
120
+ dwDrx = Dr2
121
+ if (cast[int ](pExceptionInfo.ExceptionRecord.ExceptionAddress) == pExceptionInfo.ContextRecord.Dr3):
122
+ dwDrx = Dr3
123
+
124
+ discard removeHardwareBreakpoint(dwDrx)
125
+
126
+ # Execute the callback (detour function)
127
+ fnHookFunc = cast[HookFuncType](g_DetourFuncs[cast[int ](dwDrx ) ])
128
+ fnHookFunc(pExceptionInfo.ContextRecord)
129
+
130
+ discard setHardwareBreakpoint(pExceptionInfo.ExceptionRecord.ExceptionAddress, g_DetourFuncs[cast [int ](dwDrx)], dwDrx)
131
+
132
+ LeaveCriticalSection(g_CriticalSection.addr )
133
+
134
+ return EXCEPTION_CONTINUE_EXECUTION
135
+ # The exception is not handled
136
+ return EXCEPTION_CONTINUE_SEARCH
137
+
138
+ #[ Function argument handling ]#
139
+ proc getFunctionArgument(pThreadCtx: PCONTEXT, dwParamIdx: int ): pointer =
140
+ # amd64
141
+ case dwParamIdx:
142
+ of 1 :
143
+ return cast [PULONG](pThreadCtx.Rcx)
144
+ of 2:
145
+ return cast[PULONG](pThreadCtx.Rdx)
146
+ of 3:
147
+ return cast[PULONG](pThreadCtx.R8)
148
+ of 4:
149
+ return cast[PULONG](pThreadCtx.R9)
150
+ else:
151
+ # else more arguments are pushed to the stack
152
+ return cast[PULONG](pThreadCtx.Rsp + (dwParamIdx * sizeof(PVOID)))
153
+
154
+ proc setFunctionArgument(pThreadCtx: PCONTEXT, uValue: PULONG, dwParamIdx: int ) =
155
+ # amd64
156
+ case dwParamIdx:
157
+ of 1:
158
+ pThreadCtx.Rcx = cast[int ](uValue)
159
+ of 2:
160
+ pThreadCtx.Rdx = cast[int ](uValue)
161
+ of 3:
162
+ pThreadCtx.R8 = cast[int ](uValue)
163
+ of 4:
164
+ pThreadCtx.R9 = cast[int ](uValue)
165
+ else:
166
+ # else more arguments are pushed to the stack
167
+ cast[ptr int ](pThreadCtx.Rsp + (dwParamIdx * sizeof(PVOID)))[] = cast[int ](uValue)
168
+
169
+ # getFunctionArgument macros
170
+ template GETPARAM_1(ctx: PCONTEXT): pointer = getFunctionArgument(ctx, 1)
171
+ template GETPARAM_2(ctx: PCONTEXT): pointer = getFunctionArgument(ctx, 2)
172
+ template GETPARAM_3(ctx: PCONTEXT): pointer = getFunctionArgument(ctx, 3)
173
+ template GETPARAM_4(ctx: PCONTEXT): pointer = getFunctionArgument(ctx, 4)
174
+ template GETPARAM_5(ctx: PCONTEXT): pointer = getFunctionArgument(ctx, 5)
175
+ template GETPARAM_6(ctx: PCONTEXT): pointer = getFunctionArgument(ctx, 6)
176
+ template GETPARAM_7(ctx: PCONTEXT): pointer = getFunctionArgument(ctx, 7)
177
+ template GETPARAM_8(ctx: PCONTEXT): pointer = getFunctionArgument(ctx, 8)
178
+ template GETPARAM_9(ctx: PCONTEXT): pointer = getFunctionArgument(ctx, 9)
179
+ template GETPARAM_a(ctx: PCONTEXT): pointer = getFunctionArgument(ctx, a)
180
+ template GETPARAM_b(ctx: PCONTEXT): pointer = getFunctionArgument(ctx, b)
181
+
182
+ # setFunctionArgument macros
183
+ template SETPARAM_1(ctx: PCONTEXT, value: untyped ) = setFunctionArgument(ctx, value, 1)
184
+ template SETPARAM_2(ctx: PCONTEXT, value: untyped ) = setFunctionArgument(ctx, value, 2)
185
+ template SETPARAM_3(ctx: PCONTEXT, value: untyped ) = setFunctionArgument(ctx, value, 3)
186
+ template SETPARAM_4(ctx: PCONTEXT, value: untyped ) = setFunctionArgument(ctx, value, 4)
187
+ template SETPARAM_5(ctx: PCONTEXT, value: untyped ) = setFunctionArgument(ctx, value, 5)
188
+ template SETPARAM_6(ctx: PCONTEXT, value: untyped ) = setFunctionArgument(ctx, value, 6)
189
+ template SETPARAM_7(ctx: PCONTEXT, value: untyped ) = setFunctionArgument(ctx, value, 7)
190
+ template SETPARAM_8(ctx: PCONTEXT, value: untyped ) = setFunctionArgument(ctx, value, 8)
191
+ template SETPARAM_9(ctx: PCONTEXT, value: untyped ) = setFunctionArgument(ctx, value, 9)
192
+ template SETPARAM_a(ctx: PCONTEXT, value: untyped ) = setFunctionArgument(ctx, value, a)
193
+ template SETPARAM_b(ctx: PCONTEXT, value: untyped ) = setFunctionArgument(ctx, value, b)
194
+
195
+ #[ init/uninit HW BP]#
196
+ proc initializeHardwareBPVariables(): bool =
197
+ # If 'g_CriticalSection' is not yet initialized
198
+ if g_CriticalSection.DebugInfo == NULL:
199
+ InitializeCriticalSection(g_CriticalSection.addr)
200
+
201
+ # If 'g_VectorHandler' is not yet initialized
202
+ if (cast[int ](g_VectorHandler) == 0):
203
+ # Add 'VectorHandler' as the VEH
204
+ g_VectorHandler = AddVectoredExceptionHandler(1, cast[PVECTORED_EXCEPTION_HANDLER](vectorHandler))
205
+ if cast[int ](g_VectorHandler) == 0:
206
+ echo "[ ! ] AddVectoredExceptionHandler Failed"
207
+ return false
208
+
209
+ if (cast [int ](g_VectorHandler) and cast [int ](g_CriticalSection.DebugInfo)) != 0 :
210
+ return true
211
+
212
+ proc uninitializeHardwareBPVariables() =
213
+ # Remove breakpoints
214
+ for i in 0 ..< 4 :
215
+ discard removeHardwareBreakpoint(cast [DRX](i))
216
+ # If the critical section is initialized, delete it
217
+ if (cast[int ](g_CriticalSection.DebugInfo) != 0):
218
+ DeleteCriticalSection(g_CriticalSection.addr)
219
+ # If VEH if registered, remove it
220
+ if (cast[int ](g_VectorHandler) != 0):
221
+ RemoveVectoredExceptionHandler(g_VectorHandler)
222
+
223
+ # Cleanup the global variables
224
+ zeroMem(g_CriticalSection.addr, sizeof(g_CriticalSection))
225
+ zeroMem(g_DetourFuncs.addr, sizeof(g_DetourFuncs))
226
+ g_VectorHandler = cast[PVOID](0)
227
+
228
+ template CONTINUE_EXECUTION(ctx: PCONTEXT) = (ctx.EFlags = (ctx.EFlags or (1 shl 16)))
229
+
230
+
231
+ #[ EXMAPLE USAGE ]#
232
+ proc MessageBoxADetour(pThreadCtx: PCONTEXT) =
233
+ echo " [i] MessageBoxA's Old Parameters:"
234
+ echo " [i] " , cast [cstring ](GETPARAM_2(pThreadCtx))
235
+ echo " [i] " , cast [cstring ](GETPARAM_3(pThreadCtx))
236
+
237
+ var msg1 = " HOOKED" .cstring
238
+ var msg2 = " HOOKED" .cstring
239
+ SETPARAM_2(pThreadCtx, cast [PULONG](msg1[ 0].addr ))
240
+ SETPARAM_3(pThreadCtx, cast [PULONG](msg2[ 0].addr ))
241
+ SETPARAM_4(pThreadCtx, cast [PULONG](MB_OK or MB_ICONEXCLAMATION))
242
+
243
+ # CONTINUTE_EXECUTION needs to be called in the hooked function
244
+ CONTINUE_EXECUTION(pThreadCtx)
245
+
246
+ proc main() =
247
+ # initialize
248
+ if not initializeHardwareBPVariables():
249
+ echo "[ ! ] Failed to initialize"
250
+ quit(1 )
251
+
252
+ MessageBoxA(0 , " Normal 1" , " Normal 1" , MB_OK)
253
+
254
+ echo " [i] Installing Hooks..."
255
+ if not setHardwareBreakpoint(MessageBoxA, MessageBoxADetour, Dr0):
256
+ quit(1 )
257
+
258
+ MessageBoxA(0 , " Should be hooked" , " Should be hooked" , MB_OK)
259
+
260
+ # Unhooking the installed hook on 'Dr0'
261
+ echo " [i] Uninstalling Hooks..."
262
+ if not removeHardwareBreakpoint(Dr0):
263
+ quit(1 )
264
+
265
+ #[ NOT HOOKED ]#
266
+ MessageBoxA(0 , " Normal 2" , " Normal 2" , MB_OK)
267
+
268
+ # Clean up
269
+ uninitializeHardwareBPVariables()
270
+ stdout.write " [#] Press <Enter> to Quit ..."
271
+ discard stdin.readLine()
272
+
273
+ when isMainModule :
274
+ main()
0 commit comments