实现拦截API的钩子(Hook)

道理不多讲,简单说就是将系统API的跳转地址,替换为我们自己写的API的地址,所以要求我们自定义的API函数要和被拦截的API有相同的参数。在用完后,记得恢复。

因为要挂全局的钩子,所以Hook的部分,做成DLL。   源码下载

Hook.DLL主工程文件代码

[delphi] view plaincopy
 
  1. library Hook;  
  2.   
  3. uses  
  4.   SysUtils,  
  5.   Windows,  
  6.   Classes,  
  7.   ApiDefine in 'ApiDefine.pas',  
  8.   APIHook in 'APIHook.pas';  
  9.   
  10. {$R *.res}  
  11. var  
  12.   HookHandle: HHook;  
  13.   
  14. function HookProc(code:Integer;wparam:WPARAM;lparam:LPARAM):LRESULT;stdcall;  
  15. begin  
  16.   Result := CallNextHookEx(HookHandle,code,wparam,lparam);  
  17. end;  
  18.   
  19. procedure SetHook;stdcall;  
  20. begin  
  21.   HookHandle := SetWindowsHookEx(WH_GETMESSAGE,@HookProc,HInstance,0);  
  22. end;  
  23.   
  24. procedure StopHook;stdcall;  
  25. begin  
  26.   UnhookWindowsHookEx(HookHandle);  
  27. end;  
  28.   
  29. exports  
  30.   SetHook name 'SetHook',  
  31.   StopHook name 'StopHook';  
  32.   
  33. {已启动就挂上,修改API函数指向}  
  34. begin  
  35.   API_Hook;  
  36. end.  

APIHook单元,这个单元实现对API地址的替换

[c-sharp] view plaincopy
 
  1. unit APIHook;  
  2.   
  3. interface  
  4.   
  5. uses  
  6.   Windows, SysUtils, Classes;  
  7.   
  8. type  
  9.   //引入表入口数据结构  
  10.   Image_Import_Entry = packed record  
  11.     OriginalFirstThunk:DWORD;  
  12.     TimeDateStamp:DWORD;  
  13.     ForwarderChain:DWORD;  
  14.     Name:DWORD;  
  15.     FirstThunk:DWORD;  
  16.   end;  
  17.   PImage_Import_Entry = ^Image_Import_Entry;  
  18.   TImportCode = packed record  
  19.     JmpCode: Word;  
  20.     AddressOfPFun: PPointer;  
  21.   end;  
  22.   PImportCode = ^TImportCode;  
  23.   
  24.   function GetFunTrueAddress(Code:Pointer):Pointer;  
  25.   function ReplaceFunAddress(oldfun:Pointer;newfun:Pointer):Integer;  
  26.   
  27. implementation  
  28.   
  29. //获得实际地址  
  30. function GetFunTrueAddress(Code: Pointer): Pointer;  
  31. var  
  32.    func: PImportCode;  
  33. begin  
  34.    Result := Code;  
  35.    if Code = nil then exit;  
  36.    try  
  37.       func := code;  
  38.       if (func.JmpCode = $25FF) then  
  39.       begin  
  40.          Result := func.AddressOfPFun^;  
  41.       end;  
  42.    except  
  43.       Result := nil;  
  44.    end;  
  45. end;  
  46.   
  47. //替换地址  
  48. function ReplaceFunAddress(oldfun:Pointer;newfun:Pointer): Integer;  
  49. var  
  50.    IsDone: TList;  
  51.    function ReplaceAddressInModule(hModule: THandle; OldFunc, NewFunc: Pointer): Integer;  
  52.    var  
  53.       DosHeader: PImageDosHeader;  
  54.       NTHeader: PImageNTHeaders;  
  55.       ImportDesc: PImage_Import_Entry;  
  56.       RVA: DWORD;  
  57.       Func: ^Pointer;  
  58.       DLL: string;  
  59.       f: Pointer;  
  60.       written: DWORD;  
  61.    begin  
  62.       Result := 0;  
  63.       DosHeader := Pointer(hModule);  
  64.       //已经找过,则退出  
  65.       if IsDone.IndexOf(DosHeader) >= 0 then exit;  
  66.       IsDone.Add(DosHeader);  
  67.   
  68.       oldfun := GetFunTrueAddress(OldFunc);  
  69.   
  70.       if IsBadReadPtr(DosHeader, SizeOf(TImageDosHeader)) then exit;  
  71.       if DosHeader.e_magic <> IMAGE_DOS_SIGNATURE then exit;  
  72.       NTHeader := Pointer(Integer(DosHeader) + DosHeader._lfanew);  
  73.       //引入表的虚拟地址  
  74.       RVA := NTHeader^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;  
  75.   
  76.       if RVA = 0 then exit;  
  77.       ImportDesc := pointer(integer(DosHeader) + RVA);  
  78.       while (ImportDesc^.Name <> 0) do  
  79.       begin  
  80.         //引入文件名  
  81.          DLL := PChar(Integer(DosHeader) + ImportDesc^.Name);  
  82.          //获得该DLL的句柄,然后递归查找  
  83.          ReplaceAddressInModule(GetModuleHandle(PChar(DLL)), oldfun, newfun);  
  84.          //引入函数入口  
  85.          Func := Pointer(Integer(DOSHeader) + ImportDesc.FirstThunk);  
  86.          //如果函数指针不为空  
  87.          while Func^ <> nil do  
  88.          begin  
  89.            //取得真是地址  
  90.             f := GetFunTrueAddress(Func^);  
  91.             //如果和我们要拦截的Api函数地址一样  
  92.             if f = oldfun then  
  93.             begin  
  94.               //替换成我们自己的Api地址  
  95.                WriteProcessMemory(GetCurrentProcess, Func, @NewFunc, 4, written);  
  96.                if Written > 0 then Inc(Result);  
  97.             end;  
  98.             //继续找  
  99.             Inc(Func);  
  100.          end;  
  101.          Inc(ImportDesc);  
  102.       end;  
  103.    end;  
  104.   
  105. begin  
  106.    IsDone := TList.Create;  
  107.    try  
  108.      //GetModuleHandle,参数nil,为获取自身的模块句柄  
  109.       Result := ReplaceAddressInModule(GetModuleHandle(nil), oldfun, newfun);  
  110.    finally  
  111.       IsDone.Free;  
  112.    end;  
  113. end;  
  114. end.  

