diff --git a/GUI/host/host.cpp b/GUI/host/host.cpp index 013f587..9c5f875 100644 --- a/GUI/host/host.cpp +++ b/GUI/host/host.cpp @@ -48,6 +48,11 @@ namespace }).detach(); } + Host::HookEventHandler OnHookFound = [](HookParam hp, DWORD processId, const std::wstring& text) + { + Host::AddConsoleOutput(Util::GenerateCode(hp, 0) + L": " + text); + }; + private: DWORD processId; HANDLE pipe; @@ -100,6 +105,17 @@ namespace while (ReadFile(hookPipe, buffer, PIPE_BUFFER_SIZE, &bytesRead, nullptr)) switch (*(HostNotificationType*)buffer) { + case HOST_NOTIFICATION_FOUND_HOOK: + { + auto info = *(HookFoundNotif*)buffer; + auto& OnHookFound = processRecordsByIds->at(processId).OnHookFound; + OnHookFound(info.hp, processId, info.text); + info.hp.type = USING_STRING; + if (auto converted = Util::StringToWideString((char*)info.text, Host::defaultCodepage)) if (converted->size() > 12) OnHookFound(info.hp, processId, converted.value()); + info.hp.codepage = CP_UTF8; + if (auto converted = Util::StringToWideString((char*)info.text, CP_UTF8)) if (converted->size() > 12) OnHookFound(info.hp, processId, converted.value()); + } + break; case HOST_NOTIFICATION_RMVHOOK: { auto info = *(HookRemovedNotif*)buffer; @@ -206,6 +222,12 @@ namespace Host processRecordsByIds->at(processId).Send(InsertHookCmd(hp)); } + void FindHooks(DWORD processId, SearchParam sp, HookEventHandler HookFound) + { + if (HookFound) processRecordsByIds->at(processId).OnHookFound = HookFound; + processRecordsByIds->at(processId).Send(FindHookCmd(sp)); + } + HookParam GetHookParam(ThreadParam tp) { return processRecordsByIds->at(tp.processId).GetHook(tp.addr).hp; diff --git a/GUI/host/host.h b/GUI/host/host.h index 6c990b6..6f39405 100644 --- a/GUI/host/host.h +++ b/GUI/host/host.h @@ -7,11 +7,13 @@ namespace Host { using ProcessEventHandler = std::function; using ThreadEventHandler = std::function; + using HookEventHandler = std::function; void Start(ProcessEventHandler Connect, ProcessEventHandler Disconnect, ThreadEventHandler Create, ThreadEventHandler Destroy, TextThread::OutputCallback Output); void InjectProcess(DWORD processId); void DetachProcess(DWORD processId); void InsertHook(DWORD processId, HookParam hp); + void FindHooks(DWORD processId, SearchParam sp, HookEventHandler HookFound = {}); HookParam GetHookParam(ThreadParam tp); diff --git a/GUI/mainwindow.cpp b/GUI/mainwindow.cpp index cdec206..7a68d35 100644 --- a/GUI/mainwindow.cpp +++ b/GUI/mainwindow.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,7 @@ extern const char* LAUNCH; extern const char* DETACH; extern const char* ADD_HOOK; extern const char* SAVE_HOOKS; +extern const char* FIND_HOOKS; extern const char* SETTINGS; extern const char* EXTENSIONS; extern const char* SELECT_PROCESS; @@ -25,6 +27,15 @@ extern const char* ATTACH_INFO; extern const char* SEARCH_GAME; extern const char* PROCESSES; extern const char* CODE_INFODUMP; +extern const char* HOOH_SEARCH_UNSTABLE_WARNING; +extern const char* SEARCH_PATTERN; +extern const char* SEARCH_DURATION; +extern const char* PATTERN_OFFSET; +extern const char* MIN_ADDRESS; +extern const char* MAX_ADDRESS; +extern const char* START_HOOK_SEARCH; +extern const char* SAVE_SEARCH_RESULTS; +extern const char* TEXT_FILES; extern const char* SAVE_SETTINGS; extern const char* USE_JP_LOCALE; extern const char* FILTER_REPETITION; @@ -49,6 +60,7 @@ MainWindow::MainWindow(QWidget *parent) : { DETACH, &MainWindow::DetachProcess }, { ADD_HOOK, &MainWindow::AddHook }, { SAVE_HOOKS, &MainWindow::SaveHooks }, + { "Find hooks", &MainWindow::FindHooks }, { SETTINGS, &MainWindow::Settings }, { EXTENSIONS, &MainWindow::Extensions } }) @@ -317,6 +329,74 @@ void MainWindow::SaveHooks() } } +void MainWindow::FindHooks() +{ + QMessageBox::information(this, FIND_HOOKS, HOOH_SEARCH_UNSTABLE_WARNING); + struct : QDialog + { + using QDialog::QDialog; + void launch() + { + auto layout = new QFormLayout(this); + auto patternInput = new QLineEdit("8B FF 55 8B EC", this); + layout->addRow(SEARCH_PATTERN, patternInput); + for (auto[value, label] : Array>{ + { sp.searchTime, SEARCH_DURATION }, + { sp.offset, PATTERN_OFFSET }, + }) + { + auto spinBox = new QSpinBox(this); + spinBox->setMaximum(INT_MAX); + spinBox->setValue(value); + layout->addRow(label, spinBox); + connect(spinBox, qOverload(&QSpinBox::valueChanged), [=, &value] { value = spinBox->value(); }); + } + for (auto[value, label] : Array>{ + { sp.minAddress, MIN_ADDRESS }, + { sp.maxAddress, MAX_ADDRESS }, + }) + { + auto input = new QLineEdit(QString::number(value, 16), this); + layout->addRow(label, input); + connect(input, &QLineEdit::textEdited, [&value](QString input) + { + bool ok; + if (uintptr_t newValue = input.toULongLong(&ok, 16); ok) value = newValue; + }); + } + auto save = new QPushButton(START_HOOK_SEARCH, this); + layout->addWidget(save); + connect(save, &QPushButton::clicked, this, &QDialog::accept); + connect(save, &QPushButton::clicked, [this, patternInput] + { + QByteArray pattern = QByteArray::fromHex(patternInput->text().toUtf8()); + if (pattern.size() < 3) return; + memcpy(sp.pattern, pattern.data(), sp.length = min(pattern.size(), 25)); + auto hooks = std::make_shared(); + Host::FindHooks(processId, sp, [hooks](HookParam hp, DWORD processId, const std::wstring& text) + { + hooks->append(S(Util::GenerateCode(hp, processId)) + ": " + S(text) + "\n"); + }); + QString fileName = QFileDialog::getSaveFileName(this, SAVE_SEARCH_RESULTS, "./Hooks.txt", TEXT_FILES); + if (fileName.isEmpty()) fileName = "Hooks.txt"; + std::thread([hooks, fileName] + { + for (int lastSize = 0; hooks->size() == 0 || hooks->size() != lastSize; Sleep(2000)) lastSize = hooks->size(); + QTextFile(fileName, QIODevice::WriteOnly | QIODevice::Truncate).write(hooks->toUtf8()); + hooks->clear(); + }).detach(); + }); + setWindowTitle(FIND_HOOKS); + exec(); + } + + SearchParam sp = {}; + DWORD processId; + } searchDialog(this, Qt::WindowCloseButtonHint); + searchDialog.processId = GetSelectedProcessId(); + searchDialog.launch(); +} + void MainWindow::Settings() { struct : QDialog diff --git a/GUI/mainwindow.h b/GUI/mainwindow.h index 9962de7..cee7ec2 100644 --- a/GUI/mainwindow.h +++ b/GUI/mainwindow.h @@ -36,6 +36,7 @@ private: void DetachProcess(); void AddHook(); void SaveHooks(); + void FindHooks(); void Settings(); void Extensions(); void ViewThread(int index); diff --git a/include/const.h b/include/const.h index 469a5c3..9779867 100644 --- a/include/const.h +++ b/include/const.h @@ -6,9 +6,9 @@ enum Misc { MESSAGE_SIZE = 500, PIPE_BUFFER_SIZE = 2000, SHIFT_JIS = 932, MAX_MODULE_SIZE = 120, HOOK_NAME_SIZE = 30, FIXED_SPLIT_VALUE = 0x10001 }; -enum HostCommandType { HOST_COMMAND_NEW_HOOK, HOST_COMMAND_REMOVE_HOOK, HOST_COMMAND_MODIFY_HOOK, HOST_COMMAND_HIJACK_PROCESS, HOST_COMMAND_DETACH }; +enum HostCommandType { HOST_COMMAND_NEW_HOOK, HOST_COMMAND_REMOVE_HOOK, HOST_COMMAND_FIND_HOOK, HOST_COMMAND_MODIFY_HOOK, HOST_COMMAND_HIJACK_PROCESS, HOST_COMMAND_DETACH }; -enum HostNotificationType { HOST_NOTIFICATION_TEXT, HOST_NOTIFICATION_NEWHOOK, HOST_NOTIFICATION_RMVHOOK }; +enum HostNotificationType { HOST_NOTIFICATION_TEXT, HOST_NOTIFICATION_NEWHOOK, HOST_NOTIFICATION_FOUND_HOOK, HOST_NOTIFICATION_RMVHOOK }; enum HookParamType : unsigned { diff --git a/include/types.h b/include/types.h index 3b7248b..a59f693 100644 --- a/include/types.h +++ b/include/types.h @@ -58,6 +58,14 @@ struct ThreadParam uint64_t ctx2; // The subcontext of the hook: 0 by default, generated in a method specific to the hook }; +struct SearchParam +{ + BYTE pattern[25] = {}; // pattern in memory to search for + int length, // length of pattern + offset = 0, // offset from start of pattern to add hook + searchTime = 30000; // ms + uintptr_t minAddress = 0, maxAddress = (uintptr_t)-1LL; +}; struct InsertHookCmd // From host { @@ -66,14 +74,29 @@ struct InsertHookCmd // From host HookParam hp; }; -struct ConsoleOutputNotif // From hook +struct FindHookCmd // From host +{ + FindHookCmd(SearchParam sp) : sp(sp) {} + HostCommandType command = HOST_COMMAND_FIND_HOOK; + SearchParam sp; +}; + +struct ConsoleOutputNotif // From dll { ConsoleOutputNotif(std::string message = "") { strncpy_s(this->message, message.c_str(), MESSAGE_SIZE - 1); } HostNotificationType command = HOST_NOTIFICATION_TEXT; char message[MESSAGE_SIZE] = {}; }; -struct HookRemovedNotif // From hook +struct HookFoundNotif // From dll +{ + HookFoundNotif(HookParam hp, wchar_t* text) : hp(hp) { wcsncpy_s(this->text, text, MESSAGE_SIZE - 1); } + HostNotificationType command = HOST_NOTIFICATION_FOUND_HOOK; + HookParam hp; + wchar_t text[MESSAGE_SIZE] = {}; // though type is wchar_t, may not be encoded in UTF-16 (it's just convenient to use wcs* functions) +}; + +struct HookRemovedNotif // From dll { HookRemovedNotif(uint64_t address) : address(address) {}; HostNotificationType command = HOST_NOTIFICATION_RMVHOOK; diff --git a/text.cpp b/text.cpp index 8d92129..b13e386 100644 --- a/text.cpp +++ b/text.cpp @@ -11,6 +11,7 @@ const char* LAUNCH = u8"Launch game"; const char* DETACH = u8"Detach from game"; const char* ADD_HOOK = u8"Add hook"; const char* SAVE_HOOKS = u8"Save hook(s)"; +const char* FIND_HOOKS = u8"Find hooks"; const char* SETTINGS = u8"Settings"; const char* EXTENSIONS = u8"Extensions"; const char* SELECT_PROCESS = u8"Select process"; @@ -42,6 +43,15 @@ const char* EXTEN_WINDOW_INSTRUCTIONS = u8R"(Drag and drop extension (.dll) file Drag and drop within the list to reorder Press delete with an extension selected to remove it)"; const char* USE_JP_LOCALE = u8"Emulate japanese locale?"; +extern const char* HOOH_SEARCH_UNSTABLE_WARNING = u8"Searching for hooks is unstable! Be prepared for your game to crash!"; +extern const char* SEARCH_PATTERN = u8"Search pattern (hex byte array)"; +extern const char* SEARCH_DURATION = u8"Search duration (ms)"; +extern const char* PATTERN_OFFSET = u8"Offset from pattern start"; +extern const char* MIN_ADDRESS = u8"Minimum address (hex)"; +extern const char* MAX_ADDRESS = u8"Maximum address (hex)"; +extern const char* START_HOOK_SEARCH = u8"Start hook search"; +extern const char* SAVE_SEARCH_RESULTS = u8"Save search results"; +extern const char* TEXT_FILES = u8"Text (*.txt)"; const char* FILTER_REPETITION = u8"Repetition Filter"; const char* DEFAULT_CODEPAGE = u8"Default Codepage"; const char* FLUSH_DELAY = u8"Flush Delay"; @@ -70,7 +80,10 @@ const char* INSERTING_HOOK = u8"Textractor: inserting hook: %s"; const char* REMOVING_HOOK = u8"Textractor: removing hook: %s"; const char* HOOK_FAILED = u8"Textractor: failed to insert hook"; const char* TOO_MANY_HOOKS = u8"Textractor: too many hooks: can't insert"; +const char* STARTING_SEARCH = u8"Textractor: starting search"; const char* NOT_ENOUGH_TEXT = u8"Textractor: not enough text to search accurately"; +const char* HOOK_SEARCH_INITIALIZED = u8"Textractor: search initialized with %zd hooks"; +const char* HOOK_SEARCH_FINISHED = u8"Textractor: hook search finished, %d results found"; const char* FUNC_MISSING = u8"Textractor: function not present"; const char* MODULE_MISSING = u8"Textractor: module not present"; const char* GARBAGE_MEMORY = u8"Textractor: memory constantly changing, useless to read"; diff --git a/texthook/CMakeLists.txt b/texthook/CMakeLists.txt index 8ba720e..6e05fc9 100644 --- a/texthook/CMakeLists.txt +++ b/texthook/CMakeLists.txt @@ -4,6 +4,7 @@ if(${CMAKE_SIZEOF_VOID_P} EQUAL 8) set(texthook_src main.cc texthook.cc + hookfinder.cc engine/match64.cc engine/native/pchooks.cc util/ithsys/ithsys.cc @@ -13,6 +14,7 @@ else() set(texthook_src main.cc texthook.cc + hookfinder.cc engine/engine.cc engine/match.cc engine/native/pchooks.cc @@ -25,20 +27,4 @@ endif() add_library(texthook SHARED ${texthook_src}) -set_target_properties(texthook PROPERTIES - LINK_FLAGS "/SUBSYSTEM:WINDOWS /MANIFEST:NO" -) - -set(texthook_libs - Version.lib - minhook -) - -target_link_libraries(texthook ${texthook_libs}) - -target_compile_definitions(texthook - PRIVATE - _CRT_NON_CONFORMING_SWPRINTFS - _SCL_SECURE_NO_WARNINGS # config.pri - _CRT_SECURE_NO_WARNINGS -) +target_link_libraries(texthook minhook) diff --git a/texthook/engine/engine.cc b/texthook/engine/engine.cc index cc571bc..f86cff2 100644 --- a/texthook/engine/engine.cc +++ b/texthook/engine/engine.cc @@ -7,6 +7,9 @@ # pragma warning (disable:4819) #endif // _MSC_VER +#define _SCL_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS + #include "engine/engine.h" #include "engine/match.h" #include "util/util.h" @@ -19,7 +22,6 @@ #include "disasm/disasm.h" #include "cpputil/cppcstring.h" #include "mono/monoobject.h" -#include "growl.h" #include "const.h" #include "native/pchooks.h" //#include @@ -16876,25 +16878,25 @@ bool InsertPPSSPPHooks() // http://stackoverflow.com/questions/940707/how-do-i-programatically-get-the-version-of-a-dll-or-exe-file // get the version info for the file requested - if (DWORD dwSize = ::GetFileVersionInfoSizeW(processPath, nullptr)) { - UINT len = 0; - BYTE * buf = new BYTE[dwSize]; - VS_FIXEDFILEINFO * info = nullptr; - if (::GetFileVersionInfoW(processPath, 0, dwSize, buf) - && ::VerQueryValueW(buf, L"\\", (LPVOID*)&info, &len) - && info) - { - PPSSPP_VERSION[0] = HIWORD(info->dwFileVersionMS), - PPSSPP_VERSION[1] = LOWORD(info->dwFileVersionMS), - PPSSPP_VERSION[2] = HIWORD(info->dwFileVersionLS), - PPSSPP_VERSION[3] = LOWORD(info->dwFileVersionLS); - - } - else - ConsoleOutput("vnreng: failed to get PPSSPP version"); - delete[] buf; - - } + // if (DWORD dwSize = ::GetFileVersionInfoSizeW(processPath, nullptr)) { + // UINT len = 0; + // BYTE * buf = new BYTE[dwSize]; + // VS_FIXEDFILEINFO * info = nullptr; + // if (::GetFileVersionInfoW(processPath, 0, dwSize, buf) + // && ::VerQueryValueW(buf, L"\\", (LPVOID*)&info, &len) + // && info) + // { + // PPSSPP_VERSION[0] = HIWORD(info->dwFileVersionMS), + // PPSSPP_VERSION[1] = LOWORD(info->dwFileVersionMS), + // PPSSPP_VERSION[2] = HIWORD(info->dwFileVersionLS), + // PPSSPP_VERSION[3] = LOWORD(info->dwFileVersionLS); + // + // } + // else + // ConsoleOutput("vnreng: failed to get PPSSPP version"); + // delete[] buf; + // + //} InsertPPSSPPHLEHooks(); diff --git a/texthook/engine/match.cc b/texthook/engine/match.cc index 61bf640..812941b 100644 --- a/texthook/engine/match.cc +++ b/texthook/engine/match.cc @@ -2,15 +2,9 @@ // 8/9/2013 jichi // Branch: ITH_Engine/engine.cpp, revision 133 -#ifdef _MSC_VER -# pragma warning (disable:4100) // C4100: unreference formal parameter -//# pragma warning (disable:4733) // C4733: Inline asm assigning to 'FS:0' : handler not registered as safe handler -#endif // _MSC_VER - #include "engine/match.h" #include "engine/engine.h" #include "engine/native/pchooks.h" -#include "util/growl.h" #include "util/util.h" #include "main.h" #include "ithsys/ithsys.h" @@ -455,8 +449,8 @@ bool DetermineEngineByFile4() bool DetermineEngineByProcessName() { WCHAR str[MAX_PATH]; - wcscpy(str, processName); - _wcslwr(str); // lower case + wcscpy_s(str, processName); + _wcslwr_s(str); // lower case if (wcsstr(str,L"reallive") || Util::CheckFile(L"Reallive.exe") || Util::CheckFile(L"REALLIVEDATA\\Start.ini")) { InsertRealliveHook(); @@ -541,8 +535,8 @@ bool DetermineEngineByProcessName() } // This must appear at last since str is modified - wcscpy(str + len - 4, L"_checksum.exe"); - if (Util::CheckFile(str)) { + //wcscpy(str + len - 4, L"_checksum.exe"); + if (Util::CheckFile(L"*_checksum.exe")) { InsertRyokuchaHook(); if (Util::CheckFile(L"*.iar") && Util::CheckFile(L"*.sec5")) // jichi 9/27/2014: For new Ryokucha games diff --git a/texthook/hookfinder.cc b/texthook/hookfinder.cc new file mode 100644 index 0000000..6bff29a --- /dev/null +++ b/texthook/hookfinder.cc @@ -0,0 +1,196 @@ +#include "hookfinder.h" +#include "defs.h" +#include "main.h" +#include "util.h" + +extern const char* STARTING_SEARCH; +extern const char* HOOK_SEARCH_INITIALIZED; +extern const char* HOOK_SEARCH_FINISHED; + +extern WinMutex viewMutex; + +namespace +{ + constexpr int CACHE_SIZE = 500'000; + struct HookRecord + { + HookRecord() : address(0) {} + ~HookRecord() { if (address) NotifyHookFound(address, offset, text); } + uint64_t address; + int offset; + wchar_t text[200]; + }; + std::unique_ptr records; + long recordsAvailable; + uint64_t addressCharCache[CACHE_SIZE] = {}; + long sumCache[CACHE_SIZE] = {}; + + DWORD DUMMY; +#ifndef _WIN64 + BYTE trampoline[32] = + { + 0x9c, // pushfd + 0x60, // pushad + 0x68, 0,0,0,0, // push @addr ; after this a total of 0x28 bytes are pushed + 0x8d, 0x44, 0x24, 0x28, // lea eax,[esp+0x28] + 0x50, // push eax ; stack + 0xbb, 0,0,0,0, // mov ebx,@Send + 0xff, 0xd3, // call ebx + 0x83, 0xc4, 0x08, // add esp, 0x8 ; doesn't matter which register + 0x61, // popad + 0x9d, // popfd + 0x68, 0,0,0,0, // push @original + 0xc3 // ret ; basically absolute jmp to @original + }; + constexpr int addr_offset = 3, send_offset = 13, original_offset = 25, registers = 8; +#else + BYTE trampoline[128] = { + 0x9c, // push rflags + 0x50, // push rax + 0x53, // push rbx + 0x51, // push rcx + 0x52, // push rdx + 0x54, // push rsp + 0x55, // push rbp + 0x56, // push rsi + 0x57, // push rdi + 0x41, 0x50, // push r8 + 0x41, 0x51, // push r9 + 0x41, 0x52, // push r10 + 0x41, 0x53, // push r11 + 0x41, 0x54, // push r12 + 0x41, 0x55, // push r13 + 0x41, 0x56, // push r14 + 0x41, 0x57, // push r15 + // https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention + // https://stackoverflow.com/questions/43358429/save-value-of-xmm-registers + 0x48, 0x83, 0xec, 0x20, // sub rsp,0x20 + 0xc5, 0xfa, 0x7f, 0x24, 0x24, // vmovdqu [rsp],xmm4 + 0xc5, 0xfa, 0x7f, 0x6c, 0x24, 0x10, // vmovdqu [rsp+0x10],xmm5 + 0x48, 0x8d, 0x8c, 0x24, 0xa8, 0x00, 0x00, 0x00, // lea rcx,[rsp+0xa8] + 0x48, 0xba, 0,0,0,0,0,0,0,0, // mov rcx,@addr + 0x48, 0xb8, 0,0,0,0,0,0,0,0, // mov rax,@Send + 0xff, 0xd0, // call rax + 0xc5, 0xfa, 0x6f, 0x6c, 0x24, 0x10, // vmovdqu xmm5,XMMWORD PTR[rsp + 0x10] + 0xc5, 0xfa, 0x6f, 0x24, 0x24, // vmovdqu xmm4,XMMWORD PTR[rsp] + 0x48, 0x83, 0xc4, 0x20, // add rsp,0x20 + 0x41, 0x5f, // pop r15 + 0x41, 0x5e, // pop r14 + 0x41, 0x5d, // pop r13 + 0x41, 0x5c, // pop r12 + 0x41, 0x5b, // pop r11 + 0x41, 0x5a, // pop r10 + 0x41, 0x59, // pop r9 + 0x41, 0x58, // pop r8 + 0x5f, // pop rdi + 0x5e, // pop rsi + 0x5d, // pop rbp + 0x5c, // pop rsp + 0x5a, // pop rdx + 0x59, // pop rcx + 0x5b, // pop rbx + 0x58, // pop rax + 0x9d, // pop rflags + 0xff, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp qword ptr [0] ; relative to next instruction (i.e. jmp @original) + 0,0,0,0,0,0,0,0 // @original + }; + constexpr int addr_offset = 50, send_offset = 60, original_offset = 116, registers = 16; +#endif +} + +void Send(wchar_t** 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) + { + int length = 0, sum = 0; + __try { for (wchar_t* str = stack[i]; str[length] && length < 200; ++length) sum += str[length]; } + __except (EXCEPTION_EXECUTE_HANDLER) {} + if (length > 7 && length < 199) + { + __try + { + // many duplicate results with same address and second character will be found: filter them out + uint64_t addressAndChar = (((uint64_t)stack[i][1]) << 48) | address; + if (addressCharCache[addressAndChar % CACHE_SIZE] == addressAndChar) continue; + addressCharCache[addressAndChar % CACHE_SIZE] = addressAndChar; + // if there are huge amount of strings that are the same, it's probably garbage: filter them out + // can't store all the strings, so use sum as heuristic instead + if (_InterlockedIncrement(sumCache + (sum % CACHE_SIZE)) > 25) continue; + } + __except (EXCEPTION_EXECUTE_HANDLER) {} + + long n = _InterlockedDecrement(&recordsAvailable); + __try + { + if (n > 0) + { + records[n].address = address; + records[n].offset = i * sizeof(wchar_t*); + for (int j = 0; j < length; ++j) records[n].text[j] = stack[i][j]; + records[n].text[length] = 0; + } + } + __except (EXCEPTION_EXECUTE_HANDLER) { records[n].address = 0; } + + } + } +} + +void SearchForHooks(SearchParam sp) +{ + std::thread([=] + { + static std::mutex m; + std::scoped_lock lock(m); + + try + { + records = std::make_unique(recordsAvailable = CACHE_SIZE); + } + catch (std::bad_alloc&) { return ConsoleOutput("Textractor: SearchForHooks ERROR (out of memory)"); } + + uintptr_t moduleStartAddress = (uintptr_t)GetModuleHandleW(ITH_DLL); + uintptr_t moduleStopAddress = moduleStartAddress; + MEMORY_BASIC_INFORMATION info; + do + { + VirtualQuery((void*)moduleStopAddress, &info, sizeof(info)); + moduleStopAddress = (uintptr_t)info.BaseAddress + info.RegionSize; + } while (info.Protect > PAGE_NOACCESS); + moduleStopAddress -= info.RegionSize; + + ConsoleOutput(STARTING_SEARCH); + std::vector addresses = Util::SearchMemory(sp.pattern, sp.length); + for (auto& addr : addresses) addr += sp.offset; + addresses.erase(std::remove_if(addresses.begin(), addresses.end(), [&](uint64_t addr) + { + return (addr > moduleStartAddress && addr < moduleStopAddress) || addr > sp.maxAddress || addr < sp.minAddress; + }), addresses.end()); + *(void**)(trampoline + send_offset) = Send; + auto trampolines = (decltype(trampoline)*)VirtualAlloc(NULL, sizeof(trampoline) * addresses.size(), MEM_COMMIT, PAGE_READWRITE); + VirtualProtect(trampolines, addresses.size() * sizeof(trampoline), PAGE_EXECUTE_READWRITE, &DUMMY); + for (int i = 0; i < addresses.size(); ++i) + { + void* original; + MH_CreateHook((void*)addresses[i], trampolines[i], &original); + MH_QueueEnableHook((void*)addresses[i]); + memcpy(trampolines[i], trampoline, sizeof(trampoline)); + *(uintptr_t*)(trampolines[i] + addr_offset) = addresses[i]; + *(void**)(trampolines[i] + original_offset) = original; + } + ConsoleOutput(HOOK_SEARCH_INITIALIZED, addresses.size()); + MH_ApplyQueued(); + Sleep(sp.searchTime); + for (auto addr : addresses) MH_QueueDisableHook((void*)addr); + MH_ApplyQueued(); + Sleep(1000); + for (auto addr : addresses) MH_RemoveHook((void*)addr); + records.reset(); + VirtualFree(trampolines, 0, MEM_RELEASE); + for (int i = 0; i < CACHE_SIZE; ++i) addressCharCache[i] = sumCache[i] = 0; + ConsoleOutput(HOOK_SEARCH_FINISHED, CACHE_SIZE - recordsAvailable); + }).detach(); +} diff --git a/texthook/hookfinder.h b/texthook/hookfinder.h new file mode 100644 index 0000000..202a073 --- /dev/null +++ b/texthook/hookfinder.h @@ -0,0 +1,7 @@ +#pragma once + +#include "common.h" +#include "types.h" + +void SearchForText(wchar_t* text); +void SearchForHooks(SearchParam sp); diff --git a/texthook/main.cc b/texthook/main.cc index b3889f4..3718697 100644 --- a/texthook/main.cc +++ b/texthook/main.cc @@ -7,6 +7,7 @@ #include "defs.h" #include "engine/match.h" #include "texthook.h" +#include "hookfinder.h" #include "util.h" extern const char* PIPE_CONNECTED; @@ -14,6 +15,7 @@ extern const char* INSERTING_HOOK; extern const char* REMOVING_HOOK; extern const char* HOOK_FAILED; extern const char* TOO_MANY_HOOKS; +extern const char* STARTING_SEARCH; extern const char* NOT_ENOUGH_TEXT; extern const char* COULD_NOT_FIND; @@ -63,6 +65,12 @@ DWORD WINAPI Pipe(LPVOID) NewHook(info.hp, "UserHook", 0); } break; + case HOST_COMMAND_FIND_HOOK: + { + auto info = *(FindHookCmd*)buffer; + SearchForHooks(info.sp); + } + break; case HOST_COMMAND_DETACH: { running = false; @@ -91,7 +99,17 @@ void ConsoleOutput(LPCSTR text, ...) ConsoleOutputNotif buffer; va_list args; va_start(args, text); - vsprintf_s(buffer.message, text, args); + vsnprintf(buffer.message, MESSAGE_SIZE, text, args); + WriteFile(hookPipe, &buffer, sizeof(buffer), &DUMMY, nullptr); +} + +void NotifyHookFound(uint64_t addr, int offset, wchar_t* text) +{ + HookParam hp = {}; + hp.offset = offset; + hp.type = USING_UNICODE | USING_STRING; + hp.address = addr; + HookFoundNotif buffer(hp, text); WriteFile(hookPipe, &buffer, sizeof(buffer), &DUMMY, nullptr); } @@ -144,6 +162,7 @@ void NewHook(HookParam hp, LPCSTR lpname, DWORD flag) char codepageText[MAX_MODULE_SIZE * 4] = {}; WideCharToMultiByte(hp.codepage, 0, hp.text, MAX_MODULE_SIZE, codepageText, MAX_MODULE_SIZE * 4, nullptr, nullptr); if (strlen(utf8Text) < 8 || strlen(codepageText) < 8 || wcslen(hp.text) < 4) return ConsoleOutput(NOT_ENOUGH_TEXT); + ConsoleOutput(STARTING_SEARCH); for (auto[addrs, type] : Array, HookParamType>>{ { Util::SearchMemory(utf8Text, strlen(utf8Text), PAGE_READWRITE), USING_UTF8 }, { Util::SearchMemory(codepageText, strlen(codepageText), PAGE_READWRITE), USING_STRING }, diff --git a/texthook/main.h b/texthook/main.h index 274df34..3342912 100644 --- a/texthook/main.h +++ b/texthook/main.h @@ -9,6 +9,7 @@ void TextOutput(ThreadParam tp, BYTE* text, int len); void ConsoleOutput(LPCSTR text, ...); +void NotifyHookFound(uint64_t addr, int offset, wchar_t* text); void NotifyHookRemove(uint64_t addr, LPCSTR name); void NewHook(HookParam hp, LPCSTR name, DWORD flag = HOOK_ENGINE); void RemoveHook(uint64_t addr, int maxOffset = 9); @@ -48,9 +49,14 @@ extern "C" // minhook library MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget); MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget); MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget); + MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget); + MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget); + MH_STATUS WINAPI MH_ApplyQueued(VOID); const char* WINAPI MH_StatusToString(MH_STATUS status); } +#define MH_ALL_HOOKS NULL + #define ITH_RAISE (*(int*)0 = 0) // raise C000005, for debugging only #define ITH_TRY __try #define ITH_EXCEPT __except(EXCEPTION_EXECUTE_HANDLER) diff --git a/texthook/util/growl.h b/texthook/util/growl.h deleted file mode 100644 index a99b5b2..0000000 --- a/texthook/util/growl.h +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once - -// growl.h -// 9/17/2013 jichi - -//#ifdef GROWL_HAS_GROWL - -#include -#include - -#define GROWL_MSG_A(_msg) MessageBoxA(nullptr, _msg, "VNR Message", MB_OK) -#define GROWL_MSG(_msg) MessageBoxW(nullptr, _msg, L"VNR Message", MB_OK) -#define GROWL_WARN(_msg) MessageBoxW(nullptr, _msg, L"VNR Warning", MB_OK) -#define GROWL_ERROR(_msg) MessageBoxW(nullptr, _msg, L"VNR Error", MB_OK) - -inline void GROWL_DWORD(DWORD value) -{ - WCHAR buf[100]; - swprintf(buf, L"DWORD: %x", value); - GROWL_MSG(buf); -} - -inline void GROWL_DWORD2(DWORD v, DWORD v2) -{ - WCHAR buf[100]; - swprintf(buf, L"DWORD2: %x,%x", v, v2); - GROWL_MSG(buf); -} - -inline void GROWL_DWORD3(DWORD v, DWORD v2, DWORD v3) -{ - WCHAR buf[100]; - swprintf(buf, L"DWORD3: %x,%x,%x", v, v2, v3); - GROWL_MSG(buf); -} - -inline void GROWL_DWORD4(DWORD v, DWORD v2, DWORD v3, DWORD v4) -{ - WCHAR buf[100]; - swprintf(buf, L"DWORD4: %x,%x,%x,%x", v, v2, v3, v4); - GROWL_MSG(buf); -} - -inline void GROWL_DWORD5(DWORD v, DWORD v2, DWORD v3, DWORD v4, DWORD v5) -{ - WCHAR buf[100]; - swprintf(buf, L"DWORD5: %x,%x,%x,%x,%x", v, v2, v3, v4, v5); - GROWL_MSG(buf); -} - -inline void GROWL_DWORD6(DWORD v, DWORD v2, DWORD v3, DWORD v4, DWORD v5, DWORD v6) -{ - WCHAR buf[100]; - swprintf(buf, L"DWORD6: %x,%x,%x,%x,%x,%x", v, v2, v3, v4, v5, v6); - GROWL_MSG(buf); -} - -inline void GROWL_DWORD7(DWORD v, DWORD v2, DWORD v3, DWORD v4, DWORD v5, DWORD v6, DWORD v7) -{ - WCHAR buf[100]; - swprintf(buf, L"DWORD7: %x,%x,%x,%x,%x,%x,%x", v, v2, v3, v4, v5, v6, v7); - GROWL_MSG(buf); -} - -inline void GROWL_DWORD8(DWORD v, DWORD v2, DWORD v3, DWORD v4, DWORD v5, DWORD v6, DWORD v7, DWORD v8) -{ - WCHAR buf[100]; - swprintf(buf, L"DWORD8: %x,%x,%x,%x,%x,%x,%x,%x", v, v2, v3, v4, v5, v6, v7, v8); - GROWL_MSG(buf); -} - -inline void GROWL_DWORD9(DWORD v, DWORD v2, DWORD v3, DWORD v4, DWORD v5, DWORD v6, DWORD v7, DWORD v8, DWORD v9) -{ - WCHAR buf[100]; - swprintf(buf, L"DWORD9: %x,%x,%x,%x,%x,%x,%x,%x,%x", v, v2, v3, v4, v5, v6, v7, v8, v9); - GROWL_MSG(buf); -} - -inline void GROWL(DWORD v) { GROWL_DWORD(v); } -inline void GROWL(LPCWSTR v) { GROWL_MSG(v); } -inline void GROWL(LPCSTR v) { GROWL_MSG_A(v); } - -//#endif // GROWL_HAS_GROWL - -// EOF diff --git a/texthook/util/util.cc b/texthook/util/util.cc index 7911359..b19bd62 100644 --- a/texthook/util/util.cc +++ b/texthook/util/util.cc @@ -6,7 +6,6 @@ #include "util/util.h" #include "ithsys/ithsys.h" #include "main.h" -#include "growl.h" namespace { // unnamed @@ -215,7 +214,7 @@ bool CheckFile(LPCWSTR name) wchar_t path[MAX_PATH * 2]; wchar_t* end = path + GetModuleFileNameW(nullptr, path, MAX_PATH); while (*(--end) != L'\\'); - wcscpy(end + 1, name); + wcscpy_s(end + 1, MAX_PATH, name); file = FindFirstFileW(path, &unused); if (file != INVALID_HANDLE_VALUE) {