打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
[转]DelphiAPIHOOK完全说明[一]

elphi API HOOK 完全说明[一]

(2010-06-27 22:22:49)
标签:

杂谈

分类: DELPHI

// 本文转自网络, 原始出处不明确.
// 转载目的: 学习 + 分享

一、关于 API Hook


1. 什么是 API Hook
不知道大家是否还记得,在 DOS系统中编程,经常会采取截取中断向量的技术:我们可以设置新的中断服务程序,当系统其他的程序调用这个中断时,就让它先调用我们自己设置的新的中断服务程序,然后再调用原来的中断服务程序,这样就能够获得非凡的控制权。许多优秀的软件和大多数DOS 病毒程序都采用了这个方法。

在 Windows 中,我们也可以采取类似技术。当系统调用某个 API 函数时,就会先进入我们自己的函数,然后再调用原来的 API 函数,这样,我们的程序就可以取得更多的控制权,我们就可对 Windows系统中的任意一个函数调用进行动态拦截、跟踪、修改和恢复,就可让 Windows系统中的任意一个函数按我们的设想工作。这种技术有许多名称,比如“陷阱技术”、“重入技术”等,不过我认为还是 API Hook最贴切。原因嘛,等一下你看编程就明白了。

这样重要的技术,大家已经都知道了吧?哈哈,知道的都不说,不知道的呢,你就自己慢慢去摸索吧。偶尔有一两篇文章见于报端,不是藏头露尾,就是已经过时了。还有的即使把原理都告诉你了,但就是不说它调用了哪些函数。要源代码?行,拿钱来。有人说了,这种技术用来编病毒最合适,所以..(因为菜刀可以杀人,所以菜刀已被禁止使用了)。而实际呢,你看看使用了这种技术的国产软件就知道了:金山词霸、东方快车、RichWin、东方词圣..在这里,我感觉有必要简单说说金山词霸的工作原理。

2. 金山词霸的工作原理
大家都用过金山词霸吧?当你把光标指向一个单词,词霸就会自动弹出一个窗口并把单词的意思翻译出来。这究竟是怎么做出来的呢?我在这里简单说明一下。

(1) 安装鼠标钩子。
(2) 一旦光标在屏幕上移动,系统就会调用鼠标钩子,词霸通过鼠标钩子能够获得光标的坐标 (x, y) ,并安装TextOut()、ExtTextOut() 等 API 函数钩子。
(3) 词霸向光标下的窗口发重画消息,负责绘制该点的应用程序在收到 WM_PAINT 消息后,就可能调用TextOut()、ExtTextOut() 等函数重绘字体。
(4) 调用的函数将被钩子函数拦截,词霸就能够截获该次调用,从应用程序的数据段中将“文字”指针的内容取出,并作翻译处理。
(5) 退出跟踪程序,返回到鼠标钩子,解除对 TextOut()、ExtTextOut() 等 API 函数的跟踪。
(6) 完成了一次“屏幕抓字”。
这里的关键有两点:安装鼠标钩子和 API 钩子。安装鼠标钩子非常简单,而 API 钩子正是取词的核心代码。

3. 关于 Delphi
事实上,随着互联网的普及,许多秘密都已不再是秘密,API Hook也一样。在网上,你已经可以找到这样的免费源代码,但是大部分可能已经过时,而且这些源代码大都是基于 VC++ 的。如果你想找到用Delphi 编写的源代码,那么,你还是读一读我的文章吧。
Delphi是编程工具史上的一个里程碑式的作品。如果你在使用它,我向你表示祝贺。如果你没有使用它,你也没有什么损失。网上关于几种语言谁好谁坏都吵得天翻地覆的了,我不想增加新仇也不想算算旧恨。每种语言都有它的优缺点,每个人都有自己选择的权利嘛!
不过,用 Delphi 编写 API Hook 有几处“陷阱”。我想,除了介绍 API Hook以外,这也是为什么我要写这篇文章的一个原因吧!

4. 哪些人可以读这篇文章
当然,读这篇文章并没有什么限制。但是你最好已经懂得鼠标钩子的制作过程,手边有 MSDN 那就再好不过了。我认为,只要你是Windows 的程序员,就一定要有 MSDN。原因?有一套就明白啦。如果你懂得 PE文件结构,那就更好了。在这篇文章里,我给出了所有的源代码(还不到 200 行)。如果你想修改程序,最好用 SoftIce。

5. 关于我的程序
本文中的程序在 Windows Me 的操作环境下,使用 Delphi5.0编程调试通过。无论是商用还是个人使用,你都可以随意使用和修改本文中的程序,并且不需要在程序中加注我的个人信息。

 


