This class creates a hook with a Static procedure

From: Alexander Muylaert (ask_at_hotmail.com)
Date: 06/28/04


Date: Mon, 28 Jun 2004 10:45:11 +0200

Hi

I've written a class that hooks a static procedure.

It should work transparent. When the original code is called it should call
my procedure en then it should call the original. It call's my code. But
my theorie of copying the original code to something a buffer, doesn't seem
to work.
Can somebody please check what is wrong please, It would help me a lot.

First unit contains the source, the second is test code.

Kind regards and many thanks

Alexander

{***************************************************************************
**
 Name : unit_SecurityHacks
 Author : amuylaer
 Copyright : (c) Peopleware
 Description : This unit contains hacks required by the security
component.
 History :

 Date By Description
 ---- -- -----------
 28/06/2004 AMuylaert Initial creation of the Unit.

****************************************************************************
*}

unit unit_SecurityHacks;

interface

uses Windows;

type

  {
    Use this class to hack/hook a static procedure
    the procedure needs to be at least 10 bytes.
    If you have no idea what this does, get the f**k out of this unit.
  }
  TStaticHack = class(TObject)
  private
    FOrgCode : Pointer;
    FOrgSize : Integer;
    FOrgCopy : Pointer;
    FNewCode : Pointer;
    FHacked : Boolean;
  private
    procedure CopyOriginal;
    procedure ReplaceWithHack;
    procedure RestoreOriginal;
  public
    constructor Create(aOrgCode, aNewCode : Pointer; aOrgSize : Integer);
    destructor Destroy; override;
    procedure Hack;
    procedure Restore;
  public
    property Hacked : Boolean read FHacked;
  end;

implementation

uses SysUtils;

{ TStaticHack }

constructor TStaticHack.Create(aOrgCode, aNewCode: Pointer;
  aOrgSize: Integer);
begin
  if aOrgSize < 10 then begin
    raise Exception.Create('Cannot perform this hack on code less then 10
bytes');
  end;
  FOrgCode := aOrgCode;
  FOrgSize := aOrgSize;
  FNewCode := aNewCode;

  FOrgCopy := nil;
end;

destructor TStaticHack.Destroy;
begin
  Restore;
  if assigned(FOrgCopy) then FreeMemory(FOrgCopy);
  inherited Destroy;
end;

procedure TStaticHack.CopyOriginal;
var
  OldProtect, TempProtect : DWord;
begin
  {Change page access to pointer, otherwise AV}
  if VirtualProtect(FOrgCode, FOrgSize, PAGE_EXECUTE_READWRITE, OldProtect)
then try
    {Allocate the buffer}

    { This needs to be virtual memory, since we are changing the MemAccess.
      We need a fresh page, because the Virtualprotect changes the thing for
      the whole page. }
    FOrgCopy := VirtualAlloc(nil, FOrgSize, MEM_COMMIT, PAGE_READWRITE);

    {Copy the memory}
    Move(FOrgCode^, FOrgCopy^, FOrgSize);

    {Give copy execute rights}
    if not VirtualProtect(FOrgCopy, FOrgSize, PAGE_EXECUTE, tempProtect)
then begin
      raiseLastWin32Error;
    end;
  finally
    {Restore original page access}
    VirtualProtect(FOrgCode, FOrgSize, OldProtect, OldProtect);
  end else begin
    RaiseLastWin32Error;
  end;
end;

procedure TStaticHack.ReplaceWithHack;
type
  PHack = ^THack;
  THack = packed record
    OpCodeNew : Byte;
    OFFToNew : Integer;
    OpCodeOrg : Byte;
    OFFToOrg : Integer;
  end;
var
  OldProtect : DWord;
  Temp : Pointer;
begin
  {Change page access to pointer, otherwise AV}
  if VirtualProtect(FOrgCode, FOrgSize, PAGE_EXECUTE_READWRITE, OldProtect)
then try
    {Create a temp buffer with all bytes set to zero}
    Temp := AllocMem(FOrgSize);
    try
      {Prepare hack}
      with PHack(Temp)^ do begin
        OpCodeNew := $E8; {E8 = CALL}
        OFFToNew := PChar(FNewCode) - PChar(FOrgCode) - 5;
        OpCodeOrg := $E9; {E9 = JMP
        {Something is wrong with this pointer}
        OFFToOrg := PChar(FOrgCopy) - PChar(FOrgCode) - 10;
      end;
      {Replacement}
      Move(Temp^, FOrgCode^, FOrgSize);
    finally
      FreeMem(Temp);
    end;
  finally
    {Restore original page access}
    VirtualProtect(FOrgCode, FOrgSize, OldProtect, OldProtect);
  end else begin
    RaiseLastWin32Error;
  end;
end;

procedure TStaticHack.Hack;
var
  OldProtect : DWord;
begin
  if FHacked then exit;
  FHacked := True;

  CopyOriginal;
  ReplaceWithHack;
end;

procedure TStaticHack.Restore;
begin
  if not FHacked then exit;
  FHacked := False;
end;

procedure TStaticHack.RestoreOriginal;
var
  OldProtect : DWord;
begin
  {Change page access to pointer, otherwise AV}
  if VirtualProtect(FOrgCode, FOrgSize, PAGE_EXECUTE_READWRITE, OldProtect)
then try
    {restore the memory}
    Move(FOrgCopy^, FOrgCode^, FOrgSize);
    {Free the buffer}
    VirtualFree(FOrgCopy, 0, MEM_RELEASE);
    FOrgCopy := nil;
  finally
    {Restore original page access}
    VirtualProtect(FOrgCode, FOrgSize, OldProtect, OldProtect);
  end else begin
    RaiseLastWin32Error;
  end;
end;

end.

{--TEST CODE STARTS HERE}

type
  TSecurityControlHack = class(TObject)
  private
    FVisibleHack : TStaticHack;
  published
    procedure SetVisible(aValue : Boolean);
  public
    constructor Create;
   destructor Destroy; override;
  end;

procedure TSecurityControlHack.SetVisible(aValue: Boolean);
begin
  {
    Warning!! Since this is a dirty hack
    The register EAX and therefor "self" doesn't reference to a
TSecurityControlHack
    It references to the control!!!
  }
  beep;
end;

  TMakePropertiesPublishedHack = class(TControl)
  published
    property Visible;
  end;

constructor TSecurityControlHack.Create;
var
  Prop : PPropInfo;
begin
   inherited Create;
  {$IFDEF VER150}
    {This is written for D7}
    Prop := GetPropInfo(TMakePropertiesPublishedHack, 'Visible',
[Low(TTypeKind)..High(TTypeKind)]);
    FVisibleHack := TStaticHack.Create( Prop^.SetProc,
                                        MethodAddress('SetVisible'),
                                        60); {we know the length of
TControl.SetVisible = 60 bytes}
    FVisibleHack.Hack;
  {$ENDIF}
end;

destructor TSecurityControlHack.Destroy;
begin
    FVisibleHack.Restore;
    FVisibleHack.Free;
    inherited Destroy;
end;