Files
HeidiSQL/source/detours/Source/DDetours.pas
2022-12-31 18:29:54 +01:00

2854 lines
76 KiB
ObjectPascal

// **************************************************************************************************
// Delphi Detours Library.
// Unit DDetours
// https://github.com/MahdiSafsafi/DDetours
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License, v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at
// https://mozilla.org/MPL/2.0/.
// **************************************************************************************************
//
// Contributors:
// - David Millington : Added TDetours<T> class.
// **************************************************************************************************
unit DDetours;
{define FIX_MADEXCEPT if you are using crash on buffer overrun/underrun feature from MadExcept }
{.$DEFINE FIX_MADEXCEPT}
{.$define DEVMODE}
{$IFDEF FPC}
{$MODE DELPHI}
{$HINTS OFF}
{$WARN 4045 OFF}
{$WARN 4055 OFF}
{$WARN 4056 OFF}
{$WARN 4082 OFF}
{$WARN 5024 OFF}
{$WARN 5028 OFF}
{$WARN 5057 OFF}
{$WARN 5058 OFF}
{$ENDIF FPC}
interface
{$I DDetoursDefs.inc}
uses
{$IFDEF RENAMED_NAMESPACE}
System.SysUtils,
System.Classes,
WinApi.Windows,
WinApi.TLHelp32,
{$IFNDEF SUPPORTS_MONITOR}
System.SyncObjs,
{$ENDIF SUPPORTS_MONITOR}
{$ELSE !RENAMED_NAMESPACE}
SysUtils,
Windows,
Classes,
{$IFNDEF SUPPORTS_MONITOR}
SyncObjs,
{$ENDIF SUPPORTS_MONITOR}
{$IFNDEF FPC}
TLHelp32,
{$ENDIF FPC}
{$ENDIF RENAMED_NAMESPACE}
{$IFDEF SUPPORTS_RTTI}
System.Generics.Collections,
System.Typinfo, System.RTTI,
{$ENDIF SUPPORTS_RTTI}
LegacyTypes,
CPUID,
InstDecode;
type
InterceptException = Exception;
TTransactionOption = (toSuspendThread);
TTransactionOptions = set of TTransactionOption;
TInterceptOption = (ioForceLoad, ioRecursive);
TInterceptOptions = set of TInterceptOption;
const
{ Maximum allowed number of hooks. }
MAX_HOOKS = 7;
DefaultInterceptOptions = [];
SErrorInvalidTType = '<T> must be a method';
{ ========================================= DDetours Interface ========================================= }
function InterceptCreate(const TargetProc, InterceptProc: Pointer; const Param: Pointer = nil; const Options: TInterceptOptions = DefaultInterceptOptions)
: Pointer; overload;
function InterceptCreate(const TargetInterface; MethodIndex: Integer; const InterceptProc: Pointer; const Param: Pointer = nil;
const Options: TInterceptOptions = DefaultInterceptOptions): Pointer; overload;
function InterceptCreate(const Module, MethodName: String; const InterceptProc: Pointer; const Param: Pointer = nil;
const Options: TInterceptOptions = DefaultInterceptOptions): Pointer; overload;
procedure InterceptCreate(const TargetProc, InterceptProc: Pointer; var TrampoLine: Pointer; const Param: Pointer = nil;
const Options: TInterceptOptions = DefaultInterceptOptions); overload;
{$IFDEF SUPPORTS_RTTI}
function InterceptCreate(const TargetInterface; const MethodName: String; const InterceptProc: Pointer; const Param: Pointer = nil;
const Options: TInterceptOptions = DefaultInterceptOptions): Pointer; overload;
{$ENDIF SUPPORTS_RTTI}
function InterceptRemove(const TrampoLine: Pointer): Integer; overload;
function GetHookCount(const TargetProc: Pointer): Integer; overload;
function GetHookCount(const TargetInterface; MethodIndex: Integer): Integer; overload;
{$IFDEF SUPPORTS_RTTI}
function GetHookCount(const TargetInterface; const MethodName: String): Integer; overload;
{$ENDIF SUPPORTS_RTTI}
function IsHooked(const TargetProc: Pointer): Boolean; overload;
function IsHooked(const TargetInterface; MethodIndex: Integer): Boolean; overload;
{$IFDEF SUPPORTS_RTTI}
function IsHooked(const TargetInterface; const MethodName: String): Boolean; overload;
{$ENDIF SUPPORTS_RTTI}
function PatchVt(const TargetInterface; MethodIndex: Integer; InterceptProc: Pointer): Pointer;
function UnPatchVt(const TrampoLine: Pointer): Boolean;
function BeginTransaction(Options: TTransactionOptions = [toSuspendThread]): THandle;
function EndTransaction(Handle: THandle): Boolean;
function EnterRecursiveSection(var TrampoLine; MaxRecursionLevel: NativeInt = 0): Boolean;
function ExitRecursiveSection(var TrampoLine): Boolean;
function GetCreatorThreadIdFromTrampoline(var TrampoLine): TThreadId;
function GetTrampolineParam(var TrampoLine): Pointer;
{$IFDEF SUPPORTS_GENERICS}
type
IIntercept<T, U> = interface(IInterface)
['{EECBF3C2-3938-4923-835A-B0A6AD27744D}']
function GetTrampoline(): T;
function GetParam(): U;
function GetCreatorThreadId(): TThreadId;
function GetInterceptOptions(): TInterceptOptions;
function EnterRecursive(MaxRecursionLevel: NativeInt = 0): Boolean;
function ExitRecursive(): Boolean;
property NextHook: T read GetTrampoline;
property TrampoLine: T read GetTrampoline; // alias to NextHook
property Param: U read GetParam;
property CreatorThreadId: TThreadId read GetCreatorThreadId;
property InterceptOptions: TInterceptOptions read GetInterceptOptions;
end;
{
Based on David Millington's original implementation TDetours<T>.
}
TIntercept<T, U> = class(TInterfacedObject, IIntercept<T, U>)
private
FNextHook: T;
FTrampolinePtr: Pointer;
FParam: U;
FCreatorThreadId: TThreadId;
FInterceptOptions: TInterceptOptions;
function TToPointer(const A): Pointer;
function PointerToT(const P): T;
function EnsureTIsMethod(): Boolean;
public
function GetTrampoline(): T;
function GetParam(): U;
function GetCreatorThreadId(): TThreadId;
function GetInterceptOptions(): TInterceptOptions;
function EnterRecursive(MaxRecursionLevel: NativeInt = 0): Boolean;
function ExitRecursive(): Boolean;
constructor Create(const TargetProc, InterceptProc: T; const AParam: U; const AInterceptOptions: TInterceptOptions = DefaultInterceptOptions); virtual;
destructor Destroy(); override;
property Param: U read FParam;
property NextHook: T read FNextHook;
property TrampoLine: T read FNextHook; // alias to NextHook
property CreatorThreadId: TThreadId read FCreatorThreadId;
property InterceptOptions: TInterceptOptions read FInterceptOptions;
end;
TIntercept<T> = class(TIntercept<T, Pointer>)
public
constructor Create(const TargetProc, InterceptProc: T; const AParam: Pointer = nil;
const AInterceptOptions: TInterceptOptions = DefaultInterceptOptions); override;
end;
{$ENDIF SUPPORTS_GENERICS}
type
DetourException = Exception;
implementation
const
{ Nops }
Nop9: array [0 .. 8] of Byte = ($66, $0F, $1F, $84, $00, $00, $00, $00, $00);
Nop8: array [0 .. 7] of Byte = ($0F, $1F, $84, $00, $00, $00, $00, $00);
Nop7: array [0 .. 6] of Byte = ($0F, $1F, $80, $00, $00, $00, $00);
Nop6: array [0 .. 5] of Byte = ($66, $0F, $1F, $44, $00, $00);
Nop5: array [0 .. 4] of Byte = ($0F, $1F, $44, $00, $00);
Nop4: array [0 .. 3] of Byte = ($0F, $1F, $40, $00);
Nop3: array [0 .. 2] of Byte = ($0F, $1F, $00);
Nop2: array [0 .. 1] of Byte = ($66, $90);
Nop1: array [0 .. 0] of Byte = ($90);
MultiNops: array [0 .. 8] of PByte = ( //
@Nop1, { Standard Nop }
@Nop2, { 2 Bytes Nop }
@Nop3, { 3 Bytes Nop }
@Nop4, { 4 Bytes Nop }
@Nop5, { 5 Bytes Nop }
@Nop6, { 6 Bytes Nop }
@Nop7, { 7 Bytes Nop }
@Nop8, { 8 Bytes Nop }
@Nop9 { 9 Bytes Nop }
);
{ Arithmetic operands }
arNone = $00;
arPlus = $08;
arMin = $10;
arAdd = arPlus or $01;
arSub = arMin or $01;
arInc = arPlus or $02;
arDec = arMin or $02;
{ Instructions OpCodes }
opJmpRelz = $E9;
opJmpRelb = $EB;
opJmpMem = $25FF;
opTestb = $85;
opPrfOpSize = $66;
opPrfAddrSize = $67;
opNop = $90;
{ thread constants }
THREAD_SUSPEND_RESUME = $0002;
{ Error messages }
SErrorSmallFunctionSize = 'Size of function is too small, risk to override others adjacent functions.';
SErrorInvalidJmp = 'Invalid JMP Type.';
SErrorInvalidJmp64 = 'Invalid JMP Type for x64.';
SErrorInvalidJmp32 = 'Invalid JMP Type for x32.';
SErrorInvalidDstSave = 'Invalid DstSave Address pointer.';
SErrorUnsupportedMultiNop = 'Multi Bytes Nop Instructions not supported by your CPU.';
SErrorRipDisp = 'Failed to correcr RIP Displacement.';
SErrorBigTrampoSize = 'Exceed maximum TrampoSize.';
SErrorMaxHook = 'Exceed maximum allowed of hooks.';
SErrorInvalidTargetProc = 'Invalid TargetProc Pointer.';
SErrorInvalidInterceptProc = 'Invalid InterceptProc Pointer.';
SErrorInvalidDescriptor = 'Invalid Descriptor.';
SErrorInvalidTrampoline = 'Invalid TrampoLine Pointer.';
SErrorBeginUnHook = 'BeginUnHooks must be called outside BeginHooks/EndHooks.';
SErrorRecursiveSectionUnsupported = 'Trampoline was not marked to use recursive section.';
SErrorTlsOutOfIndexes = 'Tls out of indexes.';
{ JMP Type }
JT_NONE = 0;
JT_REL8 = 1;
JT_REL16 = 2;
JT_REL32 = 3;
JT_MEM16 = 4;
JT_MEM32 = 5;
JT_MEM64 = 6;
JT_RIPZ = 7;
{$IFDEF CPUX64}
JT_MEMN = JT_MEM64;
{$ELSE !CPUX64}
JT_MEMN = JT_MEM32;
{$ENDIF CPUX64}
{ Jmp Type To Size }
JmpTypeToSize: array [0 .. 7] of Byte = ( //
0, { None }
2, { JT_REL8 = $EB + Rel8 }
4, { JT_REL16 = OpSizePrf + $E9 + Rel16 }
5, { JT_REL32 = $E9 + Rel32 }
7, { JT_MEM16 = OpSizePrf + $FF /4 + Disp32 }
6, { JT_MEM32 = $FF /4 + Disp32 }
6, { JT_MEM64 = $FF /4 + Disp32 }
14 { JT_RIPZ = $FF /4 + Disp32 + DQ }
);
SizeToJmpType: array [0 .. 4] of Byte = ( //
{$IFDEF CPUX86}
JT_REL8, { db }
JT_REL16, { dw }
JT_REL32, { dd }
JT_MEM32, { dd }
JT_MEM32 { dd }
{$ELSE !CPUX86}
JT_REL8, { db }
JT_REL32, { dw }
JT_REL32, { dd }
JT_MEM64, { dq }
JT_MEM64 { dq }
{$ENDIF CPUX86}
);
DscrSigSize = $08;
TmpSize = 32;
TrampolineSignature = $544C544C;
type
TArrayOfThreadId = array [0 .. HIGH(SmallInt) - 1] of DWORD;
PArrayOfThreadId = ^TArrayOfThreadId;
TTransactionStruct = record
Options: TTransactionOptions;
TID: DWORD;
PID: DWORD;
ThreadPriority: Integer;
SuspendedThreadCount: Integer;
SuspendedThreads: PArrayOfThreadId;
end;
PTransactionStruct = ^TTransactionStruct;
TOpenThread = function(dwDesiredAccess: DWORD; bInheritHandle: BOOL; dwThreadId: DWORD): THandle; stdcall;
TDscrSig = array [0 .. DscrSigSize - 1] of Byte;
TVirtualProtect = function(lpAddress: Pointer; dwSize: SIZE_T; flNewProtect: DWORD; var OldProtect: DWORD): BOOL; stdcall;
TVirtualAlloc = function(lpvAddress: Pointer; dwSize: SIZE_T; flAllocationType, flProtect: DWORD): Pointer; stdcall;
TVirtualQuery = function(lpAddress: Pointer; var lpBuffer: TMemoryBasicInformation; dwLength: SIZE_T): SIZE_T; stdcall;
TFlushInstructionCache = function(hProcess: THandle; const lpBaseAddress: Pointer; dwSize: SIZE_T): BOOL; stdcall;
TGetCurrentProcess = function: THandle; stdcall;
TVirtualFree = function(lpAddress: Pointer; dwSize: SIZE_T; dwFreeType: DWORD): BOOL; stdcall;
{ TEnumThreadCallBack for EnumProcessThreads }
TEnumThreadCallBack = function(ID: DWORD; Param: Pointer): Boolean;
TInternalFuncs = record
VirtualAlloc: TVirtualAlloc;
VirtualFree: TVirtualFree;
VirtualProtect: TVirtualProtect;
VirtualQuery: TVirtualQuery;
FlushInstructionCache: TFlushInstructionCache;
GetCurrentProcess: TGetCurrentProcess;
end;
TTrampoInfo = record
Addr: PByte; // Pointer to first trampoline instruction .
Size: Byte; // Stolen bytes size .
PData: PByte; // Original Stolen bytes.
end;
PTrampoInfo = ^TTrampoInfo;
TJmpMem = packed record
OpCode: WORD; // $0F$25
Disp32: Integer;
end;
PJmpMem = ^TJmpMem;
TDescriptor = packed record
Sig: TDscrSig; { Table signature. }
DscrAddr: PByte; { Pointer that hold jmp address (if Used)! }
nHook: Byte; { Number of hooks . }
Flags: Byte; { Reserved for future use! }
ExMem: PByte; { Reserved for jmp (if used) & for Trampoline ! }
OrgPtr: PByte; { Original Target Proc address. }
Trampo: PTrampoInfo; { Pointer to TrampoInfo struct. }
{ Array that hold jmp destination address. }
JmpAddrs: array [0 .. MAX_HOOKS] of PByte;
{
Mark the beginning of descriptor code executing .
==> Must be NOP .
}
CodeEntry: Byte;
{ Jmp Instruction for NextHook call and Trampoline call ! }
JmpMems: array [0 .. MAX_HOOKS] of TJmpMem;
end;
PDescriptor = ^TDescriptor;
TNextHook = packed record
ID: Byte; { Hook ID . }
PDscr: PDescriptor;
Signature: Cardinal;
threadid: TThreadId;
Param: Pointer;
TlsRecursionLevelIndex: DWORD;
InterceptOptions: TInterceptOptions;
end;
PNextHook = ^TNextHook;
TTrampoDataVt = record
vAddr: Pointer;
Addr: Pointer;
end;
PTrampoDataVt = ^TTrampoDataVt;
const
TrampoSize = SizeOf(TNextHook) + 64;
{ Descriptor Signature }
{$IFDEF CPUX64}
DscrSig: TDscrSig = ( //
$90, { NOP }
$40, { REX }
$40, { REX }
$40, { REX }
$0F, { ESCAPE TWO BYTE }
$1F, { HINT_NOP }
$F3, { PRF }
$F3 { PRF }
);
{$ELSE !CPUX64}
DscrSig: TDscrSig = ( //
$90, { NOP }
$40, { INC EAX }
$48, { DEC EAX }
$90, { NOP }
$0F, { ESCAPE TWO BYTE }
$1F, { HINT_NOP }
$F3, { PRF }
$F3 { PRF }
);
{$ENDIF CPUX64}
{$IFDEF FPC}
{$I 'TlHelp32.inc'}
{$ENDIF FPC}
var
OpenThread: TOpenThread = nil;
{$IFDEF FPC}
CreateToolhelp32Snapshot: TCreateToolhelp32Snapshot = nil;
Thread32First: TThread32First = nil;
Thread32Next: TThread32Next = nil;
{$ENDIF FPC}
hKernel: THandle;
OpenThreadExist: Boolean = False;
FreeKernel: Boolean = False;
SizeOfAlloc: DWORD = 0; // See initialization !
SysInfo: TSystemInfo;
InternalFuncs: TInternalFuncs;
{$IFDEF SUPPORTS_MONITOR}
FLock: TObject = nil;
{$ELSE !SUPPORTS_MONITOR}
FLock: TCriticalSection = nil;
{$ENDIF SUPPORTS_MONITOR }
{ ================================== Utils ================================== }
function GetUInt64Size(const Value: UInt64): Integer; {$IFDEF SUPPORTS_INLINE}inline; {$ENDIF SUPPORTS_INLINE}
begin
if UInt8(Value) = Value then
Result := 1
else if UInt16(Value) = Value then
Result := 2
else if UInt32(Value) = Value then
Result := 4
else
Result := 8;
end;
function GetInt64Size(const Value: Int64): Integer; {$IFDEF SUPPORTS_INLINE}inline; {$ENDIF SUPPORTS_INLINE}
begin
if Int8(Value) = Value then
Result := 1
else if Int16(Value) = Value then
Result := 2
else if Int32(Value) = Value then
Result := 4
else
Result := 8;
end;
procedure EnterLook(LockedObject: TObject); {$IFDEF SUPPORTS_INLINE}inline; {$ENDIF SUPPORTS_INLINE}
begin
{$IFDEF SUPPORTS_MONITOR}
TMonitor.Enter(LockedObject);
{$ELSE !SUPPORTS_MONITOR}
TCriticalSection(LockedObject).Enter();
{$ENDIF SUPPORTS_MONITOR}
end;
procedure LeaveLook(LockedObject: TObject); {$IFDEF SUPPORTS_INLINE}inline; {$ENDIF SUPPORTS_INLINE}
begin
{$IFDEF SUPPORTS_MONITOR}
TMonitor.Exit(LockedObject);
{$ELSE !SUPPORTS_MONITOR}
TCriticalSection(LockedObject).Leave();
{$ENDIF SUPPORTS_MONITOR}
end;
function EnumProcessThreads(PID: DWORD; CallBack: TEnumThreadCallBack; Param: Pointer): BOOL;
var
hSnap: THandle;
te: TThreadEntry32;
Next: Boolean;
begin
hSnap := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, PID);
Result := hSnap <> INVALID_HANDLE_VALUE;
if Result then
begin
te.dwSize := SizeOf(TThreadEntry32);
Next := Thread32First(hSnap, te);
while Next do
begin
if (te.th32OwnerProcessID = PID) then
begin
try
if not CallBack(te.th32ThreadID, Param) then
break;
except
end;
end;
Next := Thread32Next(hSnap, te);
end;
Result := CloseHandle(hSnap);
end;
end;
function SetMemPermission(const P: Pointer; const Size: SIZE_T; const NewProtect: DWORD): DWORD;
const
PAGE_EXECUTE_FLAGS = PAGE_EXECUTE or PAGE_EXECUTE_READ or PAGE_EXECUTE_READWRITE or PAGE_EXECUTE_WRITECOPY;
begin
Result := 0;
if Assigned(P) and (Size > 0) and (NewProtect > 0) then
begin
if InternalFuncs.VirtualProtect(P, Size, NewProtect, Result) then
if (NewProtect and PAGE_EXECUTE_FLAGS <> 0) then
{
If the protected region will be executed
=> We need to update the cpu cache !
}
InternalFuncs.FlushInstructionCache(InternalFuncs.GetCurrentProcess(), P, Size);
end;
end;
function GetDispDataSize(PInst: PInstruction): Integer;
begin
Result := 0;
if PInst^.Disp.Flags and dfUsed <> 0 then
begin
if PInst^.Archi = CPUX32 then
begin
if PInst^.Prefixes and Prf_OpSize <> 0 then
Result := ops16bits
else
Result := ops32bits;
Exit;
end
else
begin
case PInst^.OperandFlags of
opdD64:
begin
{
Defaults to O64 in PM64.
PrfOpSize results in O16.
}
if PInst^.Prefixes and Prf_OpSize <> 0 then
Result := ops16bits
else
Result := ops64bits;
end;
opdF64, opdDv64:
begin
{ The operand size is forced to a 64-bit operand size in PM64 ! }
Result := (ops64bits);
Exit;
end;
opdDf64:
begin
{
Defaults to O64 in PM64.
PrfOpSize results in O16 in AMD64.
PrfOpSize is ignored in EM64T.
}
if (CPUVendor = vAMD) and (PInst^.Prefixes and Prf_OpSize <> 0) then
Result := (ops16bits)
else
Result := (ops64bits);
Exit;
end;
else
begin
if PInst^.Rex.W then
Result := (ops64bits)
else if (PInst^.Prefixes and Prf_OpSize <> 0) then
Result := (ops16bits)
else
Result := (ops32bits);
Exit;
end;
end;
end;
end;
end;
function fDecodeInst(PInst: PInstruction): Integer;
var
IsNxtInstData: Boolean;
begin
{ Include VEX decoding if the cpu support it! }
if (VEX in CPUEncoding) then
PInst.Options := DecodeVex;
Result := DecodeInst(PInst);
{$IFDEF CPUX64}
IsNxtInstData := ((PInst^.Disp.Flags and (dfUsed or dfRip) = (dfUsed or dfRip)) and (PInst^.Disp.Value = 0));
{$ELSE !CPUX64}
IsNxtInstData := (PInst^.Disp.Value = Int64(PInst^.NextInst));
{$ENDIF CPUX64}
if IsNxtInstData then
begin
{
Check if the Next Instruction is data !
If so , That's mean it's not a valid instruction .
We must skip this data ..
otherwise , disassembling next instructions will fail !
}
Inc(Result, GetDispDataSize(PInst));
PInst^.InstSize := Result;
end;
end;
function RoundMultipleOf(const Value, MultipleOf: NativeInt): NativeInt; {$IFDEF SUPPORTS_INLINE}inline; {$ENDIF SUPPORTS_INLINE}
begin
if Value = 0 then
begin
Result := (MultipleOf);
Exit;
end;
Result := ((Value + (MultipleOf - 1)) and not(MultipleOf - 1));
end;
function AllocMemAt(const Addr: Pointer; const MemSize, flProtect: DWORD): Pointer;
var
mbi: TMemoryBasicInformation;
SysInfo: TSystemInfo;
pBase: PByte;
P: PByte;
Q: PByte;
pMax, pMin: PByte;
dwAllocGran: DWORD;
begin
{ Alloc memory on the specific nearest address from the Addr . }
Result := nil;
P := PByte(Addr);
if not Assigned(P) then
begin
Result := InternalFuncs.VirtualAlloc(nil, MemSize, MEM_RESERVE or MEM_COMMIT, flProtect);
Exit;
end;
GetSystemInfo(SysInfo);
pMin := SysInfo.lpMinimumApplicationAddress;
pMax := SysInfo.lpMaximumApplicationAddress;
dwAllocGran := SysInfo.dwAllocationGranularity;
if (NativeUInt(P) < NativeUInt(pMin)) or (NativeUInt(P) > NativeUInt(pMax)) then
Exit;
if InternalFuncs.VirtualQuery(P, mbi, SizeOf(mbi)) = 0 then
Exit;
pBase := mbi.BaseAddress;
Q := pBase;
while NativeUInt(Q) < NativeUInt(pMax) do
begin
if InternalFuncs.VirtualQuery(Q, mbi, SizeOf(mbi)) = 0 then
Exit;
if (mbi.State = MEM_FREE) and (mbi.RegionSize >= dwAllocGran) and (mbi.RegionSize >= MemSize) then
begin
{ The address (P) must be multiple of the allocation granularity (dwAllocationGranularity) . }
P := PByte(RoundMultipleOf(NativeInt(Q), dwAllocGran));
Result := InternalFuncs.VirtualAlloc(P, MemSize, MEM_RESERVE or MEM_COMMIT, flProtect);
if Assigned(Result) then
Exit;
end;
Inc(Q, mbi.RegionSize); // Next Region .
end;
{
If thre is no memory available in the range [Addr - pMax]
try to allocate at the range [pMin - Addr]
}
Q := pBase;
while NativeUInt(Q) > NativeUInt(pMin) do
begin
if InternalFuncs.VirtualQuery(Q, mbi, SizeOf(mbi)) = 0 then
Exit;
if (mbi.State = MEM_FREE) and (mbi.RegionSize >= dwAllocGran) and (mbi.RegionSize >= MemSize) then
begin
P := PByte(RoundMultipleOf(NativeInt(Q), dwAllocGran));
Result := InternalFuncs.VirtualAlloc(P, MemSize, MEM_RESERVE or MEM_COMMIT, flProtect);
if Assigned(Result) then
Exit;
end;
Dec(Q, mbi.RegionSize); // Previous Region.
end;
end;
function TryAllocMemAt(const Addr: Pointer; const MemSize, flProtect: DWORD): Pointer;
var
MEM_64: DWORD;
begin
MEM_64 := 0;
Result := AllocMemAt(Addr, MemSize, flProtect);
if not Assigned(Result) then
begin
{$IFDEF CPUX64}
{ Allocates memory at the highest possible address }
if (UInt64(Addr) and $FFFFFFFF00000000 <> 0) then
MEM_64 := MEM_TOP_DOWN;
{$ENDIF CPUX64}
Result := InternalFuncs.VirtualAlloc(nil, MemSize, MEM_RESERVE or MEM_COMMIT or MEM_64, flProtect);
end;
end;
function InsertJmp(Src, Dst: PByte; JmpType: Integer; const DstSave: PByte = nil): Integer;
var
Offset: NativeInt;
JmpSize: Integer;
begin
Result := 1;
JmpSize := JmpTypeToSize[JmpType];
Offset := NativeInt(NativeInt(Dst) - NativeInt(Src)) - JmpSize;
case JmpType of
JT_NONE:
begin
raise InterceptException.Create(SErrorInvalidJmp);
end;
JT_REL8:
begin
PByte(Src)^ := opJmpRelb;
Inc(Src);
PInt8(Src)^ := Int8(Offset);
end;
JT_REL16:
begin
{$IFDEF CPUX64}
{
JMP Rel16
==> Not supported on x64!
}
raise InterceptException.Create(SErrorInvalidJmp64);
{$ENDIF CPUX64}
PByte(Src)^ := opPrfOpSize;
Inc(Src);
PByte(Src)^ := opJmpRelz;
Inc(Src);
PInt16(Src)^ := Int16(Offset);
end;
JT_REL32:
begin
PByte(Src)^ := opJmpRelz;
Inc(Src);
PInt32(Src)^ := Offset;
end;
JT_MEM16:
begin
{$IFDEF CPUX64}
{
JMP WORD [012345]
==> Not supported on x64!
}
raise InterceptException.Create(SErrorInvalidJmp64);
{$ENDIF CPUX64}
if not Assigned(DstSave) then
raise InterceptException.Create(SErrorInvalidDstSave);
PByte(Src)^ := opPrfOpSize;
Inc(Src);
PWord(Src)^ := opJmpMem;
Inc(Src, 2);
PUInt32(Src)^ := UInt32(DstSave);
PUInt16(DstSave)^ := UInt16(Dst);
end;
JT_MEM32:
begin
{$IFDEF CPUX64}
{
JMP DWORD [012345]
==> Not supported on x64!
}
raise InterceptException.Create(SErrorInvalidJmp64);
{$ENDIF CPUX64}
if not Assigned(DstSave) then
raise InterceptException.Create(SErrorInvalidDstSave);
PWord(Src)^ := opJmpMem;
Inc(Src, 2);
PUInt32(Src)^ := UInt32(DstSave);
PUInt32(DstSave)^ := UInt32(Dst);
end;
JT_MEM64:
begin
{$IFDEF CPUX86}
{
JMP QWORD [0123456789]
==> Not supported on x32!
}
raise InterceptException.Create(SErrorInvalidJmp32);
{$ENDIF CPUX86}
if not Assigned(DstSave) then
raise InterceptException.Create(SErrorInvalidDstSave);
{ RIP Disp ! }
PUInt64(DstSave)^ := UInt64(Dst);
Offset := NativeInt(NativeInt(DstSave) - NativeInt(Src)) - JmpSize;
PWord(Src)^ := opJmpMem;
Inc(Src, 2);
PInt32(Src)^ := Offset;
end;
JT_RIPZ:
begin
{$IFDEF CPUX86}
raise InterceptException.Create(SErrorInvalidJmp32);
{$ENDIF CPUX86}
{
This is the most harder way to insert a jump !
Why ?
because we are going to mix code & data !
Thats mean when disassembling instructions after
this branch .. you will have a corrupted dissambled
structure !
The only way to detect this kind of jmp is:
to use fDecodeInst rather than DecodeInst routine .
==> We should avoid using this kind of jmp
in the original target proc .
==> It's Ok to use in others situation .
}
PWord(Src)^ := opJmpMem;
Inc(Src, 2);
PInt32(Src)^ := $00;
Inc(Src, 4);
PUInt64(Src)^ := UInt64(Dst);
end;
end;
end;
function GetJmpType(Src, Dst, DstSave: PByte): Integer;
var
Offset: NativeInt;
OffsetSize: Integer;
begin
Offset := NativeInt(NativeInt(Src) - NativeInt(Dst));
OffsetSize := GetInt64Size(Offset);
Result := SizeToJmpType[OffsetSize shr 1];
{$IFDEF CPUX64}
if Result = JT_MEM64 then
begin
if not Assigned(DstSave) then
raise InterceptException.Create(SErrorInvalidDstSave);
Offset := NativeInt(NativeInt(DstSave) - NativeInt(Src)) - 7;
if Integer(Offset) <> Offset then
begin
Result := (JT_RIPZ);
Exit;
end;
end;
{$ENDIF CPUX64}
end;
function IsMultiBytesNop(P: Pointer; Size: Integer): Boolean;
var
i: Integer;
begin
Result := False;
if Size > 0 then
begin
while (Size > 0) do
begin
for i := Length(MultiNops) downto 1 do
begin
if Size >= i then
begin
Result := CompareMem(MultiNops[i - 1], P, i);
if Result then
begin
Inc(PByte(P), i);
Dec(Size, i);
break;
end;
end;
end;
if not Result then
Exit;
end;
Result := True;
end;
end;
procedure FillMultiNop(var Buffer; Size: Integer);
var
i: Integer;
P: PByte;
begin
{ Multi Bytes Nop Instruction is fast to execute compared to
the traditional NOP instruction.
However it's not supported by all CPU !
==> Use FillNop(P,Size,True).
==> CPUID implements a routine to detect
if the CPU supports Multi Bytes Nop .
}
if not(iMultiNop in CPUInsts) then
raise InterceptException.Create(SErrorUnsupportedMultiNop);
P := PByte(@Buffer);
for i := Length(MultiNops) downto 1 do
begin
while Size >= i do
begin
Move(MultiNops[i - 1]^, P^, i);
Dec(Size, i);
Inc(P, i);
end;
if Size = 0 then
Exit;
end;
end;
function IsNop(P: PByte; Size: Integer): Boolean;
var
i: Integer;
begin
{ Return True if the first instructions are nop/multi nop. }
Result := False;
if iMultiNop in CPUInsts then
Result := IsMultiBytesNop(P, Size)
else
for i := 0 to Size - 1 do
begin
Result := (P^ = opNop);
if not Result then
Exit;
Inc(P); // Next Byte.
end;
end;
procedure FillNop(var P; Size: Integer; MultipleNop: Boolean);
begin
if MultipleNop and (iMultiNop in CPUInsts) then
FillMultiNop(P, Size)
else
FillChar(P, Size, opNop);
end;
function GetPrefixesCount(Prefixes: WORD): Byte;
var
Prf: WORD;
i: Byte;
begin
{ Get prefixes count used by the instruction. }
Result := 0;
if Prefixes = 0 then
Exit;
Prf := 0;
i := 0;
Prefixes := Prefixes and not Prf_VEX;
while Prf < $8000 do
begin
Prf := (1 shl i);
if (Prf and Prefixes = Prf) then
Inc(Result);
Inc(i);
end;
end;
function GetInstOpCodes(PInst: PInstruction; P: PByte): ShortInt;
var
nPrfs: Byte;
begin
{
Return opcodes length
Instruction OpCodes in arg P .
}
Result := 0;
FillChar(P^, MAX_INST_LENGTH_N, $90);
nPrfs := GetPrefixesCount(PInst^.Prefixes);
Inc(Result, nPrfs);
case PInst^.OpTable of
tbTwoByte:
if PInst^.Prefixes and Prf_VEX3 = 0 then
Inc(Result); // $0F
tbThreeByte:
begin
if PInst^.Prefixes and Prf_VEX3 = 0 then
Inc(Result, 2); // 0F + 38|3A !
end;
tbFPU:
Inc(Result, 2); // [$D8..$D9] + ModRm !
end;
if PInst^.Prefixes and Prf_Vex2 <> 0 then
Inc(Result); // VEX.P0
if PInst^.Prefixes and Prf_VEX3 <> 0 then
Inc(Result, 2); // VEX.P0 + VEX.P1
if PInst^.OpKind = kGrp then
Inc(Result, 2) // Group + ModRm
else
Inc(Result); // OpCode
if Assigned(P) then
Move(PInst^.Addr^, P^, Result);
end;
function GetJccOpCode(PInst: PInstruction; RelSize: Integer): DWORD;
var
OpCode: Byte;
Opcodes: array [0 .. 3] of Byte;
begin
FillChar(PByte(@Opcodes[0])^, 4, #00);
OpCode := PInst^.OpCode and $F;
case RelSize of
ops8bits:
begin
Opcodes[0] := $70 or OpCode;
end;
ops16bits:
begin
Opcodes[0] := opPrfOpSize;
Opcodes[1] := $0F;
Opcodes[2] := $80 or OpCode;
end;
ops32bits:
begin
Opcodes[0] := $0F;
Opcodes[1] := $80 or OpCode;
end;
end;
Result := PDWORD(@Opcodes[0])^;
end;
function CorrectJ(PInst: PInstruction; NewAddr: PByte): Integer;
const
{ Convert LOOP instruction to relative word jcc ! }
LOOP_To_JccZ: array [0 .. 3] of WORD = ($850F, $840F, $840F, $9090);
{ Convert LOOP instruction to relative byte jcc ! }
LOOP_To_JccB: array [0 .. 3] of Byte = ($75, $74, $75, $90);
var
Offset: Int64;
POpc: PByte;
NOpc: DWORD;
PQ: PByte;
Relsz: Integer;
JmpType: Integer;
JmpSize: Integer;
begin
PQ := NewAddr;
JmpSize := 0;
GetMem(POpc, MAX_INST_LENGTH_N + 1);
try
// Opcsz := GetInstOpCodes(PInst, POpc);
Offset := Int64(Int64(PInst^.Branch.Target) - Int64(PQ) - 6);
Relsz := GetInt64Size(Offset);
{$IFDEF CPUX64}
if Relsz = ops16bits then
Relsz := ops32bits;
{$ENDIF CPUX64}
if PInst^.OpType and otJcc = 0 then
begin
{ Not Jcc ! }
if PInst^.OpCode in [$E0 .. $E2] then
begin
{ LOOPNE/LOOPZ/LOOP }
if Relsz = ops8bits then
begin
if PInst^.Prefixes and Prf_AddrSize <> 0 then
begin
PQ^ := opPrfAddrSize;
Inc(PQ);
end;
PQ^ := PInst^.OpCode;
Inc(PQ);
PQ^ := Int8(Offset);
Inc(PQ);
end
else
case PInst^.AddrMode of
am16:
begin
{ Dec CX ! }
{$IFDEF CPUX64}
{ . $49 result in REX
==> Use $FF group !
}
PQ^ := opPrfOpSize;
Inc(PQ);
PQ^ := $FF;
Inc(PQ);
PQ^ := $C9;
Inc(PQ);
{$ELSE !CPUX64}
PQ^ := opPrfOpSize;
Inc(PQ);
PQ^ := $49;
Inc(PQ);
{$ENDIF CPUX64}
end;
am32:
begin
{ Dec ECX ! }
{$IFDEF CPUX64}
PQ^ := $FF;
Inc(PQ);
PQ^ := $C9;
Inc(PQ);
{$ELSE !CPUX64}
PQ^ := $49;
Inc(PQ);
{$ENDIF CPUX64}
end;
am64:
begin
{ Dec RCX ! }
PQ^ := $48; // REX.W = True !
Inc(PQ);
PQ^ := $FF;
Inc(PQ);
PQ^ := $C9;
Inc(PQ);
end;
end;
case Relsz of
ops16bits:
begin
Offset := Int64(Int64(PInst^.Branch.Target) - Int64(PQ) - 5);
PQ^ := opPrfOpSize;
Inc(PQ);
PWord(PQ)^ := LOOP_To_JccZ[PInst^.OpCode and 3];
Inc(PQ, 2);
PInt16(PQ)^ := Int16(Offset);
Inc(PQ, 2);
end;
ops32bits:
begin
Offset := Int64(Int64(PInst^.Branch.Target) - Int64(PQ) - 6);
PWord(PQ)^ := LOOP_To_JccZ[PInst^.OpCode and 3];
Inc(PQ, 2);
PInt32(PQ)^ := Int32(Offset);
Inc(PQ, 4);
end;
ops64bits:
begin
{
Dec RCX
Jcc @Tmp
Jmp @NextInst
@Tmp:
Jmp @LoopDst
}
{ Insert Jcc ! }
PQ^ := LOOP_To_JccB[PInst^.OpCode and 3];
Inc(PQ);
PQ^ := 2;
Inc(PQ);
{ Insert Jmp NextInst }
PQ^ := opJmpRelb;
Inc(PQ);
PQ^ := 14;
Inc(PQ);
{ Insert Jmp @LoopDst }
InsertJmp(PQ, PInst.Branch.Target, JT_RIPZ);
Inc(PQ, 14);
end;
end;
end
else if PInst^.OpCode = $E3 then
begin
{ JCXZ/JECX/JRCX }
if Relsz = ops8bits then
begin
if PInst^.Prefixes and Prf_AddrSize <> 0 then
begin
PQ^ := opPrfAddrSize;
Inc(PQ);
end;
PQ^ := PInst^.OpCode;
Inc(PQ);
PQ^ := Int8(Offset);
Inc(PQ);
end
else
case PInst^.AddrMode of
am16:
begin
{ TEST CX,CX }
PQ^ := opPrfOpSize;
Inc(PQ);
PQ^ := opTestb;
Inc(PQ);
PQ^ := $C9; // ModRm [Mod = 3; Reg = Rm = CX = 1]
Inc(PQ);
end;
am32:
begin
{ TEST ECX,ECX }
PQ^ := opTestb;
Inc(PQ);
PQ^ := $C9;
Inc(PQ);
end;
am64:
begin
{ TEST RCX,RCX }
PQ^ := $48; // REX.W = True !
Inc(PQ);
PQ^ := opTestb;
Inc(PQ);
PQ^ := $C9;
Inc(PQ);
end;
end;
case Relsz of
ops16bits:
begin
{
TEST CX,CX
JZ @Dst
}
Offset := Int64(Int64(PInst^.Branch.Target) - Int64(PQ) - 5);
PQ^ := opPrfOpSize;
Inc(PQ);
PQ^ := $0F;
Inc(PQ);
PQ^ := $84; // JZ !
Inc(PQ);
PInt16(PQ)^ := Int16(Offset);
Inc(PQ, 2);
end;
ops32bits:
begin
{
TEST ECX,ECX
JZ @Dst
}
Offset := Int64(Int64(PInst^.Branch.Target) - Int64(PQ) - 6);
PQ^ := $0F;
Inc(PQ);
PQ^ := $84; // JZ !
Inc(PQ);
PInt32(PQ)^ := Int32(Offset);
Inc(PQ, 4);
end;
ops64bits:
begin
{
TEST RCX,RCX
JZ @Tmp
Jmp @NextInst
@Tmp:
Jmp @Dst
}
{ Insert JZ ! }
PQ^ := $74;
Inc(PQ);
PQ^ := 2;
Inc(PQ);
{ Insert Jmp NextInst }
PQ^ := opJmpRelb;
Inc(PQ);
PQ^ := 14;
Inc(PQ);
{ Insert Jmp @Dst }
InsertJmp(PQ, PInst.Branch.Target, JT_RIPZ);
Inc(PQ, 14);
end;
end;
end;
end
else
begin
{ Jcc ! }
NOpc := GetJccOpCode(PInst, Relsz);
case Relsz of
ops8bits:
begin
Offset := Int64(Int64(PInst^.Branch.Target) - Int64(PQ) - 2);
PInt8(PQ)^ := UInt8(NOpc);
Inc(PQ);
PInt8(PQ)^ := Int8(Offset);
Inc(PQ);
end;
ops16bits:
begin
Offset := Int64(Int64(PInst^.Branch.Target) - Int64(PQ) - 5);
PUInt32(PQ)^ := UInt32(NOpc);
Inc(PQ, 3);
PInt16(PQ)^ := Int16(Offset);
Inc(PQ, 2);
end;
ops32bits:
begin
Offset := Int64(Int64(PInst^.Branch.Target) - Int64(PQ) - 6);
PUInt16(PQ)^ := UInt16(NOpc);
Inc(PQ, 2);
PInt32(PQ)^ := Int32(Offset);
Inc(PQ, 4);
end;
ops64bits:
begin
{
Unfortunately there is no Jcc Rel 64bits !
===>Original implementation<===
test eax,eax
jz @DstAddr
===>New implementation<===
test eax,eax
Q: jz @tmp
Q+2: jmp @NextInst
Q+4: @tmp:
Q+4: jmp @DstAddr
Q+4+jmpsize: [DstAddr]
@NextInstruction:
}
{ jz @tmp is guaranteed to be 2 Bytes in length ! }
{ Trampo.NextInstruction = Q + 4 + jmp @DstAddr Size }
PQ^ := PInst^.OpCode;
Inc(PQ);
PQ^ := 2;
Inc(PQ);
JmpType := GetJmpType(PByte(NativeInt(NewAddr) + 4), PInst^.Branch.Target, PByte(NativeInt(NewAddr) + 4 + 6));
JmpSize := JmpTypeToSize[JmpSize];
if JmpType > JT_REL32 then
Inc(JmpSize, SizeOf(Pointer));
{ Jmp To Next Valid Instruction ! }
PQ^ := opJmpRelb;
Inc(PQ);
PQ^ := JmpSize;
Inc(PQ);
InsertJmp(PByte(NativeInt(NewAddr) + 4), PInst^.Branch.Target, JmpType, PByte(NativeInt(NewAddr) + 4 + 6));
Inc(PQ, JmpSize);
end;
end;
end;
finally
FreeMem(POpc);
end;
Result := Integer(NativeInt(PQ) - NativeInt(NewAddr));
if Result = 00 then
begin
Move(PInst^.Addr^, NewAddr^, PInst^.InstSize);
Result := PInst^.InstSize;
end;
end;
function MakeModRm(iMod, Reg, Rm: Byte): Byte; {$IFDEF MustInline}inline; {$ENDIF}
begin
Result := (iMod shl 6) or (Reg shl 3) or (Rm);
end;
function CorrectRipDisp(PInst: PInstruction; NewAddr: PByte): Integer;
var
Offset: Int64;
P: PByte;
rReg: Byte;
POpc: PByte;
pMR: PByte;
pFrst: PByte;
L: ShortInt;
begin
pFrst := NewAddr;
P := PInst^.NextInst;
{
If AddressMode is 32-bits :
===> EIP + Disp32 !
else
If AddressMode is 64-bits:
===> RIP + Disp32 !
}
if PInst^.AddrMode = am32 then
P := PByte(UInt64(P) and $FFFFFFFF);
P := PByte(Int64(P) + Int64(PInst^.Disp.Value));
Offset := Int64(Int64(P) - Int64(NewAddr) - PInst^.InstSize);
if Int32(Offset) <> Offset then
begin
rReg := rEAX;
if PInst^.ModRm.Flags and mfUsed <> 0 then
begin
Assert(PInst^.Disp.Flags and dfRip <> 0);
if PInst^.ModRm.Reg = rReg then
rReg := rECX;
{ PUSH UsedReg }
PByte(NewAddr)^ := $50 + (rReg and $7);
Inc(NewAddr);
{$IFDEF CPUX64}
PByte(NewAddr)^ := $48; // REX.W!
Inc(NewAddr);
{$ENDIF CPUX64}
{ MOV REG,Imm(NativeInt) }
PByte(NewAddr)^ := $B8 + (rReg and $7);
Inc(NewAddr);
PNativeInt(NewAddr)^ := NativeInt(P);
Inc(NewAddr, SizeOf(NativeInt));
{ Set the original instruction opcodes }
POpc := GetMemory(MAX_INST_LENGTH_N);
L := GetInstOpCodes(PInst, POpc);
Move(POpc^, NewAddr^, L);
Inc(NewAddr, L);
pMR := NewAddr;
if (PInst^.OpKind and kGrp <> 0) or (PInst^.OpTable = tbFPU) then
Dec(pMR);
PByte(pMR)^ := MakeModRm($00, PInst^.ModRm.Reg, rReg);
Inc(pMR);
NewAddr := pMR;
{ POP UsedReg }
PByte(NewAddr)^ := $58 + (rReg and $7);
Inc(NewAddr);
FreeMemory(POpc);
Result := (NativeInt(NewAddr) - NativeInt(pFrst));
Exit;
end
else
raise InterceptException.Create(SErrorRipDisp);
end;
Move(PInst^.Addr^, NewAddr^, PInst^.InstSize);
Inc(NewAddr, PInst^.InstSize);
PInt32(NativeInt(NewAddr) - SizeOf(Int32))^ := Int32(Offset);
Result := PInst^.InstSize;
end;
function CorrectJmpRel(PInst: PInstruction; NewAddr: PByte): Integer;
var
JmpType: Byte;
begin
JmpType := GetJmpType(NewAddr, PInst^.Branch.Target, PByte(NativeInt(NewAddr) + 6));
InsertJmp(NewAddr, PInst^.Branch.Target, JmpType, PByte(NativeInt(NewAddr) + 6));
Result := JmpTypeToSize[JmpType];
end;
function CorrectCallRel(PInst: PInstruction; NewAddr: PByte): Integer;
var
Offset: Int64;
Relsz: Byte;
P: PByte;
begin
P := NewAddr;
Offset := Int64(Int64(PInst^.Branch.Target) - Int64(P) - 6);
Relsz := GetInt64Size(Offset);
{$IFDEF CPUX64}
{ Only 32-bits relative offset is supported on x64! }
if Relsz < ops32bits then
Relsz := ops32bits;
{$ELSE !CPUX64}
{ Only 16/32-bits relative offset is supported on x32! }
if Relsz < ops16bits then
Relsz := ops32bits;
{$ENDIF CPUX64}
case Relsz of
ops16bits:
begin
Offset := Int64(Int64(PInst^.Branch.Target) - Int64(P) - 4);
P^ := opPrfOpSize;
Inc(P);
P^ := $E8;
Inc(P);
PInt16(P)^ := Int16(Offset);
Inc(P, 2);
end;
ops32bits:
begin
Offset := Int64(Int64(PInst^.Branch.Target) - Int64(P) - 5);
P^ := $E8;
Inc(P);
PInt32(P)^ := Int32(Offset);
Inc(P, 4);
end;
ops64bits:
begin
{
64-bits Relative offset is not supported
==> Map to a new opcode !
}
{
CALL [02]
Jmp @NextValidInstruction
dq : Call dst address !
@NextValidInstruction:
}
P^ := $FF; // Group 5 !
Inc(P);
{
ModRm.Mod = 00
ModRm.Reg = 02
ModRm.Rm = 05
==> ModRm = $15 !
}
P^ := MakeModRm($00, $02, $05);
Inc(P);
P^ := 2;
Inc(P, 4);
{ Jmp Next Instruction ! }
P^ := opJmpRelb;
Inc(P);
P^ := $08;
Inc(P);
PUInt64(P)^ := UInt64(PInst^.Branch.Target);
Inc(P, SizeOf(UInt64));
end;
end;
Result := NativeInt(P) - NativeInt(NewAddr);
if Result = 0 then
begin
Move(PInst^.Addr^, P^, PInst^.InstSize);
Result := PInst^.InstSize;
end;
end;
function MapInsts(Addr, NewAddr: PByte; Size: Integer): Integer;
var
P, Q: PByte;
PInst: PInstruction;
sz, iz, nz: Integer;
begin
{ Map Data from Addr to NewAddr ! }
{ This function will fix Relative offset & RIP displacement . }
Result := 0;
sz := 0;
P := Addr;
Q := NewAddr;
PInst := GetMemory(SizeOf(TInstruction));
FillChar(PInst^, SizeOf(TInstruction), #0);
PInst^.Archi := CPUX;
PInst^.NextInst := P;
PInst^.VirtualAddr := nil;
while sz < Size do
begin
PInst^.Addr := PInst^.NextInst;
iz := fDecodeInst(PInst);
nz := iz;
if PInst^.Disp.Flags and (dfUsed or dfRip) = (dfUsed or dfRip) then
nz := CorrectRipDisp(PInst, Q)
else if (PInst^.Branch.Falgs and bfRel = bfRel) then
begin
{ Instruction use relative offset }
if (PInst^.OpType = otJMP) then
nz := CorrectJmpRel(PInst, Q)
else if (PInst^.OpType = otCALL) then
nz := CorrectCallRel(PInst, Q)
else
nz := CorrectJ(PInst, Q)
end
else
Move(PInst^.Addr^, Q^, nz);
Inc(Q, nz);
Inc(Result, nz);
Inc(sz, iz);
end;
FreeMemory(PInst);
end;
{$IFNDEF FPC}
{$WARN COMPARISON_TRUE OFF}
{$ENDIF FPC}
function GetInstArithmeticType(PInst: PInstruction): Integer;
function IsInstAdd(PInst: PInstruction): Boolean;
begin
Result := False;
if PInst^.OpTable = tbOneByte then
begin
if (PInst^.OpCode >= $00) and (PInst^.OpCode < $06) then
begin
Result := (True);
Exit;
end;
end;
if (PInst^.OpKind = kGrp) and (PInst^.ModRm.Reg = $00) then
begin
if (PInst^.OpCode > $7F) and (PInst^.OpCode < $84) then
begin
Result := (True);
Exit;
end;
end;
end;
function IsInstSub(PInst: PInstruction): Boolean;
begin
Result := False;
if PInst^.OpTable = tbOneByte then
begin
if (PInst^.OpCode > $27) and (PInst^.OpCode < $2E) then
begin
Result := (True);
Exit;
end;
end;
if (PInst^.OpKind = kGrp) and (PInst^.ModRm.Reg = $05) then
begin
if (PInst^.OpCode > $7F) and (PInst^.OpCode < $84) then
begin
Result := (True);
Exit;
end;
end;
end;
function IsInstInc(PInst: PInstruction): Boolean;
begin
Result := False;
if (PInst^.Archi = CPUX32) and (PInst^.OpTable = tbOneByte) then
begin
if (PInst^.OpCode >= $40) and (PInst^.OpCode <= $47) then
begin
Result := (True);
Exit;
end;
end;
if (PInst^.OpKind = kGrp) and (PInst^.ModRm.Reg = $00) then
begin
if (PInst^.OpCode = $FE) or (PInst^.OpCode = $FF) then
begin
Result := (True);
Exit;
end;
end;
end;
function IsInstDec(PInst: PInstruction): Boolean;
begin
Result := False;
if (PInst^.Archi = CPUX32) and (PInst^.OpTable = tbOneByte) then
begin
if (PInst^.OpCode >= $48) and (PInst^.OpCode <= $4F) then
begin
Result := (True);
Exit;
end;
end;
if (PInst^.OpKind = kGrp) and (PInst^.ModRm.Reg = $01) then
begin
if (PInst^.OpCode = $FE) or (PInst^.OpCode = $FF) then
begin
Result := (True);
Exit;
end;
end;
end;
begin
{ Return Instruction Arithmetic (+ or - or ..) }
Result := arNone;
if IsInstAdd(PInst) or IsInstInc(PInst) then
Result := (arAdd)
else if IsInstSub(PInst) or IsInstDec(PInst) then
Result := (arSub);
end;
{$IFNDEF FPC}
{$WARN COMPARISON_TRUE ON}
{$ENDIF FPC}
function EvalArith(Arith: Integer; Value: NativeInt; Offset: NativeInt): NativeInt;
begin
Result := Value;
case Arith of
arAdd:
Inc(Result, Offset);
arInc:
Inc(Result);
arSub:
Dec(Result, Offset);
arDec:
Dec(Result);
end;
end;
{$HINTS OFF}
function InterfaceToObj(const AIntf): TObject;
const
{
Delphi insert QueryInterface,_AddRef,_Release methods
as the last functions in the code entry.
=> We must skip them to point to the first function declared in the interface.
}
Offset = SizeOf(Pointer) * 3;
{$IFDEF CPUX64}
ObjReg = rECX;
{$ELSE !CPUX64}
ObjReg = rEAX;
{$ENDIF CPUX64}
var
Pvt, PCode: PByte;
Inst: TInstruction;
PObj: PByte;
imm: Int64;
Arith: Integer;
Skip: Boolean;
sReg: ShortInt;
begin
if not Assigned(@AIntf) then
begin
Result := nil;
Exit;
end;
sReg := -1;
PObj := PByte(AIntf);
FillChar(Inst, SizeOf(TInstruction), #00);
Inst.Archi := CPUX;
Pvt := PPointer(AIntf)^; // vTable !
PCode := PPointer(NativeInt(Pvt) + Offset)^; // Code Entry !
Inst.NextInst := PCode;
{
At the top of code entry delphi will generate :
int 3
add/sub eax/rcx,offset <===
jmp FirstFunction
}
while True do
begin
Inst.imm.Value := 0;
Inst.Addr := Inst.NextInst;
fDecodeInst(@Inst);
{ Keep looping until JMP/RET ! }
if (Inst.Branch.Falgs and bfUsed <> 0) or (Inst.OpType = otRET) then
break;
Arith := GetInstArithmeticType(@Inst);
Skip := (Arith = arNone);
if not Skip then
begin
{$IFDEF CPUX86}
if Inst.ModRm.iMod <> $03 then
begin
{
====> stdcall ! <====
If the method (declared in interface)
calling convention is stdcall,
Delphi will generate :
add/sub [esp+offset],imm !
}
if Inst.Sib.Flags and sfUsed <> 0 then
sReg := Inst.Sib.Index
else
sReg := Inst.ModRm.Rm;
Skip := not(sReg = rESP);
end
else
{$ENDIF CPUX86}
begin
if (Inst.ModRm.Flags and mfUsed <> 0) then
Skip := not((Inst.ModRm.iMod = $03) and (Inst.ModRm.Rm = ObjReg))
else if Arith in [arInc, arDec] then
{ Is Inc/Dec EAX/RCX ? }
Skip := (Inst.OpCode and $07 <> ObjReg);
end;
end;
if not Skip then
begin
imm := Inst.imm.Value;
PObj := PByte(EvalArith(Arith, NativeInt(PObj), imm));
end;
end;
Result := TObject(PObj);
end;
{$HINTS ON}
function GetInterfaceMethodPtrByIndex(const PInterface; MethodIndex: Integer): PByte;
var
Pvt: PPointer;
P: PPointer;
PDst: PByte;
Inst: TInstruction;
i: Integer;
begin
{
Return original method ptr
=> Return first instruction that was
implemented on Interface object !
}
FillChar(Inst, SizeOf(TInstruction), #00);
Inst.Archi := CPUX;
Pvt := PPointer(PInterface)^; // Virtual Table !
P := Pvt;
Inc(P, MethodIndex);
P := PPointer(P)^;
PDst := PByte(P);
Inst.NextInst := PByte(P);
for i := 0 to 3 do
begin
Inst.Addr := Inst.NextInst;
fDecodeInst(@Inst);
if Assigned(Inst.Branch.Target) then
begin
PDst := Inst.Branch.Target;
break;
end;
end;
Result := PDst;
end;
{$IFDEF SUPPORTS_RTTI}
function GetMethodPtrFromObjByName(Obj: TObject; const MethodName: String): Pointer;
var
LCtx: TRttiContext;
LType: TRttiType;
LMethods: TArray<TRttiMethod>;
LMethod: TRttiMethod;
begin
Result := nil;
if (not Assigned(Obj)) or (MethodName = EmptyStr) then
Exit;
LCtx := TRttiContext.Create;
LType := LCtx.GetType(Obj.ClassType);
LMethods := LType.GetMethods;
for LMethod in LMethods do
begin
if SameText(LMethod.Name, MethodName) then
begin
Result := LMethod.CodeAddress;
Exit;
end;
end;
end;
function GetInterfaceMethodPtrByName(const PInterface; const MethodName: String): PByte;
var
Obj: TObject;
begin
Result := nil;
if (not Assigned(@PInterface)) or (MethodName = EmptyStr) then
Exit;
Obj := InterfaceToObj(PInterface);
if Assigned(Obj) then
begin
Result := GetMethodPtrFromObjByName(Obj, MethodName);
end;
end;
{$ENDIF SUPPORTS_RTTI}
function GetRoot(P: PByte): PByte;
var
Inst: TInstruction;
begin
Result := P;
FillChar(Inst, SizeOf(TInstruction), #00);
Inst.Addr := P;
Inst.Archi := CPUX;
Inst.VirtualAddr := nil;
{
While the opcode is jmp and the jmp destination
address is known get the next jmp .
}
fDecodeInst(@Inst);
if (Inst.OpType = otJMP) and (Assigned(Inst.Branch.Target)) then
Result := GetRoot(Inst.Branch.Target);
end;
function IsValidDescriptor(P: PByte): Boolean;
begin
Result := CompareMem(P, PByte(@DscrSig[0]), SizeOf(DscrSig));
end;
function GetDescriptor(P: PByte): PDescriptor;
var
Inst: TInstruction;
function IsDscrpInst(PInst: PInstruction): Boolean;
begin
Result := Assigned(PInst.Branch.Target) or IsNop(PInst.Addr, 6);
end;
begin
Result := nil;
FillChar(Inst, SizeOf(TInstruction), #00);
Inst.Archi := CPUX;
Inst.VirtualAddr := nil;
{ Find last JMP ! }
P := GetRoot(P);
Inst.Addr := P;
fDecodeInst(@Inst);
{ The first instruction must be NOP ! }
if Inst.OpCode = opNop then
begin
Inst.Addr := Inst.NextInst;
fDecodeInst(@Inst);
if IsDscrpInst(@Inst) then
begin
Inc(P); // Skip CodeEntry !
Inc(P, SizeOf(TJmpMem) * (MAX_HOOKS + 1)); // Skip JmpMems !
{ Go to the Top ! }
Dec(P, SizeOf(TDescriptor));
if IsValidDescriptor(P) then
Result := PDescriptor(P);
end;
end;
end;
function CreateNewDescriptor(): PDescriptor;
begin
{ Create a new descriptor tables ! }
Result := AllocMem(SizeOf(TDescriptor));
FillNop(Result^, SizeOf(TDescriptor), False);
FillNop(Result^.JmpMems[0], SizeOf(TJmpMem) * (MAX_HOOKS + 1), True);
{ A valid descriptor have a valid signature . }
CopyMemory(Result, PByte(@DscrSig[0]), DscrSigSize);
Result^.nHook := 0;
Result^.Flags := 0;
Result^.ExMem := nil;
end;
procedure InsertDescriptor(PAt: PByte; PDscr: PDescriptor);
const
{ JMP from Target to Code Entry }
kJmpCE = 1;
{ JMP from Target to Temporal address than JMP to Code Entry }
kJmpTmpJmpCE = 2;
{ JMP from Target to Temporal address than JMP (Rip Zero) to Code Entry }
kJmpTmpJmpRipZCE = 3;
{ JMP (Rip Zero) from Target to Code Entry }
kJmpRipZCE = 4;
var
fJmpType: Byte; { First JMP }
{$IFDEF CPUX64}
sJmpType: Byte; { Second JMP (if used !) }
Tmp: PByte;
{$ENDIF CPUX64}
JmpKind: Byte;
P, T: PByte;
JmpSize: Byte;
Inst: TInstruction;
Sb: Byte;
OrgAccess: DWORD;
Tsz: Integer;
PExMem: PByte;
LPExMem: PByte;
begin
Sb := 0;
P := PAt;
PDscr^.OrgPtr := P;
fJmpType := GetJmpType(P, @PDscr^.CodeEntry, @PDscr^.DscrAddr);
{$IFDEF CPUX64}
Tmp := nil;
PExMem := TryAllocMemAt(P, SizeOfAlloc, PAGE_EXECUTE_READWRITE);
LPExMem := PExMem;
sJmpType := JT_NONE;
JmpKind := kJmpRipZCE;
{ Try to find the perfect jump instruction ! }
{
That's mean that we try to avoid using tJmpRelN on TargetProc .
==> Because it use more than 6 bytes in length .
}
if JmpTypeToSize[fJmpType] > 6 then
begin
Tmp := PExMem;
Inc(PExMem, TmpSize);
if Assigned(Tmp) then
begin
JmpKind := kJmpRipZCE;
fJmpType := GetJmpType(P, Tmp, Tmp + 6);
if JmpTypeToSize[fJmpType] < 7 then
begin
JmpKind := kJmpTmpJmpRipZCE;
sJmpType := GetJmpType(Tmp, @PDscr^.CodeEntry, Tmp + 6 + 8);
if JmpTypeToSize[sJmpType] < 7 then
JmpKind := kJmpTmpJmpCE;
end;
end;
end
else
begin
JmpKind := kJmpCE;
end;
{$ELSE !CPUX64}
PExMem := TryAllocMemAt(nil, SizeOfAlloc, PAGE_EXECUTE_READWRITE);
JmpKind := kJmpCE;
LPExMem := PExMem;
{$ENDIF CPUX64}
FillChar(Inst, SizeOf(TInstruction), #00);
Inst.Archi := CPUX;
Inst.NextInst := P;
Inst.VirtualAddr := nil;
JmpSize := JmpTypeToSize[fJmpType];
while Sb < JmpSize do
begin
if Inst.OpType = otRET then
raise InterceptException.Create(SErrorSmallFunctionSize);
Inst.Addr := Inst.NextInst;
Inc(Sb, fDecodeInst(@Inst));
end;
if Sb > TrampoSize then
raise InterceptException.Create(SErrorBigTrampoSize);
{ Trampoline momory }
T := PExMem;
FillNop(T^, TrampoSize, False);
PDscr^.Trampo := AllocMem(SizeOf(TTrampoInfo));
PDscr^.Trampo^.PData := AllocMem(Sb + 6);
FillNop(PDscr^.Trampo^.PData^, Sb + 6, False);
{ Save original target routine instruction . }
Move(P^, PDscr^.Trampo^.PData^, Sb);
PDscr^.Trampo^.Addr := T; // Pointer to the first trampoline instruction.
PDscr^.Trampo^.Size := Sb; // Size of stolen instructions .
Tsz := MapInsts(P, T, Sb);
OrgAccess := SetMemPermission(P, Sb, PAGE_EXECUTE_READWRITE);
try
FillNop(P^, Sb, False);
case JmpKind of
kJmpCE:
begin
{ A very good jump ! }
{
TargetProc :
JMP @PDscr^.CodeEntry
}
InsertJmp(P, @PDscr^.CodeEntry, fJmpType, @PDscr^.DscrAddr);
end;
{$IFDEF CPUX64}
kJmpTmpJmpCE:
begin
{
TargetProc :
JMP @Tmp ==> Tmp is allocated nearly from TargetProc !
Tmp:
JMP @PDscr^.CodeEntry
}
InsertJmp(P, Tmp, fJmpType, Tmp + 6);
InsertJmp(Tmp, @PDscr^.CodeEntry, sJmpType, Tmp + 6 + 8);
end;
kJmpTmpJmpRipZCE:
begin
{
TargetProc :
JMP @Tmp ==> Tmp is allocated nearly from TargetProc !
Tmp:
JMP @PDscr^.CodeEntry ==> JT_RIPZ
}
InsertJmp(P, Tmp, fJmpType, Tmp + 6);
InsertJmp(Tmp, @PDscr^.CodeEntry, JT_RIPZ, nil);
end;
kJmpRipZCE:
begin
{
Not a good jump !
TargetProc :
JMP @PDscr^.CodeEntry ==> JT_RIPZ
}
InsertJmp(P, @PDscr^.CodeEntry, JT_RIPZ, nil);
end;
{$ENDIF CPUX64}
end;
{
Insert a JMP instruction after the stolen instructions
on the trampoline.
==> This JMP will return to TargetProc to allow
executing originals instructions.
}
{$IFDEF CPUX64}
InsertJmp(T + Tsz, P + Sb, JT_RIPZ);
{$ELSE !CPUX64}
InsertJmp(PByte(NativeInt(T) + Tsz), PByte(NativeInt(P) + Sb), JT_MEM32, PByte(NativeInt(T) + Tsz + 6));
{$ENDIF CPUX64}
{ Save LPExMem ==> we need it when deleting descriptor }
PDscr^.ExMem := LPExMem;
SetMemPermission(LPExMem, SizeOfAlloc, PAGE_EXECUTE_READWRITE);
SetMemPermission(PDscr, SizeOf(TDescriptor), PAGE_EXECUTE_READWRITE);
finally
SetMemPermission(P, Sb, OrgAccess);
end;
end;
procedure MadExceptFreeMem(P: Pointer);
var
Page: Pointer;
mbi: TMemoryBasicInformation;
Permission: DWORD;
begin
if InternalFuncs.VirtualQuery(P, mbi, SizeOf(mbi)) <> 0 then
begin
Page := mbi.BaseAddress;
Permission := SetMemPermission(Page, SysInfo.dwPageSize, PAGE_READWRITE);
FreeMem(P);
SetMemPermission(Page, SysInfo.dwPageSize, Permission);
end
else
FreeMem(P);
end;
function GetNextHookPtrFromTrampoline(TrampoLine: Pointer): PNextHook;
begin
if Assigned(TrampoLine) then
begin
Result := PNextHook(NativeInt(TrampoLine) - SizeOf(TNextHook));
if Result^.Signature = TrampolineSignature then
Exit;
end;
raise DetourException.Create(SErrorInvalidTrampoline);
end;
function AddHook(PDscr: PDescriptor; InterceptProc: PByte; Param: Pointer; Options: TInterceptOptions): PByte;
var
n: ShortInt;
NxHook: PByte;
LTlsRecursionLevelIndex: DWORD;
begin
{
Return a pointer to a function that can
call next installed Hooks.
}
n := PDscr^.nHook;
if n + 1 > MAX_HOOKS then
raise InterceptException.Create(SErrorMaxHook);
{ Alloc memory for the NextHook ! }
NxHook := AllocMem(TrampoSize);
Result := NxHook;
FillNop(Result^, TrampoSize, False);
PNextHook(Result)^.PDscr := PDscr;
PNextHook(Result)^.ID := n + 1;
PNextHook(Result)^.threadid := GetCurrentThreadId();
PNextHook(Result)^.Param := Param;
PNextHook(Result)^.Signature := TrampolineSignature;
PNextHook(Result)^.InterceptOptions := Options;
if ioRecursive in Options then
begin
LTlsRecursionLevelIndex := TlsAlloc();
if LTlsRecursionLevelIndex <> TLS_OUT_OF_INDEXES then
PNextHook(Result)^.TlsRecursionLevelIndex := LTlsRecursionLevelIndex
else
raise DetourException.Create(SErrorTlsOutOfIndexes);
end;
Inc(Result, SizeOf(TNextHook));
{ Redirect code to InterceptProc ! }
InsertJmp(@PDscr^.JmpMems[n], InterceptProc, JT_MEMN, @PDscr^.JmpAddrs[n]);
{ Redirect code to TrampoLine ! }
InsertJmp(@PDscr^.JmpMems[n + 1], PDscr^.Trampo^.Addr, JT_MEMN, @PDscr^.JmpAddrs[n + 1]);
{ Redirect code to next hook ! }
InsertJmp(Result, @PDscr^.JmpMems[n + 1], JT_MEMN, PByte(NativeInt(Result) + 6));
Inc(PDscr^.nHook);
SetMemPermission(Result, JmpTypeToSize[JT_RIPZ], PAGE_EXECUTE_READWRITE);
end;
function InstallHook(TargetProc, InterceptProc: PByte; Param: Pointer; Options: TInterceptOptions): PByte;
var
P: PByte;
PDscr: PDescriptor;
begin
if not Assigned(TargetProc) then
raise InterceptException.Create(SErrorInvalidTargetProc);
if not Assigned(InterceptProc) then
raise InterceptException.Create(SErrorInvalidInterceptProc);
PDscr := GetDescriptor(TargetProc);
if not Assigned(PDscr) then
begin
P := GetRoot(TargetProc);
PDscr := CreateNewDescriptor();
try
InsertDescriptor(P, PDscr);
except
FreeMem(PDscr);
raise;
end;
end;
Result := AddHook(PDscr, InterceptProc, Param, Options);
end;
procedure RemoveDescriptor(PDscr: PDescriptor);
var
OrgAccess: DWORD;
P: PByte;
sz: Integer;
vr: Boolean;
begin
P := PDscr^.OrgPtr;
sz := PDscr^.Trampo^.Size;
OrgAccess := SetMemPermission(P, sz, PAGE_EXECUTE_READWRITE);
try
SetMemPermission(PDscr^.ExMem, TrampoSize, PAGE_EXECUTE_READWRITE);
{ Restore the old stolen instructions ! }
Move(PDscr^.Trampo^.PData^, PDscr^.OrgPtr^, PDscr^.Trampo^.Size);
FillNop(PDscr^.ExMem^, SizeOfAlloc, False);
FreeMem(PDscr^.Trampo^.PData);
FreeMem(PDscr^.Trampo);
if Assigned(PDscr^.ExMem) then
begin
vr := InternalFuncs.VirtualFree(PDscr^.ExMem, 0, MEM_RELEASE);
if not vr then
RaiseLastOSError;
end;
FillNop(PDscr^, SizeOf(TDescriptor), False);
{$IFDEF FIX_MADEXCEPT}
MadExceptFreeMem(PDscr);
{$ELSE !FIX_MADEXCEPT}
FreeMem(PDscr);
{$ENDIF FIX_MADEXCEPT}
finally
SetMemPermission(P, sz, OrgAccess);
end;
end;
function RemoveHook(TrampoLine: PByte): Integer;
var
PNxtHook: PNextHook;
PDscr: PDescriptor;
n: Byte;
begin
if not Assigned(TrampoLine) then
raise InterceptException.Create(SErrorInvalidTrampoline);
PNxtHook := GetNextHookPtrFromTrampoline(TrampoLine);
if not Assigned(PNxtHook) then
raise InterceptException.Create(SErrorInvalidTrampoline);
PDscr := PNxtHook^.PDscr;
if not IsValidDescriptor(PByte(PDscr)) then
raise InterceptException.Create(SErrorInvalidDescriptor);
n := PNxtHook^.ID;
Dec(PDscr^.nHook);
PDscr^.JmpAddrs[n - 1] := nil;
{ Remove JMP from descriptor table }
FillNop(PByte(@PDscr^.JmpMems[n - 1])^, SizeOf(TJmpMem), True);
{
Return the number of hooks
that are still alive !
}
Result := PDscr^.nHook;
if Result = 0 then
RemoveDescriptor(PDscr);
if ioRecursive in PNxtHook^.InterceptOptions then
TlsFree(PNxtHook^.TlsRecursionLevelIndex);
{$IFDEF FIX_MADEXCEPT}
MadExceptFreeMem(PNxtHook);
{$ELSE !FIX_MADEXCEPT}
FreeMem(PNxtHook);
{$ENDIF FIX_MADEXCEPT}
end;
{ ======================================= InterceptCreate ======================================= }
function InterceptCreate(const TargetProc, InterceptProc: Pointer; const Param: Pointer = nil;
const Options: TInterceptOptions = DefaultInterceptOptions): Pointer;
begin
Result := InstallHook(TargetProc, InterceptProc, Param, Options);
end;
function InterceptCreate(const TargetInterface; MethodIndex: Integer; const InterceptProc: Pointer; const Param: Pointer = nil;
const Options: TInterceptOptions = DefaultInterceptOptions): Pointer;
var
P: PByte;
begin
Result := nil;
if not Assigned(@TargetInterface) then
Exit;
P := GetInterfaceMethodPtrByIndex(TargetInterface, MethodIndex);
if Assigned(P) then
begin
Result := InterceptCreate(P, InterceptProc, Param, Options);
end;
end;
function InterceptCreate(const Module, MethodName: string; const InterceptProc: Pointer; const Param: Pointer = nil;
const Options: TInterceptOptions = DefaultInterceptOptions): Pointer;
var
pOrgPointer: Pointer;
LModule: THandle;
begin
{ RRUZ's idea ==> Looks great ! }
Result := nil;
LModule := GetModuleHandle(PChar(Module));
if (LModule = 0) and (ioForceLoad in Options) then
LModule := LoadLibrary(PChar(Module));
if LModule <> 0 then
begin
pOrgPointer := GetProcAddress(LModule, PChar(MethodName));
if Assigned(pOrgPointer) then
Result := InterceptCreate(pOrgPointer, InterceptProc, Param, Options);
end;
end;
procedure InterceptCreate(const TargetProc, InterceptProc: Pointer; var TrampoLine: Pointer; const Param: Pointer = nil;
const Options: TInterceptOptions = DefaultInterceptOptions);
begin
TrampoLine := InstallHook(TargetProc, InterceptProc, Param, Options);
end;
{$IFDEF SUPPORTS_RTTI}
function InterceptCreate(const TargetInterface; const MethodName: String; const InterceptProc: Pointer; const Param: Pointer = nil;
const Options: TInterceptOptions = DefaultInterceptOptions): Pointer; overload;
var
P: PByte;
begin
{ Interface support }
Result := nil;
if (not Assigned(@TargetInterface)) or (MethodName = EmptyStr) then
Exit;
P := GetInterfaceMethodPtrByName(TargetInterface, MethodName);
if Assigned(P) then
Result := InterceptCreate(P, InterceptProc);
end;
{$ENDIF SUPPORTS_RTTI}
{ ======================================= InterceptRemove ======================================= }
function InterceptRemove(const TrampoLine: Pointer): Integer;
begin
if Assigned(TrampoLine) then
Result := RemoveHook(TrampoLine)
else
Result := -1;
end;
{ ======================================= GetHookCount ======================================= }
function GetHookCount(const TargetProc: Pointer): Integer;
var
PDscr: PDescriptor;
begin
{ Return the number of installed hooks. }
if Assigned(TargetProc) then
begin
PDscr := GetDescriptor(TargetProc);
if Assigned(PDscr) then
begin
Result := PDscr^.nHook;
Exit;
end;
end
else
raise InterceptException.Create(SErrorInvalidTargetProc);
Result := 0;
end;
function GetHookCount(const TargetInterface; MethodIndex: Integer): Integer; overload;
var
P: PByte;
begin
P := GetInterfaceMethodPtrByIndex(TargetInterface, MethodIndex);
Result := GetHookCount(P);
end;
{$IFDEF SUPPORTS_RTTI}
function GetHookCount(const TargetInterface; const MethodName: String): Integer; overload;
var
P: PByte;
begin
{ Interface support }
P := GetInterfaceMethodPtrByName(TargetInterface, MethodName);
Result := GetHookCount(P);
end;
{$ENDIF SUPPORTS_RTTI}
{ ======================================= IsHooked ======================================= }
function IsHooked(const TargetProc: Pointer): Boolean;
begin
Result := GetHookCount(TargetProc) > 0;
end;
function IsHooked(const TargetInterface; MethodIndex: Integer): Boolean; overload;
var
P: PByte;
begin
P := GetInterfaceMethodPtrByIndex(TargetInterface, MethodIndex);
Result := IsHooked(P);
end;
{$IFDEF SUPPORTS_RTTI}
function IsHooked(const TargetInterface; const MethodName: String): Boolean; overload;
var
P: PByte;
begin
{ Interface support }
P := GetInterfaceMethodPtrByName(TargetInterface, MethodName);
Result := IsHooked(P);
end;
{$ENDIF SUPPORTS_RTTI}
{ ======================================= Patch ======================================= }
function PatchVt(const TargetInterface; MethodIndex: Integer; InterceptProc: Pointer): Pointer;
var
vt: PPointer;
P, DstAddr: PPointer;
Q: PByte;
OrgAccess: DWORD;
PInfo: PTrampoDataVt;
begin
{
NB: PatchVt does not support multi hook !!
PatchVt will patch only vtable !!
}
Result := nil;
if not Assigned(@TargetInterface) then
Exit;
if not Assigned(InterceptProc) then
Exit;
try
vt := PPointer(TargetInterface)^;
P := vt;
Inc(P, MethodIndex);
DstAddr := P^; // address !
OrgAccess := SetMemPermission(P, 32, PAGE_EXECUTE_READWRITE);
try
P^ := InterceptProc;
finally
SetMemPermission(P, 32, OrgAccess);
end;
Result := InternalFuncs.VirtualAlloc(nil, 32, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE);
SetMemPermission(Result, 32, PAGE_EXECUTE_READWRITE);
PInfo := Result;
PInfo^.vAddr := P;
PInfo^.Addr := DstAddr;
Inc(PByte(Result), SizeOf(TTrampoDataVt));
Q := Result;
{$IFDEF CPUX64}
{ Use JMP RipZero ! }
PWord(Q)^ := opJmpMem;
Inc(Q, 2);
PInt32(Q)^ := $00;
Inc(Q, 4);
PNativeInt(Q)^ := NativeInt(DstAddr);
{$ELSE !CPUX64}
PWord(Q)^ := opJmpMem;
Inc(Q, 2);
PUInt32(Q)^ := UInt32(NativeInt(Q) + 4);
PUInt32(NativeInt(Q) + 4)^ := UInt32(DstAddr);
{$ENDIF CPUX64}
finally
end;
end;
function UnPatchVt(const TrampoLine: Pointer): Boolean;
var
OrgAccess: DWORD;
PInfo: PTrampoDataVt;
begin
if not Assigned(TrampoLine) then
begin
Result := False;
Exit;
end;
try
PInfo := PTrampoDataVt(NativeInt(TrampoLine) - SizeOf(TTrampoDataVt));
OrgAccess := SetMemPermission(PInfo^.vAddr, 32, PAGE_EXECUTE_READWRITE);
try
PPointer(PInfo^.vAddr)^ := PInfo^.Addr;
finally
SetMemPermission(PInfo^.vAddr, 32, OrgAccess);
end;
Result := InternalFuncs.VirtualFree(TrampoLine, 0, MEM_RELEASE);
finally
end;
end;
{ ======================================= Trampoline misc =================================== }
function GetCreatorThreadIdFromTrampoline(var TrampoLine): TThreadId;
var
PNxtHook: PNextHook;
begin
PNxtHook := GetNextHookPtrFromTrampoline(PPointer(@TrampoLine)^);
Result := PNxtHook^.threadid;
end;
function GetTrampolineParam(var TrampoLine): Pointer;
var
PNxtHook: PNextHook;
begin
PNxtHook := GetNextHookPtrFromTrampoline(PPointer(@TrampoLine)^);
Result := PNxtHook^.Param;
end;
{ ======================================= Recursive Section ======================================= }
function EnterRecursiveSection(var TrampoLine; MaxRecursionLevel: NativeInt = 0): Boolean;
var
PNxtHook: PNextHook;
RecursionLevel: NativeInt;
begin
PNxtHook := GetNextHookPtrFromTrampoline(PPointer(@TrampoLine)^);
if ioRecursive in PNxtHook^.InterceptOptions then
begin
RecursionLevel := NativeInt(TlsGetValue(PNxtHook^.TlsRecursionLevelIndex));
Result := RecursionLevel <= MaxRecursionLevel;
if Result then
begin
Inc(RecursionLevel);
TlsSetValue(PNxtHook^.TlsRecursionLevelIndex, Pointer(RecursionLevel));
end;
end
else
raise DetourException.Create(SErrorRecursiveSectionUnsupported);
end;
function ExitRecursiveSection(var TrampoLine): Boolean;
var
PNxtHook: PNextHook;
RecursionLevel: NativeInt;
begin
PNxtHook := GetNextHookPtrFromTrampoline(PPointer(@TrampoLine)^);
if ioRecursive in PNxtHook^.InterceptOptions then
begin
RecursionLevel := NativeInt(TlsGetValue(PNxtHook^.TlsRecursionLevelIndex));
Result := RecursionLevel >= 0;
if Result then
begin
Dec(RecursionLevel);
TlsSetValue(PNxtHook^.TlsRecursionLevelIndex, Pointer(RecursionLevel));
end;
end
else
raise DetourException.Create(SErrorRecursiveSectionUnsupported);
end;
{ ======================================= Transaction ======================================= }
function CountThreadCallBack(ID: DWORD; Param: Pointer): BOOL;
begin
Assert(Assigned(Param));
Inc(PInteger(Param)^);
Result := True;
end;
function SuspendOrResumeThread(threadid: DWORD; Suspend: Boolean): DWORD;
var
hThread: THandle;
begin
hThread := OpenThread(THREAD_SUSPEND_RESUME, False, threadid);
if hThread <> THandle(0) then
begin
if Suspend then
Result := SuspendThread(hThread)
else
Result := ResumeThread(hThread);
CloseHandle(hThread);
end
else
Result := DWORD(-1);
end;
function SuspendThreadCallBack(ID: DWORD; Param: Pointer): BOOL;
var
PStruct: PTransactionStruct;
SuspendCount: DWORD;
begin
Assert(Assigned(Param));
PStruct := PTransactionStruct(Param);
if ID <> PStruct^.TID then
begin
SuspendCount := SuspendOrResumeThread(ID, True);
if SuspendCount <> DWORD(-1) then
// thread's previously was running .
begin
{ Only add threads that was running before suspending them ! }
PStruct^.SuspendedThreads^[PStruct^.SuspendedThreadCount] := ID;
Inc(PStruct^.SuspendedThreadCount);
end;
end;
Result := True;
end;
function BeginTransaction(Options: TTransactionOptions = [toSuspendThread]): THandle;
var
PStruct: PTransactionStruct;
ThreadCount: Integer;
P: Pointer;
ThreadHandle: THandle;
begin
EnterLook(FLock);
try
ThreadHandle := GetCurrentThread();
PStruct := GetMemory(SizeOf(TTransactionStruct));
FillChar(PStruct^, SizeOf(TTransactionStruct), #00);
PStruct^.Options := Options;
PStruct^.PID := GetCurrentProcessId();
PStruct^.TID := GetCurrentThreadId();
PStruct^.ThreadPriority := GetThreadPriority(ThreadHandle);
SetThreadPriority(ThreadHandle, THREAD_PRIORITY_TIME_CRITICAL);
Result := THandle(PStruct);
if toSuspendThread in Options then
begin
ThreadCount := 0;
EnumProcessThreads(PStruct^.PID, @CountThreadCallBack, @ThreadCount);
if ThreadCount > 1 then
begin
P := GetMemory(ThreadCount * 2 * SizeOf(DWORD));
PStruct^.SuspendedThreads := P;
EnumProcessThreads(PStruct^.PID, @SuspendThreadCallBack, PStruct);
end;
end;
finally
LeaveLook(FLock);
end;
end;
function EndTransaction(Handle: THandle): Boolean;
var
PStruct: PTransactionStruct;
i: Integer;
begin
EnterLook(FLock);
Result := True;
PStruct := PTransactionStruct(Handle);
try
if PStruct^.SuspendedThreadCount > 0 then
begin
for i := 0 to PStruct^.SuspendedThreadCount - 1 do
begin
SuspendOrResumeThread(PStruct^.SuspendedThreads^[i], False);
end;
FreeMemory(PStruct^.SuspendedThreads);
end;
SetThreadPriority(GetCurrentThread(), PStruct^.ThreadPriority);
FreeMemory(PTransactionStruct(Handle));
finally
LeaveLook(FLock);
end;
end;
{$IFDEF SUPPORTS_GENERICS}
{ TIntercept<T,U> }
function TIntercept<T, U>.TToPointer(const A): Pointer;
begin
Result := Pointer(A);
end;
function TIntercept<T, U>.PointerToT(const P): T;
begin
Result := T(P);
end;
function TIntercept<T, U>.EnsureTIsMethod(): Boolean;
var
LPInfo: PTypeInfo;
begin
Result := SizeOf(T) = SizeOf(Pointer);
if Result then
begin
LPInfo := TypeInfo(T);
if LPInfo.Kind = tkProcedure then
Exit
else
raise DetourException.Create(SErrorInvalidTType);
end;
end;
constructor TIntercept<T, U>.Create(const TargetProc, InterceptProc: T; const AParam: U; const AInterceptOptions: TInterceptOptions = DefaultInterceptOptions);
begin
EnsureTIsMethod();
FCreatorThreadId := GetCurrentThreadId();
FInterceptOptions := AInterceptOptions;
FParam := AParam;
FTrampolinePtr := InterceptCreate(TToPointer(TargetProc), TToPointer(InterceptProc), @FParam, AInterceptOptions);
FNextHook := PointerToT(FTrampolinePtr);
end;
function TIntercept<T, U>.GetTrampoline(): T;
begin
Result := FNextHook;
end;
function TIntercept<T, U>.GetParam(): U;
begin
Result := FParam;
end;
function TIntercept<T, U>.GetCreatorThreadId(): TThreadId;
begin
Result := FCreatorThreadId;
end;
function TIntercept<T, U>.GetInterceptOptions(): TInterceptOptions;
begin
Result := FInterceptOptions;
end;
function TIntercept<T, U>.EnterRecursive(MaxRecursionLevel: NativeInt = 0): Boolean;
begin
Result := EnterRecursiveSection(FTrampolinePtr, MaxRecursionLevel);
end;
function TIntercept<T, U>.ExitRecursive(): Boolean;
begin
Result := ExitRecursiveSection(FTrampolinePtr);
end;
destructor TIntercept<T, U>.Destroy();
begin
InterceptRemove(TToPointer(FNextHook));
inherited;
end;
{ TIntercept<T> }
constructor TIntercept<T>.Create(const TargetProc, InterceptProc: T; const AParam: Pointer = nil;
const AInterceptOptions: TInterceptOptions = DefaultInterceptOptions);
begin
inherited Create(TargetProc, InterceptProc, AParam, InterceptOptions);
end;
{$ENDIF SUPPORTS_GENERICS}
{ ======================================= Initialization ======================================= }
procedure InitInternalFuncs();
function CloneFunc(Func: PByte): PByte;
var
mb, ns, Sb, fn: Byte;
P: PByte;
Inst: TInstruction;
begin
Sb := 0;
Func := GetRoot(Func);
Result := VirtualAlloc(nil, 64, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE);
P := Result;
mb := JmpTypeToSize[JT_RIPZ];
FillChar(Inst, SizeOf(TInstruction), #00);
Inst.Archi := CPUX;
Inst.NextInst := Func;
while Sb <= mb do
begin
Inst.Addr := Inst.NextInst;
ns := fDecodeInst(@Inst);
Inc(Sb, ns);
end;
fn := MapInsts(Func, P, Sb);
Inc(P, fn);
{$IFDEF CPUX64}
InsertJmp(P, PByte(NativeInt(Func) + Sb), JT_RIPZ);
{$ELSE !CPUX64}
InsertJmp(P, PByte(NativeInt(Func) + Sb), JT_REL32);
{$ENDIF CPUX64}
end;
begin
{$IFDEF HOOK_INTERNAL_FUNCTIONS}
@InternalFuncs.VirtualAlloc := CloneFunc(@VirtualAlloc);
@InternalFuncs.VirtualFree := CloneFunc(@VirtualFree);
@InternalFuncs.VirtualProtect := CloneFunc(@VirtualProtect);
@InternalFuncs.VirtualQuery := CloneFunc(@VirtualQuery);
@InternalFuncs.FlushInstructionCache := CloneFunc(@FlushInstructionCache);
@InternalFuncs.GetCurrentProcess := CloneFunc(@GetCurrentProcess);
{$ELSE !HOOK_INTERNAL_FUNCTIONS}
@InternalFuncs.VirtualAlloc := @VirtualAlloc;
@InternalFuncs.VirtualFree := @VirtualFree;
@InternalFuncs.VirtualProtect := @VirtualProtect;
@InternalFuncs.VirtualQuery := @VirtualQuery;
@InternalFuncs.FlushInstructionCache := @FlushInstructionCache;
@InternalFuncs.GetCurrentProcess := @GetCurrentProcess;
{$ENDIF HOOK_INTERNAL_FUNCTIONS}
end;
procedure FreeInternalFuncs;
begin
{$IFDEF HOOK_INTERNAL_FUNCTIONS}
InternalFuncs.VirtualFree(@InternalFuncs.VirtualAlloc, 0, MEM_RELEASE);
InternalFuncs.VirtualFree(@InternalFuncs.VirtualProtect, 0, MEM_RELEASE);
InternalFuncs.VirtualFree(@InternalFuncs.VirtualQuery, 0, MEM_RELEASE);
InternalFuncs.VirtualFree(@InternalFuncs.FlushInstructionCache, 0, MEM_RELEASE);
InternalFuncs.VirtualFree(@InternalFuncs.GetCurrentProcess, 0, MEM_RELEASE);
// VirtualFree must be the last one !
InternalFuncs.VirtualFree(@InternalFuncs.VirtualFree, 0, MEM_RELEASE);
{$ENDIF HOOK_INTERNAL_FUNCTIONS}
end;
initialization
{$IFDEF SUPPORTS_MONITOR}
FLock := TObject.Create();
{$ELSE SUPPORTS_MONITOR}
FLock := TCriticalSection.Create();
{$ENDIF SUPPORTS_MONITOR}
GetSystemInfo(SysInfo);
SizeOfAlloc := SysInfo.dwPageSize;
if SizeOfAlloc < (TmpSize + TrampoSize + 64) then
SizeOfAlloc := (TmpSize + TrampoSize + 64);
{$IFDEF FPC}
OpenThread := nil;
{$ELSE !FPC}
@OpenThread := nil;
{$ENDIF !FPC}
FreeKernel := False;
hKernel := GetModuleHandle(kernel32);
if hKernel <= 0 then
begin
hKernel := LoadLibrary(kernel32);
FreeKernel := (hKernel > 0);
end;
if hKernel > 0 then
begin
{$IFDEF FPC}
@OpenThread := GetProcAddress(hKernel, 'OpenThread');
@CreateToolhelp32Snapshot := GetProcAddress(hKernel, 'CreateToolhelp32Snapshot');
@Thread32First := GetProcAddress(hKernel, 'Thread32First');
@Thread32Next := GetProcAddress(hKernel, 'Thread32Next');
{$ELSE !FPC}
@OpenThread := GetProcAddress(hKernel, 'OpenThread');
{$ENDIF !FPC}
end;
{ The OpenThread function does not exist on OS version < Win XP }
OpenThreadExist := (@OpenThread <> nil);
InitInternalFuncs();
finalization
if (FreeKernel) and (hKernel > 0) then
FreeLibrary(hKernel);
FreeInternalFuncs();
if Assigned(FLock) then
FreeAndNil(FLock);
end.