Как изменить реализацию (обход) внешне объявленной функции

У меня есть сторонняя функция

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

Он используется в другом стороннем устройстве.

Я хочу заменить тело функции во время выполнения другой новой реализацией.

Это возможно? Думаю, понадобится какой-нибудь хак (аля VirtualMemoryUnprotect). Решение без ассемблера приветствуется.


person Gad D Lord    schedule 01.08.2011    source источник
comment
у вас есть исходный код вызываемой функции? у вас есть исходный код вызывающей функции? у вас есть основной исполняемый код? Вы можете скомпилировать и заменить любой из них?   -  person PA.    schedule 02.08.2011
comment
У меня есть их исходники, но я не хочу их менять, т.к. перекомпилировать, переустанавливать. Я хочу заменить реализацию во время выполнения. У меня также есть основной исполняемый исходный код, и я могу изменить его, поскольку он мой.   -  person Gad D Lord    schedule 02.08.2011
comment
У вас есть исходный код, и вы не хотите его перекомпилировать? Это такое глубоко неправильное использование крючка. Я надеюсь, что никому больше не придется использовать программное обеспечение, которое вы пишете таким образом.   -  person Warren P    schedule 02.08.2011
comment
+1 за это такое глубоко неправильное использование крючка.   -  person Vector    schedule 02.08.2011
comment
@warren Я делаю это, чтобы исправить кучу ошибок VCL, потому что я не хочу изменять его источник. Это можно оправдать. В этом случае я бы использовал статическую ссылку и изменил источник.   -  person David Heffernan    schedule 02.08.2011
comment
@Warren: проблема не в перекомпиляции, проблема в изменении. Я также использую хуки для стороннего кода, исходный код которого у меня есть. Если я изменяю исходный код, обновление стороннего кода становится более хрупким процессом. Используя перехват, вы можете просто добавить пару модульных тестов, чтобы показать, что материал перехватывается правильно. Если обновление стороннего кода каким-то образом сломает ваши хуки, юнит-тесты обнаружат это.   -  person Marjan Venema    schedule 02.08.2011
comment
Дэвид. В случае с VCL и использованием пакетов я обычно либо учусь обходить ошибки VCL (а это редко случается со мной), либо использую альтернативы рассматриваемому коду VCL. За 10 лет я не сталкивался с ошибкой VCL в фрагменте кода, который я не мог (а) обойти или (б) просто не использовать, а вместо этого использовать что-то другое. Но, если все остальное не помогло, и я исчерпал все (a) или (b) обходные пути стиля, тогда и только тогда я бы использовал перехват. И никогда для кода, отличного от VCL, исходники которого принадлежат мне!   -  person Warren P    schedule 02.08.2011
comment
@Warren Мой текущий список хуков: Variants._VarFromCurr для QC # 87786, GetCursorPos исправляет ошибку Windows 64-битной ОС LARGEADDRESSAWARE, AllocateHWnd/MakeObjectInstance делает потокобезопасным, форматирование научной нотации FloatToText использует как минимум 3 цифры для показателя степени, что выглядит ужасно в моем приложении, Реализации Cosh/Sinh/Tanh в современном Delphi дьявольски неэффективны, обходной путь Windows.HtmlHelp ошибка hhctrl.ocx, вызывающая зависание при завершении работы при подключении к DLL, которая показывает пользовательский интерфейс. Я не нашел лучшего способа исправить каждую из этих проблем.   -  person David Heffernan    schedule 03.08.2011
comment
@DavidHeffernan - не могли бы поделиться? :-)   -  person Leonardo Herrera    schedule 24.01.2012
comment
@leonardo, что конкретно тебя интересует?   -  person David Heffernan    schedule 24.01.2012
comment
@DavidHeffernan Мне интересно узнать об этой проблеме и исправлении для AllocateHWnd/MakeObjectInstance, чтобы сделать его потокобезопасным.   -  person Leonardo Herrera    schedule 24.01.2012
comment
@LeonardoHerrera Ну, взгляните на мой последний вопрос: stackoverflow.com/questions/8820294/ Это касается AllocateHWnd. MakeObjectInstance сложнее, но, скорее всего, вам нужно исправить AllocateHWnd.   -  person David Heffernan    schedule 24.01.2012


Ответы (2)


Да, вы можете сделать это, используя ReadProcessMemory. и WriteProcessMemory для исправления кода текущий процесс. По сути, вы получаете адрес процедуры или функции для исправления, а затем вставляете инструкцию перехода по адресу новой процедуры.

Проверьте этот код

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.

person RRUZ    schedule 01.08.2011
comment
Где ты этому научился? Это круто, когда тебя называют хаком. Вместо того, чтобы называть это хаком, я думаю, вам следует назвать это ниндзя. ДанныеСравнитьNinja - person Michael Riley - AKA Gunny; 02.08.2011
comment
Такой вид перенаправления известен как обход. У Microsoft есть исследовательский проект по этому поводу: research.microsoft.com/en-us/projects. /обход - person Remy Lebeau; 02.08.2011
comment
@ Преждевременно, я подозреваю, что это может быть как-то связано с тем, чтобы не менять защиту памяти. Я не вижу здесь вызовов VirtualProtect, которые обычно требуются для перезаписи исполняемого кода. Я прав, Рруз? В противном случае, почему бы просто не использовать старый добрый Move? - person Rob Kennedy; 01.06.2012

Я думаю, что JCL имеет некоторые утилиты для такого рода вещей... Я сам не использовал их, но бегло посмотрел, и следующие пункты выглядят многообещающе:

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

Я думаю, что jclHookExcept.JclHookExceptions() демонстрирует, как их использовать.

person ain    schedule 01.08.2011