From 96f235732c58593b34eaf2bb10ab60c0d45e1f16 Mon Sep 17 00:00:00 2001 From: Akash Mozumdar Date: Wed, 4 Sep 2019 12:23:23 -0400 Subject: [PATCH] hook search through all exported functions, hook search deeper into stack, fix bug with dlls being loaded next to each other --- GUI/mainwindow.cpp | 29 ++++++++++++++++++------- include/types.h | 1 + texthook/hookfinder.cc | 48 +++++++++++++++++++++++++++++++++++------- 3 files changed, 62 insertions(+), 16 deletions(-) diff --git a/GUI/mainwindow.cpp b/GUI/mainwindow.cpp index 7b35d8e..f4c4980 100644 --- a/GUI/mainwindow.cpp +++ b/GUI/mainwindow.cpp @@ -500,8 +500,16 @@ void MainWindow::FindHooks() layout.addWidget(&startButton); connect(&startButton, &QPushButton::clicked, &dialog, &QDialog::accept); if (!dialog.exec()) return; - QByteArray pattern = QByteArray::fromHex(patternInput.text().replace("??", QString::number(XX, 16)).toUtf8()); - memcpy(sp.pattern, pattern.data(), sp.length = min(pattern.size(), 25)); + if (patternInput.text().contains('.')) + { + wcsncpy_s(sp.module, S(patternInput.text()).c_str(), MAX_MODULE_SIZE - 1); + sp.length = 1; + } + else + { + QByteArray pattern = QByteArray::fromHex(patternInput.text().replace("??", QString::number(XX, 16)).toUtf8()); + memcpy(sp.pattern, pattern.data(), sp.length = min(pattern.size(), PATTERN_SIZE)); + } try { filter = S(filterInput.text()); } catch (std::regex_error) {} } else @@ -510,10 +518,13 @@ void MainWindow::FindHooks() filter = std::wregex(cjkCheckbox.isChecked() ? L"[\\u3000-\\ua000]{4,}" : L"[\\u0020-\\u1000]{4,}"); } - auto hooks = std::make_shared>>(); + auto hooks = std::make_shared>(); try { - Host::FindHooks(processId, sp, [hooks, filter](HookParam hp, const std::wstring& text) { if (std::regex_search(text, filter)) hooks->push_back({ hp, text }); }); + Host::FindHooks(processId, sp, [hooks, processId, filter](HookParam hp, const std::wstring& text) + { + if (std::regex_search(text, filter)) hooks->push_back(S(Util::GenerateCode(hp, processId) + L" => " + text)); + }); } catch (std::out_of_range) { return; } std::thread([this, hooks, processId] @@ -529,8 +540,7 @@ void MainWindow::FindHooks() hookList->setAttribute(Qt::WA_DeleteOnClose); hookList->resize({ 750, 300 }); hookList->setWindowTitle(SEARCH_FOR_HOOKS); - for (auto [hp, text] : *hooks) - new QListWidgetItem(S(Util::GenerateCode(hp, processId) + L" => " + text), hookList); + for (const auto& hook : *hooks) new QListWidgetItem(hook, hookList); connect(hookList, &QListWidget::itemClicked, [this](QListWidgetItem* item) { AddHook(item->text().split(" => ")[0]); }); hookList->show(); @@ -538,8 +548,11 @@ void MainWindow::FindHooks() if (!saveFileName.isEmpty()) { QTextFile saveFile(saveFileName, QIODevice::WriteOnly | QIODevice::Truncate); - for (auto [hp, text] : *hooks) - saveFile.write(S(Util::GenerateCode(hp, processId) + L" => " + text + L"\n").toUtf8()); + for (const auto& hook : *hooks) + { + saveFile.write(hook.toUtf8()); + saveFile.write("\n"); + } } hooks->clear(); }); diff --git a/include/types.h b/include/types.h index aae1ba2..b96ae22 100644 --- a/include/types.h +++ b/include/types.h @@ -67,6 +67,7 @@ struct SearchParam maxRecords = 100000, codepage = SHIFT_JIS; uintptr_t padding = 0, minAddress = 0, maxAddress = (uintptr_t)-1; + wchar_t module[MAX_MODULE_SIZE] = {}; wchar_t text[PATTERN_SIZE] = {}; // text to search for void(*hookPostProcessor)(HookParam&) = nullptr; }; diff --git a/texthook/hookfinder.cc b/texthook/hookfinder.cc index 93ffc0c..76dee33 100644 --- a/texthook/hookfinder.cc +++ b/texthook/hookfinder.cc @@ -15,7 +15,7 @@ namespace { SearchParam sp; - constexpr int CACHE_SIZE = 500'000; + constexpr int MAX_STRING_SIZE = 500, CACHE_SIZE = 300'000; struct HookRecord { ~HookRecord() @@ -32,7 +32,7 @@ namespace } uint64_t address = 0; int offset = 0; - char text[500] = {}; + char text[MAX_STRING_SIZE] = {}; }; std::unique_ptr records; long recordsAvailable; @@ -114,18 +114,31 @@ namespace #endif } +bool IsBadStrPtr(void* str) +{ + if (str < (void*)0x1000) return true; + + MEMORY_BASIC_INFORMATION info; + if (VirtualQuery(str, &info, sizeof(info)) == 0 || info.Protect < PAGE_READONLY || info.Protect & (PAGE_GUARD | PAGE_NOACCESS)) return true; + + void* regionEnd = (BYTE*)info.BaseAddress + info.RegionSize; + if ((BYTE*)str + MAX_STRING_SIZE <= regionEnd) return false; + return IsBadStrPtr(regionEnd); +} + void Send(char** stack, uintptr_t address) { // it is unsafe to call ANY external functions from this, as they may have been hooked (if called the hook would call this function making an infinite loop) // the exceptions are compiler intrinsics like _InterlockedDecrement if (recordsAvailable <= 0) return; - for (int i = -registers; i < 6; ++i) + for (int i = -registers; i < 10; ++i) { int length = 0, sum = 0; char* str = stack[i] + sp.padding; - __try { for (; (str[length] || str[length + 1]) && length < 500; length += 2) sum += str[length] + str[length + 1]; } + if (IsBadStrPtr(str)) return; // seems to improve performance; TODO: more tests and benchmarks to confirm + __try { for (; (str[length] || str[length + 1]) && length < MAX_STRING_SIZE; length += 2) sum += str[length] + str[length + 1]; } __except (EXCEPTION_EXECUTE_HANDLER) {} - if (length > STRING && length < 499) + if (length > STRING && length < MAX_STRING_SIZE - 1) { __try { @@ -156,6 +169,24 @@ void Send(char** stack, uintptr_t address) } } +std::vector GetFunctions(uintptr_t module) +{ + if (!module) return {}; + IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)module; + if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) return {}; + IMAGE_NT_HEADERS* ntHeader = (IMAGE_NT_HEADERS*)(module + dosHeader->e_lfanew); + if (ntHeader->Signature != IMAGE_NT_SIGNATURE) return {}; + DWORD exportAddress = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + if (!exportAddress) return {}; + IMAGE_EXPORT_DIRECTORY* exportDirectory = (IMAGE_EXPORT_DIRECTORY*)(module + exportAddress); + std::vector functions; + for (int i = 0; i < exportDirectory->NumberOfNames; ++i) + //char* funcName = (char*)(module + *(DWORD*)(module + exportDirectory->AddressOfNames + i * sizeof(DWORD))); + functions.push_back(module + *(DWORD*)(module + exportDirectory->AddressOfFunctions + + sizeof(DWORD) * *(WORD*)(module + exportDirectory->AddressOfNameOrdinals + i * sizeof(WORD)))); + return functions; +} + void SearchForHooks(SearchParam spUser) { std::thread([=] @@ -175,12 +206,13 @@ void SearchForHooks(SearchParam spUser) { VirtualQuery((void*)moduleStopAddress, &info, sizeof(info)); moduleStopAddress = (uintptr_t)info.BaseAddress + info.RegionSize; - } while (info.Protect > PAGE_NOACCESS); + } while (info.Protect > PAGE_EXECUTE); moduleStopAddress -= info.RegionSize; ConsoleOutput(STARTING_SEARCH); - std::vector addresses = Util::SearchMemory(sp.pattern, sp.length, PAGE_EXECUTE, sp.minAddress, sp.maxAddress); - for (auto& addr : addresses) addr += sp.offset; + std::vector addresses; + if (*sp.module) addresses = GetFunctions((uintptr_t)GetModuleHandleW(sp.module)); + else for (auto& addr : addresses = Util::SearchMemory(sp.pattern, sp.length, PAGE_EXECUTE, sp.minAddress, sp.maxAddress)) addr += sp.offset; addresses.erase(std::remove_if(addresses.begin(), addresses.end(), [&](uint64_t addr) { return addr > moduleStartAddress && addr < moduleStopAddress; }), addresses.end()); *(void**)(trampoline + send_offset) = Send; auto trampolines = (decltype(trampoline)*)VirtualAlloc(NULL, sizeof(trampoline) * addresses.size(), MEM_COMMIT, PAGE_READWRITE);