Skip to content

Commit c056dc8

Browse files
authored
hardware breakpoints (byt3bl33d3r#69)
1 parent a864a5b commit c056dc8

File tree

2 files changed

+275
-1
lines changed

2 files changed

+275
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ My experiments in weaponizing [Nim](https://nim-lang.org/) for implant developme
103103
| [anti_debug_via_tls.nim](../master/src/anti_debug_via_tls.nim) | Anti-debugging vis TLS |
104104
| [local_pe_execution.nim](../master/src/local_pe_execution.nim) | Execute exe and dll files in memory |
105105
| [stack_string_allocation.nim](../master/src/stack_string_allocation.nim) | Allocate c and wide strings on the stack using arrays |
106-
106+
| [hardware_breakpoints.nim](../master/src/hardware_breakpoints.nim) | Hook functions using hardware breakpoints |
107107

108108
## Examples that are a WIP
109109

src/hardware_breakpoints.nim

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
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

Comments
 (0)