ApiDefine单元,这里实现我们自定义的API

[delphi] view plaincopy
 
  1. unit ApiDefine;  
  2.   
  3. interface  
  4.   
  5. uses  
  6.   Windows, SysUtils, Classes,Messages,APIHook,ShellAPI;  
  7.   
  8.   procedure API_Hook;  
  9.   procedure API_UnHook;  
  10.   
  11. implementation  
  12.   
  13. //自定义Api的类型  
  14. type  
  15.   TMsgA = function(hwn: hwnd; lptext: pchar; lpcapion: pchar; utype: cardinal):integer; stdcall;  
  16.   TShellExc = function(hwn: HWND;lpoperate: PChar;lpfilename: PChar; lpparam: PChar; lpdir:PChar;cmd:Integer):Integer;stdcall;  
  17.   TTextOut = function(DC:HDC;X:Integer;Y:Integer;options:Integer;rect:PRect;str:PAnsiChar;count:Integer;dx:PInteger):Boolean;stdcall;  
  18. var  
  19.   oldMsgA : TMsgA;  
  20.   oldShellExc : TShellExc;  
  21.   oldTextOut : TTextOut;  
  22.   
  23. //自定义Api的实现  
  24. function NewMsgA(hwn: hwnd; lptext: pchar; lpcaption: pchar; utype: cardinal):integer; stdcall;  
  25. begin  
  26.   Result := oldMsgA(hwn,'成功拦截MessageBoxA','哈哈',utype);  
  27. end;    
  28.   
  29. function NewShellExc(hwn: HWND;lpoperate: PChar;lpfilename: PChar; lpparam: PChar; lpdir:PChar;cmd:Integer):Integer;stdcall;  
  30. begin  
  31.   Result := oldShellExc(hwn,lpoperate,'c:/2.txt',lpfilename,lpdir,cmd);  
  32. end;  
  33.   
  34. {TextOut调用的是ExtTextOut}  
  35. function NewTextOut(DC:HDC;X:Integer;Y:Integer;options:Integer;rect:PRect;str:PAnsiChar;count:Integer;dx:PInteger):Boolean;stdcall;  
  36. begin  
  37.   {这个rect也是可以修改的,以便容纳更多的字符显示}  
  38.   Result := oldTextOut(DC,50,50,options,rect,'中国',count,dx);  
  39. end;  
  40.   
  41. procedure API_Hook;  
  42. begin  
  43.   if @oldMsgA = nil then  
  44.     @oldMsgA := GetFunTrueAddress(@MessageBoxA);  
  45.   if @oldShellExc = nil then  
  46.     @oldShellExc := GetFunTrueAddress(@ShellExecute);  
  47.   if @oldTextOut = nil then  
  48.     @oldTextOut := GetFunTrueAddress(@ExtTextOut);  
  49.   //替换    
  50.   ReplaceFunAddress(@oldMsgA,@NewMsgA);  
  51.   ReplaceFunAddress(@oldShellExc,@NewShellExc);  
  52.   ReplaceFunAddress(@oldTextOut,@NewTextOut);  
  53. end;  
  54.   
  55. procedure API_UnHook;  
  56. begin  
  57.   if @oldMsgA <> nil then  
  58.     ReplaceFunAddress(@NewMsgA,@oldMsgA);  
  59.   if @oldShellExc <> nil then  
  60.     ReplaceFunAddress(@NewShellExc,@oldShellExc);  
  61.   if @oldTextOut <> nil then  
  62.     ReplaceFunAddress(@NewTextOut,@oldTextOut);  
  63. end;  
  64.   
  65. initialization  
  66. //结束时恢复原Api地址  
  67. finalization  
  68.   API_UnHook;  
  69.   
  70. end.  

