You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

162 lines
5.6 KiB

  1. /*
  2. * EAT-based hooking for x86/x64.
  3. *
  4. * Big thanks to ez (https://github.com/ezdiy/) for making this!
  5. *
  6. * Creates "hooks" by modifying the module's export address table.
  7. * The procedure works in three main parts:
  8. *
  9. * 1. Reading the module's PE file and getting all exported functions.
  10. * 2. Finding the right function to "hook" by simple address lookup
  11. * 3. Modify the entry to point to the hook.
  12. *
  13. * The idea is based on the fact that the export table allows forwarding imports:
  14. *
  15. * https://en.wikibooks.org/wiki/X86_Disassembly/Windows_Executable_Files#Forwarding
  16. *
  17. * You can also find some reference material on the same page to understand this code better.
  18. *
  19. */
  20. #pragma once
  21. #pragma warning( disable : 4267 90 )
  22. #include <windows.h>
  23. // PE format uses RVAs (Relative Virtual Addresses) to save addresses relative to the base of the module
  24. // More info: https://en.wikibooks.org/wiki/X86_Disassembly/Windows_Executable_Files#Relative_Virtual_Addressing_(RVA)
  25. //
  26. // This helper macro converts the saved RVA to a fully valid pointer to the data in the PE file
  27. #define RVA2PTR(t,base,rva) ((t)(((PCHAR) base) + rva))
  28. // A helper function to write into protected memory
  29. inline int vpmemcpy(void *dst, void *src, size_t sz)
  30. {
  31. DWORD oldp;
  32. // Make the memory page writeable
  33. if (!VirtualProtect(dst, sz, PAGE_READWRITE, &oldp))
  34. return 1;
  35. memcpy(dst, src, sz);
  36. // Restore the protection level
  37. VirtualProtect(dst, sz, oldp, &oldp);
  38. return 0;
  39. }
  40. /**
  41. * \brief Replaces the specified function entry in the EAT with a forward to a custom one, thus creating the "hook" effect.
  42. * \param hostDll The address of the module to hook.
  43. * \param originalFunction Address of the original function.
  44. * \param forwardFunctionEntry Name of the function to add a forward to. Must be of form `dll.API`.
  45. * \return TRUE, if hooking succeeded, otherwise, FALSE.
  46. */
  47. inline BOOL ezHook(HMODULE hostDll, void *originalFunction, char *forwardFunctionEntry)
  48. {
  49. /*
  50. * Note that we are not doing any trampoline magic or editing the assembly!
  51. *
  52. * Instead, we are reading the module's PE file, find the original function's entry in the export address table (EAT),
  53. * and replace it with a forward import.
  54. *
  55. * This ultimately will fool the game/executable to call our hook, while keeping original function intact.
  56. *
  57. * Thus, in order to work, the proxy DLL has to export the hook, because we are essentially
  58. * asking the game to call our hook without ever going to the original function (unlike with trampolines).
  59. */
  60. size_t fwdlen = strlen(forwardFunctionEntry);
  61. // The module always starts with a DOS (or "MZ") header
  62. IMAGE_DOS_HEADER *mz = (PIMAGE_DOS_HEADER)hostDll;
  63. // Next, get the NT headers. The offset to them is saved in e_lfanew
  64. IMAGE_NT_HEADERS *nt = RVA2PTR(PIMAGE_NT_HEADERS, mz, mz->e_lfanew);
  65. // Get the pointer to the data directory of the exports
  66. IMAGE_DATA_DIRECTORY *edirp = &nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
  67. IMAGE_DATA_DIRECTORY edir = *edirp;
  68. // Finally, the virtual address in the data direcotry tells the location of the exports table
  69. IMAGE_EXPORT_DIRECTORY *exports = RVA2PTR(PIMAGE_EXPORT_DIRECTORY, mz, edir.VirtualAddress);
  70. // Read the addrress of the function list and the address of function names
  71. DWORD *addrs = RVA2PTR(DWORD*, mz, exports->AddressOfFunctions);
  72. // DWORD* names = RVA2PTR(DWORD*, mz, exports->AddressOfNames);
  73. // Iterate through all functions
  74. for (unsigned i = 0; i < exports->NumberOfFunctions; i++)
  75. {
  76. //char* name = RVA2PTR(char*, mz, names[i]); // Name of the exported function
  77. void *addr = RVA2PTR(void*, mz, addrs[i]); // Address of the exported function
  78. // Check if we have the function we need to modify
  79. if (addr == originalFunction)
  80. {
  81. DWORD fptr = edir.VirtualAddress + edir.Size;
  82. int err = 0;
  83. // Update the entry to go the the last entry (which we will populate in the next memcpy)
  84. err |= vpmemcpy(&addrs[i], &fptr, sizeof(fptr));
  85. // Add the forwarding import to our function at the end of the EAT
  86. err |= vpmemcpy(((char*)exports + edir.Size), forwardFunctionEntry, fwdlen);
  87. // Increment the size of the export data directory
  88. // and write the new export data directory
  89. edir.Size += fwdlen + 1;
  90. err |= vpmemcpy(edirp, &edir, sizeof(edir));
  91. return err == 0;
  92. }
  93. }
  94. return FALSE;
  95. }
  96. /**
  97. * \brief Hooks the given function through the Import Address Table
  98. * \param dll Module to hook
  99. * \param targetFunction Address of the target function to hook
  100. * \param detourFunction Address of the detour function
  101. * \return TRUE if successful, otherwise FALSE
  102. */
  103. inline BOOL iat_hook(HMODULE dll, char const* targetDLL, void *targetFunction, void *detourFunction)
  104. {
  105. IMAGE_DOS_HEADER *mz = (PIMAGE_DOS_HEADER)dll;
  106. IMAGE_NT_HEADERS *nt = RVA2PTR(PIMAGE_NT_HEADERS, mz, mz->e_lfanew);
  107. IMAGE_IMPORT_DESCRIPTOR *imports = RVA2PTR(PIMAGE_IMPORT_DESCRIPTOR, mz, nt->OptionalHeader.DataDirectory[
  108. IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
  109. for (int i = 0; imports[i].Characteristics; i++)
  110. {
  111. char *name = RVA2PTR(char*, mz, imports[i].Name);
  112. if(lstrcmpiA(name, targetDLL) != 0)
  113. continue;
  114. void **thunk = RVA2PTR(void**, mz, imports[i].FirstThunk);
  115. void **nextThunk = RVA2PTR(void**, mz, imports[i+1].FirstThunk);
  116. for (; thunk && thunk < nextThunk; thunk++)
  117. {
  118. void *import = *thunk;
  119. if (import != targetFunction)
  120. continue;
  121. DWORD oldState;
  122. if (!VirtualProtect(thunk, sizeof(void*), PAGE_READWRITE, &oldState))
  123. return FALSE;
  124. *thunk = (void*)detourFunction;
  125. VirtualProtect(thunk, sizeof(void*), oldState, &oldState);
  126. return TRUE;
  127. }
  128. }
  129. return FALSE;
  130. }