13

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.

Gad D Lord
  • 6,620
  • 12
  • 60
  • 106
  • 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. Aug 01 '11 at 22:55
  • I do have their source code but I do not want to change them since and recompile, reinstall them. I wish to replace the implementaton at run-time. I do have the main executable source code as well and I can change it since it is mine. – Gad D Lord Aug 01 '11 at 22:56
  • 5
    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 Aug 01 '11 at 23:20
  • +1 for "This is such a deeply wrong use of hooking." – Vector Aug 02 '11 at 04:20
  • @warren I do this to fix a bunch of VCL bugs because I don't want to modify its sorce. It can be justified. In this case I'd use static linking and change the source. – David Heffernan Aug 02 '11 at 05:51
  • 2
    @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 Aug 02 '11 at 06:57
  • 1
    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 Aug 02 '11 at 12:58
  • 3
    @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 Aug 02 '11 at 23:24
  • @DavidHeffernan - mind to share? :-) – Leonardo Herrera Jan 23 '12 at 20:50
  • @leonardo what in particular are you interested in? – David Heffernan Jan 23 '12 at 20:54
  • @DavidHeffernan I'm interested to know about the issue and fix for "AllocateHWnd/MakeObjectInstance make threadsafe" one. – Leonardo Herrera Jan 23 '12 at 22:12
  • @LeonardoHerrera Well, take a look at my most recent question: http://stackoverflow.com/questions/8820294/how-can-i-make-allocatehwnd-threadsafe That deals with AllocateHWnd. MakeObjectInstance is harder but most likely it's AllocateHWnd that you need fixed. – David Heffernan Jan 23 '12 at 22:15

2 Answers2

38

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.

Lieven Keersmaekers
  • 57,207
  • 13
  • 112
  • 146
RRUZ
  • 134,889
  • 20
  • 356
  • 483
  • Now where did you learn that? That's way to cool to be called a hack. Instead of calling it a "Hack" I think you should call it a "Ninja". DataCompareNinja – Michael Riley - AKA Gunny Aug 01 '11 at 23:23
  • 4
    This kind of redirection is known as a Detour. Microsoft has a research project about it: http://research.microsoft.com/en-us/projects/detours/ – Remy Lebeau Aug 02 '11 at 01:09
  • @Premature, I suspect it may have something to do with avoiding having to change memory protection. I see no calls to `VirtualProtect` here, which would ordinarily be required to overwrite executable code. Am I right, Rruz? Otherwise, why not just use plain old `Move`? – Rob Kennedy Jun 01 '12 at 19:45
3

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.

ain
  • 22,394
  • 3
  • 54
  • 74