火山视窗EAT_HOOK源码分享 支持32和64
火山视窗EAT_HOOK源码分享 支持32和64https://www.leybc.cn/thread-597-1-1.html
(出处: 火山编程教程培训交流论坛-乐易网络)
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;
@ 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);
@
@ if (strcmp(name, (const char *)@<局_函数>.GetPtr()) == 0)
@ {
@ WORD ordinal = pOrdinals;
@ if (ordinal >= pExportDir->NumberOfFunctions)
@ {
调试输出 ("错误: 无效的导出序号\n")
@ return false;
@ }
@
@ // 计算函数地址指针
@ DWORD_PTR* pFuncAddr = reinterpret_cast<DWORD_PTR*>(&pFunctions);
@
@ // 修改内存保护
@ 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;
}
方法 取原函数地址 <公开 类型 = 变整数 折叠 @禁止流程检查 = 真>
{
@ returnoldFunc;
}
方法 调用原函数 <公开 注释 = "调用所指定的类静态方法" 折叠 @嵌入式方法 = "">
参数 所欲调用的方法地址 <类型 = 变整数 注释 = "提供所欲调用类静态方法的地址,该地址可以使用"取静态方法地址"方法获取." "">
参数 返回值类型 <注释 = "该返回值数据类型必须与被调用方法的返回值数据类型保持一致," 注释 = "否则将导致不可意料的问题." @需求类型 = 数据类型 @匹配类型 = 通用型
@返回值类型 = 0>
参数 调用参数表 <注释 = "调用参数表的格式务必和被调用方法的参数表一致,否则将导致不" 注释 = "可意料的问题." @可扩展 = "" @匹配类型 = 通用型>
{
@ ((@<返回值类型> (CALLBACK *) (@pdt_list<调用参数表>))@<所欲调用的方法地址>) (@<调用参数表>)
}
#
}
:)
页:
[1]