-
-
Notifications
You must be signed in to change notification settings - Fork 225
/
EatHook.cpp
149 lines (119 loc) · 5.41 KB
/
EatHook.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#include "polyhook2/PE/EatHook.hpp"
PLH::EatHook::EatHook(const std::string& apiName, const std::wstring& moduleName, const char* fnCallback, uint64_t* userOrigVar)
: EatHook(apiName, moduleName, (uint64_t)fnCallback, userOrigVar)
{}
PLH::EatHook::EatHook(const std::string& apiName, const std::wstring& moduleName, const uint64_t fnCallback, uint64_t* userOrigVar)
: m_moduleName(moduleName)
, m_apiName(apiName)
, m_fnCallback(fnCallback)
, m_userOrigVar(userOrigVar)
, m_allocator(64, 64) // arbitrary, size is big enough but an overshoot
, m_trampoline(0)
, m_moduleBase(0)
, m_origFunc(0)
{}
bool PLH::EatHook::hook() {
assert(m_userOrigVar != nullptr);
uint32_t* pExport = FindEatFunction(m_apiName, m_moduleName);
if (pExport == nullptr)
return false;
uint64_t offset = m_fnCallback - m_moduleBase;
/* account for when offset to our function is beyond EAT slots size. We
instead allocate a small trampoline within +- 2GB which will do the full
width jump to the final destination, and point the EAT to the stub.*/
if (offset > std::numeric_limits<uint32_t>::max()) {
m_trampoline = (uint64_t)m_allocator.allocate(m_moduleBase, PLH::calc_2gb_above(m_moduleBase));
if (m_trampoline == 0) {
Log::log("EAT hook offset is > 32bit's. Allocation of trampoline necessary and failed to find free page within range", ErrorLevel::INFO);
return false;
}
MemoryProtector protector(m_trampoline, 64, ProtFlag::R | ProtFlag::W | ProtFlag::X, *this, false);
PLH::ADisassembler::writeEncoding(makeAgnosticJmp(m_trampoline, m_fnCallback), *this);
offset = m_trampoline - m_moduleBase;
Log::log("EAT hook offset is > 32bit's. Allocation of trampoline necessary", ErrorLevel::INFO);
}
// Just like IAT, EAT is by default a writeable section
// any EAT entry must be an offset
MemoryProtector prot((uint64_t)pExport, sizeof(uintptr_t), ProtFlag::R | ProtFlag::W, *this);
m_origFunc = *pExport; // original offset
*pExport = (uint32_t)offset;
m_hooked = true;
*m_userOrigVar = m_moduleBase + m_origFunc; // original pointer (base + off)
return true;
}
bool PLH::EatHook::unHook() {
assert(m_userOrigVar != nullptr);
assert(m_hooked);
if (!m_hooked) {
Log::log("EatHook unhook failed: no hook present", ErrorLevel::SEV);
return false;
}
uint32_t* pExport = FindEatFunction(m_apiName, m_moduleName);
if (pExport == nullptr)
return false;
MemoryProtector prot((uint64_t)pExport, sizeof(uintptr_t), ProtFlag::R | ProtFlag::W, *this);
*pExport = (uint32_t)m_origFunc;
m_hooked = false;
*m_userOrigVar = NULL;
// TODO: change hook to re-use existing trampoline rather than free-ing here to avoid overwrite later and dangling pointer
if (m_trampoline) {
m_allocator.deallocate(m_trampoline);
m_trampoline = 0;
}
return true;
}
uint32_t* PLH::EatHook::FindEatFunction(const std::string& apiName, const std::wstring& moduleName) {
#if defined(_WIN64)
PEB* peb = (PPEB)__readgsqword(0x60);
#else
PEB* peb = (PPEB)__readfsdword(0x30);
#endif
uint32_t* pExportAddress = nullptr;
PEB_LDR_DATA* ldr = (PPEB_LDR_DATA)peb->Ldr;
// find loaded module from peb
for (auto* dte = (LDR_DATA_TABLE_ENTRY*)ldr->InLoadOrderModuleList.Flink;
dte->DllBase != NULL;
dte = (LDR_DATA_TABLE_ENTRY*)dte->InLoadOrderLinks.Flink) {
// try all modules if none given, otherwise only try specified
const ci_wstring_view baseModuleName{dte->BaseDllName.Buffer, dte->BaseDllName.Length / sizeof(wchar_t)};
if (!moduleName.empty() && baseModuleName.compare(moduleName.data()) != 0)
continue;
//std::wcout << moduleName << L" Found module" << std::endl;
m_moduleBase = (uint64_t)dte->DllBase;
pExportAddress = FindEatFunctionInModule(apiName);
if (pExportAddress != nullptr)
return pExportAddress;
}
if (pExportAddress == nullptr) {
Log::log("Failed to find export address from requested dll", ErrorLevel::SEV);
}
return pExportAddress;
}
uint32_t* PLH::EatHook::FindEatFunctionInModule(const std::string& apiName) {
assert(m_moduleBase != NULL);
if (m_moduleBase == NULL)
return NULL;
auto* pDos = (IMAGE_DOS_HEADER*)m_moduleBase;
auto* pNT = RVA2VA(IMAGE_NT_HEADERS*, m_moduleBase, pDos->e_lfanew);
auto* pDataDir = (IMAGE_DATA_DIRECTORY*)pNT->OptionalHeader.DataDirectory;
if (pDataDir[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == NULL) {
Log::log("PEs without export tables are unsupported", ErrorLevel::SEV);
return NULL;
}
auto* pExports = RVA2VA(IMAGE_EXPORT_DIRECTORY*, m_moduleBase, pDataDir[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
auto* pAddressOfFunctions = RVA2VA(uint32_t*, m_moduleBase, pExports->AddressOfFunctions);
auto* pAddressOfNames = RVA2VA(uint32_t*, m_moduleBase, pExports->AddressOfNames);
auto* pAddressOfNameOrdinals = RVA2VA(uint16_t*, m_moduleBase, pExports->AddressOfNameOrdinals);
for (uint32_t i = 0; i < pExports->NumberOfNames; i++)
{
if(my_narrow_stricmp(RVA2VA(char*, m_moduleBase, pAddressOfNames[i]),
apiName.c_str()) != 0)
continue;
// std::cout << RVA2VA(char*, m_moduleBase, pAddressOfNames[i]) << std::endl;
const uint16_t iExportOrdinal = pAddressOfNameOrdinals[i];
uint32_t* pExportAddress = &pAddressOfFunctions[iExportOrdinal];
return pExportAddress;
}
Log::log("API not found before end of EAT", ErrorLevel::SEV);
return nullptr;
}