【问题标题】:Patch routine call in delphidelphi中的补丁例程调用
【发布时间】:2012-02-17 04:31:51
【问题描述】:

我想修补一个例程调用,以便能够通过一些修改自己处理它。 我正在写一个资源加载器。我想修补 Delphi 的 LoadResourceModule 和 InitInheritedComponent 例程与我的例程相同。我检查了 MadExcept.pas 单元中的 PatchAPI 调用,但无法确定是否可以将其用于我的项目。

我想要类似的东西

我的 exe 在运行时调用 -> LoadResourceModule -> 跳转到 -> MyCustomResourceModule...

对此的任何指示都会非常有帮助。

【问题讨论】:

  • 我今天只是在考虑同样的问题 - 所以使用这种技术将允许例如在组件流(从 DFM 到应用程序)机制中添加代码?因此,例如,我可以有一个中心位置来记录使用过的组件类,或者做一些质量保证(“不要使用 BDE 类!或者那个旧版本的组件 X!”)?
  • @mjn 还有其他扩展点可以更轻松地完成。例如TReader.OnFindComponentClass。当没有其他方法可以完成工作时,修补代码应该始终是最后的手段。

标签: delphi monkeypatching


【解决方案1】:

我使用以下代码:

procedure PatchCode(Address: Pointer; const NewCode; Size: Integer);
var
  OldProtect: DWORD;
begin
  if VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, OldProtect) then 
  begin
    Move(NewCode, Address^, Size);
    FlushInstructionCache(GetCurrentProcess, Address, Size);
    VirtualProtect(Address, Size, OldProtect, @OldProtect);
  end;
end;

type
  PInstruction = ^TInstruction;
  TInstruction = packed record
    Opcode: Byte;
    Offset: Integer;
  end;

procedure RedirectProcedure(OldAddress, NewAddress: Pointer);
var
  NewCode: TInstruction;
begin
  NewCode.Opcode := $E9;//jump relative
  NewCode.Offset := NativeInt(NewAddress)-NativeInt(OldAddress)-SizeOf(NewCode);
  PatchCode(OldAddress, NewCode, SizeOf(NewCode));
end;

你可以通过调用RedirectProcedure来实现你的钩子/补丁/detour:

RedirectProcedure(@LoadResourceModule, @MyLoadResourceModule);

这适用于 32 位代码。它也适用于 64 位代码,前提是旧功能和新功能都驻留在同一个可执行模块中。否则跳跃距离可能会超出 32 位整数的范围。

如果有人可以提供一种适用于 64 位地址空间的替代方案,无论两个地址相距多远,我都会非常感兴趣。

【讨论】:

  • 最好unpatch 重定向或确保在应用程序关闭时不会出现代码中断 - 可以进行重定向调用(例如通过 RTL或在重定向单元之前加载的另一个单元),并跳转到一些未初始化的代码。
  • @Arnaud 这可能是真的。在我所有的使用中,我在进行任何调用之前重定向,或者这是一个没有副作用的例程,因此不打补丁并不重要
  • @DavidHeffernan 想一想,如果我想获得默认值然后处理该值,我该如何调用旧程序。由于在上面的代码中,我们覆盖了旧程序的地址以跳转到新程序。像 MyLoadResourceModule 这样的东西在内部使用 LoadResourceModule 并做一些额外的事情......
  • @RahulW 然后你需要一个更好的补丁库,一个支持蹦床的。查看 MS Detours。
  • FlushInstructionCache 在 x86 或 x64 CPU 架构上不是必需的,因为它们具有透明缓存,它在那里实际上是无操作的。只是给那些像我一样在这个答案中有类似代码并想知道是否缺少 FlushInstructionCache 的人的注释。不是。
【解决方案2】:

已经有一个Delphi detours library 用于此。

Delphi Detours Library 是一个允许你挂钩 delphi 的库 和 windows API 函数。它提供了一种简单的方法来插入和 移除钩子。

特点:

  • 支持 x86 和 x64 架构。
  • 允许通过 Trampoline 函数调用原始函数。
  • 支持多挂钩。
  • COM/接口/win32api 支持。
  • 支持 COM vtable 修补。
  • 完全线程安全的代码挂钩和解除挂钩。
  • 支持挂钩对象方法。
  • 支持 Delphi 7/2005-2010/XE-XE7 。
  • 支持拉撒路/FPC。
  • 支持64位地址。
  • 该库不使用任何外部库。
  • 库可以随时插入和移除挂钩。
  • 该库包含 InstDecode 库,可让您解码 asm 指令(x86 和 x64)。

【讨论】:

    【解决方案3】:

    我修改了 David Heffernan 的代码以支持 64 位和间接跳转到 BPL 中的方法。在一些帮助下: http://chee-yang.blogspot.com.tr/2008/11/hack-into-delphi-class.html

    type
      PAbsoluteIndirectJmp = ^TAbsoluteIndirectJmp;
      TAbsoluteIndirectJmp = packed record
        OpCode: Word;  // $FF25(Jmp, FF /4) 
        Addr: DWORD;  // 32-bit address
                      // in 32-bit mode: it is a direct jmp address to target method
                      // in 64-bit mode: it is a relative pointer to a 64-bit address used to jmp to target method
      end;
    
      PInstruction = ^TInstruction;
      TInstruction = packed record
        Opcode: Byte;
        Offset: Integer;
      end;
    
    
    function GetActualAddr(Proc: Pointer): Pointer;
    begin
      Result := Proc;
      if Result <> nil then
        if PAbsoluteIndirectJmp(Result)^.OpCode = $25FF then  // we need to understand if it is proc entry or a jmp following an address
    {$ifdef CPUX64}
          Result := PPointer( NativeInt(Result) + PAbsoluteIndirectJmp(Result)^.Addr + SizeOf(TAbsoluteIndirectJmp))^;
          // in 64-bit mode target address is a 64-bit address (jmp qword ptr [32-bit relative address] FF 25 XX XX XX XX)
          // The address is in a loaction pointed by ( Addr + Current EIP = XX XX XX XX + EIP)
          // We also need to add (instruction + operand) size (SizeOf(TAbsoluteIndirectJmp)) to calculate relative address
          // XX XX XX XX + Current EIP + SizeOf(TAbsoluteIndirectJmp)
    {$else}
          Result := PPointer(PAbsoluteIndirectJmp(Result)^.Addr)^;
          // in 32-bit it is a direct address to method
    {$endif}
    end;
    
    procedure PatchCode(Address: Pointer; const NewCode; Size: Integer);
    var
      OldProtect: DWORD;
    begin
      if VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, OldProtect) then //FM: remove the write protect on Code Segment
      begin
        Move(NewCode, Address^, Size);
        FlushInstructionCache(GetCurrentProcess, Address, Size);
        VirtualProtect(Address, Size, OldProtect, @OldProtect); // restore write protection
      end;
    end;
    
    procedure RedirectProcedure(OldAddress, NewAddress: Pointer);
    var
      NewCode: TInstruction;
    begin
      OldAddress := GetActualAddr(OldAddress); 
    
      NewCode.Opcode := $E9;//jump relative
      NewCode.Offset := NativeInt(NewAddress) - NativeInt(OldAddress) - SizeOf(NewCode);
    
      PatchCode(OldAddress, NewCode, SizeOf(NewCode));
    end;
    

    【讨论】:

    • 它适用于 64 位代码,前提是旧功能和新功能都驻留在同一个可执行模块中。否则跳跃距离可能会超出 32 位整数的范围。所以它和大卫的代码有同样的限制。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-21
    • 2020-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多