如何更改外部声明函数的实现(拦截)

13

我有一个第三方函数

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

这个函数被用在第三方单元中。

我希望在运行时将函数体替换为另一个新实现。是否可能?我猜需要一些技巧(比如VirtualMemoryUnprotect)。非汇编解决方案也可以。


5
你拥有源代码但不想重新编译它?这是对hooking使用的深度错误。我希望没有其他人会使用你用这种方式编写的软件。 - Warren P
2
@Warren:问题不在于重新编译,而在于修改。我也使用钩子来处理第三方代码,而且我有源代码。如果我修改了源代码,更新第三方代码就会变得更加脆弱。使用钩子,您只需添加几个单元测试即可显示已正确挂钩的内容。如果更新第三方代码某种方式破坏了您的钩子,那么单元测试将捕获到这一点。 - Marjan Venema
1
David - 在使用VCL和包时,我倾向于学习如何解决VCL的错误(这很少发生在我身上),或者使用替代VCL代码的方案。在过去的10年中,我从未遇到过无法解决的VCL错误,要么是通过(a)解决方法或(b)不使用并使用其他替代品。但是,如果所有其他方法都失败了,并且我已经尝试了所有(a)或(b)风格的解决方法,那么只有在这种情况下,我才会使用hooking。而且绝对不会用于我拥有源代码的非VCL代码! - Warren P
3
我的当前挂钩列表如下:Variants._VarFromCurr 用于 QC#87786,GetCursorPos 修复 Windows 64位操作系统 LARGEADDRESSAWARE 的 bug,AllocateHWnd/MakeObjectInstance 使线程安全,FloatToText 科学计数法格式至少使用3个指数数字,在我的应用程序中看起来非常糟糕,现代 Delphi 中的 Cosh / Sinh / Tanh 实现效率极低,Windows.HtmlHelp 解决了 hhctrl.ocx 的问题,该问题会在链接到 DLL 并显示 UI 时导致关闭时挂起。我还没有找到更好的方法来解决这些问题。 - David Heffernan
@leonardo 你对什么特别感兴趣? - David Heffernan
显示剩余7条评论
2个回答

38

是的,你可以这样做,使用 ReadProcessMemoryWriteProcessMemory 函数来修补当前进程的代码。基本上,你需要获取要修补的过程或函数的地址,然后插入一个跳转指令到新过程的地址。

查看此代码

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.

现在每次你执行应用程序并调用 DataCompare 函数时,跳转指令(到新地址)将被执行,导致 DataCompareHack 函数将被调用。


你从哪里学到的?这太酷了,不能称之为“黑客”。我认为你应该称之为“忍者”,而不是“黑客”。DataCompareNinja - Michael Riley - AKA Gunny
4
这种重定向被称为“Detour”。微软有一个相关的研究项目:http://research.microsoft.com/en-us/projects/detours/ - Remy Lebeau
“@Premature,我怀疑这可能与避免更改内存保护有关。我在这里没有看到对VirtualProtect的调用,这通常需要覆盖可执行代码。我是对的,Rruz吗?否则,为什么不只使用普通的Move?” - Rob Kennedy

3

我认为JCL有一些适用于这种情况的工具...我自己没有使用过,但是快速浏览后发现以下几项看起来很有希望:

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

我认为jclHookExcept.JclHookExceptions()演示了如何使用它们。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接