|
EAT(Export Address Table)用于修改动态链接库(DLL)中导出函数的调用。与IAT Hook不同,EAT Hook是在DLL自身中进行钩子操作,而不是修改应用程序的导入表。它的原理是通过修改DLL的导出函数地址,将原本要导出的函数指向另一个自定义的函数。这样,在应用程序调用DLL的导出函数时,实际上会执行自定义的函数。
EAT Hook的步骤通常包括以下几个步骤:
获取目标DLL的基址:通过模块加载和遍历PE文件的导出表,找到目标DLL的基址。
定位导出函数:根据导出函数的名称或序号,在导出表中找到目标函数的位置。
保存原始函数地址:将目标函数的地址保存下来,以便后续恢复。
修改导出函数地址:将目标函数在导出表中对应的地址修改为自定义函数的地址。
实现自定义函数:编写自定义的函数,该函数会在被钩子函数被调用时执行。
调用原始函数:在自定义函数中,可以选择是否调用原始的被钩子函数。
与IAT不同是EAT存放的不是函数地址,而是导出函数地址的偏移,使用时需要加上指定Dll的模块基地址,当Hook挂钩之后,所有试图通过导出表获取函数地址的行为都会受到影响,EATHook并不会直接生效,它只能影响Hook之后对该函数地址的获取。
实现导出表劫持的详细流程如下所示:
首先获取到DOS头,并加上偏移得到NT头,再通过Nt头得到数据目录表基地址。
数据目录表DataDirectory中的第0个成员指向导出表的首地址,直接拿到导出表的虚拟地址。
循环查找导出表的导出函数是否与我们的函数名称一致,一致则取出导出函数地址。
设置导出函数位置读写属性,将新的导出函数地址写入到该位置
- <火山程序 类型 = "通常" 版本 = 1 />
- 类 类_EATHOOK <公开 注释 = "EATHOOK类" 折叠 @输出名 = "L_EATHOOK"
- @视窗.外部头文件 = "<windows.h>\r\n<cstdio>\r\n<psapi.h>\r\n<type_traits>">
- {
- 变量 集_跳板函数 <类型 = 变整数>
- #
- # @begin
- # // 根据架构选择正确的NT头结构
- # #ifdef _WIN64
- # using PIMAGE_NT_HEADERS_EX = PIMAGE_NT_HEADERS64;
- # using PIMAGE_OPTIONAL_HEADER_EX = PIMAGE_OPTIONAL_HEADER64;
- # // DWORD IMAGE_ORDINAL_FLAG = IMAGE_ORDINAL_FLAG64;
- # #else
- # using PIMAGE_NT_HEADERS_EX = PIMAGE_NT_HEADERS32;
- # using PIMAGE_OPTIONAL_HEADER_EX = PIMAGE_OPTIONAL_HEADER32;
- # //constexpr DWORD IMAGE_ORDINAL_FLAG = IMAGE_ORDINAL_FLAG32;
- # #endif
- # struct HookContext {
- # DWORD_PTR* pFuncAddress; // 导出表中函数地址的指针
- # DWORD_PTR originalRva; // 原始RVA值
- # HMODULE hModule; // 模块基址
- # };
- # HookContext g_hookContext;
- # INT_P oldFunc = 0;
- # // 原始函数指针类型
- # @end
- 方法 写入长JMP <类型 = 字节集类 折叠>
- 参数 目标地址 <类型 = 变整数>
- {
- 变量 局_代码 <类型 = 字节集类>
- 局_代码 = 创建字节集 (0xFF, 0x25, 0x0, 0x0, 0x0, 0x0)
- 局_代码.添加字节集 (到字节集 (目标地址))
- 返回 (局_代码)
- }
- 方法 申请附近的内存 <类型 = 变整数 折叠 "">
- 参数 参_开始地址 <类型 = 变整数>
- 参数 参_大小 <类型 = 整数>
- 参数 参_是否附近 <类型 = 逻辑型 @默认值 = 真>
- {
- 变量 局_申请的地址 <类型 = 变整数>
- 变量 局_指针 <类型 = 变整数>
- 局_指针 = 参_开始地址
- 循环判断首 ()
- {
- 如果 (参_是否附近)
- {
- @ @<局_申请的地址> = (INT_P)VirtualAlloc((LPVOID)@<局_指针>, @<参_大小>, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
- }
- 否则
- {
- @ @<局_申请的地址> = (INT_P)VirtualAlloc(NULL, @<参_大小>, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
- }
- 变量 局_方向 <类型 = 字节 值 = 0>
- 变量 局_增加量 <类型 = 变整数>
- 如果 (局_申请的地址 == 0) // 申请附近的内存失败了
- {
- 如果 (局_方向 == 0)
- {
- 如果 (参_开始地址 + 2147483642 >= 局_指针) // 不能跳转了 2147483647-5
- {
- 局_增加量 = 局_增加量 + 10240
- }
- 否则
- {
- 局_增加量 = 0
- 局_方向 = 1
- }
- }
- 否则
- {
- 如果 (参_开始地址 - 2147483642 >= 局_指针) // 不能跳转了 2147483647-5
- {
- 局_增加量 = 局_增加量 - 10240
- }
- 否则
- {
- 返回 (0)
- }
- }
- 局_指针 = 局_指针 + 局_增加量
- }
- }
- 循环判断尾 (局_申请的地址 == 0)
- 返回 (局_申请的地址)
- }
- 方法 开始HOOK <公开 类型 = 逻辑型 折叠 @输出名 = "StartHook" @禁止流程检查 = 真>
- 参数 参_DLL名字 <类型 = 文本型 @输出名 = "wDll">
- 参数 参_函数名 <类型 = 文本型 @输出名 = "wFunc">
- 参数 参_回调函数 <类型 = 变整数 @输出名 = "lpCallBak">
- {
- 变量 局_函数 <类型 = 字节集类>
- 局_函数 = 文本到多字节 (参_函数名, 假)
- // 获取模块基址(不增加引用计数)
- @ HMODULE hModule = GetModuleHandleW(@<参_DLL名字>);
- @ if (!hModule)
- @ {
- 调试输出 ("错误: 无法获取模块 %ls\n", 参_DLL名字)
- @ return false;
- @ }
- 变量 局_DLL句柄 <类型 = 变整数>
- @ @<局_DLL句柄>= (INT_P)hModule;
- 如果 (为64位程序 ())
- {
- 集_跳板函数 = 申请附近的内存 (局_DLL句柄, 0x32, 真)
- 变量 局_跳转2 <类型 = 字节集类>
- 局_跳转2 = 写入长JMP (参_回调函数)
- 内存复制 (集_跳板函数, 局_跳转2.取字节集指针 (), 局_跳转2.取字节集长度 ())
- }
- 否则
- {
- 集_跳板函数 = 参_回调函数
- }
- @ // 验证PE头
- @ auto* pDosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(hModule);
- @ if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
- @ {
- 调试输出 ("错误: 无效的DOS头\n")
- @ return false;
- @ }
- @
- @ auto* pNtHeaders = reinterpret_cast<PIMAGE_NT_HEADERS_EX>(
- @ reinterpret_cast<BYTE*>(hModule) + pDosHeader->e_lfanew);
- @
- @ if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE)
- @ {
- 调试输出 ("错误: 无效的NT头\n")
- @ return false;
- @ }
- @
- @ // 获取导出表
- @ auto& exportDir = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
- @ if (exportDir.VirtualAddress == 0 || exportDir.Size == 0)
- @ {
- 调试输出 ("错误: 模块没有导出表\n")
- @ return false;
- @ }
- @
- @ auto* pExportDir = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(
- @ reinterpret_cast<BYTE*>(hModule) + exportDir.VirtualAddress);
- @
- @ // 获取导出表数组
- @ DWORD* pNames = reinterpret_cast<DWORD*>(
- @ reinterpret_cast<BYTE*>(hModule) + pExportDir->AddressOfNames);
- @ WORD* pOrdinals = reinterpret_cast<WORD*>(
- @ reinterpret_cast<BYTE*>(hModule) + pExportDir->AddressOfNameOrdinals);
- @ DWORD* pFunctions = reinterpret_cast<DWORD*>(
- @ reinterpret_cast<BYTE*>(hModule) + pExportDir->AddressOfFunctions);
- @
- @ // 遍历导出表
- @ for (DWORD i = 0; i < pExportDir->NumberOfNames; ++i)
- @ {
- @ const char* name = reinterpret_cast<const char*>(
- @ reinterpret_cast<BYTE*>(hModule) + pNames[i]);
- @
- @ if (strcmp(name, (const char *)@<局_函数>.GetPtr()) == 0)
- @ {
- @ WORD ordinal = pOrdinals[i];
- @ if (ordinal >= pExportDir->NumberOfFunctions)
- @ {
- 调试输出 ("错误: 无效的导出序号\n")
- @ return false;
- @ }
- @
- @ // 计算函数地址指针
- @ DWORD_PTR* pFuncAddr = reinterpret_cast<DWORD_PTR*>(&pFunctions[ordinal]);
- @
- @ // 修改内存保护
- @ DWORD oldProtect = 0;
- @ if (!VirtualProtect(pFuncAddr, sizeof(DWORD_PTR), PAGE_READWRITE, &oldProtect))
- @ {
- 调试输出 ("错误: VirtualProtect失败 (0x%X)\n")
- @ return false;
- @ }
- @
- @ // 保存原始地址
- @ oldFunc = (INT_P)hModule+*(int*)pFuncAddr;
- @ g_hookContext.pFuncAddress = pFuncAddr;
- @ g_hookContext.originalRva = *(int*)pFuncAddr;
- @ g_hookContext.hModule = hModule;
- @ // 计算新地址的RVA 如果64位的情况下,会出现问题,导致偏移错误
- @ *pFuncAddr = static_cast<DWORD_PTR>(
- @ reinterpret_cast<BYTE*>(@<集_跳板函数>) - reinterpret_cast<BYTE*>(hModule));
- @
- @ // 恢复内存保护
- @ VirtualProtect(pFuncAddr, sizeof(DWORD_PTR), oldProtect, &oldProtect);
- @
- 调试输出 ("成功")
- @ return true;
- @ }
- @ }
- @
- 调试输出 ("错误: 未找到函数 %s\n", 参_函数名)
- @ return false;
- }
- 方法 卸载HOOK <公开 类型 = 逻辑型 折叠 @输出名 = "UnhookEAT" @禁止流程检查 = 真>
- {
- @ if (!g_hookContext.pFuncAddress) return false;
- @ DWORD oldProtect = 0;
- @ if (!VirtualProtect(g_hookContext.pFuncAddress, sizeof(DWORD_PTR), PAGE_READWRITE, &oldProtect)) {
- @ return false;
- @ }
- @ // 恢复原始RVA
- @ *g_hookContext.pFuncAddress = g_hookContext.originalRva;
- @ VirtualProtect(g_hookContext.pFuncAddress, sizeof(DWORD_PTR), oldProtect, &oldProtect);
- @ return true;
- }
- 方法 取原函数地址 <公开 类型 = 变整数 折叠 @禁止流程检查 = 真>
- {
- @ return oldFunc;
- }
- 方法 调用原函数 <公开 注释 = "调用所指定的类静态方法" 折叠 @嵌入式方法 = "">
- 参数 所欲调用的方法地址 <类型 = 变整数 注释 = "提供所欲调用类静态方法的地址,该地址可以使用"取静态方法地址"方法获取." "">
- 参数 返回值类型 <注释 = " 该返回值数据类型必须与被调用方法的返回值数据类型保持一致," 注释 = "否则将导致不可意料的问题." @需求类型 = 数据类型 @匹配类型 = 通用型
- @返回值类型 = 0>
- 参数 调用参数表 <注释 = " 调用参数表的格式务必和被调用方法的参数表一致,否则将导致不" 注释 = "可意料的问题." @可扩展 = "" @匹配类型 = 通用型>
- {
- @ ((@<返回值类型> (CALLBACK *) (@pdt_list<调用参数表>))@<所欲调用的方法地址>) (@<调用参数表>)
- }
- #
- }
复制代码
|
|