二、用 Delphi 编写 API Hook

 

1. 改写 API 函数
为了使我们改写的代码正确运行,我们的函数必须和要改写的 API 函数具有同样形式的形参。在我的程序中,我拦截了 MessageBoxA和 MessageBoxW 两个函数。所以我这样定义自己的函数:
view plaincopy to clipboardprint?
function MyBoxA(hwn: HWND; lpText: PAnsiChar; lpCapion: PAnsiChar;uType: Cardinal): Integer;stdcall;  
function MyBoxW(hwn: HWND; lpText: PWideChar; lpCapion: PWideChar;uType: Cardinal): Integer; stdcall; 
function MyBoxA(hwn: HWND; lpText: PAnsiChar; lpCapion: PAnsiChar;uType: Cardinal): Integer; stdcall;
function MyBoxW(hwn: HWND; lpText: PWideChar; lpCapion: PWideChar;uType: Cardinal): Integer; stdcall;
注意到我使用了 stdcall 关键字,这是为了我的函数的形参的进出栈顺序与我们要拦截的函数一致。我们知道,为了系统的安全,Win32并不允许直接改写内存中的代码段。所以,有人想了好多种方法绕过系统的保护。实际上,Win32为了我们能安全地改写内存中的代码,提供了一个函数:WriteProcessMemory。有许多人曾经告诉我,WriteProcessMemory也不能用来改写,不过我一直使用得很好。也许用它产生了一些 BUG,只是我并不知道罢了,所以还请这方面的专家指正。在 PE文件中,当你呼叫另一模块中的函数(例如 USER32.DLL 中的GetMessage),编译器编译出来的 CALL指令并不会把控制权直接传给 DLL 中的函数,而是传给一个 JMP DWORDPTR [XXXXXXXX] 指令,[XXXXXXXX]内含该函数的真正地址(函数进入点)。为了得到 API 函数的地址,我们可以这样:Address :=@MessageBoxA。如上文所说的,我们得到的仅仅是一个跳转指令,后面紧接着的才是 MessageBoxA真正的开始代码的地址(具体可以查阅 PE文件的资料)。在下面的程序中我自定义了一个结构(叫记录?我习惯了,改不过口来),注意,这里使用了 packed 关键字:
view plaincopy to clipboardprint?
TImportCode = packed record 
  JumpInstruction: Word; // 应该是 $25FF, JUMP指令  
  AddressOfPointerToFunction: PPointer; //真正的开始地址  
end;  
PImportCode = ^TImportCode; 
TImportCode = packed record
  JumpInstruction: Word; // 应该是 $25FF, JUMP指令
  AddressOfPointerToFunction: PPointer; //真正的开始地址
end;
PImportCode = ^TImportCode;
其中,PPointer = ^Pointer ;
用以下函数返回函数的真正地址:
view plaincopy to clipboardprint?
function TrueFunctionAddress(func: Pointer):Pointer;  
var 
  Code:PImportCode;  
begin 
  Result :=func;  
  if func = nil thenexit;  
  try 
    Code :=func;  
    ifCode.JumpInstruction = $25FF then 
   begin 
     Result :=Code.AddressOfPointerToFunction^;  
   end;  
  except 
    Result :=nil;  
  end;  
end; 
function TrueFunctionAddress(func: Pointer): Pointer;
var
  Code: PImportCode;
begin
  Result := func;
  if func = nil then exit;
  try
    Code :=func;
    ifCode.JumpInstruction = $25FF then
    begin
     Result := Code.AddressOfPointerToFunction^;
    end;
  except
    Result :=nil;
  end;
end;
这样,只要用我们的函数的地址替代它就可以了。替换函数:
view plaincopy to clipboardprint?
procedure PermuteFunction(OldFunc: PPOinter; NewFunc:Pointer);  
var 
  written:DWORD;  
begin 
  WriteProcessMemory(GetCurrentProcess, OldFunc,@NewFunc, 4, written);  
end; 
procedure PermuteFunction(OldFunc: PPOinter; NewFunc:Pointer);
var
  written: DWORD;
begin
  WriteProcessMemory(GetCurrentProcess, OldFunc,@NewFunc, 4, written);
end;
你新建一个 Unit APIHook,把上面的函数和结构写进去并保存下来。

2. 第一个程序
新建一个 Application TRY1,主 Form 的单元名称不妨叫 TRYUnit1。把上面的 Unit APIHook加进来。再新建一个 UnitMESS,添加如下代码:
view plaincopy to clipboardprint?
unit mess;  
interface 
uses 
  Windows, Messages, SysUtils, Classes,APIHook;  
