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í.