windows hook (转)

http://blog.csdn.net/friendan/article/details/12226201

原文地址:http://blog.sina.com.cn/s/blog_628821950100xmuc.html

原文对我的帮助极大,正是因为看了原文,我才学会了HOOK,鉴于原文的排版不是很好,

又没有原工程例子源码下载,因此我决定对其重新整理,文章后面附有我测试时的工程源码下载地址。

注:我测试的环境为Win7+VS2008+MFC

原文出处,好像是这篇:http://blog.csdn.net/glliuxueke/article/details/2702608      //后来才看到的

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

前言
       本文主要介绍了如何实现替换Windows上的API函数,实现Windows API Hook

(当然,对于socket的Hook只是其中的一种特例)。这种Hook API技术被广泛的采用在一些领域中,

如屏幕取词,个人防火墙等。这种API Hook技术并不是很新,但是涉及的领域比较宽广,

要想做好有一定的技术难度。本文是采集了不少达人的以前资料并结合自己的实验得出的心得体会,

在这里进行总结发表,希望能够给广大的读者提供参考,达到抛砖引玉的结果。

-------------------------------------------------------------------------------------------------------------------------------------------------------------

问题
       最近和同学讨论如何构建一个Windows上的简单的个人防火墙。后来讨论涉及到了如何让进程关联套接字端口,

替换windows API,屏幕取词等技术。其中主要的问题有:

1) 采用何种机制来截获socket的调用?

一般来说,实现截获socket的方法有很多很多,最基本的,可以写驱动,驱动也有很多种,TDI驱动, NDIS驱动,Mini port驱动…

由于我使用的是Win2000系统,所以截获socket也可以用Windows SPI来进行。另外一种就是Windows API Hook技术。

由于我没什么硬件基础,不会写驱动,所以第一种方法没有考虑,而用SPI相对比较简单。

但是后来觉得Windows API Hook适应面更广,而且觉得自己动手能学到不少东西,

就决定用Windows API Hook来尝试做socket Hook.
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2) API Hook的实现方法?

实际上就是对系统函数的替换,当然实现替换的方法大概不下5,6种吧,可以参考《Windows核心编程》第22章。

不过我使用的方法与其不近相同,应该相对比较简单易懂。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
原理

我们知道,系统函数都是以DLL封装起来的,应用程序应用到系统函数时,应首先把该DLL加载到当前的进程空间中,

调用的系统函数的入口地址,可以通过 GetProcAddress函数进行获取。当系统函数进行调用的时候,

首先把所必要的信息保存下来(包括参数和返回地址,等一些别的信息),然后就跳转到函数的入口地址,继续执行。

其实函数地址,就是系统函数“可执行代码”的开始地址。那么怎么才能让函数首先执行我们的函数呢?

呵呵,应该明白了吧,把开始的那段可执行代码替换为我们自己定制的一小段可执行代码,这样系统函数调用时,

不就按我们的意图乖乖行事了吗?其实,就这么简单。Very very简单。 :P

实际的说,就可以修改系统函数入口的地方,让他调转到我们的函数的入口点就行了。

采用汇编代码就能简单的实现Jmp XXXX, 其中XXXX就是要跳转的相对地址。

我们的做法是:把系统函数的入口地方的内容替换为一条Jmp指令,目的就是跳到我们的函数进行执行。

而Jmp后面要求的是相对偏移,也就是我们的函数入口地址到系统函数入口地址之间的差异,再减去我们这条指令的大小。

用公式表达如下:(1)int nDelta = UserFunAddr – SysFunAddr - (我们定制的这条指令的大小);(2)Jmp nDleta;

为了保持原程序的健壮性,我们的函数里做完必要的处理后,要回调原来的系统函数,然后返回。

所以调用原来系统函数之前必须先把原来修改的系统函数入口地方给恢复,否则,

系统函数地方被我们改成了Jmp XXXX就会又跳到我们的函数里,死循环了。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
那么说一下程序执行的过程。
       我们的dll“注射”入被hook的进程 -> 保存系统函数入口处的代码 -> 替换掉进程中的系统函数入口指向我们的函数 -> 当系统函数被

调用,立即跳转到我们的函数 -> 我们函数进行处理 -> 恢复系统函数入口的代码 -> 调用原来的系统函数 -> 再修改系统函数入口指向

