How to change the implementation (detour) of an externally declared function
Asked Answered
K

2

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.

Kayser answered 1/8, 2011 at 22:34 Comment(12)
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?Portfire
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.Kayser
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.Jilly
+1 for "This is such a deeply wrong use of hooking."Tilden
@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.Appendectomy
@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.Claypool
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!Jilly
@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.Appendectomy
@DavidHeffernan - mind to share? :-)Pasha
@leonardo what in particular are you interested in?Appendectomy
@DavidHeffernan I'm interested to know about the issue and fix for "AllocateHWnd/MakeObjectInstance make threadsafe" one.Pasha
@LeonardoHerrera Well, take a look at my most recent question: #8820794 That deals with AllocateHWnd. MakeObjectInstance is harder but most likely it's AllocateHWnd that you need fixed.Appendectomy
S
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.

Sanjiv answered 1/8, 2011 at 22:59 Comment(3)
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". DataCompareNinjaOlio
This kind of redirection is known as a Detour. Microsoft has a research project about it: research.microsoft.com/en-us/projects/detoursCensor
@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?Lali
B
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.

Barthelemy answered 1/8, 2011 at 23:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.