递归火山软件开发平台

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
热搜: 火山 源码 类库
查看: 718|回复: 1
打印 上一主题 下一主题

[视窗] 火山视窗EAT_HOOK源码分享 支持32和64

[复制链接]

56

主题

494

帖子

3920

积分

贵宾

火山官方交流群:831858564

Rank: 9Rank: 9Rank: 9

积分
3920
QQ
跳转到指定楼层
楼主
发表于 2025-5-19 17:39:26 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
火山视窗EAT_HOOK源码分享 支持32和64
https://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. <火山程序 类型 = "通常" 版本 = 1 />

  2. 类 类_EATHOOK <公开 注释 = "EATHOOK类" 折叠 @输出名 = "L_EATHOOK"
  3.         @视窗.外部头文件 = "<windows.h>\r\n<cstdio>\r\n<psapi.h>\r\n<type_traits>">
  4. {
  5.     变量 集_跳板函数 <类型 = 变整数>

  6.     #
  7.     # @begin
  8.     # // 根据架构选择正确的NT头结构
  9.     # #ifdef _WIN64
  10.     # using PIMAGE_NT_HEADERS_EX = PIMAGE_NT_HEADERS64;
  11.     # using PIMAGE_OPTIONAL_HEADER_EX = PIMAGE_OPTIONAL_HEADER64;
  12.     # // DWORD IMAGE_ORDINAL_FLAG = IMAGE_ORDINAL_FLAG64;
  13.     # #else
  14.     # using PIMAGE_NT_HEADERS_EX = PIMAGE_NT_HEADERS32;
  15.     # using PIMAGE_OPTIONAL_HEADER_EX = PIMAGE_OPTIONAL_HEADER32;
  16.     # //constexpr DWORD IMAGE_ORDINAL_FLAG = IMAGE_ORDINAL_FLAG32;
  17.     # #endif
  18.     # struct HookContext {
  19.     #     DWORD_PTR* pFuncAddress;  // 导出表中函数地址的指针
  20.     #     DWORD_PTR originalRva;    // 原始RVA值
  21.     #     HMODULE hModule;          // 模块基址
  22.     # };
  23.     # HookContext g_hookContext;
  24.     # INT_P oldFunc = 0;
  25.     # // 原始函数指针类型
  26.     # @end

  27.     方法 写入长JMP <类型 = 字节集类 折叠>
  28.     参数 目标地址 <类型 = 变整数>
  29.     {
  30.         变量 局_代码 <类型 = 字节集类>
  31.         局_代码 = 创建字节集 (0xFF, 0x25, 0x0, 0x0, 0x0, 0x0)
  32.         局_代码.添加字节集 (到字节集 (目标地址))
  33.         返回 (局_代码)
  34.     }

  35.     方法 申请附近的内存 <类型 = 变整数 折叠 "">
  36.     参数 参_开始地址 <类型 = 变整数>
  37.     参数 参_大小 <类型 = 整数>
  38.     参数 参_是否附近 <类型 = 逻辑型 @默认值 = 真>
  39.     {
  40.         变量 局_申请的地址 <类型 = 变整数>
  41.         变量 局_指针 <类型 = 变整数>
  42.         局_指针 = 参_开始地址
  43.         循环判断首 ()
  44.         {
  45.             如果 (参_是否附近)
  46.             {
  47.                 @ @<局_申请的地址> = (INT_P)VirtualAlloc((LPVOID)@<局_指针>, @<参_大小>, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  48.             }
  49.             否则
  50.             {
  51.                 @ @<局_申请的地址> = (INT_P)VirtualAlloc(NULL, @<参_大小>, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  52.             }
  53.             变量 局_方向 <类型 = 字节 值 = 0>
  54.             变量 局_增加量 <类型 = 变整数>

  55.             如果 (局_申请的地址 == 0)  // 申请附近的内存失败了
  56.             {
  57.                 如果 (局_方向 == 0)
  58.                 {
  59.                     如果 (参_开始地址 + 2147483642 >= 局_指针)  // 不能跳转了 2147483647-5
  60.                     {
  61.                         局_增加量 = 局_增加量 + 10240
  62.                     }
  63.                     否则
  64.                     {
  65.                         局_增加量 = 0
  66.                         局_方向 = 1
  67.                     }
  68.                 }
  69.                 否则
  70.                 {
  71.                     如果 (参_开始地址 - 2147483642 >= 局_指针)  // 不能跳转了 2147483647-5
  72.                     {
  73.                         局_增加量 = 局_增加量 - 10240
  74.                     }
  75.                     否则
  76.                     {
  77.                         返回 (0)
  78.                     }
  79.                 }
  80.                 局_指针 = 局_指针 + 局_增加量
  81.             }

  82.         }
  83.         循环判断尾 (局_申请的地址 == 0)

  84.         返回 (局_申请的地址)
  85.     }

  86.     方法 开始HOOK <公开 类型 = 逻辑型 折叠 @输出名 = "StartHook" @禁止流程检查 = 真>
  87.     参数 参_DLL名字 <类型 = 文本型 @输出名 = "wDll">
  88.     参数 参_函数名 <类型 = 文本型 @输出名 = "wFunc">
  89.     参数 参_回调函数 <类型 = 变整数 @输出名 = "lpCallBak">
  90.     {
  91.         变量 局_函数 <类型 = 字节集类>
  92.         局_函数 = 文本到多字节 (参_函数名, 假)
  93.         // 获取模块基址(不增加引用计数)
  94.         @     HMODULE hModule = GetModuleHandleW(@<参_DLL名字>);
  95.         @     if (!hModule)
  96.         @     {
  97.         调试输出 ("错误: 无法获取模块 %ls\n", 参_DLL名字)
  98.         @         return false;
  99.         @     }
  100.         变量 局_DLL句柄 <类型 = 变整数>
  101.         @  @<局_DLL句柄>= (INT_P)hModule;
  102.         如果 (为64位程序 ())
  103.         {
  104.             集_跳板函数 = 申请附近的内存 (局_DLL句柄, 0x32, 真)
  105.             变量 局_跳转2 <类型 = 字节集类>
  106.             局_跳转2 = 写入长JMP (参_回调函数)
  107.             内存复制 (集_跳板函数, 局_跳转2.取字节集指针 (), 局_跳转2.取字节集长度 ())
  108.         }
  109.         否则
  110.         {
  111.             集_跳板函数 = 参_回调函数
  112.         }


  113.         @     // 验证PE头
  114.         @     auto* pDosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(hModule);
  115.         @     if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
  116.         @     {
  117.         调试输出 ("错误: 无效的DOS头\n")
  118.         @         return false;
  119.         @     }
  120.         @
  121.         @     auto* pNtHeaders = reinterpret_cast<PIMAGE_NT_HEADERS_EX>(
  122.         @         reinterpret_cast<BYTE*>(hModule) + pDosHeader->e_lfanew);
  123.         @
  124.         @     if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE)
  125.         @     {
  126.         调试输出 ("错误: 无效的NT头\n")
  127.         @         return false;
  128.         @     }
  129.         @
  130.         @     // 获取导出表
  131.         @     auto& exportDir = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
  132.         @     if (exportDir.VirtualAddress == 0 || exportDir.Size == 0)
  133.         @     {
  134.         调试输出 ("错误: 模块没有导出表\n")
  135.         @         return false;
  136.         @     }
  137.         @
  138.         @     auto* pExportDir = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(
  139.         @         reinterpret_cast<BYTE*>(hModule) + exportDir.VirtualAddress);
  140.         @
  141.         @     // 获取导出表数组
  142.         @     DWORD* pNames = reinterpret_cast<DWORD*>(
  143.         @         reinterpret_cast<BYTE*>(hModule) + pExportDir->AddressOfNames);
  144.         @     WORD* pOrdinals = reinterpret_cast<WORD*>(
  145.         @         reinterpret_cast<BYTE*>(hModule) + pExportDir->AddressOfNameOrdinals);
  146.         @     DWORD* pFunctions = reinterpret_cast<DWORD*>(
  147.         @         reinterpret_cast<BYTE*>(hModule) + pExportDir->AddressOfFunctions);
  148.         @
  149.         @     // 遍历导出表
  150.         @     for (DWORD i = 0; i < pExportDir->NumberOfNames; ++i)
  151.         @     {
  152.         @         const char* name = reinterpret_cast<const char*>(
  153.         @             reinterpret_cast<BYTE*>(hModule) + pNames[i]);
  154.         @
  155.         @         if (strcmp(name, (const char *)@<局_函数>.GetPtr()) == 0)
  156.         @         {
  157.         @             WORD ordinal = pOrdinals[i];
  158.         @             if (ordinal >= pExportDir->NumberOfFunctions)
  159.         @             {
  160.         调试输出 ("错误: 无效的导出序号\n")
  161.         @                 return false;
  162.         @             }
  163.         @
  164.         @             // 计算函数地址指针
  165.         @             DWORD_PTR* pFuncAddr = reinterpret_cast<DWORD_PTR*>(&pFunctions[ordinal]);
  166.         @
  167.         @             // 修改内存保护
  168.         @             DWORD oldProtect = 0;
  169.         @             if (!VirtualProtect(pFuncAddr, sizeof(DWORD_PTR), PAGE_READWRITE, &oldProtect))
  170.         @             {
  171.         调试输出 ("错误: VirtualProtect失败 (0x%X)\n")
  172.         @                 return false;
  173.         @             }
  174.         @
  175.         @             // 保存原始地址
  176.         @             oldFunc = (INT_P)hModule+*(int*)pFuncAddr;
  177.         @            g_hookContext.pFuncAddress = pFuncAddr;
  178.         @            g_hookContext.originalRva = *(int*)pFuncAddr;
  179.         @            g_hookContext.hModule = hModule;

  180.         @             // 计算新地址的RVA 如果64位的情况下,会出现问题,导致偏移错误
  181.         @             *pFuncAddr = static_cast<DWORD_PTR>(
  182.         @                 reinterpret_cast<BYTE*>(@<集_跳板函数>) - reinterpret_cast<BYTE*>(hModule));
  183.         @
  184.         @             // 恢复内存保护
  185.         @             VirtualProtect(pFuncAddr, sizeof(DWORD_PTR), oldProtect, &oldProtect);
  186.         @
  187.         调试输出 ("成功")
  188.         @             return true;
  189.         @         }
  190.         @     }
  191.         @
  192.         调试输出 ("错误: 未找到函数 %s\n", 参_函数名)
  193.         @     return false;

  194.     }

  195.     方法 卸载HOOK <公开 类型 = 逻辑型 折叠 @输出名 = "UnhookEAT" @禁止流程检查 = 真>
  196.     {
  197.         @     if (!g_hookContext.pFuncAddress) return false;
  198.         @     DWORD oldProtect = 0;
  199.         @     if (!VirtualProtect(g_hookContext.pFuncAddress, sizeof(DWORD_PTR), PAGE_READWRITE, &oldProtect)) {
  200.         @         return false;
  201.         @     }
  202.         @     // 恢复原始RVA
  203.         @ *g_hookContext.pFuncAddress = g_hookContext.originalRva;
  204.         @     VirtualProtect(g_hookContext.pFuncAddress, sizeof(DWORD_PTR), oldProtect, &oldProtect);
  205.         @     return true;

  206.     }

  207.     方法 取原函数地址 <公开 类型 = 变整数 折叠 @禁止流程检查 = 真>
  208.     {
  209.         @ return  oldFunc;
  210.     }

  211.     方法 调用原函数 <公开 注释 = "调用所指定的类静态方法" 折叠 @嵌入式方法 = "">
  212.     参数 所欲调用的方法地址 <类型 = 变整数 注释 = "提供所欲调用类静态方法的地址,该地址可以使用"取静态方法地址"方法获取." "">
  213.     参数 返回值类型 <注释 = "  该返回值数据类型必须与被调用方法的返回值数据类型保持一致," 注释 = "否则将导致不可意料的问题." @需求类型 = 数据类型 @匹配类型 = 通用型
  214.             @返回值类型 = 0>
  215.     参数 调用参数表 <注释 = "  调用参数表的格式务必和被调用方法的参数表一致,否则将导致不" 注释 = "可意料的问题." @可扩展 = "" @匹配类型 = 通用型>
  216.     {
  217.         @ ((@<返回值类型> (CALLBACK *) (@pdt_list<调用参数表>))@<所欲调用的方法地址>) (@<调用参数表>)
  218.     }

  219.     #
  220. }
复制代码


回复

使用道具 举报

14

主题

120

帖子

840

积分

核心用户

Rank: 9Rank: 9Rank: 9

积分
840
沙发
发表于 2025-5-20 10:05:48 | 只看该作者
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|递归火山软件开发平台 ( 鄂ICP备18029190号 )

GMT+8, 2025-6-7 00:57 , Processed in 0.088470 second(s), 22 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表