DLL 注入hook API
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
二、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
三、实现前的准备 两种拦截方法,最终目的都是使程序对欲拦截 API 的调用跳转到自己的 API 。所以我们的 API 代码对 欲拦截进程必须是可见的,即我们的代码要映射到欲拦截进程的地址空间中。 在《隐藏进程》一文中我介绍了远程线程注入代码的技术,这里我们可以采用这种方法向欲拦截进 程中注入我们的 API 代码。同样有两种注入方式,一种是,直接将代码 WriteProcessMemory 到欲拦截进 程中,写入的代码包括我们的 API 代码和远程线程的入口函数代码。这种的缺点是有一些细节问题要解 决,如参数传递、写入代码大小的确定等并且由于多了一个远程线程效率不是很高,如果要拦截所有的 进程,即必须在每个进程中注入代码、插入线程。另一种是,注入 DLL,远程线程入口函数为 LoadLirary, 当然也存在效率的问题,但免去了一些麻烦。 这里我们主要介绍通过设置钩子(Hook)来自动注入 DLL 到欲拦截进程。先简单说明一下钩子是怎么回事。 Hook 指出了系统消息处理机制。利用 Hook,可以在应用程序中安装子程序监视系统和进程之间的消息 传递,这个监视过程是在消息到达目的窗口过程之前。系统支持很多不同类型的 Hooks,不同的 hook 提供不 同的消息处理机制。比如,应用程序可以使用 WH_MOUSE_hook 来监视鼠标消息的传递。系统为不同类型的 Hook 提供单独的 Hook 链。Hook 链是一个指针列表,这个列表的指针指向指定的,应用程序定义的,被 hook 过程调用的回调函数。当与指定的 Hook 类型关联的消息发生时,系统就把这个消息传递到 Hook 过程。一些 Hook 过程可以只监视消息,或者修改消息,或者停止消息的前进,避免这些消息传递到下一个 Hook 过程或 者目的窗口。 为了利用特殊的 Hook 类型,可由开发者提供了 Hook 过程,使用 SetWindowsHookEx 函数来把 Hook 过程安 装到关联的 Hook 链。Hook 过程必须按照以下的语法: LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam);
HookProc 是应用程序定义的名字,nCode 参数是 Hook 代码,Hook 过程使用这个参数来确定任务。这个参 数的值依赖于 Hook 类型,每一种 Hook 都有自己的 Hook 代码特征字符集。wParam 和 lParam 参数的值依赖于 Hook 代码,但是它们的典型值是包含了关于发送或者接收消息的信息。 SetWindowsHookEx 函数总是在 Hook 链的开头安装 Hook 过程。当指定类型的 Hook 监视的事件发生时,系统 就调用与这个 Hook 关联的 Hook 链的开头的 Hook 过程。每一个 Hook 链中的 Hook 过程都决定是否把这个事件传递 到下一个 Hook 过程。Hook 过程传递事件到下一个 Hook 过程需要调用 CallNextHookEx 函数。有些类型 Hook 的 Hook 过程只能监视消息,不管是否调用了 CallNextHookEx 函数,系统都把消息传递到每一个 Hook 过程。全局 hook 监视同一桌面的所有线程。而特定线程的 Hook 只能监视单独的线程。全局 Hook 过程可以被同一桌面的任 何应用程序调用,就象调用线程一样,所以这个过程必须和 DLL 模块分开。特定线程 Hook 过程只可以被相关 线程调用。只有在有调试目的的时候才使用全局 Hook,应该避免使用,全局 Hook 损害了系统性能。 本文使用全局的 WH_GETMESSAGE Hook,它也是经常用到的 Hook,应用程序使用 WH_GETMESSAGE Hook 来监 视从 GetMessage or PeekMessage 函数返回的消息。你可以使用 WH_GETMESSAGE Hook 去监视鼠标和键盘输入, 以及其他发送到消息队列中的消息。关于 Hook 的详细信息请参考 MSDN。 使用 SetWindowsHookEx 设置全局的 WH_GETMESSAGE Hook,传入 DLL 的映射到内存时的模块句柄(HANDLE) 和 Hook 过程,这样系统不但会将此 DLL 映射到当前所有进程的地址空间,并调用 DllMain 函数,而且也会将 此 DLL 映射到新创建的进程的地址空间了。也就是自动完成了代码的注入工作,省了很多力气,调用 UnhookWindowsHookEx 卸载钩子。
[说明]
通过 Hook 系统消息, 再加上 Hook API,
系统的各种操作就被截获了,
来源:大富翁 一、什么是 API Hook 见下图所示,API Hook 就是对 API 的正常调用起一个拦截或中间层的作用,这样可以 在调用正常的 API 之前得到控制权,执行自己的代码。其中 Module 指映射到内存中的可执 行文件或 DLL。 module0 module1 | | CALL module1!API001 --------------------------------->| API001 |<-------------------------------------------| | | API215 |<----------------------------------CALL module0!API215 |------------------------------------------->| | | * * vs. module0 Hooooks.dll module1 | | | CALL module1!API001 -------->API001>----------------->| API001 |<-----------------<HOOOOK<------------------| | | | API215 |<-----------------<API215<---------CALL module0!API215 |------------------>HOOOOK>----------------->| | | | * * *
|Fra Baidu bibliotek|
在 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 调用去了,完成了拦截。不拦截时,再改写回来就是了。
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 -------------------/-->---------------------------------------- <--\ | OriginalFirstThunk |--/ |IMAGE_THUNK_DATA|-->|01| 函数 1 |<--|IMAGE_THUNK_DATA| |--------------------| |----------------| |----------| |----------------| | | TimeDateStamp | |IMAGE_THUNK_DATA|-->|02| 函数 2 |<--|IMAGE_THUNK_DATA| |--------------------| |----------------| |----------| |----------------| | | | | ForwarderChain | | ... |-->| n| ... |<--| ... |--------------------| ---------------------------------------| | Name |------>"USER32.dll" | |--------------------| | FirstThunk -------------------| |---------------------------------------------------------------/