From ed4879ba5c63901366f0856ca8babafe9d4ceb53 Mon Sep 17 00:00:00 2001 From: Akash Mozumdar Date: Sat, 4 Aug 2018 18:01:59 -0400 Subject: [PATCH] implement read code --- GUI/mainwindow.cpp | 18 +++---- GUI/misc.cpp | 61 +++++++++++++++++++-- GUI/misc.h | 15 ++++-- vnrhook/include/types.h | 6 ++- vnrhook/src/engine/engine.cc | 6 --- vnrhook/src/hijack/texthook.cc | 97 ++++++++++++++++++++++++++++++---- vnrhook/src/hijack/texthook.h | 4 ++ 7 files changed, 171 insertions(+), 36 deletions(-) diff --git a/GUI/mainwindow.cpp b/GUI/mainwindow.cpp index b343013..0b92d9f 100644 --- a/GUI/mainwindow.cpp +++ b/GUI/mainwindow.cpp @@ -71,7 +71,7 @@ MainWindow::MainWindow(QWidget *parent) : std::map extensions = LoadExtensions(); for (auto i : extensions) extenCombo->addItem(QString::number(i.first) + ":" + i.second); Host::Open(); - Host::AddConsoleOutput(L"NextHooker beta v2.0.2 by Artikash\r\nSource code and more information available under GPLv3 at https://github.com/Artikash/NextHooker"); + Host::AddConsoleOutput(L"NextHooker beta v2.1.0 by Artikash\r\nSource code and more information available under GPLv3 at https://github.com/Artikash/NextHooker"); } MainWindow::~MainWindow() @@ -95,7 +95,7 @@ void MainWindow::AddProcess(unsigned int processId) for (int j = 1; j < hooks.length(); ++j) { Sleep(10); - Host::InsertHook(processId, ParseHCode(hooks.at(j))); + Host::InsertHook(processId, ParseCode(hooks.at(j))); } return; } @@ -112,7 +112,7 @@ void MainWindow::AddThread(TextThread* thread) TextThreadString(thread) + QString::fromWCharArray(Host::GetHookName(thread->GetThreadParameter().pid, thread->GetThreadParameter().hook).c_str()) + " (" + - GenerateHCode(Host::GetHookParam(thread->GetThreadParameter().pid, thread->GetThreadParameter().hook), thread->GetThreadParameter().pid) + + GenerateCode(Host::GetHookParam(thread->GetThreadParameter().pid, thread->GetThreadParameter().hook), thread->GetThreadParameter().pid) + ")" ); thread->RegisterOutputCallBack([&](TextThread* thread, std::wstring output) @@ -188,15 +188,15 @@ void MainWindow::on_detachButton_clicked() void MainWindow::on_hookButton_clicked() { bool ok; - QString hookCode = QInputDialog::getText(this, "Add Hook", HCodeInfoDump, QLineEdit::Normal, "/H", &ok); + QString hookCode = QInputDialog::getText(this, "Add Hook", CodeInfoDump, QLineEdit::Normal, "", &ok); if (!ok) return; - HookParam toInsert = ParseHCode(hookCode); + HookParam toInsert = ParseCode(hookCode); if (toInsert.type == 0 && toInsert.length_offset == 0) { - Host::AddConsoleOutput(L"invalid /H code"); + Host::AddConsoleOutput(L"invalid code"); return; } - Host::InsertHook(processCombo->currentText().split(":")[0].toInt(), ParseHCode(hookCode)); + Host::InsertHook(processCombo->currentText().split(":")[0].toInt(), ParseCode(hookCode)); } void MainWindow::on_unhookButton_clicked() @@ -206,7 +206,7 @@ void MainWindow::on_unhookButton_clicked() for (auto i : hooks) hookList.push_back( QString::fromWCharArray(Host::GetHookName(processCombo->currentText().split(":")[0].toInt(), i.address).c_str()) + ": " + - GenerateHCode(i, processCombo->currentText().split(":")[0].toInt()) + GenerateCode(i, processCombo->currentText().split(":")[0].toInt()) ); bool ok; QString hook = QInputDialog::getItem(this, "Unhook", "Which hook to remove?", hookList, 0, false, &ok); @@ -219,7 +219,7 @@ void MainWindow::on_saveButton_clicked() QString hookList = GetFullModuleName(processCombo->currentText().split(":")[0].toInt());; for (auto i : hooks) if (!(i.type & HOOK_ENGINE)) - hookList += " , " + GenerateHCode(i, processCombo->currentText().split(":")[0].toInt()); + hookList += " , " + GenerateCode(i, processCombo->currentText().split(":")[0].toInt()); QFile file("SavedHooks.txt"); if (!file.open(QIODevice::Append | QIODevice::Text)) return; file.write((hookList + "\r\n").toUtf8()); diff --git a/GUI/misc.cpp b/GUI/misc.cpp index 56d8c98..51207d2 100644 --- a/GUI/misc.cpp +++ b/GUI/misc.cpp @@ -40,12 +40,38 @@ DWORD Hash(QString module) return hash; } +HookParam ParseRCode(QString RCode) +{ + HookParam hp = {}; + switch (RCode.at(0).unicode()) + { + case L'S': + break; + case L'Q': + hp.type |= USING_STRING | USING_UNICODE; + break; + case L'V': + hp.type |= USING_STRING | USING_UTF8; + break; + default: + return {}; + } + RCode.remove(0, 1); + QRegExp stringGap("^\\-?[\\dA-F]+"); + if (stringGap.indexIn(RCode) == -1) return {}; + hp.offset = stringGap.cap(0).toInt(nullptr, 16); + RCode.remove(0, stringGap.cap(0).length()); + if (RCode.at(0).unicode() != L'@') return {}; + RCode.remove(0, 1); + QRegExp address("[\\dA-F]+$"); + if (address.indexIn(RCode) == -1) return {}; + hp.address = address.cap(1).toInt(nullptr, 16); + return hp; +} + HookParam ParseHCode(QString HCode) { HookParam hp = {}; - HCode = HCode.toUpper(); - if (!HCode.startsWith("/H")) return {}; - HCode.remove(0, 2); switch (HCode.at(0).unicode()) { case L'S': @@ -120,6 +146,14 @@ HookParam ParseHCode(QString HCode) return hp; } +HookParam ParseCode(QString code) +{ + code = code.toUpper(); + if (code.startsWith("/H")) return ParseHCode(code.remove(0, 2)); + else if (code.startsWith("/R")) return ParseRCode(code.remove(0, 2)); + else return {}; +} + QString GenerateHCode(HookParam hp, DWORD processId) { QString code = "/H"; @@ -183,3 +217,24 @@ QString GenerateHCode(HookParam hp, DWORD processId) code += QString::fromWCharArray(wcsrchr(buffer, L'\\') + 1); return code; } + +QString GenerateRCode(HookParam hp) +{ + QString code = "/R"; + if (hp.type & USING_UNICODE) + code += "Q"; + else if (hp.type & USING_UTF8) + code += "V"; + else + code += "S"; + code += QString::number(hp.offset, 16); + code += "@"; + code += QString::number(hp.address, 16); + return code; +} + +QString GenerateCode(HookParam hp, DWORD processId) +{ + if (hp.type & DIRECT_READ) return GenerateRCode(hp); + else return GenerateHCode(hp, processId); +} diff --git a/GUI/misc.h b/GUI/misc.h index 998d60d..ce0f561 100644 --- a/GUI/misc.h +++ b/GUI/misc.h @@ -8,16 +8,21 @@ QString GetFullModuleName(DWORD processId, HMODULE module = NULL); QString GetModuleName(DWORD processId, HMODULE module = NULL); QStringList GetAllProcesses(); -HookParam ParseHCode(QString HCode); -QString GenerateHCode(HookParam hp, DWORD processId); +HookParam ParseCode(QString HCode); +QString GenerateCode(HookParam hp, DWORD processId); -static QString HCodeInfoDump = -"Enter hook code\r\n /H{A|B|W|S|Q|V}[N]data_offset[*deref_offset1][:split_offset[*deref_offset2]]@addr[:module]\r\n\ +static QString CodeInfoDump = +"Enter hook code\r\n\ +/H{A|B|W|S|Q|V}[N]data_offset[*deref_offset1][:split_offset[*deref_offset2]]@addr[:module]\r\n\ +OR\r\n\ +Enter read code\r\n\ +/R{S|Q|V}string_gap@addr\r\n\ All numbers in hexadecimal\r\n\ A/B: Shift-JIS char little/big endian\r\n\ W: UTF-16 char\r\n\ S/Q/V: Shift-JIS/UTF-16/UTF-8 string\r\n\ Negatives for data_offset/sub_offset refer to registers\r\n\ -4 for EAX, -8 for ECX, -C for EDX, -10 for EBX, -14 for ESP, -18 for EBP, -1C for ESI, -20 for EDI\r\n\ -* means dereference pointer+deref_offset"; +* means dereference pointer+deref_offset\r\n\r\n\ +Use 0 for string_gap if string is in same location every time"; #endif // MISC_H diff --git a/vnrhook/include/types.h b/vnrhook/include/types.h index 4f870f9..e5acde0 100644 --- a/vnrhook/include/types.h +++ b/vnrhook/include/types.h @@ -43,8 +43,10 @@ struct HookParam { recover_len; // ? // 7/20/2014: jichi additional parameters for PSP games - DWORD user_flags, - user_value; + DWORD user_value; + + // Artikash 8/4/2018: handle for reader thread + HANDLE readerHandle; }; // jichi 6/1/2014: Structure of the esp for extern functions diff --git a/vnrhook/src/engine/engine.cc b/vnrhook/src/engine/engine.cc index 9ae821f..a54251f 100644 --- a/vnrhook/src/engine/engine.cc +++ b/vnrhook/src/engine/engine.cc @@ -16694,11 +16694,6 @@ void SpecialPSPHook(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *spl LPCSTR text = LPCSTR(offset + hp->user_value); static LPCSTR lasttext; if (*text) { - if (hp->user_flags & HPF_IgnoreSameAddress) { - if (text == lasttext) - return; - lasttext = text; - } *data = (DWORD)text; // I only considered SHIFT-JIS/UTF-8 case if (hp->length_offset == 1) @@ -17451,7 +17446,6 @@ bool InsertImageepochPSPHook() hp.type = USING_STRING|USING_SPLIT|NO_CONTEXT; // UTF-8, though hp.offset = pusha_eax_off - 4; hp.split = pusha_ecx_off - 4; - hp.user_flags = HPF_IgnoreSameAddress; //hp.text_fun = SpecialPSPHook; hp.text_fun = SpecialPSPHookImageepoch; // since this function is common, use its own static lasttext for HPF_IgnoreSameAddress ConsoleOutput("vnreng: Imageepoch PSP: INSERT"); diff --git a/vnrhook/src/hijack/texthook.cc b/vnrhook/src/hijack/texthook.cc index bdfd583..17e21af 100644 --- a/vnrhook/src/hijack/texthook.cc +++ b/vnrhook/src/hijack/texthook.cc @@ -316,9 +316,11 @@ DWORD TextHook::UnsafeSend(DWORD dwDataBase, DWORD dwRetn) int TextHook::InsertHook() { + int ok = 1; //ConsoleOutput("vnrcli:InsertHook: enter"); WaitForSingleObject(hmMutex, 0); - int ok = InsertHookCode(); + if (hp.type & DIRECT_READ) ok = InsertReadCode(); + else ok = InsertHookCode(); ReleaseMutex(hmMutex); //ConsoleOutput("vnrcli:InsertHook: leave"); return ok; @@ -326,7 +328,6 @@ int TextHook::InsertHook() int TextHook::InsertHookCode() { - enum : int { yes = 0, no = 1 }; DWORD ret = no; // jichi 9/17/2013: might raise 0xC0000005 AccessViolationException on win7 ITH_WITH_SEH(ret = UnsafeInsertHookCode()); @@ -335,10 +336,76 @@ int TextHook::InsertHookCode() return ret; } +DWORD WINAPI ReaderThread(LPVOID threadParam) +{ + TextHook* hook = (TextHook*)threadParam; + BYTE buffer[PIPE_BUFFER_SIZE] = {}; + char testChar; + unsigned int changeCount = 0; + const char* currentAddress = (char*)hook->hp.address; + while (true) + { + Sleep(50); + if (testChar == *currentAddress) + { + changeCount = 0; + continue; + } + testChar = *currentAddress; + if (++changeCount > 10) + { + ConsoleOutput("NextHooker: memory constantly changing, useless to read"); + ConsoleOutput("NextHooker: remove read code"); + break; + } + + int dataLen; + if (hook->hp.type & USING_UNICODE) + dataLen = wcslen((const wchar_t*)currentAddress) * 2; + else + dataLen = strlen(currentAddress); + + *(DWORD*)buffer = hook->hp.address; + *(DWORD*)(buffer + 4) = 0; + *(DWORD*)(buffer + 8) = 0; + memcpy(buffer + HEADER_SIZE, currentAddress, dataLen); + DWORD unused; + WriteFile(::hookPipe, buffer, dataLen + HEADER_SIZE, &unused, nullptr); + + if (hook->hp.offset == 0) continue; + currentAddress += dataLen + hook->hp.offset; + testChar = *currentAddress; + } + hook->ClearHook(); + return 0; +} + +int TextHook::InsertReadCode() +{ + hp.hook_len = 0x40; + //Check if the new hook range conflict with existing ones. Clear older if conflict. + TextHook *it = hookman; + for (int i = 0; i < currentHook; it++) { + if (it->Address()) + i++; + if (it == this) + continue; + if ((it->Address() >= hp.address && it->Address() < hp.hook_len + hp.address) || (it->Address() <= hp.address && it->Address() + it->Length() > hp.address)) + it->ClearHook(); + } + if (!IthGetMemoryRange((LPCVOID)hp.address, 0, 0)) + { + ConsoleOutput("cannot access read address"); + return no; + } + hp.readerHandle = CreateThread(nullptr, 0, ReaderThread, this, 0, nullptr); + return yes; + +} + int TextHook::UnsafeInsertHookCode() { //ConsoleOutput("vnrcli:UnsafeInsertHookCode: enter"); - enum : int { yes = 0, no = 1 }; if (hp.module && (hp.type & MODULE_OFFSET)) { // Map hook offset to real address. if (DWORD base = GetModuleBase(hp.module)) { hp.address += base; @@ -467,29 +534,36 @@ int TextHook::InitHook(const HookParam &h, LPCSTR name, WORD set_flag) int TextHook::RemoveHookCode() { - enum : int { yes = 1, no = 0 }; if (!hp.address) return no; - ConsoleOutput("vnrcli:RemoveHook: enter"); - WaitForSingleObject(hmMutex, TIMEOUT); // jichi 9/28/2012: wait at most for 5 seconds + DWORD l = hp.hook_len; //with_seh({ // jichi 9/17/2013: might crash >< // jichi 12/25/2013: Actually, __try cannot catch such kind of exception ITH_TRY { NtWriteVirtualMemory(GetCurrentProcess(), (LPVOID)hp.address, original, hp.recover_len, &l); NtFlushInstructionCache(GetCurrentProcess(), (LPVOID)hp.address, hp.recover_len); - } ITH_EXCEPT {} + } + ITH_EXCEPT {} //}); - hp.hook_len = 0; - ReleaseMutex(hmMutex); - ConsoleOutput("vnrcli:RemoveHook: leave"); return yes; } +int TextHook::RemoveReadCode() +{ + if (!hp.address) return no; + TerminateThread(hp.readerHandle, 0); + CloseHandle(hp.readerHandle); + return yes; +} + int TextHook::ClearHook() { + int err; WaitForSingleObject(hmMutex, 0); - int err = RemoveHookCode(); + ConsoleOutput("vnrcli:RemoveHook: enter"); + if (hp.type & DIRECT_READ) err = RemoveReadCode(); + else err = RemoveHookCode(); NotifyHookRemove(hp.address); if (hook_name) { delete[] hook_name; @@ -499,6 +573,7 @@ int TextHook::ClearHook() //if (current_available>this) // current_available = this; currentHook--; + ConsoleOutput("vnrcli:RemoveHook: leave"); ReleaseMutex(hmMutex); return err; } diff --git a/vnrhook/src/hijack/texthook.h b/vnrhook/src/hijack/texthook.h index b1bd5c1..4bb319e 100644 --- a/vnrhook/src/hijack/texthook.h +++ b/vnrhook/src/hijack/texthook.h @@ -29,9 +29,11 @@ void InitFilterTable(); class TextHook : public Hook { int InsertHookCode(); + int InsertReadCode(); int UnsafeInsertHookCode(); DWORD UnsafeSend(DWORD dwDataBase, DWORD dwRetn); int RemoveHookCode(); + int RemoveReadCode(); int SetHookName(LPCSTR name); public: int InsertHook(); @@ -71,4 +73,6 @@ DWORD WINAPI PipeManager(LPVOID unused); void CliLockPipe(); void CliUnlockPipe(); +enum : int { yes = 0, no = 1 }; + // EOF