实现拦截API的钩子(Hook)

时间:2023-01-19 23:04:29

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

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

Hook.DLL主工程文件代码

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

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

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

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

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

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

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

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

实现拦截API的钩子(Hook)

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