procedure API_Hookup;  
procedure Un_API_Hook;  
var 
  FuncMessageboxA, FuncMessageboxW:PImportCode;  
implementation 
type 
  TMessageA = function(hwn: HWND; lpText:PAnsiChar; lpCapion: PAnsiChar; uType: Cardinal): Integer;stdcall;  
  TMessageW = function(hwn: HWND; lpText:PWideChar; lpCapion: PWideChar; uType: Cardinal): Integer;stdcall;  
var 
  OldMessageBoxA:TMessageA;  
  OldMessageBoxW:TMessageW;  
function MyBoxA(hwn: HWND; lpText: PAnsiChar; lpCapion: PAnsiChar;uType: Cardinal): Integer;stdcall;  
begin 
  Result := OldMessageBoxA(hwn, 'Succes Hook A !',lpCapion, uType);  
end;  
function MyBoxw(hwn: HWND; lpText: PWideChar; lpCapion: PWideChar;uType: Cardinal): Integer;stdcall;  
begin 
  Result := OldMessageBoxW(hwn, '成功挂上W!',lpCapion, uType);  
end;  
procedure API_Hookup;  
begin 
  if @OldMessageBoxA = nilthen 
  begin 
   @OldMessageBoxA :=TrueFunctionAddress(@MessageBoxA);  
  end;  
  if @OldMessageBoxW = nilthen 
  begin 
   @OldMessageBoxW :=TrueFunctionAddress(@MessageBoxW);  
  end;  
 PermuteFunction(FuncMessageboxA.AddressOfPointerToFunction,@MyBoxA);  
 PermuteFunction(FuncMessageboxW.AddressOfPointerToFunction,@MyBoxW);  
end;  
procedure Un_API_hook;  
begin 
  If @OldMessageBoxA<> nil then 
  begin 
   PermuteFunction(FuncMessageboxA.AddressOfPointerToFunction,@OldMessageboxA);  
   PermuteFunction(FuncMessageboxW.AddressOfPointerToFunction,@OldMessageboxW);  
  end;  
end;  
initialization 
FuncMessageboxA :=@MessageBoxA;  
FuncMessageboxW :=@MessageBoxW;  
end. 
unit mess;
interface
uses
  Windows, Messages, SysUtils, Classes,APIHook;
procedure API_Hookup;
procedure Un_API_Hook;
var
  FuncMessageboxA, FuncMessageboxW:PImportCode;
implementation
type
  TMessageA = function(hwn: HWND; lpText:PAnsiChar; lpCapion: PAnsiChar; uType: Cardinal): Integer;stdcall;
  TMessageW = function(hwn: HWND; lpText:PWideChar; lpCapion: PWideChar; uType: Cardinal): Integer;stdcall;
var
  OldMessageBoxA: TMessageA;
  OldMessageBoxW: TMessageW;
function MyBoxA(hwn: HWND; lpText: PAnsiChar; lpCapion: PAnsiChar;uType: Cardinal): Integer; stdcall;
begin
  Result := OldMessageBoxA(hwn, 'Succes Hook A !',lpCapion, uType);
end;
function MyBoxw(hwn: HWND; lpText: PWideChar; lpCapion: PWideChar;uType: Cardinal): Integer; stdcall;
begin
  Result := OldMessageBoxW(hwn, '成功挂上W!',lpCapion, uType);
end;
procedure API_Hookup;
begin
  if @OldMessageBoxA = nil then
  begin
   @OldMessageBoxA := TrueFunctionAddress(@MessageBoxA);
  end;
  if @OldMessageBoxW = nil then
  begin
   @OldMessageBoxW := TrueFunctionAddress(@MessageBoxW);
  end;
 PermuteFunction(FuncMessageboxA.AddressOfPointerToFunction,@MyBoxA);
 PermuteFunction(FuncMessageboxW.AddressOfPointerToFunction,@MyBoxW);
end;
procedure Un_API_hook;
begin
  If @OldMessageBoxA<> nil then
  begin
   PermuteFunction(FuncMessageboxA.AddressOfPointerToFunction,@OldMessageboxA);
   PermuteFunction(FuncMessageboxW.AddressOfPointerToFunction,@OldMessageboxW);
  end;
end;
initialization
FuncMessageboxA := @MessageBoxA;
FuncMessageboxW := @MessageBoxW;
end.
在主窗体上添加三个按钮,添加 Onclick 代码,如下:
view plaincopy to clipboardprint?
procedure TForm1.Button1Click(Sender:TObject);  
begin 
 API_HookUp;  
