原文对我的帮助极大,正是因为看了原文,我才学会了HOOK,鉴于原文的排版不是很好,
又没有原工程例子源码下载,因此我决定对其重新整理,文章后面附有我测试时的工程源码下载地址。
注:我测试的环境为Win7+VS2008+MFC
//后来才看到的
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
前言
本文主要介绍了如何实现替换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的内容:
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
然后别忘了在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文件,如果函数导出成功的话,我们就可以
在里面看到导出的函数名字了,如下图所示:
//dll函数查看器
----------------------------------------------------------------------------------------------------------------------------------------------------------
有了dll文件后,接下来我们新建一个MFC对话框程序来调用该dll中导出的函数add,
程序界面即运行效果截图如下:
主要代码如下:
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
接下来我们进行HOOK,即HOOK我们的Add.dll文件中的函数int add(int a,int b)
新建一个MFC的 dll工程,工程名为Hook,然后我们在Hook.cpp文件里面编写代码如下:
首先在头部声明如下变量:
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
编写鼠标钩子安装、卸载和处理函数:
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
在dll实例化函数InitInstance()中,初始化变量和进行注入:
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
编写注射函数,即HOOK函数Inject()了:
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
编写HOOK开启和停止函数HookOn()和HookOff()
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
编写我们自己的Myadd函数()
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
然后别忘记在hook.def里面导出我们的两个函数 :
InstallHook
UninstallHook
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
接下来就可以进行HOOK的测试了,给前面的对话框程序,再添加两个按钮,一个用于安装钩子,另一个用于卸载钩子,
程序和运行效果截图如下:
//未HOOK之前
//HOOK之后
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
安装钩子和卸载钩子主要代码如下:
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
以上就是之前我看的那篇文章的主要内容了,关于HOOK系统API,我会在其它的文章里面进行说明。
这里再说一下原文的缺点,我认为其有两个缺点:
1.停止HOOK时,没有恢复被HOOK函数的入口。
2.没有处理dll退出事件,没有在dll退出事件中恢复被HOOK函数入口。
以上两个缺点,很容易导致程序的崩溃,因此在我的例子程序中,都对它们进行了处理:
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
以上我这个例子工程的下载地址:hook dll文件中的函数add.zip
友情提示:我在Debug模式运行程序时,HOOK会失败,在Release模式运行程序则HOOK成功。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
您的十分满意是我追求的宗旨。
您的一点建议是我后续的动力。