Skip to content
bott edited this page Aug 14, 2021 · 3 revisions

Welcome to the DDetours wiki!

Introduction

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.

Hooking rules

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.

DDetours functions

InterceptCreate

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.

InterceptRemove

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.

GetHookCount

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.

IsHooked

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.

BeginTransaction

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;
// ...

EndTransaction

function EndTransaction(Handle: THandle): Boolean;

Ends a transaction. This also resumes suspended threads if BeginTransaction suspended them.

EnterRecursiveSection

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]);

ExitRecursiveSection

function ExitRecursiveSection(var TrampoLine): Boolean;

Exits a recursive section. Sees above example.

GetCreatorThreadIdFromTrampoline

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;

GetTrampolineParam

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));

Examples

Hooking WinApi function

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.

Hooking COM Object

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.

Hooking interface

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.

Hooking object method

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.

Hooking object constructor

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.

Using TIntercept class

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.