Win2K下的Api函数的拦截
简介:
Api拦截并不是一个新的技术,很多商业软件都采用这种技术。对windows的Api函数的拦截,不外乎两种方法,第一种是Mr. Jeffrey Richter 的修改exe文件的模块输入节,种方法,很安全,但很复杂,而且有些exe文件,没有Dll的输入符号的列表,有可能出现拦截不到的情况。第二种方法就是常用的JMP XXX的方法,虽然很古老,却很简单实用。 
^^^^^^==================================================================
以上是从某处复制过来的,请问有谁知道那个 Mr. Jeffrey Richter 哪里有下的。谢谢了

解决方案 »

  1.   

    我在这里找到一篇文章不知对你有没有帮助:浅谈API HOOK技术(一)
            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的内容,让他跳到你想去的地方。浅谈API HOOK技术(二) 
           在这里我将要实现转跳。有人说修改内存内容要进入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映射进去就行了,我就不再浪费口水了。
      

  2.   

    谢谢了,我现在更想要的是哪个软件,因为我现在有一个DLL文件要分析,如果自已写程序的话,可能太慢了。
      

  3.   

    呵呵,想要例子是吗?
    给你一个屏幕取词的例子,其中用Hook进行API拦截
    你修改一下这个DLL就差不多了你的邮箱?
      

  4.   

    谢谢了,我有一个DLL文件,不知道里面的函数的参数,和一些隐含的函数,有没有什么工具
      

  5.   

    要知道 Mr. Jeffrey Richter 是谁,去查 Microsoft MSDN 上的介绍。
      

  6.   

    用《浅谈API HOOK技术(一)》和(二)的方法有个问题,就是目标程序不能被压缩或者加密(如用ASPack进行压缩)。