-
Notifications
You must be signed in to change notification settings - Fork 45
/
FuncHook.pas
168 lines (147 loc) · 5.39 KB
/
FuncHook.pas
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
(*==============================================================================
====== Function hooking ======
Functions exported from DLL contain single instruction "JMP [fn_addr]" that
points to actual code. So there could be 2 ways of hooking:
1) Patch the JMP not touching the code
+ Original proc could be called via HookInfo.OrigProc
- (or feature) Only the one imported function is hooked. If there's another
export definition or dynamic load, the hook won't change them.
2) Patch the very code as usual
+ All function imports are impacted
- No way to use original proc
2nd way is called "strict address mode" here and is controlled by HookProcedure()'s
StrictMode parameter.
Compatibility: RAD Studio 2009+ (uses advanced records and Exit(param)), x32/x64.
==============================================================================*)
unit FuncHook;
interface
uses
Windows;
type
TInjectInstr = packed record
Opcode: UInt8;
Offset: Int32;
end;
PInjectInstr = ^TInjectInstr;
THookInfo = record
private
OrigCode: TInjectInstr; // Original instruction
HookCode: TInjectInstr; // Hook instruction (saved only for check on uninstall)
ProcAddr: Pointer; // Address of a routine to hook
TrampAddr: Pointer; // Address of a trampoline (original routine; DLL exports only)
HookAddr: Pointer; // Address of a hook routine
StrictMode: Boolean; // Hook was installed in strict address mode
public
procedure Init(ProcAddr, HookAddr: Pointer);
property OrigProc: Pointer read TrampAddr;
end;
function HookProcedure(StrictMode: Boolean; var HookInfo: THookInfo): Boolean;
function UnhookProcedure(var HookInfo: THookInfo): Boolean;
implementation
const
INDIRECT_JMP = $25FF;
RELATIVE_JMP = $E9;
EmptyInstr: TInjectInstr = (Opcode: 0; Offset: Int32($DEADBEEF));
function IsEqual(const Instr1, Instr2: TInjectInstr): Boolean;
begin
Result := (Instr1.Opcode = Instr2.Opcode) and (Instr1.Offset = Instr2.Offset);
end;
{ THookInfo }
procedure THookInfo.Init(ProcAddr, HookAddr: Pointer);
begin
ZeroMemory(@Self, SizeOf(Self));
OrigCode := EmptyInstr;
Self.ProcAddr := ProcAddr;
Self.HookAddr := HookAddr;
end;
// Utility function to (un)install hook by patching function code.
// Install: (un)install the hook
// HookInfo: all required data
// Returns: True = success, False = fail
function PatchCode(Install: Boolean; var HookInfo: THookInfo): Boolean;
// Get the real address of a function (for functions exported from DLL)
function GetStrictAddr: Pointer;
type
TAbsIndirectJmp = packed record
OpCode: UInt16; // $FF25 (x32: Jmp, FF /4; x64: Jmp, Rel /4)
Addr : Int32;
end;
PAbsIndirectJmp = ^TAbsIndirectJmp;
var
jmp: PAbsIndirectJmp;
begin
Result := nil;
jmp := PAbsIndirectJmp(HookInfo.ProcAddr);
if jmp.OpCode = INDIRECT_JMP then
{$IFDEF CPUX86}
Result := PPointer(jmp.Addr)^;
{$ENDIF}
{$IFDEF CPUX64}
Result := PPointer(PByte(HookInfo.ProcAddr) + jmp.Addr + SizeOf(TAbsIndirectJmp))^;
{$ENDIF}
end;
var
OldProtect: DWORD;
DestAddr: PInjectInstr;
begin
// Check strict address mode
if HookInfo.StrictMode
then DestAddr := GetStrictAddr
else DestAddr := HookInfo.ProcAddr;
Result := VirtualProtect(DestAddr, SizeOf(TInjectInstr), PAGE_EXECUTE_READWRITE, OldProtect);
if not Result then Exit;
if Install then
begin
// For functions exported from DLL, the only instruction they contain is
// "JMP [fn_addr]" so we can save the address as a trampoline
if not HookInfo.StrictMode
then HookInfo.TrampAddr := GetStrictAddr
else HookInfo.TrampAddr := nil;
HookInfo.OrigCode := DestAddr^;
DestAddr^ := HookInfo.HookCode;
end
else
begin
// Check that patch wasn't overwritten
if IsEqual(HookInfo.HookCode, DestAddr^) then
begin
DestAddr^ := HookInfo.OrigCode;
// Clear OrigCode field thus indicating that hook is not installed
HookInfo.OrigCode := EmptyInstr;
HookInfo.StrictMode := False;
end
else
Result := False;
end;
FlushInstructionCache(GetCurrentProcess, DestAddr, SizeOf(TInjectInstr));
VirtualProtect(DestAddr, SizeOf(TInjectInstr), OldProtect, OldProtect);
end;
// Install the hook
// StrictMode: "strict address mode" flag
// HookInfo: all required data
// Returns: True = success, False = fail
function HookProcedure(StrictMode: Boolean; var HookInfo: THookInfo): Boolean;
begin
// Required data is missing?
if (HookInfo.HookAddr = nil) or (HookInfo.ProcAddr = nil) or
// Hook is installed already?
not IsEqual(HookInfo.OrigCode, EmptyInstr) then
Exit(False);
HookInfo.HookCode.Opcode := RELATIVE_JMP;
HookInfo.HookCode.Offset := PByte(HookInfo.HookAddr) - PByte(HookInfo.ProcAddr) - SizeOf(TInjectInstr);
HookInfo.StrictMode := StrictMode;
Result := PatchCode(True, HookInfo);
end;
// Uninstall the hook
// HookInfo: all required data
// Returns: True = success, False = fail
function UnhookProcedure(var HookInfo: THookInfo): Boolean;
begin
// Required data is missing?
if (HookInfo.HookAddr = nil) or (HookInfo.ProcAddr = nil) or
// Hook is not installed yet?
IsEqual(HookInfo.OrigCode, EmptyInstr) then
Exit(False);
Result := PatchCode(False, HookInfo);
end;
end.