13
votes

I have a third party function

function DataCompare(const S1, S2: string; APartial: Boolean): Boolean;
begin
   ...
end;

It is used in another third party unit.

I wish to replace the body of the function at runtime with another new implementation.

Is this possible? I guess there will be a need of some hack (ala VirtualMemoryUnprotect). A non-assembler solution is very welcome.

2
do you have the source code of the called function available? do you have the source code of the caller function available? do you have the main executable code available? can you compile and replace any of them?PA.
You have the source code and you don't want to recompile it? This is such a deeply wrong use of hooking. I hope nobody else has to use software you write this way.Warren P
@Warren: the issue is not recompiling, the issue is modifying. I use hooks as well for third party code to which I have the source. If I modify the source, updating the third party code becomes a more brittle process. Using hooking you can simply add a couple of unit tests to show that the stuff is hooked correctly. If updating the third party code somehow breaks your hooks, the unit tests will catch that.Marjan Venema
David - In the case of VCL and using packages, I tend to either learn to work around the VCL bugs (and this seldom happens to me) or to use alternatives to the VCL code in question. I have not in 10 years run into a VCL bug in a bit of code I couldn't (a) work around or (b) just not use, and use something else instead. But, if all else failed, and I had exhausted all (a) or (b) style workarounds then, and only then, would I use hooking. And never ever for non-VCL code that is something I own the sources to!Warren P
@Warren My current list of hooks is: Variants._VarFromCurr for QC#87786, GetCursorPos fix Windows bug 64 bit OS LARGEADDRESSAWARE, AllocateHWnd/MakeObjectInstance make threadsafe, FloatToText scientific notation formatting uses at least 3 digits for exponent which looks dire in my app, Cosh/Sinh/Tanh implementations in modern Delphi are diabolically inefficient, Windows.HtmlHelp workaround hhctrl.ocx bug causing hangs on shutdown when linked into DLL which shows UI. I have not found a better way to fix each of these issues.David Heffernan

2 Answers

37
votes

Yes you can do that, using the ReadProcessMemory and WriteProcessMemory functions to patch the code of the current process. Basically, you get the address of the procedure or function to patch and then insert a Jump instruction to the address of the new procedure.

Check this code

Uses
  uThirdParty; //this is the unit where the original DataCompare function is declarated

type
  //strctures to hold the address and instructions to patch
  TJumpOfs = Integer;
  PPointer = ^Pointer;

  PXRedirCode = ^TXRedirCode;
  TXRedirCode = packed record
    Jump: Byte;
    Offset: TJumpOfs;
  end;

  PAbsoluteIndirectJmp = ^TAbsoluteIndirectJmp;
  TAbsoluteIndirectJmp = packed record
    OpCode: Word;
    Addr: PPointer;
  end;

var
 DataCompareBackup: TXRedirCode; //Store the original address of the function to patch


//this is the implementation of the new function
function DataCompareHack(const S1, S2: string; APartial: Boolean): Boolean;
begin
  //here write your own code
end;

//get the address of a procedure or method of a function 
function GetActualAddr(Proc: Pointer): Pointer;
begin
  if Proc <> nil then
  begin
    if (Win32Platform = VER_PLATFORM_WIN32_NT) and (PAbsoluteIndirectJmp(Proc).OpCode = $25FF) then
      Result := PAbsoluteIndirectJmp(Proc).Addr^
    else
      Result := Proc;
  end
  else
    Result := nil;
end;

//patch the original function or procedure
procedure HookProc(Proc, Dest: Pointer; var BackupCode: TXRedirCode);
var
  n: {$IFDEF VER230}NativeUInt{$ELSE}DWORD{$ENDIF};
  Code: TXRedirCode;
begin
  Proc := GetActualAddr(Proc);
  Assert(Proc <> nil);
  //store the address of the original procedure to patch
  if ReadProcessMemory(GetCurrentProcess, Proc, @BackupCode, SizeOf(BackupCode), n) then
  begin
    Code.Jump := $E9;
    Code.Offset := PAnsiChar(Dest) - PAnsiChar(Proc) - SizeOf(Code);
    //replace the target procedure address  with the new one.
    WriteProcessMemory(GetCurrentProcess, Proc, @Code, SizeOf(Code), n);
  end;
end;
//restore the original address of the hooked function or procedure
procedure UnhookProc(Proc: Pointer; var BackupCode: TXRedirCode);
var
  n: {$IFDEF VER230}NativeUInt{$ELSE}Cardinal{$ENDIF};
begin
  if (BackupCode.Jump <> 0) and (Proc <> nil) then
  begin
    Proc := GetActualAddr(Proc);
    Assert(Proc <> nil);
    WriteProcessMemory(GetCurrentProcess, Proc, @BackupCode, SizeOf(BackupCode), n);
    BackupCode.Jump := 0;
  end;
end;

//Patch the original procedure or function
procedure HookDataCompare;
begin
  //look how is passed the address of the original procedure (including the unit name)
  HookProc(@uThirdParty.DataCompare, @DataCompareHack, DataCompareBackup);
end;

//restore the address of the original procedure or function
procedure UnHookDataCompare;
begin
  UnhookProc(@uThirdParty.DataCompare, DataCompareBackup);
end;


initialization
 HookDataCompare;
finalization
 UnHookDataCompare;
end.

Now every time you execute your app and a call to the DataCompare function was made, the jump instruction (to he new address) will be executed causing which the DataCompareHack function will be called instead.

3
votes

I think JCL has some utils for this kind of stuff... I haven't used it myself but had a quick look and following items look promising:

jclSysUtils.WriteProtectedMemory()
jclPeImage.TJclPeMapImgHooks.ReplaceImport()

I think the jclHookExcept.JclHookExceptions() demonstrates how to use them.