乐易论坛 发表于 2025-5-19 17:39:26

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

火山视窗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 />

类 类_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<调用参数表>))@<所欲调用的方法地址>) (@<调用参数表>)
    }

    #
}


一曲 发表于 2025-5-20 10:05:48

:)
页: [1]
查看完整版本: 火山视窗EAT_HOOK源码分享 支持32和64