1、Windows Hook 易核心编程(3) API Hook 续 拦截 API上一期,我们讲了用 HOOK 技术实现远程线程插入,相信大家还记忆犹新.这一期我们来谈谈 API HOOKAPI Hook 技术应用广泛,常用于屏幕取词,网络防火墙,病毒木马,加壳软件,串口红外通讯,游戏外挂,internet 通信等领域 API HOOK 的中文意思就是钩住 API,对 API进行预处理,先执行我们的函数,例如我们用 API Hook 技术挂接 ExitWindowsEx API 函数,使关机失效,挂接 ZwOpenProcess 函数,隐藏进程等等总的来说,常用的挂钩 API 方法有以下两种: 改
2、写 IAT 导入表法我们知道,Windows 下的可执行文档的文件格式是一种叫PE(“portable executable” ,可移植 的可执行文件)的文件格式,这种文件格式是由微软设计的,接下来这张图描述了 PE 文件的结构:+-+ - offset 0| MS DOS 标志(“MZ“) 和 DOS 块 |+-+ | PE 标志 (“PE“) |+-+| .text | - 模块代码| 程序代码 | |+-+| .data | - 已初始化的(全局静态)数据| 已初始化的数据 | |+-+| .idata | - 导入函数的信息和数据| 导入表 | | |+-+| .edata | - 导
3、出函数的信息和数据| 导出表 | | |+-+| 调试符号 |+-+这里对我们比较重要的是.idata 部分的导入地址表(IAT)。这个部分包含了导入的相关信息和导入函数的地址。有一点很重要的是我们必须知道 PE 文件是如何创建的。当在编程语言里间接调用任意 API(这意味着我们是用函数的名字来调用它,而不是用它的地址),编译器并不直接把调用连接到模块,而是用 jmp 指令连接调用到 IAT,IAT 在系统把进程调入内存时时会由进程载入器填满。这就是我们可以在两个不同版本的 Windows 里使用相同的二进制代码的原因,虽然模块可能会加载到不同的地址。进程载入器会在程序代码里调用所使用的 IA
4、T 里填入直接跳转的 jmp 指令。所以我们能在 IAT 里找到我们想要挂钩的指定函数,我们就能很容易改变那里的 jmp 指令并重定向代码到我们的地址。完成之后每次调用都会执行我们的代码了。我们通过使用 imagehlp.dll 里的 ImageDirectoryEntryToData来很容易地找到 IAT。.DLL 命令 ImageDirectoryEntryToData, 整数型, “imagehlp“, , , 返回 IMAGE_IMPORT_DESCRIPTOR 数组的首地址.参数 Base, 整数型, , 模块句柄.参数 MappedAsImage, 逻辑型, , 真.参数 Dire
5、ctoryEntry, 整数型, , 恒量:IMAGE_DIRECTORY_ENTRY_IMPORT,1.参数 Size, 整数型, 传址, IMAGE_IMPORT_DESCRIPTOR 数组的大小.数据类型 IMAGE_IMPORT_DESCRIPTOR, , 输入描述结构.成员 OriginalFirstThunk, 整数型, , , 它是一个 RVA(32位) ,指向一个以 0 结尾的、由 IMAGE_THUNK_DATA(换长数据)的RVA 构成的数 组,其每个 IMAGE_THUNK_DATA(换长数据)元素都描述一个函数。此数组永不改变。.成员 TimeDateStamp, 整数
6、型, , , 它是一个具有好几个目的的 32 位的时间日期戳.成员 ForwarderChain, 整数型, , , 它是输入函数列表中第一个中转的、32 位的索引。.成员 Name, 整数型, , , 它是一个 DLL 文件的名称(0 结尾的 ASCII 码字符串)的、32 位的 RVA。.成员 FirstThunk, 整数型, , , 它也是一个 RVA(32 位) ,指向一个 0 结尾的、由 IMAGE_THUNK_DATA(换长数据)的 RVA 构成的数组,其每 个 IMAGE_THUNK_DATA(换长数据)元素都描述一个函数。此数组是输入地址表的一部分,并且可以改变。如果我们找到了
7、就必须用 VirtualProtectEx 函数来改变内存页面的保护属性,然后就可以通过WriteProcessMemory 在内存中的这些部分写入代码了。在改写了地址之后我们要把保护属性改回来。在调用 VirtualProtectEx 之前我们还要先知道有关页面的信息,这通过 VirtualQueryEx 来实现。这种方法的好处是比较稳定,但有漏 API 的可能,因为并不是所有的 API 调用都是通过 IAT 的,可能易程序就是这种情况,我当初也是想用这种方法,但是在易里面调试时总不能在 IAT 里得到正确的程序调用的 API,常常是些无关的 API(可能是易的核心支持库在做怪),不得不放弃
8、.本来计划中是没有这一章的,但自从我的 Windows Hook 易核心编程(3) API Hook 发表以来,得到了广大易友的支持,这种情况很令我感动,于是再接再力,写出了这章,希望没有辜负广大易友对我的支持与信任,好,废话不多说了,我们言归正转.在开始之前,让我们先来回顾一下:什么叫 Hook API?所谓 Hook 就是钩子的意思,而 API 是指 Windows 开放给程序员的编程接口,使得在用户级别下可以对操作系统进行控制,也就是一般的应用程序都需要调用 API 来完成某些功能,Hook API 的意思就是在这些应用程序调用真正的系统 API 前可以先被截获,从而进行一些处理再调用真
9、正的 API 来完成功能。在讲 Hook API 之前先来看一下如何 Hook 消息,例如 Hook全局键盘消息,从而可以知道用户按了哪些键.这种 Hook 消息的功能可以由以下函数来完成,该函数将一新的 Hook 加入到原来的 Hook链中,当某一消息到达后会依次经过它的 Hook 链再交给应用程序,这个在我的 Windows Hook 易核心编程(1)和(2)里有具体的说明和应用,大家可以看一看.DLL 命令 api_SetWindowsHookExA, 整数型, , “SetWindowsHookExA“.参数 idHook, 整数型, , Hook 类型,例如WH_KEYBOARD,W
10、H_MOUSE.参数 lpfn, 子程序指针, , 钩子回调函数,Hook 处理过程函数的地址,.参数 nMod, 整数型, , 包含 Hook 处理过程函数的 dll 句柄(若在本进程可以为 NULL).参数 dwThreadID, 整数型, , 要 Hook 的线程 ID,若为 0,表示全局 Hook 所有.DLL 命令 api_UnhookWindowsHookEx, 逻辑型, , “UnhookWindowsHookEx“.参数 hhook, 整数型,要关闭钩子的句柄.这里需要提一下的就是如果是 Hook 全局的而不是某个特定的进程则需要将 Hook 过程编写为一个DLL,以便让任何程
11、序都可以加载它来获取 Hook 过程函数。而对于Hook API 微软并没有提供直接的接口函数,也许它并不想让我们这样做,不过有 2 种方法可以完成该功能。第一种,修改可执行文件的 IAT 表(即输入表) ,因为在该表中记录了所有调用 API 的函数地址,则只需将这些地址改为自己函数的地址即可,但是这样有一个局限,因为有的程序会加壳,这样会隐藏真实的 IAT 表,从而使该方法失效。第二种方法是直接跳转,改变 API 函数的头几个字节,使程序跳转到自己的函数,然后恢复 API 开头的几个字节,在调用 API 完成功能后再改回来又能继续 Hook 了,但是这种方法也有一个问题就是同步的问题,当然这
12、是可以克服的,并且该方法不受程序加壳的限制。上一章我们就是用的第二种方法,通过直接改写 API 函数ExitWindowsEx 入口点为195(即汇编的返回命令 retn),达到关机失效的目地,其实这不算是真正的 API HOOK,没有实现自定 API 函数的功能.记得我说过,如果要实现自定 API 函数,可以写入一个跳转指令,JMP OX00000(其中 OX00000 为自定 API函数地址),最后还给大家留了一道题,就是参照论坛上的教程写出自定 API 函数的功能,不知道大家完成的什么样了,呵呵.其实,要真正实现 API HOOK,用 JMP OX00000 是不能达到我们的目地的,因为
13、要转移参数,我们先要mov eax OX00000 ,然后再 jmp eax,在易里面用用字节集连起来表示就是: 184 到字节集 (到整数 (.子程序 安装全局钩子, 整数型, , 安装全局消息钩子.参数 DLLPath, 文本型,DLL 名.局部变量 hMod, 整数型.局部变量 lpProc, 子程序指针hMod api_LoadLibraryA (DLLPath)lpProc api_GetProcAddress (hMod, “GetMsgProc”)hook api_SetWindowsHookExA (#WH_GETMESSAGE, lpProc, hMod, 0)返回 (hoo
14、k)=主程序通过 api_UnhookWindowsHookEx(hook)就可以关闭全局钩子.结合下面的子程序(上期的 HOOK_API)便可以还原 API.=.子程序 还原 API.局部变量 i, 整数型.局部变量 结果, 逻辑型.局部变量 进程信息, 进程信息输出, , “0“.局部变量 进程句柄, 整数型.局部变量 修改内容, 整数型刷新进程信息 (进程信息) 这个在上期中讲过,我就不重复了.计次循环首 (取数组成员数 (进程信息), i)进程句柄 打开进程 (2035711, 0, 进程信息 i.进程标识).如果真 (API_BAK 0 )结果 修改 API 首地址 (进程句柄, A
15、PI, API_BAK).如果真结束关闭内核对象 (进程句柄).计次循环尾 ()=可以看出,我们创建的 Hook 类型是 WH_CALLWNDPROC 类型,该类型的 Hook 在进程与系统一通信时就会被加载到进程空间,从而调用 dll 的初始化函数完成真正的Hook,而在 SetWindowsHookEx 函数中指定的 HookProc 函数将不作任何处理,只是调用CallNextHookEx 将消息交给 Hook 链中下一个环节处理,因为这里 SetWindowsHookEx 的唯一作用就是让进程加载我们的 dll。整个框架就是这样了,具体的就不在细说了.附件里有完整的易源码,大家可以下载下来研究.以上就是一个最简单的 Hook API 的例子,该种技术可以完成许多功能。例如网游外挂制作过程中截取发送的与收到的封包(API 函数 send)即可使用该方法,或者也可以在 Hook 到 API 后加入木马功能,反向连接指定的主机或者监听某一端口,还有许多加壳也是用该原理来隐藏 IAT 表,填入自己的函数地址,等等.