我们的函数(为了下次hook)-> 返回。于是,一次完整的Hook就完成了。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        好,这个问题明白以后,讲一下下个问题,就是如何进行dll“注射”?即将我们的dll注射到要Hook的进程中去呢?

很简单哦,这里我们采用调用Windows提供给我们的一些现成的Hook来进行注射。举个例子,鼠标钩子,

键盘钩子大家都知道吧?我们可以给系统装一个鼠标钩子,然后所有响应到鼠标事件的进程,

就会“自动”(其实是系统处理了)载入我们的dll然后设置相应的钩子函数。其实我们的目的只是需要让被注射进程

载入我们的dll就可以了,我们可以再dll实例化的时候进行函数注射的,我们的这个鼠标钩子什么都不干的。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

简单的例子OneAddOne

讲了上面的原理,现在我们应该实战一下了。先不要考虑windows系统那些繁杂的函数,

我们自己编写一个API函数来进行Hook与被Hook的练习吧,哈哈。

第一步,首先编写一个Add.dll,很简单,这个dll只输出一个API函数,就是add啦。
新建一个win32 dll工程,

dllmain.cpp的内容:

[cpp] view plain copy print?

  1. //千万别忘记声明WINAPI,否则调用的时候回产生声明错误哦!
  2. int WINAPI add(int a,int b)
  3. {
  4. return a+b;
  5. }
  6. BOOL APIENTRY DllMain( HANDLE hModule,
  7. DWORD  ul_reason_for_call,
  8. LPVOID lpReserved
  9. )
  10. {
  11. return TRUE;
  12. }

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

然后别忘了在add.def里面输出函数add:
LIBRARY  Add
DESCRIPTION "ADD LA"
EXPORTS
 add  @1;

建完工程后,你会发现没有Add.def文件,这时我们自己新建一个Add.def文件,然后添加到工程中即可,

添加Add.def文件到工程后,我们还需要设置工程的属性,将Add.def添加到【项目】-->【Add属性】-->

【链接器】-->【输入】-->【模块定义文件】,如下图所示,不这样设置的话,我们添加的Add.def文件是

不起作用的哦。

设置好后,编译,ok,我们获得了Add.dll

-----------------------------------------------------------------------------------------------------------------------------------------------------

得到Add.dll后,我们可以用一个小工具【dll函数查看器】来打开我们的Add.dll文件,如果函数导出成功的话,我们就可以

在里面看到导出的函数名字了,如下图所示:

该工具下载地址:http://download.csdn.net/detail/friendan/6347455       //dll函数查看器

----------------------------------------------------------------------------------------------------------------------------------------------------------

有了dll文件后,接下来我们新建一个MFC对话框程序来调用该dll中导出的函数add,

程序界面即运行效果截图如下:

主要代码如下:

[cpp] view plain copy print?

  1. //调用dll函数 add(int a,int b)
  2. void CCallAddDlg::OnBnClickedBtnCallAdd()
  3. {
  4. HINSTANCE hAddDll=NULL;
  5. typedef int (WINAPI*AddProc)(int a,int b);//函数原型定义
  6. AddProc add;
  7. if (hAddDll==NULL)
  8. {
  9. hAddDll=::LoadLibrary(_T("Add.dll"));//加载dll
  10. }
  11. add=(AddProc)::GetProcAddress(hAddDll,"add");//获取函数add地址
  12. int a=1;
  13. int b=2;
  14. int c=add(a,b);//调用函数
  15. CString tem;
  16. tem.Format(_T("%d+%d=%d"),a,b,c);
  17. AfxMessageBox(tem);
  18. }

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

接下来我们进行HOOK,即HOOK我们的Add.dll文件中的函数int add(int a,int b)

新建一个MFC的 dll工程,工程名为Hook,然后我们在Hook.cpp文件里面编写代码如下:

首先在头部声明如下变量:

[cpp] view plain copy print?

  1. //变量定义
  2. //不同Instance共享的该变量
  3. #pragma data_seg("SHARED")
  4. static HHOOK  hhk=NULL; //鼠标钩子句柄
  5. static HINSTANCE hinst=NULL; //本dll的实例句柄 (hook.dll)
  6. #pragma data_seg()
  7. #pragma comment(linker, "/section:SHARED,rws")
  8. //以上的变量共享哦!
  9. CString temp; //用于显示错误的临时变量
  10. bool bHook=false; //是否Hook了函数
  11. bool m_bInjected=false; //是否对API进行了Hook
  12. BYTE OldCode[5]; //老的系统API入口代码
  13. BYTE NewCode[5]; //要跳转的API代码 (jmp xxxx)
  14. typedef int (WINAPI*AddProc)(int a,int b);//add.dll中的add函数定义
  15. AddProc add; //add.dll中的add函数
  16. HANDLE hProcess=NULL; //所处进程的句柄
  17. FARPROC pfadd;  //指向add函数的远指针
  18. DWORD dwPid;  //所处进程ID
  19. //end of 变量定义

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

