-
Notifications
You must be signed in to change notification settings - Fork 159
Home
Welcome to the DDetours wiki!
DDetours is a library that allows you to insert and remove hook from function. It supports both (x86 and x64) architecture. The basic idea of this library is to replace the prologue of the target function by inserting unconditional jump instruction to the intercepted function.
In order to run your hook correctly, you must follow the following rules:
- Intercept procedure must have the same original procedure signature. That means same parameters (types) and same calling convention.
- When hooking Delphi Object/COM Object, the first parameter of the InterceptProc/Trampoline must be a pointer to that object, that's what we call Self.
- When doing multi-hooks, each hook needs its own NextHook/Trampoline function.
- When building a multi-thread application, inserting/removing hooks should be done inside BeginTransaction/EndTransaction frame.
- Never try to call the original function directly inside Intercept function as this may result in a recursive call. Always use NextHook/Trampoline function.
- Use EnterRecursiveSection/ExitRecursiveSection when calling a function that may call internally the original function.
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;
function InterceptCreate(const TargetInterface; const MethodName: String; const InterceptProc: Pointer; const Param: Pointer = nil; const Options: TInterceptOptions = DefaultInterceptOptions): Pointer; overload;
-
TargetProc: A pointer to the target function that you are going to hook.
-
InterceptProc: A pointer to the intercept function, this one will be executed instead of the original function.
-
MethodName: Function name that you will hook.
-
TargetInterface: The interface that contains the method that you are going to hook.
-
MethodIndex: Method's index inside the interface.
- Index is zero based numbering. Meaning you must start counting from zero.
- Counting must be from the top to the bottom.
- You must count all methods declared in parent interface.
-
Param: An optional user data. See GetTrampolineParam function.
-
Options: A combined value of:
- ioForceLoad : Force DLL load if GetModuleHandle function returns 0.
- ioRecursive : Allows Trampoline function to use EnterRecursiveSection/ExitRecursiveSection function.
-
Return: If the function succeeds, the return value is a pointer to a TrampoLine function that can be used either to call the original function or to call the next hook (if exists). If the function fails, the return value is nil.
function InterceptRemove(const TrampoLine: Pointer): Integer; overload;
Remove the registered hook from the TargetProc.
-
Trampoline: A pointer to the Trampoline that was returned by the InterceptCreate function .It is necessary that you provide a valid parameter .
-
Return: The function returns the number of hook that are still alive.
function GetHookCount(const TargetProc: Pointer): Integer; overload;
function GetHookCount(const TargetInterface; MethodIndex: Integer): Integer; overload;
function GetHookCount(const TargetInterface; const MethodName: String): Integer; overload;
Returns the number of installed hooks. The function returns -1 if the TargetProc was not hooked at all.
function IsHooked(const TargetProc: Pointer): Boolean; overload;
function IsHooked(const TargetInterface; MethodIndex: Integer): Boolean; overload;
function IsHooked(const TargetInterface; const MethodName: String): Boolean; overload;
Returns True is TargetProc was hooked. Otherwise it returns False.
function BeginTransaction(Options: TTransactionOptions = [toSuspendThread]): THandle;
-
Options: Transaction options. Could be a combined value of the following options:
- toSuspendThread : Suspend all thread except the current thread.
-
Return: It returns a handle that could be passed to EndTransaction function.
N.B: You should only call DDetours function inside BeginTransaction/EndTransaction.
var Hanlde: THandle;
Handle := BeginTransaction();
try
Trampoline1 := InterceptCreate(@Function1, @InterceptFunction1);
Trampoline2 := InterceptCreate(@Function2, @InterceptFunction2);
Trampoline3 := InterceptCreate(@Function3, @InterceptFunction3);
finally
EndTransaction(Handle);
end;
// ...
function EndTransaction(Handle: THandle): Boolean;
Ends a transaction. This also resumes suspended threads if BeginTransaction suspended them.
function EnterRecursiveSection(var TrampoLine; MaxRecursionLevel: NativeInt = 0): Boolean;
-
MaxRecursionLevel: The maximum allowed recursive depth.
-
Return: The function returns True if it wasn't called recursively.
N.B : Only Trampoline function that is marked with ioRecursive may use this feature. Attempting to use EnterRecursiveSection on a Trampoline function that was not marked with ioRecursive flag, will trigger an exception.
type TSomeFunction = function (Param: ParamType): ReturnType;
var TrampolineSomeFunction: TSomeFunction;
function SomeFunction(Param: ParamType): ReturnType;
begin
// do something...
end;
function InternalFunction(Param: ParamType): ReturnType;
begin
// InternalFunction is a function that can call original function SomeFunction.
// This may lead to a recursion.
end;
function InterceptSomeFunction(Param: ParamType): ReturnType;
var NotRecursive: Boolean;
begin
// ...
NotRecursive := EnterRecursiveSection(TrampolineSomeFunction);
try
if NotRecursive then
begin
// no recursion:
Result := InternalFunction(Params);
end
else
begin
// There is a recursion ... just perform the default behaviour.
Result := TrampolineSomeFunction(Param);
end;
finally
ExitRecursiveSection(TrampolineSomeFunction);
end;
end;
TrampolineSomeFunction := InterceptCreate(@SomeFunction, @InterceptSomeFunction, nil, [ioRecursive]);
function ExitRecursiveSection(var TrampoLine): Boolean;
Exits a recursive section. Sees above example.
function GetCreatorThreadIdFromTrampoline(var TrampoLine): TThreadId;
Returns the thread ID that created the trampoline.
function InterceptSomeFunction(Param: ParamType): ReturnType;
var ThreadId: TThreadId;
begin
ThreadId := GetCreatorThreadIdFromTrampoline(TrampolineSomeFunction);
end;
function GetTrampolineParam(var TrampoLine): Pointer;
Returns a parameter/tag for the Trampoline. N.B: Each NextHook/Trampoline has its own data.
function InterceptSomeFunction(Param: ParamType): ReturnType;
var Memo: TMemo;
begin
Memo := TMemo(GetTrampolineParam(TrampolineSomeFunction));
Memo.Lines.Add('...');
end;
TrampolineSomeFunction := InterceptCreate(@SomeFunction, @InterceptSomeFunction, Pointer(Memo1));
uses
System.SysUtils,
WinApi.Windows,
DDetours;
var
TrampolineMessageBoxW: function(hWnd: hWnd; lpText, lpCaption: LPCWSTR; uType: UINT): Integer; stdcall;
function InterceptMessageBoxW(hWnd: hWnd; lpText, lpCaption: LPCWSTR; uType: UINT): Integer; stdcall;
begin
Result := TrampolineMessageBoxW(hWnd, 'Hooked', lpCaption, uType);
end;
begin
TrampolineMessageBoxW := InterceptCreate(@MessageBoxW, @InterceptMessageBoxW);
MessageBoxW(0, 'Original Text', 'Caption', MB_OK);
InterceptRemove(@TrampolineMessageBoxW);
MessageBoxW(0, 'Original Text', 'Caption', MB_OK);
end.
program HookingComObj;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
ActiveX,
ShlObj,
ComObj,
WinApi.Windows,
DDetours;
var
{ First parameter must be Self! }
Trampoline_FileOpenDialog_Show: function(Self: Pointer; hwndParent: HWND): HRESULT; stdcall;
Trampoline_FileOpenDialog_SetTitle: function(Self: Pointer; pszTitle: LPCWSTR): HRESULT; stdcall;
function Intercept_FileOpenDialog_SetTitle(Self: Pointer; pszTitle: LPCWSTR): HRESULT; stdcall;
begin
Writeln('Original Title = ' + pszTitle);
Result := Trampoline_FileOpenDialog_SetTitle(Self, 'Hooked');
end;
function Intercept_FileOpenDialog_Show(Self: Pointer; hwndParent: HWND): HRESULT; stdcall;
begin
Writeln('Execution FileOpenDialog.Show ..');
Result := Trampoline_FileOpenDialog_Show(Self, hwndParent);
end;
var
FileOpenDialog: IFileOpenDialog;
TransactionHandle: THandle;
begin
{ initialization }
CoInitialize(0);
FileOpenDialog := CreateComObject(CLSID_FileOpenDialog) as IFileOpenDialog;
{ Installing Hook }
TransactionHandle := BeginTransaction();
try
Trampoline_FileOpenDialog_Show := InterceptCreate(FileOpenDialog, 3, @Intercept_FileOpenDialog_Show);
Trampoline_FileOpenDialog_SetTitle := InterceptCreate(FileOpenDialog, 17, @Intercept_FileOpenDialog_SetTitle);
finally
EndTransaction(TransactionHandle);
end;
{ Show OpenDialog }
FileOpenDialog.SetTitle('Open..');
FileOpenDialog.Show(0);
{
Do some work ...
...
}
{ Removing Hook }
TransactionHandle := BeginTransaction();
try
InterceptRemove(@Trampoline_FileOpenDialog_Show);
InterceptRemove(@Trampoline_FileOpenDialog_SetTitle);
finally
EndTransaction(TransactionHandle);
end;
end.
program HookingInterface;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
ActiveX,
ShlObj,
ComObj,
WinApi.Windows,
DDetours;
type
IPerson = Interface(IInterface)
procedure ShowMessage(const Msg: string);
end;
TPerson = class(TInterfacedObject, IPerson)
private
FName: string;
public
constructor Create(const Name: string);
procedure ShowMessage(const Msg: string);
function GetName(): string;
end;
var
FInterface: IPerson;
TrampolineShowMessage: procedure(Self: TPerson; const Msg: String) = nil;
{ TPerson }
constructor TPerson.Create(const Name: string);
begin
FName := Name;
end;
function TPerson.GetName(): string;
begin
Result := FName;
end;
procedure TPerson.ShowMessage(const Msg: String);
begin
Writeln(Msg);
end;
procedure InterceptShowMsg(Self: TPerson; const Msg: String);
begin
TrampolineShowMessage(Self, Format('There is a message for you %s. It says: %s', [Self.GetName(), Msg]));
end;
begin
FInterface := TPerson.Create('Jack');
FInterface.ShowMessage('Simple Message 1');
{
if your Delphi version supports RTTI, you can simply intercept method by name
TrampolineShowMessage := InterceptCreate(FInterface, 'ShowMessage', @InterceptShowMsg);
}
TrampolineShowMessage := InterceptCreate(FInterface, 3, @InterceptShowMsg);
FInterface.ShowMessage('Simple Message 2');
InterceptRemove(@TrampolineShowMessage);
FInterface.ShowMessage('Simple Message 3');
ReadLn;
end.
var
Person: TPerson;
begin
Person := TPerson.Create('Jack');
Person.ShowMessage('Simple Message 1');
TrampolineShowMessage := InterceptCreate(@TPerson.ShowMessage, @InterceptShowMsg);
Person.ShowMessage('Simple Message 2');
InterceptRemove(@TrampolineShowMessage);
Person.ShowMessage('Simple Message 3');
ReadLn;
end.
type
TConstructorCreate = function(InstanceOrVMT: Pointer; Alloc: ShortInt; const Name: string): Pointer;
var
Person: TPerson;
TrampolineConstructorCreate: TConstructorCreate;
function InterceptConstructorCreate(InstanceOrVMT: Pointer; Alloc: ShortInt; const Name: string): Pointer;
const
NewName = 'John';
begin
Writeln(Format('hooked : %s is now %s', [Name, NewName]));
Result := TrampolineConstructorCreate(InstanceOrVMT, Alloc, NewName);
end;
begin
TrampolineConstructorCreate := InterceptCreate(@TPerson.Create, @InterceptConstructorCreate);
Person := TPerson.Create('Jack');
Person.ShowMessage('Hi ' + Person.GetName());
InterceptRemove(@TrampolineConstructorCreate);
ReadLn;
end.
type
TSayHi = procedure(const Msg: String);
var
Person: TPerson;
Intercept: TIntercept<TSayHi, string>;
procedure SayHi(const Name: String);
begin
Writeln('hi ', Name);
end;
procedure InterceptSayHi(const Name: String);
begin
Intercept.TrampoLine(Format('%s %s', [Name, Intercept.Param]));
end;
begin
Intercept := TIntercept<TSayHi, string>.Create(SayHi, InterceptSayHi, 'and Goodbye!');
SayHi('Jack');
ReadLn;
end.