主程序代码,大家可以把,消息、打开文件、画文字的代码写到另外的程序,本程序只负责挂钩和摘钩,那样可以看到系统钩子的效果。

[delphi] view plaincopy
 
  1. unit TestMain;  
  2.   
  3. interface  
  4.   
  5. uses  
  6.   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,  
  7.   Dialogs, StdCtrls,ShellAPI;  
  8.   
  9. type  
  10.   TForm1 = class(TForm)  
  11.     btn_Hook: TButton;  
  12.     btn_Msg: TButton;  
  13.     btn_UnHook: TButton;  
  14.     btn_OpenFiel: TButton;  
  15.     btn_TextOut: TButton;  
  16.     procedure btn_HookClick(Sender: TObject);  
  17.     procedure btn_MsgClick(Sender: TObject);  
  18.     procedure btn_UnHookClick(Sender: TObject);  
  19.     procedure btn_OpenFielClick(Sender: TObject);  
  20.     procedure btn_TextOutClick(Sender: TObject);  
  21.   private  
  22.     { Private declarations }  
  23.   public  
  24.     { Public declarations }  
  25.   end;  
  26.   
  27. var  
  28.   Form1: TForm1;  
  29.   
  30. implementation  
  31.   
  32. procedure SetHook;stdcall;external 'Hook.dll';  
  33. procedure StopHook;stdcall;external 'Hook.dll';  
  34.   
  35. {$R *.dfm}  
  36.   
  37. procedure TForm1.btn_HookClick(Sender: TObject);  
  38. begin  
  39.   SetHook;  
  40. end;  
  41. procedure TForm1.btn_UnHookClick(Sender: TObject);  
  42. begin  
  43.   StopHook;  
  44. end;  
  45.   
  46. {被拦截后,执行我们自己的NewMsgA方法}  
  47. procedure TForm1.btn_MsgClick(Sender: TObject);  
  48. begin  
  49.   MessageBoxA(Handle,'能拦住我吗','询问',MB_OK);  
  50. end;  
  51.   
  52. {本想打开c:/1.txt,被拦截后,打开c:/2.txt}  
  53. procedure TForm1.btn_OpenFielClick(Sender: TObject);  
  54. begin  
  55.   ShellExecute(Handle,'open','c:/1.txt',nil,nil,SW_NORMAL);  
  56. end;  
  57. {本想在0,0出画出'Hello',被拦截后,在50,50的位置画出'中国'}  
  58. procedure TForm1.btn_TextOutClick(Sender: TObject);  
  59. begin   
  60.   Self.Canvas.TextOut(0,0,'Hello');  
  61. end;  
  62.   
  63. end.  

下图是执行画文字代码后的效果,本想在[0,0]坐标画出'Hello',被拦截后,在[50,50]的位置画出'中国'

http://blog.csdn.net/bdmh/article/details/6104475

原文地址:https://www.cnblogs.com/findumars/p/5001977.html