编写鼠标钩子安装、卸载和处理函数:

[cpp] view plain copy print?

  1. //鼠标钩子过程,什么也不做,目的是注入dll到程序中
  2. LRESULT CALLBACK MouseProc(int nCode,WPARAM wParam,LPARAM lParam)
  3. {
  4. return CallNextHookEx(hhk,nCode,wParam,lParam);
  5. }
  6. //鼠标钩子安装函数:
  7. BOOL InstallHook()
  8. {
  9. hhk=::SetWindowsHookEx(WH_MOUSE,MouseProc,hinst,0);
  10. return true;
  11. }
  12. //卸载鼠标钩子函数
  13. void UninstallHook()
  14. {
  15. ::UnhookWindowsHookEx(hhk);
  16. }

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

在dll实例化函数InitInstance()中,初始化变量和进行注入:

[cpp] view plain copy print?

  1. //在dll实例化中获得一些参数
  2. BOOL CHookApp::InitInstance()
  3. {
  4. CWinApp::InitInstance();
  5. //获得dll 实例,进程句柄
  6. hinst=::AfxGetInstanceHandle();
  7. DWORD dwPid=::GetCurrentProcessId();
  8. hProcess=OpenProcess(PROCESS_ALL_ACCESS,0,dwPid);
  9. //调用注射函数
  10. Inject();
  11. return TRUE;
  12. }

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

编写注射函数,即HOOK函数Inject()了:

[cpp] view plain copy print?

  1. //好,最重要的HOOK函数:
  2. void Inject()
  3. {
  4. if (m_bInjected==false)
  5. { //保证只调用1次
  6. m_bInjected=true;
  7. //获取add.dll中的add()函数
  8. HMODULE hmod=::LoadLibrary(_T("Add.dll"));
  9. add=(AddProc)::GetProcAddress(hmod,"add");
  10. pfadd=(FARPROC)add;
  11. if (pfadd==NULL)
  12. {
  13. AfxMessageBox(L"cannot locate add()");
  14. }
  15. // 将add()中的入口代码保存入OldCode[]
  16. _asm
  17. {
  18. lea edi,OldCode
  19. mov esi,pfadd
  20. cld
  21. movsd
  22. movsb
  23. }
  24. NewCode[0]=0xe9;//实际上0xe9就相当于jmp指令
  25. //获取Myadd()的相对地址
  26. _asm
  27. {
  28. lea eax,Myadd
  29. mov ebx,pfadd
  30. sub eax,ebx
  31. sub eax,5
  32. mov dword ptr [NewCode+1],eax
  33. }
  34. //填充完毕,现在NewCode[]里的指令相当于Jmp Myadd
  35. HookOn(); //可以开启钩子了
  36. }
  37. }

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

编写HOOK开启和停止函数HookOn()和HookOff()

[cpp] view plain copy print?

  1. //开启钩子的函数
  2. void HookOn()
  3. {
  4. ASSERT(hProcess!=NULL);
  5. DWORD dwTemp=0;
  6. DWORD dwOldProtect;
  7. //将内存保护模式改为可写,老模式保存入dwOldProtect
  8. VirtualProtectEx(hProcess,pfadd,5,PAGE_READWRITE,&dwOldProtect);
  9. //将所属进程中add()的前5个字节改为Jmp Myadd
  10. WriteProcessMemory(hProcess,pfadd,NewCode,5,0);
  11. //将内存保护模式改回为dwOldProtect
  12. VirtualProtectEx(hProcess,pfadd,5,dwOldProtect,&dwTemp);
  13. bHook=true;
  14. }
  15. //关闭钩子的函数
  16. void HookOff()//将所属进程中add()的入口代码恢复
  17. {
  18. ASSERT(hProcess!=NULL);
  19. DWORD dwTemp=0;
  20. DWORD dwOldProtect;
  21. VirtualProtectEx(hProcess,pfadd,5,PAGE_READWRITE,&dwOldProtect);
  22. WriteProcessMemory(hProcess,pfadd,OldCode,5,0);
  23. VirtualProtectEx(hProcess,pfadd,5,dwOldProtect,&dwTemp);
  24. bHook=false;
  25. }

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

