APIHook一直是使大家感兴趣的话题。屏幕取词,内码转化,屏幕翻译,中文平台等等都涉及到了此项技术。有很多文章涉及到了这项技术,但都闪烁其词不肯明明白白的公布。我仅在这里公布以下我用Delphi制作APIHook的一些心得。
       通常的APIHOOK有这样几种方法:
      1、自己写一个动态链接库,里面定义自己写的想取代系统的API。把这个动态链接库映射到2G以上的系统动态链接库所在空间,把系统动态链接库中的该API的指向修改指向自己的函数。这种方法的好处就是可以取代系统中运行全部程序的该API。但他有个局限,就是只适用于Win9x。(原因是NT中动态链接库不是共享的,每个进程都有自己的一份动态链接库在内存中的映射)
      2、自己写一个动态链接库,里面定义自己写得象替代系统的API。把这个动态链接库映射到进程的空间里。将该进程对API的调用指向自己写的动态链接库。这种方法的好处是可以选择性的替代哪个进程的API。而且适用于所有的Windows操作系统。


      这里我选用的是第二种方法。
      第二种方法需要先了解一点PE文件格式的知识。
       首先是一个实模式的的DOS文件头,是为了保持和DOS的兼容。
       接着是一个DOS的代理模块。你在纯DOS先运行Win32的可执行文件,看看是不是也执行了,只是显示的的是一行信息大意是说该Windows程序不能在DOS实模式下运行。
       然后才是真正意义上的Windows可执行文件的文件头。它的具体位置不是每次都固定的。是由文件偏移$3C决定的。我们要用到的就是它。
       如果我们在程序中调用了一个MessageBoxA函数那么它的实现过程是这样的。他先调用在本进程中的MessageBoxA函数然后才跳到动态链接库的MessageBoxA的入口点。即:
       call messageBoxA(0040106c)
       jmp dword ptr [_jmp_MessageBoxA@16(00425294)]
其中00425294的内容存储的就是就是MessageBoxA函数的入口地址。如果我们做一下手脚,那么......
      那就开始吧!
我们需要定义两个结构
type
   PImage_Import_Entry = ^Image_Import_Entry;
   Image_Import_Entry = record
      Characteristics: DWORD;
      TimeDateStamp: DWORD;
      MajorVersion: Word;
      MinorVersion: Word;
      Name: DWORD;
      LookupTable: DWORD;
   end;
type
   TImportCode = packed record
      JumpInstruction: Word; file: //定义跳转指令jmp
      AddressOfPointerToFunction: ^Pointer; file: //定义要跳转到的函数
   end;
   PImportCode = ^TImportCode;
然后是确定函数的地址。
function LocateFunctionAddress(Code: Pointer): Pointer;
var
   func: PImportCode;
begin
   Result := Code;
   if Code = nil then exit;
   try
      func := code;
      if (func.JumpInstruction = $25FF) then
      begin
         Result := func.AddressOfPointerToFunction^;
      end;
   except
      Result := nil;
   end;
end;
参数Code是函数在进程中的指针,即那条Jmp XXX的指令。$25FF就是跳转指令的机器码。
再下一篇我会讲如何替换下那个XXX的内容,让他跳到你想去的地方。


在这里我将要实现转跳。有人说修改内存内容要进入Ring 0 才可以。可是Windows本身提供了一个写内存的指令WriteProcessMemory。有了这把利器,我们几乎无所不能。如游戏的修改等在这里我们只谈APIHOOK。
function RepointFunction(OldFunc, NewFunc: Pointer): Integer;
var
   IsDone: TList;
   function RepointAddrInModule(hModule: THandle; OldFunc, NewFunc: Pointer): Integer;
   var
      Dos: PImageDosHeader;
      NT: PImageNTHeaders;
      ImportDesc: PImage_Import_Entry;
      RVA: DWORD;
      Func: ^Pointer;
      DLL: string;
      f: Pointer;
      written: DWORD;
   begin
      Result := 0;
      Dos := Pointer(hModule);
      if IsDone.IndexOf(Dos) >= 0 then exit;
      IsDone.Add(Dos);

      OldFunc := LocateFunctionAddress(OldFunc);

      if IsBadReadPtr(Dos, SizeOf(TImageDosHeader)) then exit;
      if Dos.e_magic <> IMAGE_DOS_SIGNATURE then exit;
      NT := Pointer(Integer(Dos) + dos._lfanew);

      RVA := NT^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
         .VirtualAddress;

      if RVA = 0 then exit;
      ImportDesc := pointer(integer(Dos) + RVA);
      while (ImportDesc^.Name <> 0) do
      begin
         DLL := PChar(Integer(Dos) + ImportDesc^.Name);
         RepointAddrInModule(GetModuleHandle(PChar(DLL)), OldFunc, NewFunc);
         Func := Pointer(Integer(DOS) + ImportDesc.LookupTable);
         while Func^ <> nil do
         begin
            f := LocateFunctionAddress(Func^);
            if f = OldFunc then
            begin
               WriteProcessMemory(GetCurrentProcess, Func, @NewFunc, 4, written);
               if Written > 0 then Inc(Result);
            end;
            Inc(Func);
         end;
         Inc(ImportDesc);
      end;
   end;