end;  
procedure TForm1.Button3Click(Sender:TObject);  
begin 
 Un_API_Hook;  
end;  
procedure TForm1.Button2Click(Sender:TObject);  
begin 
  MessageBoxA(Form1.Handle, 'NO HOOK UP A','MessageBoxA', MB_OK);  
  MessageBoxW(Form1.Handle, 'NO HOOK UP W','MessageBoxW', MB_OK);  
end; 
procedure TForm1.Button1Click(Sender: TObject);
begin
  API_HookUp;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
  Un_API_Hook;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  MessageBoxA(Form1.Handle, 'NO HOOK UP A','MessageBoxA', MB_OK);
  MessageBoxW(Form1.Handle, 'NO HOOK UP W','MessageBoxW', MB_OK);
end;
记得要保存,在后面我们还要使用它们。编译一下,
运行..啊哈,成功了!且慢,别高兴得太早。如果现在新建一个 Application TestTry,在 Form上添加一个按钮,OnClick 事
件如下:
view plaincopy to clipboardprint?
procedure TForm1.Button1Click(Sender:TObject);  
begin 
  MessageBoxA(Form1.Handle, 'NO HOOK UP A','MessageBoxA', MB_OK);  
  MessageBoxW(Form1.Handle, 'NO HOOK UP W','MessageBoxW', MB_OK);  
  MessageBox (Form1.Handle, 'NO HOOK UP BOX','MessageBox', MB_OK);  
end; 
procedure TForm1.Button1Click(Sender: TObject);
begin
  MessageBoxA(Form1.Handle, 'NO HOOK UP A','MessageBoxA', MB_OK);
  MessageBoxW(Form1.Handle, 'NO HOOK UP W','MessageBoxW', MB_OK);
  MessageBox (Form1.Handle, 'NO HOOK UP BOX','MessageBox', MB_OK);
end;
先运行 TRY1,再运行 TestTry。结果呢?原来,API Hook 仅仅在 TRY1中挂上了,并没有在所有的系统进程中挂上。
你也许听说过,必须把我们的函数和 API_Hookup、Un_API_Hook放在一个动态链接库里(这是对的)。那么,你就试试吧。如果你这样做了,也并不能在所有的系统进程中挂上。原因很简单,我们仅仅改变了进程指向API 函数的指针,系统中其他的进程还是各管各的。

3. Hook Hook Hook Hook Hook Hook..
我们想想做鼠标钩子时的做法:用 SetWindowsHookEx挂上鼠标钩子,当其他的进程发出鼠标消息时,我们的程序就会拦截到并作出响应。我们还可以用 UnhookWindowsHookEx解除鼠标钩子。我们也必须为我们的函数挂上钩子。不过,鼠标有各种消息响应其他进程,我们的进程有什么消息呢?如果没有消息又怎样响应其他进程呢?即使我们自定义了消息,其他的进程又怎样“懂得”我们的消息呢?真像走入了绝境。
不用怕,至少我们有两种方法。一是完全模仿 SetWindowsHookEx,编制自己的MySetWindowsHookEx,我看过大多数的 API Hook程序里都用了这个方法。其实,我们不必舍近而求远,我们完全可以继续使用SetWindowsHookEx,因为系统还为我们提供了一个函数:GetMsgProc。在 Delphi 中输入GetMsgProc,然后光标停在上面,按 F1 键。怎么样,Delphi 帮助里讲得够清楚的吧?好了,我们的消息也有了。
想一想吧:我们在动态链接库中挂上 WH_GETMESSAGE 消息钩子,当其他的进程发出 WH_GETMESSAGE消息时,就会加载我们的动态链接库,如果在我们的 DLL 加载时自动运行 API_Hook,不就可以让其他的进程挂上我们的 APIHook 吗?

4. 第二个程序
说干就干。新建一个动态链接库 Library TryDLL,把原来的 Unit APIHook 和 Unit MESS加进来。Library TryDLL 的代码如下:
view plaincopy to clipboardprint?
uses 
 Windows,  
 SysUtils,  
 Classes,  
  APIHook in'APIHook.pas',  
  mess in'mess.pas';  
{$R *.RES} 
function GetMsgProc(Code: Integer; Removal: Integer; Msg: Pointer):Integer; stdcall;  
begin 
  Result :=0;  
end;  
var HookHandle: THandle;  
procedure StartHook;stdcall;  
begin 
  HookHandle := SetWindowsHookEx(WH_GETMESSAGE,@GetMsgProc, HInstance,0);  