编写我们自己的Myadd函数()

[cpp] view plain copy print?

  1. //然后,写我们自己的Myadd()函数
  2. int WINAPI Myadd(int a,int b)
  3. {
  4. //截获了对add()的调用,我们给a,b都加1
  5. a=a+1;
  6. b=b+1;
  7. HookOff();//关掉Myadd()钩子防止死循环
  8. int ret;
  9. ret=add(a,b);
  10. HookOn();//开启Myadd()钩子
  11. return ret;
  12. }

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

然后别忘记在hook.def里面导出我们的两个函数 :

InstallHook  
UninstallHook

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

接下来就可以进行HOOK的测试了,给前面的对话框程序,再添加两个按钮,一个用于安装钩子,另一个用于卸载钩子,

程序和运行效果截图如下:

//未HOOK之前

//HOOK之后

-------------------------------------------------------------------------------------------------------------------------------------------------------------------

安装钩子和卸载钩子主要代码如下:

[cpp] view plain copy print?

  1. HINSTANCE hinst=NULL;
  2. //安装鼠标钩子,进行HOOK
  3. void CCallAddDlg::OnBnClickedBtnStartHook()
  4. {
  5. typedef BOOL (CALLBACK *inshook)(); //函数原型定义
  6. inshook insthook;
  7. hinst=LoadLibrary(_T("Hook.dll"));//加载dll文件
  8. if(hinst==NULL)
  9. {
  10. AfxMessageBox(_T("no Hook.dll!"));
  11. return;
  12. }
  13. insthook=::GetProcAddress(hinst,"InstallHook");//获取函数地址
  14. if(insthook==NULL)
  15. {
  16. AfxMessageBox(_T("func not found!"));
  17. return;
  18. }
  19. insthook();//开始HOOK
  20. }
  21. //卸载鼠标钩子,停止HOOK
  22. void CCallAddDlg::OnBnClickedBtnStopHook()
  23. {
  24. if (hinst==NULL)
  25. {
  26. return;
  27. }
  28. typedef BOOL (CALLBACK *UnhookProc)(); //函数原型定义
  29. UnhookProc UninstallHook;
  30. UninstallHook=::GetProcAddress(hinst,"UninstallHook");//获取函数地址
  31. if(UninstallHook!=NULL)
  32. {
  33. UninstallHook();
  34. }
  35. if (hinst!=NULL)
  36. {
  37. ::FreeLibrary(hinst);
  38. }
  39. }

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

以上就是之前我看的那篇文章的主要内容了,关于HOOK系统API,我会在其它的文章里面进行说明。

这里再说一下原文的缺点,我认为其有两个缺点:

1.停止HOOK时,没有恢复被HOOK函数的入口。

2.没有处理dll退出事件,没有在dll退出事件中恢复被HOOK函数入口。

以上两个缺点,很容易导致程序的崩溃,因此在我的例子程序中,都对它们进行了处理:

[cpp] view plain copy print?

  1. //卸载鼠标钩子函数
  2. void UninstallHook()
  3. {
  4. if (hhk!=NULL)
  5. {
  6. ::UnhookWindowsHookEx(hhk);
  7. }
  8. HookOff();//记得恢复原函数入口
  9. }
  10. //dll退出时
  11. int CHookApp::ExitInstance()
  12. {
  13. HookOff();//记得恢复原函数入口
  14. return CWinApp::ExitInstance();
  15. }

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

以上我这个例子工程的下载地址:hook dll文件中的函数add.zip

http://download.csdn.net/detail/friendan/6348209

友情提示:我在Debug模式运行程序时,HOOK会失败,在Release模式运行程序则HOOK成功。

时间: 2024-10-05 04:58:46

windows hook (转)的相关文章

windows Hook 消息分类

