// ************************************************************************************************** // 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 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 = ' 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 = 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. } TIntercept = class(TInterfacedObject, IIntercept) 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 = class(TIntercept) 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; 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 } function TIntercept.TToPointer(const A): Pointer; begin Result := Pointer(A); end; function TIntercept.PointerToT(const P): T; begin Result := T(P); end; function TIntercept.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.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.GetTrampoline(): T; begin Result := FNextHook; end; function TIntercept.GetParam(): U; begin Result := FParam; end; function TIntercept.GetCreatorThreadId(): TThreadId; begin Result := FCreatorThreadId; end; function TIntercept.GetInterceptOptions(): TInterceptOptions; begin Result := FInterceptOptions; end; function TIntercept.EnterRecursive(MaxRecursionLevel: NativeInt = 0): Boolean; begin Result := EnterRecursiveSection(FTrampolinePtr, MaxRecursionLevel); end; function TIntercept.ExitRecursive(): Boolean; begin Result := ExitRecursiveSection(FTrampolinePtr); end; destructor TIntercept.Destroy(); begin InterceptRemove(TToPointer(FNextHook)); inherited; end; { TIntercept } constructor TIntercept.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.