首页
论坛
课程
招聘
[分享]API Hook程序
2007-8-3 13:39 10059

[分享]API Hook程序

2007-8-3 13:39
10059
本程序来自EurekaLog for Delphi中的EHook.pas,
经过修改后可以单独运行.
本单元实现了两种API Hook技术.
一是采用了更改各个模块中函数入口指针,实现各模块调用API时被 Hook.优点是可以选择性的对某个模块(OCX,DLL..)访问
某个API时被hook.
二是采用了所有模块都被Hook,通过修改API函数第一条指令为跳转到用户API,备份原API内容.用户可以在自己定义的API上调用原来的API.
本单元还实现了UnHook.
是一个较完整的API Hook 库程序
有兴趣的朋友可以将这个API Hook Lib扩展到对其他进程的API Hook.
本文末尾将介绍两种Hook技术的使用方法.

EHookLib.pas:

{************************************************}
{                                                }
{               EurekaLog v 6.x                  }
{              Hook Unit - EHook                 }
{                                                }
{  Copyright (c) 2001 - 2007 by Fabio Dell'Aria  }
{                                                }
{************************************************}

unit EHookLIB;

//{$I Exceptions.inc}

interface

uses Windows; 

type
  THandle = Cardinal;
  PPointer = ^Pointer;
  PShortInt = ^ShortInt;

function HookProcedureEx(ProcAddr, NewProc: Pointer; ProcName: string): Pointer;
function UnhookProcedure(ProcAddr: Pointer): Boolean;

function HookDllProcedureEx(ImportModule, ExportModule, ProcName: string;
  NewProc: Pointer): Pointer;
function TryHookDllProcedureEx(ImportModules: array of string;
  ExportModule, ProcName: string; NewProc: Pointer;
  var CallProc: Pointer; CanFail: Boolean): Boolean;
function TryHookProcedureEx(ExportModule, ProcName: string; NewProc: Pointer;
  var CallProc: Pointer): Boolean;

function HookVirtualMethod(AClass: TClass; Index: Integer; Method: Pointer): Pointer;
function UnhookVirtualMethod(AClass: TClass; Index: Integer): Boolean;

procedure JumpToMem(Addr, Jump: Pointer);

function GetFunctionSize(Addr, MaxSize: DWord): DWord;
function GetAsmSize(Start: Pointer; var Size: Byte): Boolean;

var
  CriticalError: procedure (const Section: string) = nil;

implementation

uses Classes, SysUtils;

const
  EProcNullStr = 'Cannot hook a null procedure ("%s").';
  ESharedAreaStr = 'Cannot hook the module "%s" located into the shared-area.';
  EHookingErrorStr = 'Cannot hook the procedure "%s".';

  SharedMem = $7FFFFFFF; // Don't use major value because Delphi3 don't support it.

  ModRmMod = $C0; // XX??????
  ModRmRM = $07; //  ?????XXX

  OperSizeOver = $66; // Change the operand size from 32 to 16/8 bits.
  AddrSizeOver = $67; // Change the address size from 32 to 16/8 bits.

  OpCodePrefixes: set of Byte =
    [$F0, $F2, $F3, $2E, $36, $3E, $26, $64, $65, OperSizeOver, AddrSizeOver];

  OpCodeShortJump: set of Byte = [$70..$7F, $E0..$E3, $EB]; // 1 OpCode byte

  OpCodeReturn: set of Byte = [$C2, $C3..$CA, $CB]; // "Return" first byte OpCodes

  OpCodeLongJump1Byte: set of Byte = [$E8..$E9]; // 1 OpCode byte

  OpCodeLongJump2Bytes: set of Byte = [$80..$8F]; // 2 OpCode bytes, 1th = $0F

  AsmConst: array [0..255] of Byte = ($EE, $EE, $EE, $EE, $F1, $0B, $00, $00,
    $0E, $0E, $FE, $FE, $F1, $EB, $00, $FF, $EE, $EE, $EE, $EE, $E1, $EB, $E0,
    $E0, $EE, $FE, $FE, $FE, $F1, $FB, $F0, $F0, $EE, $EE, $EE, $EE, $F1, $FB,
    $FF, $F0, $EE, $EE, $EE, $EE, $E1, $EB, $EF, $E0, $0E, $0E, $0E, $0E, $01,
    $0B, $FF, $F0, $FE, $FE, $FE, $FE, $F1, $FB, $FF, $F0, $E0, $E0, $E0, $E0,
    $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0,
    $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0,
    $EE, $EE, $EF, $EF, $EF, $EF, $EB, $EE, $E1, $EE, $F0, $F0, $E0, $E0, $E1,
    $E1, $E1, $E1, $E1, $E1, $E1, $01, $F1, $F1, $F1, $F1, $F1, $F1, $E1, $E1,
    $BE, $BE, $BE, $BE, $BE, $BE, $BE, $BE, $BE, $BE, $BE, $BE, $BE, $BE, $BE,
    $BE, $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0, $E0, $ED, $E0, $E0, $E0,
    $E0, $E0, $04, $04, $04, $E4, $E0, $E0, $E0, $E0, $01, $0B, $00, $E0, $E0,
    $E0, $E0, $E0, $E1, $E1, $E1, $E1, $E1, $E1, $E1, $E1, $FB, $FB, $EB, $EB,
    $EB, $EB, $EB, $EB, $EE, $EE, $E2, $E0, $EE, $EE, $EE, $EE, $03, $00, $02,
    $00, $00, $01, $00, $00, $FE, $EE, $EE, $EE, $E1, $E1, $F0, $E0, $EE, $EE,
    $EE, $EE, $EE, $EE, $EE, $EE, $E1, $E1, $E1, $E1, $E1, $E1, $F1, $E1, $EB,
    $EB, $ED, $E1, $E0, $E0, $E0, $E0, $FF, $E0, $EF, $EF, $E0, $E0, $EE, $EE,
    $E0, $E0, $E0, $E0, $E0, $E0, $EE, $FE);

type
  EHookError = class(Exception);
  EProcNull = class(EHookError);
  EHookingError = class(EHookError);
  ESharedArea = class(EHookError);

  EIgnoreException = class(Exception);
  TProc = procedure;

  TRedirectOpCodes = packed record
    JMPOpCode: Byte;
    JMPDistance: DWord;
  end;

  TPrefixes = set of Byte;

  THookedProcedure = record
    OriginalProc, HookedBlockPt: Pointer;
    HookedBlockSize: DWord;
    POriginalAsmPt: Pointer;
    POriginalAsmSize: DWord;
  end;
  PHookedProcedure = ^ THookedProcedure;

  PSaveDLLProc = ^TSaveDLLProc;
  TSaveDLLProc = packed record
    HookModule: THandle;
    ExportModule: string;
    OldProc, NewProc: Pointer;
  end;

  THookedData = packed record
    ClassType: TClass;
    OriginalMethod: Pointer;
    Index: Integer;
  end;
  PHookedData = ^THookedData;

  PWin9xDebugThunk = ^TWin9xDebugThunk;
  TWin9xDebugThunk = packed record
    PUSH: Byte;    // PUSH instruction opcode ($68)
    Addr: Pointer; // The actual address of the DLL routine
    JMP: Byte;     // JMP instruction opcode ($E9)
    Rel: Integer;  // Relative displacement (a Kernel32 address)
  end;

  IMAGE_IMPORT_DESCRIPTOR = packed record
    UnUsed: array [0..11] of Byte;
    Name: DWord;
    FirstThunk: DWord;  // RVA to IAT
  end;
  PImageImportDescriptor = ^IMAGE_IMPORT_DESCRIPTOR;

  IMAGE_THUNK_DATA = packed record
    Function_: DWord; // PDWord
  end;
  PImageThunkData = ^IMAGE_THUNK_DATA;

  PImageDosHeader = ^TImageDosHeader;
  TImageDosHeader = packed record    // DOS .EXE header
      e_magic: Word;                 // Magic number
      UnUsed: array [0..57] of Byte;
      _lfanew: LongInt;              // File address of new exe header
  end;

  THookedMethodsList = class(TList)
  private
    FLock: TRTLCriticalSection;
    function GetItem(Index: Integer): PHookedData;
  protected
  public
    constructor Create;
    destructor Destroy; override;
    procedure Lock;
    procedure Unlock;
    procedure Delete(Index: Integer);
    property Items[Index: Integer]: PHookedData read GetItem; default;
  end;

const
  TRedirectOpCodesSize = SizeOf(TRedirectOpCodes);

var
  HookedProcedures, DllList: TList;
  HookedMethodsList: THookedMethodsList;

//------------------------------------------------------------------------------

{ THookedMethods }

constructor THookedMethodsList.Create;
begin
  inherited;
  InitializeCriticalSection(FLock);
end;

function THookedMethodsList.GetItem(Index: Integer): PHookedData;
begin
  Result := PHookedData(TList(Self).Items[Index]);
end;

procedure THookedMethodsList.Lock;
begin
  EnterCriticalSection(FLock);
end;

procedure THookedMethodsList.Unlock;
begin
  LeaveCriticalSection(FLock);
end;

procedure THookedMethodsList.Delete(Index: Integer);
var
  Data: PHookedData;
  Ptr: Pointer;
begin
  Ptr := Items[Index];
  Data := PHookedData(Ptr);
  Dispose(Data);
  inherited;
end;

destructor THookedMethodsList.Destroy;
var
  I: Integer;
begin
  Lock;
  try
    for I := 0 to HookedMethodsList.Count - 1 do
      UnhookVirtualMethod(HookedMethodsList[0]^.ClassType, HookedMethodsList[0]^.Index);
  finally
    Unlock;
  end;
  DeleteCriticalSection(FLock);
  inherited;
end;

//------------------------------------------------------------------------------

function GetReadableSize(Address, Size: DWord): DWord;
const
  ReadAttributes = [PAGE_READONLY, PAGE_READWRITE, PAGE_WRITECOPY, PAGE_EXECUTE,
    PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, PAGE_EXECUTE_WRITECOPY];
var
  MemInfo: TMemoryBasicInformation;
  Tmp: DWord;
begin
  Result := 0;
  if (VirtualQuery(Pointer(Address), MemInfo, SizeOf(MemInfo)) = SizeOf(MemInfo)) and
    (MemInfo.State = MEM_COMMIT) and (MemInfo.Protect in ReadAttributes) then
  begin
    Result := (MemInfo.RegionSize - (Address - DWord(MemInfo.BaseAddress)));
    if (Result < Size) then
    begin
      repeat
        Tmp := GetReadableSize((DWord(MemInfo.BaseAddress) + MemInfo.RegionSize), (Size - Result));
        if (Tmp > 0) then Inc(Result, Tmp)
        else Result := 0;
      until (Result >= Size) or (Tmp = 0);
    end;
  end;
end;

function IsValidBlockAddr(Address, Size: DWord): Boolean;
begin
  Result := (GetReadableSize(Address, Size) >= Size);
end;

function ConvertAddress(Addr: DWord): DWord;
type
  TJMPCode = packed record
    JMPOpCode: Word;
    JMPPtr: PDWord;
    MOVOpCode: Word;
  end;
  PJMPCode = ^TJMPCode;
var
  JMP: PJMPCode;
begin
  Result := Addr;
  if (IsValidBlockAddr(Addr, 8)) then
  begin
    JMP := PJMPCode(Addr);
    if (JMP^.JMPOpCode = $25FF) and (IsValidBlockAddr(DWord(JMP^.JMPPtr), 4)) then
      Result := JMP^.JMPPtr^;
  end;
end;

//------------------------------------------------------------------------------

function GetVirtualMethod(AClass: TClass; const Index: Integer): Pointer;
begin
  Result := PPointer(Integer(AClass) + (Index * 4))^
end;

procedure SetVirtualMethod(AClass: TClass; Index: Integer; Method: Pointer);
var
  PatchAddress: PPointer;
  OldProtectionCode: DWord;
begin
  PatchAddress := PPointer(Integer(AClass) + (Index * 4));
  if (FindHInstance(PatchAddress) = 0) then Exit; // Check for unloaded module...
  VirtualProtect(PatchAddress, 4, PAGE_EXECUTE_READWRITE, @OldProtectionCode);
  PatchAddress^ := Method;
  VirtualProtect(PatchAddress, 4, OldProtectionCode, @OldProtectionCode);
  FlushInstructionCache(GetCurrentProcess, PatchAddress, 4);
end;

function HookVirtualMethod(AClass: TClass; Index: Integer; Method: Pointer): Pointer;
var
  HData: PHookedData;
  n: Integer;
begin
  Result := nil;
  if (Assigned(HookedMethodsList)) then
  begin
    HookedMethodsList.Lock;
    try
      Result := GetVirtualMethod(AClass, Index);
      if (Result = Method) then
      begin // Just hooked...
        for n := 0 to (HookedMethodsList.Count - 1) do
        begin
          if ((HookedMethodsList[n]^.ClassType = AClass) and
          (HookedMethodsList[n]^.Index = Index)) then
          begin
            Result := HookedMethodsList[n]^.OriginalMethod;
            Break;
          end;
        end;
      end
      else
      begin // First hook...
        SetVirtualMethod(AClass, Index, Method);
        New(HData);
        HData^.ClassType := AClass;
        HData^.OriginalMethod := Result;
        HData^.Index := Index;
        HookedMethodsList.Add(HData);
      end;
    finally
      HookedMethodsList.Unlock;
    end;
  end;
end;

function UnhookVirtualMethod(AClass: TClass; Index: Integer): Boolean;
var
  n: Integer;
begin
  Result := False;
  if (Assigned(HookedMethodsList)) then
  begin
    HookedMethodsList.Lock;
    try
      for n := 0 to (HookedMethodsList.Count - 1) do
      begin
        if ((HookedMethodsList[n]^.ClassType = AClass) and
        (HookedMethodsList[n]^.Index = Index)) then
        begin
          SetVirtualMethod(AClass, Index, HookedMethodsList[n]^.OriginalMethod);
          HookedMethodsList.Delete(n);
          Result := True;
          Break;
        end;
      end;
    finally
      HookedMethodsList.Unlock;
    end;
  end;
end;

//------------------------------------------------------------------------------

procedure WriteMem(Addr: Pointer; const Data; Size: DWord);
var
  OldProtectionCode: DWord;
begin
  VirtualProtect(Addr, Size, PAGE_EXECUTE_READWRITE, @OldProtectionCode);
  Move(Data, Addr^, Size);
  VirtualProtect(Addr, Size, oldProtectionCode, @OldProtectionCode);
  FlushInstructionCache(GetCurrentProcess, Addr, Size);
end;

procedure JumpToMem(Addr, Jump: Pointer);
var
  JumpOpCode: TRedirectOpCodes;
begin
  JumpOpCode.JMPOpCode := $E9; // JMP OpCode
  JumpOpCode.JMPDistance := (DWord(Jump) - DWord(Addr) - 5); // JMP Distance
  WriteMem(Addr, JumpOpCode, TRedirectOpCodesSize);
end;

function ModuleFileName(HModule: THandle): string;
var
  Buff: array[0..MAX_PATH - 1] of Char;
begin
  GetModuleFileName(HModule, Buff, SizeOf(Buff));
  Result := Buff;
end;

function ModRMByte(Prefixes: TPrefixes; OpCodeSize, OpCode, ModRM, SID: Byte): Byte;
var
  RmMod, RmRM: Byte;

  function AddrSize: Byte;
  begin
    Result := 4;
    if (OperSizeOver in Prefixes) then Dec(Result, 2);
  end;

  function SIDSize: Byte;
  begin
    Result := 1;
    if (SID and $07 = $05) then Inc(Result, 4);
  end;

begin
  Result := 0;
  RmMod := (ModRM and ModRmMod) shr 6;
  RmRM := (ModRM and ModRmRM);
  if (not (AddrSizeOver in Prefixes)) then
    case rmMod of // 32 bit mode...
      0: begin
          Result := 0;
          if (RmRM = 4) then Inc(Result, SIDSize)
          else
            if (RmRM = 5) then Inc(Result, 4);
        end;
      1: begin
          Result := 1;
          if (RmRM = 4) then Inc(Result);
        end;
      2: begin
          Result := 4;
          if (RmRM = 4) then Inc(Result);
        end;
      3: Result := 0;
    end
  else
    case rmMod of // 16 bit mode...
      0: begin
          Result := 0;
          if (RmRM = 6) then Inc(Result, 2);
        end;
      1: Result := 1;
      2: Result := 2;
      3: Result := 0;
    end;

  if (opCodeSize = 1) then // OpCode extensions...
  begin
    if (OpCode in [$6B, $80, $82, $83, $C0, $C1, $C6]) then Inc(Result)
    else
      if (OpCode in [$69, $81, $C7]) then Inc(Result, AddrSize)
      else
        if (OpCode = $F6) and (ModRM and $38 = 0) then Inc(Result)
        else
          if (OpCode = $F7) and (ModRM and $38 = 0) then Inc(Result, AddrSize);
  end
  else
    if (OpCode in [$70, $71, $72, $73, $A4, $AC, $BA, $C2, $C4, $C5, $C6]) then Inc(Result);
end;

function GetAsmSize(Start: Pointer; var Size: Byte): Boolean;
var
  OpCode, OpCodeSize, OpCodeType, Mask, Shift, ModRM, PrefixesSize: Byte;
  Ptr: PByte;
  Prefixes: TPrefixes;
begin
  Size := 1;
  Prefixes := [];
  Ptr := Start;
  repeat
    OpCode := Ptr^;
    if (OpCode in [AddrSizeOver, OperSizeOver]) then Prefixes := Prefixes + [OpCode];
    Inc(Ptr);
  until (not (OpCode in OpCodePrefixes));
  PrefixesSize := (DWord(Ptr) - DWord(Start) - 1);
  if (OpCode = $0F) then
  begin
    OpCodeSize := 2;
    OpCode := Ptr^;
    Inc(Ptr);
    Mask := $F0;
    Shift := 4;
  end
  else
  begin
    OpCodeSize := 1;
    Mask := $0F;
    Shift := 0;
  end;
  OpCodeType := ((AsmConst[OpCode] and Mask) shr Shift);
  Result := (OpCodeType <> $0F);
  if (Result) then
  begin
    if (OpCodeType < $0E) then
    begin
      Size := (OpCodeType + OpCodeSize);
      if (Size > OpCodeSize + 6) then
      begin
        Dec(Size, 7);
        if (OperSizeOver in Prefixes) then Dec(Size, 2);
      end;
    end
    else
    begin
      ModRM := Ptr^;
      Inc(Ptr);
      Size := (ModRMByte(Prefixes, OpCodeSize, OpCode, ModRM, Ptr^) + OpCodeSize + 1);
    end;
    Inc(Size, PrefixesSize);
  end;
end;

function GetFunctionSize(Addr, MaxSize: DWord): DWord;
var
  AsmSize: DWord;
  OpSize, OpCode: Byte;
  Pt, PtEnd: PChar;
begin
  Result := 1;

  if (MaxSize = 0) then
  begin
    Result := 0;
    Exit;
  end;

  try
    Pt := PChar(Addr);
    PtEnd := PChar(Pt + MaxSize - 1);
    AsmSize := 0;
    while (Pt <= PtEnd) do
    begin
      if (GetAsmSize(Pt, OpSize)) then
      begin
        // Skip the prefixes OpCodes...
        while ((PByte(Pt)^ in OpCodePrefixes) and (Pt <= PtEnd)) do Inc(Pt);

        OpCode := PByte(Pt)^;
        if (OpCode in OpCodeReturn) then
        begin
          Result := (AsmSize + OpSize);
          Exit;
        end;
      end
      else OpSize := 1;
      Inc(AsmSize, OpSize);
      Inc(Pt, OpSize);
    end;
  except
    Result := 0;
  end;
end;

function CalculateRelocatedAsmSize(Addr: Pointer; Size: Word): DWord;
var
  AsmSize: DWord;
  OpSize, OpCode: Byte;
  Pt, PtStart, PtEnd, JmpTo: PChar;
  Delta: Integer;
begin
  Pt := PChar(Addr);
  PtStart := Pt;
  PtEnd := PChar(Pt + Size - 1);
  Result := Size;
  AsmSize := 0;
  while (AsmSize < Size) do
  begin
    if (GetAsmSize(Pt, OpSize)) then
    begin
      // Skip the prefixes OpCodes...
      while ((PByte(Pt)^ in OpCodePrefixes) and (Pt <= PtEnd)) do Inc(Pt);

      OpCode := PByte(Pt)^;
      if (OpCode in OpCodeShortJump) then
      begin
        Delta := PShortInt(Pt + 1)^;
        JmpTo := (Pt + 2 + Delta);
        if ((JmpTo < PtStart) or (JmpTo > PtEnd + 1)) then Inc(Result, 5);
      end;
    end
    else OpSize := 1;
    Inc(AsmSize, OpSize);
    Inc(Pt, OpSize);
  end;
end;

procedure RelocateMemory(NewAddr, OldAddr: Pointer; Size: DWord);
var
  AsmSize: DWord;
  OpSize, OpCode, OpBytes: Byte;
  OldPt, Pt, PtStart, PtEnd, JmpTo, ShortJumpsPt: PChar;
  NewDistance, Distance, Delta: Integer;
begin
  OldPt := OldAddr;
  Pt := PChar(NewAddr);
  PtStart := Pt;
  PtEnd := PChar(Pt + Size - 1);
  ShortJumpsPt := (Pt + Size + SizeOf(TRedirectOpCodes));
  AsmSize := 0;
  while (AsmSize < Size) do
  begin
    if (GetAsmSize(Pt, OpSize)) then
    begin
      // Skip the prefixes OpCodes...
      while ((PByte(Pt)^ in OpCodePrefixes) and (Pt <= PtEnd)) do Inc(Pt);

      // Check for 2 bytes OpCode instructions...
      OpCode := PByte(Pt)^;
      if (OpCode = $0F) then // 2 bytes OpCode size
      begin
        Inc(Pt);
        OpCode := PByte(Pt)^;
        Dec(OpSize);
        OpBytes := 2;
      end
      else OpBytes := 1;

      // Search for relative Jump/Call instructions...
      if ((OpBytes = 1) and (OpCode in OpCodeShortJump)) then
      begin
        Distance := PShortInt(Pt + 1)^;
        JmpTo := (Pt + 2 + Distance);

        // Check if need relocation...
        if (JmpTo < PtStart) or (JmpTo > (PtEnd + 1)) then
        begin
          JmpTo := (OldPt + Integer(AsmSize) + OpSize + Distance);
          JumpToMem(ShortJumpsPt, JmpTo);
          Distance := (ShortJumpsPt - (Pt + 2));
          WriteMem((Pt + 1), Distance, 1);
          Inc(ShortJumpsPt, SizeOf(TRedirectOpCodes));
        end;
      end
      else
        if ((OpBytes = 1) and (OpCode in OpCodeLongJump1Byte)) or
          ((OpBytes = 2) and (OpCode in OpCodeLongJump2Bytes)) then
        begin
          Distance := PInteger(Pt + 1)^;
          JmpTo := (Pt + 5 + Distance);

          // Check if need relocation...
          if (JmpTo < PtStart) or (JmpTo > (PtEnd + 1)) then
          begin
            Delta := (OldPt + Integer(AsmSize) - Pt + (OpBytes - 1));
            NewDistance := (Distance + Delta);
            WriteMem(Pt + 1, NewDistance, 4);
          end;
        end;
    end
    else OpSize := 1;
    Inc(AsmSize, OpSize);
    Inc(Pt, OpSize);
  end;
end;

function HookProcedure(ProcAddr, NewProc: Pointer): Pointer;
var
  PProc, Pt, PAsm: PChar;
  AsmSize, FullAsmSize, OldProtectionCode: DWord;
  OpSize: Byte;
  n: Integer;
  PHookedBlock: PHookedProcedure;
begin
  for n := 0 to HookedProcedures.Count - 1 do
  begin
    PHookedBlock := PHookedProcedure(HookedProcedures[n]);
    if (ProcAddr = PHookedBlock^.OriginalProc) then
    begin
      Result := PHookedBlock^.HookedBlockPt;
      Exit;
    end;
  end;

  PProc := ProcAddr;
  Pt := PProc;
  AsmSize := 0;
  repeat
    if (not (GetAsmSize(Pt, OpSize))) then OpSize := 1;
    Inc(AsmSize, OpSize);
    Inc(Pt, OpSize);
  until (AsmSize >= 5);
  FullAsmSize := (CalculateRelocatedAsmSize(PProc, AsmSize) + SizeOf(TRedirectOpCodes));
  GetMem(PAsm, FullAsmSize);

  // Save hooked data...
  New(PHookedBlock);
  PHookedBlock^.OriginalProc := ProcAddr;
  PHookedBlock^.HookedBlockPt := PAsm;
  PHookedBlock^.HookedBlockSize := FullAsmSize;
  PHookedBlock^.POriginalAsmSize := AsmSize;
  GetMem(PHookedBlock^.POriginalAsmPt, AsmSize);
  Move(PProc^, PHookedBlock^.POriginalAsmPt^, AsmSize);
  HookedProcedures.Add(PHookedBlock);

  // Transform this data-block into executable code-block.
  VirtualProtect(PAsm, FullAsmSize, PAGE_EXECUTE_READWRITE, @OldProtectionCode);

  // Copy first ASM instructions from Procedure to Hook block...
  Move(PProc^, PAsm^, AsmSize);

  RelocateMemory(PAsm, PProc, AsmSize);

  JumpToMem((PAsm + AsmSize), (PProc + AsmSize)); // JMP from Hook block to Procedure...
  JumpToMem(PProc, NewProc); // JMP from Procedure to Hook block...

  Result := PAsm;
end;

function HookProcedureEx(ProcAddr, NewProc: Pointer; ProcName: string): Pointer;
begin
  ProcAddr := Pointer(ConvertAddress(DWord(ProcAddr)));
  NewProc := Pointer(ConvertAddress(DWord(NewProc)));

  if (ProcAddr = nil) then
    raise EProcNull.CreateFmt(EProcNullStr, [ProcName])
  else
    if (DWord(ProcAddr) > SharedMem) and // Shared Area...
      (Win32Platform <> VER_PLATFORM_WIN32_NT) then // Win9X/ME ...
      raise ESharedArea.CreateFmt(ESharedAreaStr,
        [ModuleFileName(FindHInstance(ProcAddr))]);

  try
    Result := HookProcedure(ProcAddr, NewProc);
  except
    raise EHookingError.CreateFmt(EHookingErrorStr, [ProcName]);
  end;
end;

function UnhookProcedure(ProcAddr: Pointer): Boolean;
var
  n: Integer;
  PHookedBlock: PHookedProcedure;
begin
  Result := False;
  n := 0;
  while (n <= HookedProcedures.Count - 1) do
  begin
    PHookedBlock := PHookedProcedure(HookedProcedures[n]);
    if (ProcAddr = PHookedBlock^.OriginalProc) then
    begin
      WriteMem(PHookedBlock^.OriginalProc, PHookedBlock^.POriginalAsmPt^, PHookedBlock^.POriginalAsmSize);
      FreeMem(PHookedBlock^.POriginalAsmPt, PHookedBlock^.POriginalAsmSize);
      FreeMem(PHookedBlock^.HookedBlockPt, PHookedBlock^.HookedBlockSize);
      FreeMem(PHookedBlock, SizeOf(THookedProcedure));
      HookedProcedures.Delete(n);
      Result := True;
    end;
    Inc(n);
  end;
end;

function HookDllProcedure(ImportModule: THandle; ExportModule: string; OldProc, NewProc: Pointer;
  ProcName: string; CanFail, Unhook: Boolean): Pointer;
var
  FromProcDebugThunk, ImportThunk: PWin9xDebugThunk;
  IsThunked, FoundProc: Boolean;
  NtHeader: PImageNtHeaders;
  ImportDir: TImageDataDirectory;
  ImportDesc: PImageImportDescriptor;
  CurrName: PChar;
  ImportEntry: PImageThunkData;
  Base: Pointer;
  SaveDLLProc: PSaveDLLProc;

  function IsWin9xDebugThunk(P: Pointer): Boolean;
  begin
    with PWin9xDebugThunk(P)^ do
      Result := (PUSH = $68) and (JMP = $E9);
  end;

  // Mapped or loaded image related functions
  function PeMapImgNtHeaders(const BaseAddress: Pointer): PImageNtHeaders;
  begin
    Result := nil;
    if (not IsValidBlockAddr(DWord(BaseAddress), SizeOf(TImageDosHeader))) then Exit;

    if (PImageDosHeader(BaseAddress)^.e_magic <> IMAGE_DOS_SIGNATURE) or
      (PImageDosHeader(BaseAddress)^._lfanew = 0) then Exit;

    Result := PImageNtHeaders(DWORD(BaseAddress) + DWORD(PImageDosHeader(BaseAddress)^._lfanew));
    if (not IsValidBlockAddr(DWord(Result), SizeOf(TImageNtHeaders))) or
      (Result^.Signature <> IMAGE_NT_SIGNATURE) then Result := nil
  end;

  procedure CheckFail;
  begin
    if (not CanFail) then
      raise EHookingError.CreateFmt(EHookingErrorStr, [ProcName]);
  end;

begin
  Result := nil;

  if (OldProc = nil) then
    raise EProcNull.CreateFmt(EProcNullStr, [ProcName]);

  if (ImportModule > SharedMem) and // Shared Area...
    (Win32Platform <> VER_PLATFORM_WIN32_NT) then // Win9X/ME ...
    raise ESharedArea.CreateFmt(ESharedAreaStr, [ModuleFileName(ImportModule)]);

  Base := Pointer(ImportModule);
  FromProcDebugThunk := PWin9xDebugThunk(OldProc);
  IsThunked := (Win32Platform <> VER_PLATFORM_WIN32_NT) and IsWin9xDebugThunk(FromProcDebugThunk);
  NtHeader := PeMapImgNtHeaders(Base);
  if (NtHeader = nil) then
  begin
    CheckFail;
    Exit;
  end;

  ImportDir := NtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
  if (ImportDir.VirtualAddress = 0) then
  begin
    CheckFail;
    Exit;
  end;

  ImportDesc := PImageImportDescriptor(DWORD(Base) + ImportDir.VirtualAddress);
  while (ImportDesc^.Name <> 0) do
  begin
    CurrName := (PChar(Base) + ImportDesc^.Name);
    if (StrIComp(CurrName, PChar(ExportModule)) = 0) then
    begin
      ImportEntry := PImageThunkData(DWORD(Base) + ImportDesc^.FirstThunk);
      while (ImportEntry^.Function_ <> 0) do
      begin
        if IsThunked then
        begin
          ImportThunk := PWin9xDebugThunk(ImportEntry^.Function_);
          FoundProc := IsWin9xDebugThunk(ImportThunk) and
            (ImportThunk^.Addr = FromProcDebugThunk^.Addr);
        end
        else
          FoundProc := Pointer(ImportEntry^.Function_) = OldProc;
        if FoundProc then
        begin
          WriteMem(@ImportEntry^.Function_, NewProc, 4);
          if (not Unhook) then
          begin
            New(SaveDLLProc);
            SaveDLLProc^.OldProc := OldProc;
            SaveDLLProc^.NewProc := NewProc;
            SaveDLLProc^.HookModule := ImportModule;
            SaveDLLProc^.ExportModule := ExportModule;
            DllList.Add(SaveDLLProc);
          end;
          Result := OldProc;
        end;
        Inc(ImportEntry);
      end;
    end;
    Inc(ImportDesc);
  end;

  if (not CanFail) and (Result = nil) then
    raise EHookingError.CreateFmt(EHookingErrorStr, [ProcName]);
end;

function TryHookDllProcedureEx(ImportModules: array of string;
  ExportModule, ProcName: string; NewProc: Pointer;
  var CallProc: Pointer; CanFail: Boolean): Boolean;
var
  TmpProc, OldProc: Pointer;
  HModule: THandle;
  n: integer;
begin
  Result := False;
  OldProc := GetProcAddress(GetModuleHandle(PChar(ExportModule)), PChar(ProcName));

  for n := low(ImportModules) to high(ImportModules) do
  begin
    HModule := GetModuleHandle(PChar(ImportModules[n]));
    if (HModule <> 0) then
    begin
      TmpProc := HookDllProcedure(HModule, ExportModule, OldProc, NewProc,
        ExportModule + '.' + ProcName, CanFail, False);
      Result := (Result) or (TmpProc <> nil);
    end;
  end;

  CallProc := OldProc; // WARNING don't move to HERE!!!
end;

function TryHookProcedureEx(ExportModule, ProcName: string; NewProc: Pointer;
  var CallProc: Pointer): Boolean;
var
  TmpProc, OldProc: Pointer;
begin
  Result := False;
  OldProc := GetProcAddress(GetModuleHandle(PChar(ExportModule)), PChar(ProcName));

  TmpProc := nil;
  if Assigned(OldProc) then TmpProc := HookProcedureEx(OldProc, NewProc, ProcName);

  Result := (Result) or (TmpProc <> nil);
  CallProc := TmpProc; // WARNING don't move to HERE!!!
end;

function HookDllProcedureEx(ImportModule, ExportModule, ProcName: string;
  NewProc: Pointer): Pointer;
var
  OldProc: Pointer;
begin
  OldProc := GetProcAddress(GetModuleHandle(PChar(ExportModule)), PChar(ProcName));

  Result := HookDllProcedure(GetModuleHandle(PChar(ImportModule)), ExportModule,
    OldProc, NewProc, ExportModule + '.' + ProcName, False, False);
end;

//------------------------------------------------------------------------------

procedure Init;
begin
  DllList := TList.Create;
  HookedMethodsList := THookedMethodsList.Create;
  HookedProcedures := TList.Create;
end;

procedure Done;
var
  n: Integer;
  P: PSaveDLLProc;
  PHookedBlock: PHookedProcedure;
begin
  for n := 0 to DllList.Count - 1 do
  begin
    P := PSaveDLLProc(DllList[n]);
    HookDLLProcedure(P^.HookModule, P^.ExportModule, P^.NewProc, P^.OldProc, '', True, True);
    Dispose(P);
  end;
  DllList.Free;
  DllList := nil;
  HookedMethodsList.Free;
  HookedMethodsList := nil;
  for n := HookedProcedures.Count - 1 downto 0 do
  begin
    PHookedBlock := HookedProcedures[n];
    UnhookProcedure(PHookedBlock^.OriginalProc);
  end;
  HookedProcedures.Free;
  HookedProcedures := nil;
end;

//------------------------------------------------------------------------------
procedure SafeExec(Proc: TProc; Section: string);
var
  Error: string;
begin
  try
    Proc;
  except
    on Err: TObject do
    begin
      if (Err is EIgnoreException) then raise;

      if (@CriticalError <> nil) then
      begin
        CriticalError(Format('%s (Address: %s)', [Section, IntToHex(DWord(@Proc), 8)]));
        Abort;
      end
      else
      begin
        if (ExceptObject is Exception) then Error := Exception(ExceptObject).Message
        else Error := 'General internal error.';
        raise Exception.CreateFmt('Critical error at: "%s"'#13#10'Error: "%s".', [Section, Error]);
      end;
    end;
  end;
end;

//------------------------------------------------------------------------------
initialization
  SafeExec(Init, 'EHook.Init');

finalization
  SafeExec(Done, 'EHook.Done');

end.


下面是示例程序:
type
  //保存原API函数地址
  Kernel_WriteFile: function(hFile: Integer; const Buffer; nNumberOfBytesToWrite: Cardinal;
    var lpNumberOfBytesWritten: Cardinal; lpOverlapped: Pointer): Integer; stdcall;
  //自定义api函数
function MyWriteFile(hFile: THandle; Buffer:PPChar; nNumberOfBytesToWrite: DWORD;
  var lpNumberOfBytesWritten: DWORD; lpOverlapped: POverlapped): BOOL; stdcall;
var
  i:DWORD;
begin
   //将所有写入的数据取反
    for i:=LongWord(Buffer) to LongWord(Buffer)+nNumberOfBytesToWrite-1 do
    begin
      A:=PByte(i)^;
      A:=not A;
      PByte(i)^:=A;
    end;
  //调用原来的系统文件
  Result:=Kernel_WriteFile(hFile, Buffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
end;

上面准备好了用户自定义API,和保存系统API函数指针.
下面介绍用法:
1.对单独模块进行API Hook
例如,假设我们的程序中包括了Mapx5.ocx, lin.dll和其他一些dll模块,
我们只想Hook程序的MapX5.OCX和 Lin.dll两个模块的WriteFile这个API函数,
调用函数TryHookDllProcedureEx:
   S:=ExtractFilePath(Application.ExeName);//获取路径
    // Hooked "WriteFile" Windows API...
    TryHookDllProcedureEx(
      [S+'MapX5.OCX', S+'Lin.dll'], //仅改变Mapx5.ocx, lin.dll两个模块的WriteFile功能
      kernel32, 'WriteFile',
      @HookedWriteFile, @Kernel_WriteFile, True);

2.对所有模块进行API Hook
更简单,调用函数TryHookProcedureEx
  TryHookProcedureEx(
    kernel32, 'CreateFileA',
    @MyCreateFileA, @Kernel_CreateFileA);


Unhook某个函数:
更更简单,调用  UnHookProcedure:
  UnHookProcedure(@Kernel_WriteFile);

[注意] 欢迎加入看雪团队!base上海,招聘安全工程师、逆向工程师多个坑位等你投递!

收藏
点赞0
打赏
分享
最新回复 (4)
雪    币: 2644
活跃值: 活跃值 (578)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
kagayaki 活跃值 2007-8-9 02:18
2
0
收藏.....
雪    币: 38
活跃值: 活跃值 (16)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
炉子 活跃值 3 2007-8-10 00:19
3
0
IAT hook?
偶用vb最满意的就是可以防iat hook :)
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ertyui 活跃值 2007-9-22 20:32
4
0
怎么用,直接编译吗?
雪    币: 201
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hexi 活跃值 2007-9-23 12:32
5
0
直接编译即可用.
游客
登录 | 注册 方可回帖
返回