调用SetWindowsHookEx的DLL的模块实例句柄,它可以经由DllMain入口的第一个参数得到.HHOOK SetWindowsHookEx( int idHook,HOOKPROC lpfn,HINSTANCE hMod,DWORD dwThreadId);至于SetWindowsHookEx的第四个参数dwThreadId,才是你需要借由窗口句柄得到的窗口线程ID,你可以通过下面的代码获得:DWORD dwThreadID = GetWindowThreadProcessId(hw

如何在C#中使用全局鼠标、键盘Hook

今天,有个同事问我,怎样在C#中使用全局钩子?以前写的全局钩子都是用unmanaged C或C++写个DLL来实现,可大家都知道,C#是基于.Net Framework的,是managed,怎么实现全局钩子呢?于是开始到网上搜索,好不容易找到一篇,318804 - HOW TO: Set a Windows Hook in Visual C# .NET,里面详细的说明了如何使用鼠标钩子捕获鼠标的移动等,可是,它只能在Application里起作用,出了Application就没用了,就是说它还是

vc++HOOK详细讲解

消息钩子函数入门 Windows 系统是建立在事件驱动的机制上的,说穿了就是整个系统都是通过消息的传递来实现的.而钩子是 Windows 系统中非常重要的系统接口,用它可以截获并处理送给其他应用程序的消息,来完成普通应用程序难以实现的功能.钩子可以监视系统或进程中的各种事件消息,截获发往目标窗口的消息并进行处理.这样,我们就可以在系统中安装自定义的钩子,监视系统中特定事件的发生,完成特定的功能,比如截获键盘.鼠标的输入,屏幕取词,日志监视等等.可见,利用钩子可以实现许多特殊而有用的功能.因此,对

Windows API参考大全新编

书名:新编Windows API参考大全 作者:本书编写组 页数:981页 开数:16开 字数:2392千字 出版日期:2000年4月第二次印刷 出版社:电子工业出版社 书号:ISBN 7-5053-5777-8 定价:98.00元 内容简介 作为Microsoft 32位平台的应用程序编程接口,Win32 API是从事Windows应用程序开发所必备的.本书首先对Win32 API函数做完整的概述:然后收录五大类函数:窗口管理.图形设备接口.系统服务.国际特性以及网络服务:在附录部分,讲解如何

我的学习笔记_Windows_HOOK编程 2009-12-03 11:19

一.什么是HOOK? "hook"这个单词的意思是"钩子","Windows Hook"是Windows消息处理机制的一个重要扩展,程序猿能够通过它来钩住(截获)感兴趣的消息,并用事先编好的一个函数(钩子过程)来处理这些消息!当然,这个处理是在消息到达目标窗体之前进行的. 钩子过程(hook procedure)实际上是一个用来处理消息的函数,通过系统调用,程序猿能够把它挂入系统或进程的钩子链中,让它成为一个钩子.每当系统中产生特定的消息时,钩子

C#可以直接调用的Win32API

以前整理的Win32 API,可以直接在C#中直接调用,在做WinForm时还是很有帮助的.以前用在一个多窗口界面中,当轮询窗口时,调用API会提高很多效率. 源码下载 http://files.cnblogs.com/lordeo/win32api.rar 源码包含三个文件Win32API.cs,Enums.cs,Structs.cs分别如下 Win32API.cs 复制  保存 using System;using System.Drawing;using System.Runtime.In

病毒分析要掌握的技能

[转载]http://bbs.pediy.com/showthread.php?t=199036 虽然这里面的技能都比较久远了,但是常识还是要了解的 1._declspec(naked) 告诉编译器不要优化代码 对于jmp类型的hook, 如果自己的过程没有使用_declspec(naked),那么系统会自动给添加一些额外的代码,控制堆栈平衡,但是这些额外的代码会破坏被hook函数的堆栈.对于call类型的hook,如果使用_declspec(naked)修饰的话,要注意自己恢复堆栈平衡.#de

关于IHttpModule的相关知识总结

一.IHttpModule相关概述 using System; namespace System.Web { public interface IHttpModule { // 销毁不再被HttpModule使用的资源 void Dispose(); // 初始化一个Module,为捕获HttpRequest做准备 void Init(HttpApplication context); } } 功能概述:向实现类提供模块初始化和处置事件.它能够截获所有请求,如同windows hook一样.所以

阻止屏保运行、显示器和系统待机(使用SystemParametersInfo和SetThreadExecutionState两种办法)

最近看了下电脑管家里面修复漏洞时阻止系统进入待机模式的实现,其实很简单,哈哈.可以看一下这个MSDN上对这个API的说明:http://msdn.microsoft.com/en-us/library/aa373208(v=vs.85).aspx 以下是转载 ----------------------------------------------------------------------------------------------- 暴风影音在播放的时候会阻止屏幕保护程序的运行,并