Háček: nahrazení funkce nebo metody

vložil Radek Červinka 27. dubna 2010 00:51

Ve třetím a asi i posledním díle mini seriálu ukáži nahrazení cizí funkce, procedury nebo metody (nevirtuální). Tímto způsobem se dá opravovat cizí kód v koupených komponentách nebo třeba v samotné RTL.

Opět jsem vykradl VCLFixPack, ale ostatní to mohou dělat trochu odlišně. Příkladem odlišného řešení (a možného použití) je vyřešení problému s Data Execution Prevention (DEP) v Delphi RTL ve starších verzích Delphi (do verze D2005).

V podstatě napíšete stínovou implementaci nahrazované funkce se stejným rozhraním a provede nahrazení původního kódu většinou přes nějaký skok na svoji implementaci.

Pro demonstraci jsem nahradil SysUtils.DateToStr. Vlastní kód je až kolem 100 řádku.

unit uHack;
{**************************************************************************************************}
{                                                                                                  }
{ The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); }
{ you may not use this file except in compliance with the License. You may obtain a copy of the    }
{ License at http://www.mozilla.org/MPL/                                                           }
{                                                                                                  }
{ The Original Code is VCLFixPack.pas.                                                             }
{                                                                                                  }
{ The Initial Developer of the Original Code is Andreas Hausladen (Andreas.Hausladen@gmx.de).      }
{ Portions created by Andreas Hausladen are Copyright © 2008 Andreas Hausladen.                  }
{ All Rights Reserved.                                                                             }
{                                                                                                  }
{**************************************************************************************************}

{$R-} // range check off
interface
implementation
uses
  SysUtils,
  Windows,
  Classes;

type
  TJumpOfs = Integer;
  PPointer = ^Pointer;

  PXRedirCode = ^TXRedirCode;
  TXRedirCode = packed record
    Jump: Byte;
    Offset: TJumpOfs;
  end;

  PWin9xDebugThunk = ^TWin9xDebugThunk;
  TWin9xDebugThunk = packed record
    PUSH: Byte;
    Addr: Pointer;
    JMP: TXRedirCode;
  end;

  PAbsoluteIndirectJmp = ^TAbsoluteIndirectJmp;
  TAbsoluteIndirectJmp = packed record
    OpCode: Word;   //$FF25(Jmp, FF /4)
    Addr: PPointer;
  end;

function GetActualAddr(Proc: Pointer): Pointer;

  function IsWin9xDebugThunk(AAddr: Pointer): Boolean;
  begin
    Result := (AAddr <> nil) and
              (PWin9xDebugThunk(AAddr).PUSH = $68) and
              (PWin9xDebugThunk(AAddr).JMP.Jump = $E9);
  end;

begin
  if Proc <> nil then
  begin
    if (Win32Platform <> VER_PLATFORM_WIN32_NT) and IsWin9xDebugThunk(Proc) then
      Proc := PWin9xDebugThunk(Proc).Addr;
    if (PAbsoluteIndirectJmp(Proc).OpCode = $25FF) then
      Result := PAbsoluteIndirectJmp(Proc).Addr^
    else
      Result := Proc;
  end
  else
    Result := nil;
end;

procedure HookProc(Proc, Dest: Pointer; var BackupCode: TXRedirCode);
var
  n: DWORD;
  Code: TXRedirCode;
begin
  Proc := GetActualAddr(Proc);
  Assert(Proc <> nil);
  if ReadProcessMemory(GetCurrentProcess, Proc, @BackupCode, SizeOf(BackupCode), n) then
  begin
    Code.Jump := $E9;
    Code.Offset := PAnsiChar(Dest) - PAnsiChar(Proc) - SizeOf(Code);
    WriteProcessMemory(GetCurrentProcess, Proc, @Code, SizeOf(Code), n);
  end;
end;

procedure UnhookProc(Proc: Pointer; var BackupCode: TXRedirCode);
var
  n: Cardinal;
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;

var
  DateToStrJmp: TXRedirCode;

// moje implementace
function OwnDateToStr(Date: TDateTime):string;
begin

  DateTimeToString(Result, ShortDateFormat, Date);
  Result := 'DateToStr:'+Result;
end;

// vlastni hack

initialization
  // instalace
  HookProc(@SysUtils.DateToStr, @OwnDateToStr, DateToStrJmp);
finalization
  // a zpet
  UnhookProc(@SysUtils.DateToStr, DateToStrJmp);
end.

Pokud použijeme unit uHack dříve než SysUtils (tj. nejlépe pokud je náš unit co nejdříve v projektu) dojde k nahrazení DateToStr za naši pochybnou implementaci. Pozor: vyšší verze Delphi používají optimalizaci pomoc inline, tj. kompilátor (nebo spíše linker) odstraní (resp. může odstranit) přebytečné volání funkcí nebo metod a obsah volané např. funkce včlení do volajícího kódu, čímž ušetří x instrukcí, ale v tom případě tento hack nemůžeme použít.

Ale zpět:

program Project2;
{$APPTYPE CONSOLE}
uses
  uHack in 'uHack.pas',
  SysUtils;

begin
  writeln(DateToStr(Now));
end.

ve výsledku vypíše např. DateToStr:27.4.2010, což znamená, že nám kód funguje. Předpokládám, že sami přijdete na lepší použití.

Tagy: , , ,

Praxe

Komentování ukončeno

Naše nabídka

MVP
Ing. Radek Červinka - Embarcadero MVP
profil na linkedin, Twitter:@delphicz

Nabízím placené poradenství a konzultace v oblasti programování a vývoje SW.
Dále nabízíme i vývoj speciálního software na zakázku.

Neváhejte nás kontaktovat (i ohledně reklamy).

Delphi Certified Developer

O Delphi.cz

Delphi je jediný moderní RAD nástroj podporující tvorbu nativních aplikací pro platformu Win32, Win64, Mac OSX a na iPhone a Android.

Delphi.cz je nezávislý portál pro uživatele Delphi. Portál není koncipován pro úplné začátečníky, i když i ti se zde nebudou nudit, ale spíše na programátory, kteří již něco znají a chtějí své znalosti dále rozvíjet a sledovat novinky.

Poslední komentáře

Comment RSS