begin
   IsDone := TList.Create;
   try
      Result := RepointAddrInModule(GetModuleHandle(nil), OldFunc, NewFunc);
   finally
      IsDone.Free;
   end;
end;
有了这两个函数我们几乎可以更改任何API函数。
我们可以先写一个DLL文件。我这里以修改Text相关函数为例:
先定义几个函数:
type
   TTextOutA = function(DC: HDC; X, Y: Integer; Str: PAnsiChar; Count: Integer): BOOL; stdcall;
   TTextOutW = function(DC: HDC; X, Y: Integer; Str: PWideChar; Count: Integer): BOOL; stdcall;
   TTextOut = function(DC: HDC; X, Y: Integer; Str: PChar; Count: Integer): BOOL; stdcall;
   TDrawTextA = function(hDC: HDC; lpString: PAnsiChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; stdcall;
   TDrawTextW = function(hDC: HDC; lpString: PWideChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; stdcall;
   TDrawText = function(hDC: HDC; lpString: PChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; stdcall;
var
   OldTextOutA: TTextOutA;
   OldTextOutW: TTextOutW;
   OldTextOut: TTextOut;
   OldDrawTextA: TDrawTextA;
   OldDrawTextW: TDrawTextW;
   OldDrawText: TDrawText;
......
function MyTextOutA(DC: HDC; X, Y: Integer; Str: PAnsiChar; Count: Integer): BOOL; stdcall;
begin
   OldTextOutA(DC, X, Y, 'ABC', length('ABC'));
end;

function MyTextOutW(DC: HDC; X, Y: Integer; Str: PWideChar; Count: Integer): BOOL; stdcall;
begin
   OldTextOutW(DC, X, Y, 'ABC', length('ABC'));
end;

function MyTextOut(DC: HDC; X, Y: Integer; Str: PChar; Count: Integer): BOOL; stdcall;
begin
   OldTextOut(DC, X, Y, 'ABC', length('ABC'));
end;

function MyDrawTextA(hDC: HDC; lpString: PAnsiChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; stdcall;
begin
   OldDrawTextA(hDC, 'ABC', length('ABC'), lpRect, uFormat);
end;

function MyDrawTextW(hDC: HDC; lpString: PWideChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; stdcall;
begin
   OldDrawTextW(hDC, 'ABC', length('ABC'), lpRect, uFormat);
end;

function MyDrawText(hDC: HDC; lpString: PChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; stdcall;
begin
   OldDrawText(hDC, 'ABC', length('ABC'), lpRect, uFormat);
end;

调用时我们要把原来的函数地址保存下来:
   if @OldTextOutA = nil then
      @OldTextOutA := LocateFunctionAddress(@TextOutA);
   if @OldTextOutW = nil then
      @OldTextOutW := LocateFunctionAddress(@TextOutW);
   if @OldTextOut = nil then
      @OldTextOut := LocateFunctionAddress(@TextOut);
   if @OldDrawTextA = nil then
      @OldDrawTextA := LocateFunctionAddress(@DrawTextA);
   if @OldDrawTextW = nil then
      @OldDrawTextW := LocateFunctionAddress(@DrawTextW);
   if @OldDrawText = nil then
      @OldDrawText := LocateFunctionAddress(@DrawText);
然后很顺其自然的用自己的函数替换掉原来的函数
   RepointFunction(@OldTextOutA, @MyTextOutA);
   RepointFunction(@OldTextOutW, @MyTextOutW);
   RepointFunction(@OldTextOut, @MyTextOut);
   RepointFunction(@OldDrawTextA, @MyDrawTextA);
   RepointFunction(@OldDrawTextW, @MyDrawTextW);
   RepointFunction(@OldDrawText, @MyDrawText);
        在结束时不要忘记恢复原来函数的入口,要不然你会死得很难看哟!好了我们在写一个Demo程序。你会说怎么文字没有变成ABC呀?是呀,你要刷新一下才行。最小化然后在最大化。看看变了没有。  
        要不然你就写代码刷新一下好了。至于去拦截其他进程的API那就用SetWindowsHookEx写一个其他的钩子将DLL映射进去就行了,我就不再浪费口水了。
掌握了该方法你几乎无所不能。你可以修改其它程序。你可以拦截Createwindow等窗口函数改变其他程序的窗口形状、你还可以入侵其它的程序,你还可以......嘿嘿。干了坏事别招出我来就行了。
我还写了个例子,请在CSDN上下载。

Last modification:July 21st, 2010 at 01:01 pm
如果觉得我的文章对你有用,请随意赞赏