169 lines
5.2 KiB
ObjectPascal
169 lines
5.2 KiB
ObjectPascal
(*==============================================================================
|
|
====== 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.
|