end;  
procedure StopHook;stdcall;  
begin 
 UnhookWindowsHookEx(HookHandle);  
end;  
exports 
 StartHook,  
 StopHook;  
begin 
  API_Hookup; //加载时挂上  
end.  
// 为了卸载时能解除钩子, 在 Unit MESS单元最后加上一句:  
finalization 
 Un_API_hook;  
end. 
uses
  Windows,
  SysUtils,
  Classes,
  APIHook in 'APIHook.pas',
  mess in 'mess.pas';
{$R *.RES}
function GetMsgProc(Code: Integer; Removal: Integer; Msg: Pointer):Integer; stdcall;
begin
  Result := 0;
end;
var HookHandle: THandle;
procedure StartHook; stdcall;
begin
  HookHandle := SetWindowsHookEx(WH_GETMESSAGE,@GetMsgProc, HInstance, 0);
end;
procedure StopHook; stdcall;
begin
  UnhookWindowsHookEx(HookHandle);
end;
exports
  StartHook,
  StopHook;
begin
  API_Hookup; // 加载时挂上
end.
// 为了卸载时能解除钩子, 在 Unit MESS 单元最后加上一句:
finalization
  Un_API_hook;
end.
当然,别忘了对 mess 做相应修改。编译好后别忘了存盘。新建 Application TRY2 程序,主 Form 的单元名称不妨叫TRYUnit2,在 Form1 上添加三个 Button,并声明:
view plaincopy to clipboardprint?
procedure StartHook; stdcall; external'TRYDLL.DLL';  
procedure StopHook; stdcall; external'TRYDLL.DLL'; 
procedure StartHook; stdcall; external 'TRYDLL.DLL';
procedure StopHook; stdcall; external 'TRYDLL.DLL';
三个 Button 的 OnClick 代码如下:
view plaincopy to clipboardprint?
procedure TForm1.Button1Click(Sender:TObject);  
begin 
 StartHook;  
end;  
procedure TForm1.Button2Click(Sender:TObject);  
begin 
  MessageBoxA(Form1.Handle, 'NO HOOK UP A','MessageBoxA',MB_OK);  
  MessageBoxW(Form1.Handle, 'NO HOOK UP W','MessageBoxW',MB_OK);  
  MessageBox (Form1.Handle, 'NO HOOK UP BOX','MessageBox',MB_OK);  
end;  
procedure TForm1.Button3Click(Sender:TObject);  
begin 
 StopHook;  
end; 
procedure TForm1.Button1Click(Sender: TObject);
begin
  StartHook;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  MessageBoxA(Form1.Handle, 'NO HOOK UP A','MessageBoxA',MB_OK);
  MessageBoxW(Form1.Handle, 'NO HOOK UP W','MessageBoxW',MB_OK);
  MessageBox (Form1.Handle, 'NO HOOK UP BOX','MessageBox',MB_OK);
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
  StopHook;
end;
编译好后运行吧。这次倒好,无论你怎样测试,都得不到钩上的信息。整理一下我们的工作吧:TRY2 运行--> TryDLL 加载 --> 运行 API_Hookup,为TryDLL 挂上 MyBox。即使我们按下了 Button1,其他的进程加载了 TryDLL,也仅仅是运行 API_Hookup,为TryDLL 挂上 MyBox 而已,而其他的进程(包括 TRY2 本身)并没有挂上 MyBox。不信,你可以在 TryDLL中加上一个启动 MyBox 的函数,测试一下。例如,你可以在 TryDLL 的 StopHook 函数中的UnhookWindowsHookEx 语句前,加上一句:MessageBoxW(0, 'MessageBoxW','这是测试DLL是否加载了MyBox', MB_OK);你可以看到弹出窗口中的信息是“成功挂上W!”而不是“MessageBoxW”。并且由于我们的 TryDLL 加载时就启动了API_Hookup,卸载时才运行 Un_API_Hook,所以不论你是否按下 Button1 和Button3,并且不论你按下了几次,每次你按下 Button3都会得到“成功挂上W!”的信息。看来,真正的麻烦才刚刚开始。实际上,我们刚才的工作都是有用的。我们剩下的工作就是改进 UnitAPIHook 中的 TrueFunctionAddress 函数而已。想不到吧?为了能让其他的进程挂上 MyBox,我们必须了解一下PE 文件的格式。

 


本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
Delphi Hook API (经典)
delphi拦截 网络数据封包
在游戏中切出外挂delphi代码(hook)
浅谈api hook技术
API Hook 原理
Delphi 中控制 Word,xml,dll 等操作
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服