diff --git a/GUI/GUI.pro b/GUI/GUI.pro index 765e480..83dd4d2 100644 --- a/GUI/GUI.pro +++ b/GUI/GUI.pro @@ -35,6 +35,9 @@ FORMS += \ win32: LIBS += \ -L$$PWD/../Builds/Debug/Debug/ -lvnrhost +QMAKE_CXXFLAGS_RELEASE += \ + /MT + # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin diff --git a/GUI/mainwindow.cpp b/GUI/mainwindow.cpp index 2917b96..ee5157c 100644 --- a/GUI/mainwindow.cpp +++ b/GUI/mainwindow.cpp @@ -1,17 +1,44 @@ #include "mainwindow.h" #include "ui_mainwindow.h" #include "QMessageBox" -#include "qlineedit.h" +#include "QLineEdit" +#include "QTableWidget" +#include "QInputDialog" #include +#include +#include #include "../texthook/host.h" +QTableWidget* processList; + +QString GetModuleName(DWORD processId, HMODULE module = NULL) +{ + HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId); + wchar_t buffer[MAX_PATH]; + GetModuleFileNameExW(handle, module, buffer, MAX_PATH); + return QString::fromWCharArray(wcsrchr(buffer, L'\\') + 1); +} + +void OnProcessAttach(DWORD processId) +{ + processList->setItem(processList->rowCount(), 0, new QTableWidgetItem(QString::number(processId))); +} + MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { + Host::Start(); ui->setupUi(this); - connect(ui->centralWidget->children().at(0), SIGNAL(returnPressed()), this, SLOT(onCommand())); - StartHost(); + + processList = this->findChild("processList"); + Host::RegisterProcessAttachCallback([](DWORD processId) + { + processList->insertRow(processList->rowCount()); + processList->setItem(processList->rowCount() - 1, 0, new QTableWidgetItem(QString::number(processId))); + processList->setItem(processList->rowCount() - 1, 1, new QTableWidgetItem(GetModuleName(processId))); + }); + Host::Open(); } MainWindow::~MainWindow() @@ -19,8 +46,9 @@ MainWindow::~MainWindow() delete ui; } -void MainWindow::onCommand() +void MainWindow::on_attachButton_clicked() { - QLineEdit* lineEdit = (QLineEdit*)sender(); - QMessageBox::information(this, "called", lineEdit->text()); + //processList->insertRow(processList->rowCount()); + //processList->setItem(processList->rowCount() - 1, 0, new QTableWidgetItem(QString::number(6000))); + Host::InjectProcess(QInputDialog::getInt(this, "Process ID?", "")); } diff --git a/GUI/mainwindow.h b/GUI/mainwindow.h index a4af74b..d1084ea 100644 --- a/GUI/mainwindow.h +++ b/GUI/mainwindow.h @@ -3,8 +3,9 @@ #include -namespace Ui { -class MainWindow; +namespace Ui +{ + class MainWindow; } class MainWindow : public QMainWindow @@ -15,8 +16,10 @@ public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); -public slots: - void onCommand(); +private slots: + + void on_attachButton_clicked(); + private: Ui::MainWindow *ui; }; diff --git a/GUI/mainwindow.ui b/GUI/mainwindow.ui index d260328..62fc05d 100644 --- a/GUI/mainwindow.ui +++ b/GUI/mainwindow.ui @@ -6,46 +6,110 @@ 0 0 - 496 - 376 + 800 + 600 NextHooker - - - - 252 - 0 - 241 - 20 - - - - - - - 3 - 40 - 491 - 291 - - - + + + + + + 5 + 0 + + + + + + + + + 2 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Attach to game + + + + + + + + 0 + 0 + + + + Currently attached to: + + + Qt::AlignCenter + + + + + + + 2 + + + 50 + + + true + + + false + + + + ID + + + AlignCenter + + + + + Name + + + AlignCenter + + + + + + + + 0 0 - 496 + 800 21 - diff --git a/texthook/CMakeLists.txt b/texthook/CMakeLists.txt index 5a51e0b..d0bcc40 100644 --- a/texthook/CMakeLists.txt +++ b/texthook/CMakeLists.txt @@ -1,12 +1,10 @@ project(host) set(vnrhost_src - hookman.h host.h pipe.h textthread.h winmutex.h - hookman.cc host.cc pipe.cc textthread.cc diff --git a/texthook/hookman.cc b/texthook/hookman.cc deleted file mode 100644 index 2df0b11..0000000 --- a/texthook/hookman.cc +++ /dev/null @@ -1,155 +0,0 @@ -// hookman.cc -// 8/24/2013 jichi -// Branch IHF/HookManager.cpp, rev 133 -// 8/24/2013 TODO: Clean up this file - -#ifdef _MSC_VER -# pragma warning (disable:4100) // C4100: unreference formal parameter -# pragma warning (disable:4146) // C4146: unary minus operator applied to unsigned type -#endif // _MSC_VER - -#include "hookman.h" -#include "../vnrhook/include/const.h" -#include "../vnrhook/include/defs.h" -#include "../vnrhook/include/types.h" -#include "winmutex.h" -#include - -#define HM_LOCK CriticalSectionLocker hmLocker(hmCs) // Synchronized scope for accessing private data - -HookManager::HookManager() : - create(nullptr), - remove(nullptr), - attach(nullptr), - detach(nullptr), - nextThreadNumber(0), - splitDelay(250), - textThreadsByParams(), - processRecordsByIds() -{ - InitializeCriticalSection(&hmCs); - // Console text thread - (textThreadsByParams[{ 0, -1UL, -1UL, -1UL }] = new TextThread({ 0, -1UL, -1UL, -1UL }, nextThreadNumber++, splitDelay))->Status() |= USING_UNICODE; -} - -HookManager::~HookManager() -{ - EnterCriticalSection(&hmCs); - RemoveThreads([](auto one, auto two) { return true; }, {}); - for (auto i : processRecordsByIds) UnRegisterProcess(i.first); - LeaveCriticalSection(&hmCs); - DeleteCriticalSection(&hmCs); -} - -TextThread* HookManager::FindSingle(DWORD number) -{ - HM_LOCK; - for (auto i : textThreadsByParams) - if (i.second->Number() == number) - return i.second; - return nullptr; -} - -void HookManager::RemoveThreads(bool(*RemoveIf)(ThreadParameter, ThreadParameter), ThreadParameter cmp) -{ - HM_LOCK; - std::vector removedThreads; - for (auto i : textThreadsByParams) - if (RemoveIf(i.first, cmp)) - { - if (remove) remove(i.second); - delete i.second; - removedThreads.push_back(i.first); - } - for (auto i : removedThreads) textThreadsByParams.erase(i); -} - -void HookManager::RegisterProcess(DWORD pid, HANDLE hostPipe) -{ - HM_LOCK; - ProcessRecord record; - record.hostPipe = hostPipe; - record.hookman_section = OpenFileMappingW(FILE_MAP_READ, FALSE, (ITH_SECTION_ + std::to_wstring(pid)).c_str()); - record.hookman_map = MapViewOfFile(record.hookman_section, FILE_MAP_READ, 0, 0, HOOK_SECTION_SIZE / 2); // jichi 1/16/2015: Changed to half to hook section size - record.process_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); - record.hookman_mutex = OpenMutexW(MUTEX_ALL_ACCESS, FALSE, (ITH_HOOKMAN_MUTEX_ + std::to_wstring(pid)).c_str()); - processRecordsByIds[pid] = record; - if (attach) attach(pid); -} - -void HookManager::UnRegisterProcess(DWORD pid) -{ - HM_LOCK; - ProcessRecord pr = processRecordsByIds[pid]; - CloseHandle(pr.hookman_mutex); - UnmapViewOfFile(pr.hookman_map); - CloseHandle(pr.process_handle); - CloseHandle(pr.hookman_section); - processRecordsByIds.erase(pid); - RemoveThreads([](auto one, auto two) { return one.pid == two.pid; }, { pid, 0, 0, 0 }); - if (detach) detach(pid); -} - -void HookManager::DispatchText(DWORD pid, DWORD hook, DWORD retn, DWORD spl, const BYTE *text, int len) -{ - // jichi 20/27/2013: When PID is zero, the text comes from console, which I don't need - if (!text || !pid || len <= 0) return; - HM_LOCK; - ThreadParameter tp = { pid, hook, retn, spl }; - TextThread *it; - if ((it = textThreadsByParams[tp]) == nullptr) - { - it = textThreadsByParams[tp] = new TextThread(tp, nextThreadNumber++, splitDelay); - if (GetHookParam(pid, hook).type & USING_UNICODE) it->Status() |= USING_UNICODE; - if (create) create(it); - } - it->AddText(text, len); -} - -void HookManager::AddConsoleOutput(std::wstring text) -{ - HM_LOCK; - textThreadsByParams[{ 0, -1UL, -1UL, -1UL }]->AddSentence(std::wstring(text)); -} - -HANDLE HookManager::GetHostPipe(DWORD pid) -{ - HM_LOCK; - return processRecordsByIds[pid].hostPipe; -} - -HookParam HookManager::GetHookParam(DWORD pid, DWORD addr) -{ - HM_LOCK; - HookParam ret = {}; - ProcessRecord pr = processRecordsByIds[pid]; - if (pr.hookman_map == nullptr) return ret; - MutexLocker locker(pr.hookman_mutex); - const Hook* hooks = (const Hook*)pr.hookman_map; - for (int i = 0; i < MAX_HOOK; ++i) - if (hooks[i].Address() == addr) - ret = hooks[i].hp; - return ret; -} - -std::wstring HookManager::GetHookName(DWORD pid, DWORD addr) -{ - HM_LOCK; - std::string buffer = ""; - ProcessRecord pr = processRecordsByIds[pid]; - if (pr.hookman_map == nullptr) return L""; - MutexLocker locker(pr.hookman_mutex); - const Hook* hooks = (const Hook*)pr.hookman_map; - for (int i = 0; i < MAX_HOOK; ++i) - { - if (hooks[i].Address() == addr) - { - buffer.resize(hooks[i].NameLength()); - ReadProcessMemory(pr.process_handle, hooks[i].Name(), &buffer[0], hooks[i].NameLength(), nullptr); - } - } - USES_CONVERSION; - return std::wstring(A2W(buffer.c_str())); -} - -// EOF diff --git a/texthook/hookman.h b/texthook/hookman.h deleted file mode 100644 index 4d3037f..0000000 --- a/texthook/hookman.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -// hookman.h -// 8/23/2013 jichi -// Branch: ITH/HookManager.h, rev 133 - -#include -#include "textthread.h" -#include -#include -#include "../vnrhook/include/types.h" - -struct ProcessRecord -{ - HANDLE process_handle; - HANDLE hookman_mutex; - HANDLE hookman_section; - LPVOID hookman_map; - HANDLE hostPipe; -}; - -typedef void(*ProcessEventCallback)(DWORD pid); -typedef void(*ThreadEventCallback)(TextThread*); - -struct ThreadParameterHasher -{ - size_t operator()(const ThreadParameter& tp) const - { - return std::hash()(tp.pid << 6) + std::hash()(tp.hook) + std::hash()(tp.retn) + std::hash()(tp.spl); - } -}; - -// Artikash 7/19/2018: This should probably be broken up into 2-4 classes... -class __declspec(dllexport) HookManager -{ -public: - HookManager(); - ~HookManager(); - - TextThread* FindSingle(DWORD number); - void AddConsoleOutput(std::wstring text); - void DispatchText(DWORD pid, DWORD hook, DWORD retn, DWORD split, const BYTE *text, int len); - void RemoveThreads(bool(*RemoveIf)(ThreadParameter, ThreadParameter), ThreadParameter cmp); - void RegisterProcess(DWORD pid, HANDLE hostPipe); - void UnRegisterProcess(DWORD pid); - HANDLE GetHostPipe(DWORD pid); - HookParam GetHookParam(DWORD pid, DWORD addr); - std::wstring GetHookName(DWORD pid, DWORD addr); - - void RegisterThreadCreateCallback(ThreadEventCallback cf) { create = cf; } - void RegisterThreadRemoveCallback(ThreadEventCallback cf) { remove = cf; } - void RegisterProcessAttachCallback(ProcessEventCallback cf) { attach = cf; } - void RegisterProcessDetachCallback(ProcessEventCallback cf) { detach = cf; } - - void SetSplitInterval(unsigned int splitDelay) { this->splitDelay = splitDelay; } - -private: - std::unordered_map textThreadsByParams; - std::unordered_map processRecordsByIds; - - CRITICAL_SECTION hmCs; - - ThreadEventCallback create, remove; - ProcessEventCallback attach, detach; - - WORD nextThreadNumber; - unsigned int splitDelay; -}; - -// EOF diff --git a/texthook/host.cc b/texthook/host.cc index e6667dc..bdf62cd 100644 --- a/texthook/host.cc +++ b/texthook/host.cc @@ -4,28 +4,37 @@ #include "host.h" #include "pipe.h" +#include "winmutex.h" +#include #include "../vnrhook/include/const.h" #include "../vnrhook/include/defs.h" #include "../vnrhook/include/types.h" +#include HANDLE preventDuplicationMutex; -HookManager* man; +std::unordered_map textThreadsByParams; +std::unordered_map processRecordsByIds; + +CRITICAL_SECTION hostCs; + +ThreadEventCallback onCreate, onRemove; +ProcessEventCallback onAttach, onDetach; + +WORD nextThreadNumber; HWND dummyWindow; bool running; -namespace -{ // unnamed - void GetDebugPrivileges() // Artikash 5/19/2018: Is it just me or is this function 100% superfluous? - { - HANDLE processToken; - TOKEN_PRIVILEGES Privileges = { 1, {0x14, 0, SE_PRIVILEGE_ENABLED} }; +#define HOST_LOCK CriticalSectionLocker hostLocker(hostCs) // Synchronized scope for accessing private data - OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &processToken); - AdjustTokenPrivileges(processToken, FALSE, &Privileges, 0, nullptr, nullptr); - CloseHandle(processToken); - } -} // unnamed namespace +void GetDebugPrivileges() // Artikash 5/19/2018: Is it just me or is this function 100% superfluous? +{ + HANDLE processToken; + TOKEN_PRIVILEGES Privileges = { 1, {0x14, 0, SE_PRIVILEGE_ENABLED} }; + OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &processToken); + AdjustTokenPrivileges(processToken, FALSE, &Privileges, 0, nullptr, nullptr); + CloseHandle(processToken); +} BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID unused) { @@ -33,12 +42,11 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID unused) { case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls(hinstDLL); - GetDebugPrivileges(); // jichi 8/24/2013: Create hidden window so that ITH can access timer and events dummyWindow = CreateWindowW(L"Button", L"InternalWindow", 0, 0, 0, 0, 0, 0, 0, hinstDLL, 0); break; case DLL_PROCESS_DETACH: - CloseHost(); + Host::Close(); DestroyWindow(dummyWindow); break; default: @@ -47,112 +55,222 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID unused) return true; } -DLLEXPORT bool StartHost() +namespace Host { - preventDuplicationMutex = CreateMutexW(nullptr, TRUE, ITH_SERVER_MUTEX); - if (GetLastError() == ERROR_ALREADY_EXISTS || ::running) + DLLEXPORT bool Start() { - MessageBoxW(nullptr, L"I am sorry that this game is attached by some other VNR ><\nPlease restart the game and try again!", L"Error", MB_ICONERROR); + preventDuplicationMutex = CreateMutexW(nullptr, TRUE, ITH_SERVER_MUTEX); + if (GetLastError() == ERROR_ALREADY_EXISTS || running) + { + MessageBoxW(nullptr, L"I am sorry that this game is attached by some other VNR ><\nPlease restart the game and try again!", L"Error", MB_ICONERROR); + return false; + } + else + { + running = true; + GetDebugPrivileges(); + InitializeCriticalSection(&hostCs); + onAttach = onDetach = nullptr; + onCreate = onRemove = nullptr; + nextThreadNumber = 0; + // Console text thread + (textThreadsByParams[{ 0, -1UL, -1UL, -1UL }] = new TextThread({ 0, -1UL, -1UL, -1UL }, nextThreadNumber++))->Status() |= USING_UNICODE; + return true; + } + } + + DLLEXPORT void Open() + { + CreateNewPipe(); + } + + DLLEXPORT void Close() + { + if (running) + { + EnterCriticalSection(&hostCs); + running = false; + RemoveThreads([](auto one, auto two) { return true; }, {}); + for (auto i : processRecordsByIds) UnregisterProcess(i.first); + LeaveCriticalSection(&hostCs); + DeleteCriticalSection(&hostCs); + CloseHandle(preventDuplicationMutex); + } + } + + DLLEXPORT bool InjectProcess(DWORD processId, DWORD timeout) + { + if (processId == GetCurrentProcessId()) return false; + + CloseHandle(CreateMutexW(nullptr, FALSE, (ITH_HOOKMAN_MUTEX_ + std::to_wstring(processId)).c_str())); + if (GetLastError() == ERROR_ALREADY_EXISTS) + { + AddConsoleOutput(L"already locked"); + return false; + } + + HMODULE textHooker = LoadLibraryExW(ITH_DLL, nullptr, DONT_RESOLVE_DLL_REFERENCES); + wchar_t textHookerPath[MAX_PATH]; + unsigned int textHookerPathSize = GetModuleFileNameW(textHooker, textHookerPath, MAX_PATH) * 2 + 2; + FreeLibrary(textHooker); + + if (HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId)) + if (LPVOID remoteData = VirtualAllocEx(processHandle, nullptr, textHookerPathSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)) + if (WriteProcessMemory(processHandle, remoteData, textHookerPath, textHookerPathSize, nullptr)) + if (HANDLE thread = CreateRemoteThread(processHandle, nullptr, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, remoteData, 0, nullptr)) + { + WaitForSingleObject(thread, timeout); + CloseHandle(thread); + VirtualFreeEx(processHandle, remoteData, 0, MEM_RELEASE); + CloseHandle(processHandle); + return true; + } + + AddConsoleOutput(L"couldn't inject dll"); return false; } - else + + DLLEXPORT bool DetachProcess(DWORD processId) { - ::running = true; - ::man = new HookManager; + DWORD command = HOST_COMMAND_DETACH; + DWORD unused; + return WriteFile(processRecordsByIds[processId].hostPipe, &command, sizeof(command), &unused, nullptr); + } + + DLLEXPORT bool InsertHook(DWORD pid, HookParam hp, std::string name) + { + BYTE buffer[PIPE_BUFFER_SIZE] = {}; + *(DWORD*)buffer = HOST_COMMAND_NEW_HOOK; + *(HookParam*)(buffer + sizeof(DWORD)) = hp; + if (name.size()) strcpy((char*)buffer + sizeof(DWORD) + sizeof(HookParam), name.c_str()); + DWORD unused; + return WriteFile(processRecordsByIds[pid].hostPipe, buffer, sizeof(DWORD) + sizeof(HookParam) + name.size(), &unused, nullptr); + } + + DLLEXPORT bool RemoveHook(DWORD pid, DWORD addr) + { + HANDLE hostPipe = processRecordsByIds[pid].hostPipe; + if (hostPipe == nullptr) return false; + HANDLE hookRemovalEvent = CreateEventW(nullptr, TRUE, FALSE, ITH_REMOVEHOOK_EVENT); + BYTE buffer[sizeof(DWORD) * 2] = {}; + *(DWORD*)buffer = HOST_COMMAND_REMOVE_HOOK; + *(DWORD*)(buffer + sizeof(DWORD)) = addr; + DWORD unused; + WriteFile(hostPipe, buffer, sizeof(DWORD) * 2, &unused, nullptr); + WaitForSingleObject(hookRemovalEvent, 1000); + CloseHandle(hookRemovalEvent); + RemoveThreads([](auto one, auto two) { return one.pid == two.pid && one.hook == two.hook; }, { pid, addr, 0, 0 }); return true; } -} -DLLEXPORT void OpenHost() -{ - CreateNewPipe(); -} - -DLLEXPORT void CloseHost() -{ - if (::running) + DLLEXPORT HookParam GetHookParam(DWORD pid, DWORD addr) { - ::running = false; - delete man; - CloseHandle(preventDuplicationMutex); - } -} - -DLLEXPORT bool InjectProcess(DWORD processId, DWORD timeout) -{ - if (processId == GetCurrentProcessId()) return false; - - CloseHandle(CreateMutexW(nullptr, FALSE, (ITH_HOOKMAN_MUTEX_ + std::to_wstring(processId)).c_str())); - if (GetLastError() == ERROR_ALREADY_EXISTS) - { - man->AddConsoleOutput(L"already locked"); - return false; + HOST_LOCK; + HookParam ret = {}; + ProcessRecord pr = processRecordsByIds[pid]; + if (pr.hookman_map == nullptr) return ret; + MutexLocker locker(pr.hookman_mutex); + const Hook* hooks = (const Hook*)pr.hookman_map; + for (int i = 0; i < MAX_HOOK; ++i) + if (hooks[i].Address() == addr) + ret = hooks[i].hp; + return ret; } - HMODULE textHooker = LoadLibraryExW(ITH_DLL, nullptr, DONT_RESOLVE_DLL_REFERENCES); - wchar_t textHookerPath[MAX_PATH]; - unsigned int textHookerPathSize = GetModuleFileNameW(textHooker, textHookerPath, MAX_PATH) * 2 + 2; - FreeLibrary(textHooker); + DLLEXPORT std::wstring GetHookName(DWORD pid, DWORD addr) + { + HOST_LOCK; + std::string buffer = ""; + ProcessRecord pr = processRecordsByIds[pid]; + if (pr.hookman_map == nullptr) return L""; + MutexLocker locker(pr.hookman_mutex); + const Hook* hooks = (const Hook*)pr.hookman_map; + for (int i = 0; i < MAX_HOOK; ++i) + if (hooks[i].Address() == addr) + { + buffer.resize(hooks[i].NameLength()); + ReadProcessMemory(pr.process_handle, hooks[i].Name(), &buffer[0], hooks[i].NameLength(), nullptr); + } + USES_CONVERSION; + return std::wstring(A2W(buffer.c_str())); + } - if (HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId)) - if (LPVOID remoteData = VirtualAllocEx(processHandle, nullptr, textHookerPathSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)) - if (WriteProcessMemory(processHandle, remoteData, textHookerPath, textHookerPathSize, nullptr)) - if (HANDLE thread = CreateRemoteThread(processHandle, nullptr, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, remoteData, 0, nullptr)) - { - WaitForSingleObject(thread, timeout); - CloseHandle(thread); - VirtualFreeEx(processHandle, remoteData, 0, MEM_RELEASE); - CloseHandle(processHandle); - return true; - } + DLLEXPORT TextThread* GetThread(DWORD number) + { + HOST_LOCK; + for (auto i : textThreadsByParams) + if (i.second->Number() == number) + return i.second; + return nullptr; + } - man->AddConsoleOutput(L"couldn't inject dll"); - return false; + DLLEXPORT void AddConsoleOutput(std::wstring text) + { + HOST_LOCK; + textThreadsByParams[{ 0, -1UL, -1UL, -1UL }]->AddSentence(std::wstring(text)); + } + + DLLEXPORT void RegisterThreadCreateCallback(ThreadEventCallback cf) { onCreate = cf; } + DLLEXPORT void RegisterThreadRemoveCallback(ThreadEventCallback cf) { onRemove = cf; } + DLLEXPORT void RegisterProcessAttachCallback(ProcessEventCallback cf) { onAttach = cf; } + DLLEXPORT void RegisterProcessDetachCallback(ProcessEventCallback cf) { onDetach = cf; } } -DLLEXPORT bool DetachProcess(DWORD processId) +void DispatchText(DWORD pid, DWORD hook, DWORD retn, DWORD split, const BYTE * text, int len) { - DWORD command = HOST_COMMAND_DETACH; - DWORD unused; - return WriteFile(man->GetHostPipe(processId), &command, sizeof(command), &unused, nullptr); + // jichi 20/27/2013: When PID is zero, the text comes from console, which I don't need + if (!text || !pid || len <= 0) return; + HOST_LOCK; + ThreadParameter tp = { pid, hook, retn, split }; + TextThread *it; + if ((it = textThreadsByParams[tp]) == nullptr) + { + it = textThreadsByParams[tp] = new TextThread(tp, nextThreadNumber++); + if (Host::GetHookParam(pid, hook).type & USING_UNICODE) it->Status() |= USING_UNICODE; + if (onCreate) onCreate(it); + } + it->AddText(text, len); } -DLLEXPORT HookManager* GetHostHookManager() +void RemoveThreads(bool(*RemoveIf)(ThreadParameter, ThreadParameter), ThreadParameter cmp) { - return man; + HOST_LOCK; + std::vector removedThreads; + for (auto i : textThreadsByParams) + if (RemoveIf(i.first, cmp)) + { + if (onRemove) onRemove(i.second); + delete i.second; + removedThreads.push_back(i.first); + } + for (auto i : removedThreads) textThreadsByParams.erase(i); } -DLLEXPORT bool InsertHook(DWORD pid, HookParam hp, std::string name) +void RegisterProcess(DWORD pid, HANDLE hostPipe) { - HANDLE commandPipe = man->GetHostPipe(pid); - if (commandPipe == nullptr) return false; - - BYTE buffer[PIPE_BUFFER_SIZE] = {}; - *(DWORD*)buffer = HOST_COMMAND_NEW_HOOK; - *(HookParam*)(buffer + sizeof(DWORD)) = hp; - if (name.size()) strcpy((char*)buffer + sizeof(DWORD) + sizeof(HookParam), name.c_str()); - DWORD unused; - return WriteFile(commandPipe, buffer, sizeof(DWORD) + sizeof(HookParam) + name.size(), &unused, nullptr); + HOST_LOCK; + ProcessRecord record; + record.hostPipe = hostPipe; + record.hookman_section = OpenFileMappingW(FILE_MAP_READ, FALSE, (ITH_SECTION_ + std::to_wstring(pid)).c_str()); + record.hookman_map = MapViewOfFile(record.hookman_section, FILE_MAP_READ, 0, 0, HOOK_SECTION_SIZE / 2); // jichi 1/16/2015: Changed to half to hook section size + record.process_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); + record.hookman_mutex = OpenMutexW(MUTEX_ALL_ACCESS, FALSE, (ITH_HOOKMAN_MUTEX_ + std::to_wstring(pid)).c_str()); + processRecordsByIds[pid] = record; + if (onAttach) onAttach(pid); } -DLLEXPORT bool RemoveHook(DWORD pid, DWORD addr) +void UnregisterProcess(DWORD pid) { - HANDLE commandPipe = man->GetHostPipe(pid); - if (commandPipe == nullptr) return false; - - HANDLE hookRemovalEvent = CreateEventW(nullptr, TRUE, FALSE, ITH_REMOVEHOOK_EVENT); - - BYTE buffer[sizeof(DWORD) * 2] = {}; - *(DWORD*)buffer = HOST_COMMAND_REMOVE_HOOK; - *(DWORD*)(buffer + sizeof(DWORD)) = addr; - DWORD unused; - WriteFile(commandPipe, buffer, sizeof(DWORD) * 2, &unused, nullptr); - - WaitForSingleObject(hookRemovalEvent, 1000); - CloseHandle(hookRemovalEvent); - - man->RemoveThreads([](auto one, auto two) { return one.pid == two.pid && one.hook == two.hook; }, { pid, addr, 0, 0 }); - return true; + HOST_LOCK; + ProcessRecord pr = processRecordsByIds[pid]; + if (!pr.hostPipe) return; + CloseHandle(pr.hookman_mutex); + UnmapViewOfFile(pr.hookman_map); + CloseHandle(pr.process_handle); + CloseHandle(pr.hookman_section); + processRecordsByIds.erase(pid); + RemoveThreads([](auto one, auto two) { return one.pid == two.pid; }, { pid, 0, 0, 0 }); + if (onDetach) onDetach(pid); } -// EOF +// EOF \ No newline at end of file diff --git a/texthook/host.h b/texthook/host.h index 1166693..9154786 100644 --- a/texthook/host.h +++ b/texthook/host.h @@ -5,17 +5,57 @@ // Branch: ITH/IHF.h, rev 105 #define DLLEXPORT __declspec(dllexport) -#include "hookman.h" -#include "../vnrhook/include/types.h" -#include -DLLEXPORT void OpenHost(); -DLLEXPORT bool StartHost(); -DLLEXPORT void CloseHost(); -DLLEXPORT HookManager* GetHostHookManager(); -DLLEXPORT bool InjectProcess(DWORD pid, DWORD timeout = 5000); -DLLEXPORT bool DetachProcess(DWORD pid); -DLLEXPORT bool InsertHook(DWORD pid, HookParam hp, std::string name = ""); -DLLEXPORT bool RemoveHook(DWORD pid, DWORD addr); +#include +#include "textthread.h" +#include +#include "../vnrhook/include/types.h" + +struct ProcessRecord +{ + HANDLE process_handle; + HANDLE hookman_mutex; + HANDLE hookman_section; + LPVOID hookman_map; + HANDLE hostPipe; +}; + +typedef void(*ProcessEventCallback)(DWORD pid); +typedef void(*ThreadEventCallback)(TextThread*); + +struct ThreadParameterHasher +{ + size_t operator()(const ThreadParameter& tp) const + { + return std::hash()(tp.pid << 6) + std::hash()(tp.hook) + std::hash()(tp.retn) + std::hash()(tp.spl); + } +}; + +namespace Host +{ + DLLEXPORT void Open(); + DLLEXPORT bool Start(); + DLLEXPORT void Close(); + DLLEXPORT bool InjectProcess(DWORD pid, DWORD timeout = 5000); + DLLEXPORT bool DetachProcess(DWORD pid); + + DLLEXPORT bool InsertHook(DWORD pid, HookParam hp, std::string name = ""); + DLLEXPORT bool RemoveHook(DWORD pid, DWORD addr); + DLLEXPORT HookParam GetHookParam(DWORD pid, DWORD addr); + DLLEXPORT std::wstring GetHookName(DWORD pid, DWORD addr); + + DLLEXPORT TextThread* GetThread(DWORD number); + DLLEXPORT void AddConsoleOutput(std::wstring text); + + DLLEXPORT void RegisterThreadCreateCallback(ThreadEventCallback cf); + DLLEXPORT void RegisterThreadRemoveCallback(ThreadEventCallback cf); + DLLEXPORT void RegisterProcessAttachCallback(ProcessEventCallback cf); + DLLEXPORT void RegisterProcessDetachCallback(ProcessEventCallback cf); +} + +void DispatchText(DWORD pid, DWORD hook, DWORD retn, DWORD split, const BYTE *text, int len); +void RemoveThreads(bool(*RemoveIf)(ThreadParameter, ThreadParameter), ThreadParameter cmp); +void RegisterProcess(DWORD pid, HANDLE hostPipe); +void UnregisterProcess(DWORD pid); // EOF diff --git a/texthook/pipe.cc b/texthook/pipe.cc index d03dd5d..304ae21 100644 --- a/texthook/pipe.cc +++ b/texthook/pipe.cc @@ -8,8 +8,6 @@ #include "../vnrhook/include/const.h" #include -extern HookManager* man; - struct Pipes { HANDLE hookPipe; @@ -34,7 +32,7 @@ DWORD WINAPI TextReceiver(LPVOID lpThreadParameter) BYTE buffer[PIPE_BUFFER_SIZE] = {}; DWORD bytesRead, processId; ReadFile(pipes->hookPipe, &processId, sizeof(processId), &bytesRead, nullptr); - man->RegisterProcess(processId, pipes->hostPipe); + RegisterProcess(processId, pipes->hostPipe); // jichi 9/27/2013: why recursion? // Artikash 5/20/2018: To create a new pipe for another process @@ -55,13 +53,13 @@ DWORD WINAPI TextReceiver(LPVOID lpThreadParameter) case HOST_NOTIFICATION_NEWHOOK: // Artikash 7/18/2018: Useless for now, but could be used to implement smth later break; case HOST_NOTIFICATION_TEXT: - man->AddConsoleOutput(A2W((LPCSTR)(buffer + sizeof(DWORD) * 2))); // Text + Host::AddConsoleOutput(A2W((LPCSTR)(buffer + sizeof(DWORD) * 2))); // Text break; } } else { - man->DispatchText(processId, + DispatchText(processId, *(DWORD*)buffer, // Hook address *(DWORD*)(buffer + sizeof(DWORD)), // Return address *(DWORD*)(buffer + sizeof(DWORD) * 2), // Split @@ -73,7 +71,7 @@ DWORD WINAPI TextReceiver(LPVOID lpThreadParameter) DisconnectNamedPipe(pipes->hookPipe); DisconnectNamedPipe(pipes->hostPipe); - man->UnRegisterProcess(processId); + UnregisterProcess(processId); CloseHandle(pipes->hookPipe); CloseHandle(pipes->hostPipe); delete pipes; diff --git a/texthook/textthread.cc b/texthook/textthread.cc index d7ca42a..1b41ddf 100644 --- a/texthook/textthread.cc +++ b/texthook/textthread.cc @@ -5,12 +5,10 @@ # pragma warning (disable:4100) // C4100: unreference formal parameter #endif // _MSC_VER -#include "host.h" #include "textthread.h" #include "../vnrhook/include/const.h" #include "winmutex.h" -extern HookManager* man; extern HWND dummyWindow; #define TT_LOCK CriticalSectionLocker ttLocker(ttCs) // Synchronized scope for accessing private data diff --git a/texthook/textthread.h b/texthook/textthread.h index a541971..b67d016 100644 --- a/texthook/textthread.h +++ b/texthook/textthread.h @@ -30,7 +30,7 @@ typedef std::wstring(*ThreadOutputCallback)(TextThread*, std::wstring data); class TextThread { public: - TextThread(ThreadParameter tp, unsigned int threadNumber, unsigned int splitDelay); + TextThread(ThreadParameter tp, unsigned int threadNumber, unsigned int splitDelay = 250); ~TextThread(); void Reset(); @@ -42,6 +42,7 @@ public: DWORD &Status() { return status; } WORD Number() const { return threadNumber; } ThreadParameter GetThreadParameter() { return tp; } + void SetSplitDelay(unsigned int splitDelay) { this->splitDelay = splitDelay; } void RegisterOutputCallBack(ThreadOutputCallback cb) { output = cb; }