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个成员指向导出表的首地址,直接拿到导出表的虚拟地址。
  • 循环查找导出表的导出函数是否与我们的函数名称一致,一致则取出导出函数地址。
  • 设置导出函数位置读写属性,将新的导出函数地址写入到该位置

根据上述流程,读者可以很容易的写出导出表劫持代码,如下所示则是完整的代码片段,当运行EATHook时,进程内如果再次获取MessageBox函数的内存地址时则会返回MyMessageBox自定函数地址,此时功能将被替换。

#include <windows.h>
#include <cstdio>
#include <tchar.h>

// 导出表劫持
BOOL EATHook(LPCTSTR szDllName, LPCTSTR szFunName, LPVOID NewFun)
{
  DWORD addr = 0, index = 0, dwProtect = 0;

  // 装入模块
  HMODULE DllBase = LoadLibrary(szDllName);
  if (NULL == DllBase)
  {
    return(FALSE);
  }

  // 得到Dos头NT头数据目录表
  PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)DllBase;
  PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
  PIMAGE_OPTIONAL_HEADER pOptHeader = (PIMAGE_OPTIONAL_HEADER)(&pNtHeader->OptionalHeader);

  // 得到导出表的虚拟地址
  PIMAGE_EXPORT_DIRECTORY pExpDes = (PIMAGE_EXPORT_DIRECTORY)
    ((PBYTE)DllBase + pOptHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

  // 获取导出表的函数地址,函数名称,函数序号
  PULONG pAddressOfFunctions = (PULONG)((PBYTE)DllBase + pExpDes->AddressOfFunctions);
  PULONG pAddressOfNames = (PULONG)((PBYTE)DllBase + pExpDes->AddressOfNames);
  PUSHORT pAddressOfNameOrdinals = (PUSHORT)((PBYTE)DllBase + pExpDes->AddressOfNameOrdinals);

  // 循环查找
  for (int i = 0; i < pExpDes->NumberOfNames; ++i)
  {
    index = pAddressOfNameOrdinals[i];

    // 得到导出函数名
    LPCTSTR pFuncName = (LPTSTR)((PBYTE)DllBase + pAddressOfNames[i]);

    // 对比函数名
    if (!_tcscmp((LPCTSTR)pFuncName, szFunName))
    {
      // 返回地址
      addr = pAddressOfFunctions[index];
      break;
    }
  }

  // 设置读写属性
  VirtualProtect(&pAddressOfFunctions[index], 0x1000, PAGE_READWRITE, &dwProtect);

  // 设置导出函数
  pAddressOfFunctions[index] = (DWORD)NewFun - (DWORD)DllBase;

  // 写出替换
  WriteProcessMemory(GetCurrentProcess(), &pAddressOfFunctions[index],
    (LPCVOID)((DWORD)NewFun - (DWORD)DllBase), sizeof(NewFun), &dwProtect);
  return(TRUE);
}

// 自定义导出函数
int __stdcall MyMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
  printf("hello lyshark \n");
  return(0);
}

typedef int (WINAPI* LPFNMESSAGEBOX)(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);

int main(int argc, char *argv[])
{
  // 对MessageBoxA进行Eat Hook
  EATHook("USER32.dll", "MessageBoxA", MyMessageBox);

  // 模拟下次调用后就是执行我们的Hook代码
  LoadLibrary("USER32.dll");
  HMODULE hDll = GetModuleHandle("USER32.dll");

  LPFNMESSAGEBOX lpMessageBox = (LPFNMESSAGEBOX)GetProcAddress(hDll, "MessageBoxA");
  lpMessageBox(NULL, "Hello, EAT Hook", "Info", MB_OK);

  system("pause");
  return(0);
}

上述代码被运行后,针对外部调用MessageBoxA函数都会转向到MyMessageBox函数上,至此传入的参数将会失效,如下图所示;

本文作者: 王瑞 本文链接: https://www.lyshark.com/post/b550753d.html 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!