《Windows核心编程》之“Windows挂钩”(一)

本文介绍了《Windows核心编程》中提到的Windows Hook技术,特别是DLL注入和窗口子类化。首先阐述了Windows应用程序的事件驱动和消息响应机制,接着详细讲解了消息循环和消息队列的工作原理。然后,文章深入讨论了窗口子类化的方法,包括SDK编程和MFC中的实现,以及如何通过截取和处理消息来扩展和监视窗口行为。
摘要由CSDN通过智能技术生成

        《Windows核心编程》一书中介绍了一种针对带窗口的Windows应用程序的“DLL注入”的方法——Windows Hook(窗口挂钩)。本系列文章将探讨这种技术的原理和分享我的实验心得。


一、Windows应用程序基本知识

1,“事件驱动,消息响应”

    Windows程序是一种“事件驱动”型的程序(相对“非事件驱动型程序”)。要理解Windows程序的运行过程,我们必须先理解Windows操作系统中设计的这套“事件驱动,消息响应”的消息机制。而要理解Windows的消息机制,我们就要先理清“事件”与“消息”的关系。

1)事件(event):

    事件是与操作系统底层相关的概念,主要分为两类:硬件事件和系统事件。按下鼠标,按下键盘,按下游戏手柄,插入U盘等都会产生在操作系统中产生事件,这类与硬件相关的事件就是硬件事件;定时器事件,绘图事件(首次调showWindow,或者移动窗口,或者窗口被遮挡后又重新出现等都会触发绘图事件)等这类与硬件没有直接关系的事件就是系统事件(纯抽象性的)。

2)消息(message):

    消息是对事件的封装,它相对事件来说是更高级和抽象的概念。举例来说:当鼠标被按下,产生了鼠标按下事件,windows侦测到这一事件的发生,随即发出鼠标被按下的消息到消息队列中,这消息附带了一系列相关的事件信息,比如鼠标哪个键被按了,在哪个窗口被按的,按下点的坐标是多少?如此等等。

    此处参考:http://www.cnblogs.com/welen/articles/5115213.html    (比较“事件”和“消息”设计的优缺点)

    在Qt中,用“信号”(Signal)来表示消息。Qt中的信号与槽(slot)函数,对应于Windows的消息与窗口函数(WndProc)。(不完全等价)

    

2,消息循环和消息队列

    我们可以通过VS新建一个名为“Hello"的默认的“Windows Application”,观察VS自动生成的一些框架代码来理解Windows应用程序的消息机制。主要是看”Hello.cpp“文件。

1)每一个Windows应用程序都会维持一个”消息循环“

    在”Hello.cpp“文件的有一个入口函数”wWinMain”,该函数的最后定义并开启了如下一个“消息循环”

二、API Hook的原理 这里的API既包括传统的Win32 APIs,也包括任何Module输出的函数调用。熟悉PE文件格 式的朋友都知道,PE文件将对外部Module输出函数的调用信息保存在输入表,即.idata段。 下面首先介绍本段的结构。 输入表首先以一个IMAGE_IMPORT_DESCRIPTOR(简称IID)数组开始。每个被PE文件隐式链接 进来的DLL都有一个IID.在这个数组的最后一个单元是NULL,可以由此计算出该数组的项数。 例如,某个PE文件从两个DLL引入函数,就存在两个IID结构来描述这些DLL文件,并在两个 IID结构的最后由一个内容全为0的IID结构作为结束。几个结构定义如下: IMAGE_IMPORT_DESCRIPTOR struct union{ DWORD Characteristics; ;00h DWORD OriginalFirstThunk; }; TimeDateStamp DWORD ;04h ForwarderChain DWORD ;08h Name DWORD ;0Ch FirstThunk DWORD ;10h IMAGE_IMPROT_DESCRIPTOR ends typedef struct _IMAGE_THUNK_DATA{ union{ PBYTE ForwarderString; PDWORD Functions; DWORD Ordinal; PIMAGE_IMPORT_BY_NAME AddressOfData; }u1; } IMAGE_IMPORT_BY_NAME结构保存一个输入函数的相关信息: IMAGE_IMPORT_BY_NAME struct Hint WORD ? ;本函数在其所驻留DLL的输出表的序号 Name BYTE ? ;输入函数的函数名,以NULL结尾的ASCII字符串 IMAGE_IMPORT_BY_NAME ends OriginalFirstThunk(Characteristics):这是一个IMAGE_THUNK_DATA数组的RVA(相对于PE文件 起始处)。其每个指针都指向IMAGE_IMPORT_BY_NAME结构。 TimeDateStamp:一个32位的时间标志,可以忽略。 ForwarderChain:正向链接索引,一般为0。当程序引用一个DLL的API,而这个API又引用别的 DLL的API时使用。 NameLL名字的指针。是个以00结尾的ASCII字符的RVA地址,如"KERNEL32.DLL"。 FirstThunk:通常也是一个IMAGE_THUNK_DATA数组的RVA。如果不是一个指针,它就是该功能在 DLL的序号。 OriginalFirstThunk与FirstThunk指向两个本质相同的数组IMAGE_THUNK_DATA,但名称不同, 分别是输入名称表(Import Name Table,INT)和输入地址表(Import Address Table,IAT)。 IMAGE_THUNK_DATA结构是个双字,在不同时刻有不同的含义,当双字最高位为1时,表示函数以 序号输入,低位就是函数序号。当双字最高位为0时,表示函数以字符串类型的函数名 方式输入,这时它是指向IMAGE_IMPORT_BY_NAME结构的RVA。 三个结构关系如下图: IMAGE_IMPORT_DESCRIPTOR INT IMAGE_IMPORT_BY_NAME IAT -------------------- /-->---------------- ---------- ---------------- |01| 函数1 ||02| 函数2 || n| ... |"USER32.dll" | |--------------------| | | FirstThunk |---------------------------------------------------------------/ -------------------- 在PE文件对DLL输出函数的调用,主要以这种形式出现: call dword ptr[xxxxxxxx] 或 jmp [xxxxxxxx] 其地址xxxxxxxx就是IAT一个IMAGE_THUNK_DATA结构的地址,[xxxxxxxx]取值为IMAGE_THUNK_DATA 的值,即IMAGE_IMPORT_BY_NAME的地址。在操作系统加载PE文件的过程,通过IID的Name加载相应 的DLL,然后根据INT或IAT所指向的IMAGE_IMPORT_BY_NAME的输入函数信息,在DLL确定函数地址, 然后将函数地址写到IAT,此时IAT将不再指向IMAGE_IMPORT_BY_NAME数组。这样[xxxxxxxx]取到的 就是真正的API地址。 从以上分析可以看出,要拦截API的调用,可以通过改写IAT来实现,将自己函数的地址写到IAT, 达到拦截目的。 另外一种方法的原理更简单,也更直接。我们不是要拦截吗,先在内存定位要拦截的API的地址, 然后改写代码的前几个字节为 jmp xxxxxxxx,其xxxxxxxx为我们的API的地址。这样对欲拦截API的 调用实际上就跳转到了咱们的API调用去了,完成了拦截。不拦截时,再改写回来就是了。 这都是自己从网上辛辛苦苦找来的,真的很好啊
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值