forked from Public-Mirror/Textractor
add cli, move exception handlers and code parsers into host
This commit is contained in:
parent
22d3e3f3a5
commit
c89b05cf42
@ -31,5 +31,6 @@ set(CMAKE_CONFIGURATION_TYPES Debug Release)
|
|||||||
file(COPY ${LIBS} DESTINATION ${CMAKE_FINAL_OUTPUT_DIRECTORY})
|
file(COPY ${LIBS} DESTINATION ${CMAKE_FINAL_OUTPUT_DIRECTORY})
|
||||||
|
|
||||||
add_subdirectory(GUI)
|
add_subdirectory(GUI)
|
||||||
|
add_subdirectory(GUI/host)
|
||||||
add_subdirectory(vnrhook)
|
add_subdirectory(vnrhook)
|
||||||
add_subdirectory(extensions)
|
add_subdirectory(extensions)
|
||||||
|
@ -6,18 +6,17 @@ set(AUTOMOC OFF)
|
|||||||
set(RESOURCE_FILES Textractor.rc Textractor.ico)
|
set(RESOURCE_FILES Textractor.rc Textractor.ico)
|
||||||
add_compile_options(/GL)
|
add_compile_options(/GL)
|
||||||
# Populate a CMake variable with the sources
|
# Populate a CMake variable with the sources
|
||||||
set(gui_SRCS
|
set(gui_src
|
||||||
main.cpp
|
main.cpp
|
||||||
mainwindow.cpp
|
mainwindow.cpp
|
||||||
misc.cpp
|
|
||||||
exception.cpp
|
|
||||||
extenwindow.cpp
|
extenwindow.cpp
|
||||||
|
host/exception.cpp
|
||||||
host/host.cpp
|
host/host.cpp
|
||||||
host/textthread.cpp
|
host/textthread.cpp
|
||||||
host/util.cpp
|
host/util.cpp
|
||||||
${RESOURCE_FILES}
|
${RESOURCE_FILES}
|
||||||
)
|
)
|
||||||
add_executable(${PROJECT_NAME} WIN32 ${gui_SRCS})
|
add_executable(${PROJECT_NAME} WIN32 ${gui_src})
|
||||||
target_link_libraries(${PROJECT_NAME} Qt5::Widgets winhttp)
|
target_link_libraries(${PROJECT_NAME} Qt5::Widgets winhttp)
|
||||||
|
|
||||||
install_qt5_libs(${PROJECT_NAME})
|
install_qt5_libs(${PROJECT_NAME})
|
||||||
|
8
GUI/host/CMakeLists.txt
Normal file
8
GUI/host/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# The CLI isn't used by Textractor itself, but is here for other people that want to build projects on top of Textractor
|
||||||
|
add_executable(TextractorCLI
|
||||||
|
cli.cpp
|
||||||
|
exception.cpp
|
||||||
|
host.cpp
|
||||||
|
textthread.cpp
|
||||||
|
util.cpp
|
||||||
|
)
|
29
GUI/host/cli.cpp
Normal file
29
GUI/host/cli.cpp
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#include "host.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include <io.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
_setmode(_fileno(stdout), _O_U16TEXT);
|
||||||
|
_setmode(_fileno(stdin), _O_U16TEXT);
|
||||||
|
wprintf_s(L"Usage: {'attach'|'detach'|hookcode} -Pprocessid\n");
|
||||||
|
Host::Start([](auto) {}, [](auto) {}, [](auto) {}, [](auto) {}, [](TextThread* thread, std::wstring& output)
|
||||||
|
{
|
||||||
|
wprintf_s(L"[%I64X:%I32X:%I64X:%I64X:%I64X:%s] %s\n", thread->handle, thread->tp.processId, thread->tp.addr, thread->tp.ctx, thread->tp.ctx2, thread->name.c_str(), output.c_str());
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
wchar_t input[500] = {};
|
||||||
|
while (fgetws(input, 500, stdin))
|
||||||
|
{
|
||||||
|
wchar_t command[500] = {};
|
||||||
|
DWORD processId = 0;
|
||||||
|
if (swscanf(input, L"%500s -P%d", command, &processId) != 2) ExitProcess(0);
|
||||||
|
if (_wcsicmp(command, L"attach") == 0) Host::InjectProcess(processId);
|
||||||
|
else if (_wcsicmp(command, L"detach") == 0) Host::DetachProcess(processId);
|
||||||
|
else if (auto hp = Util::ParseCode(command)) Host::InsertHook(processId, hp.value());
|
||||||
|
else ExitProcess(0);
|
||||||
|
}
|
||||||
|
ExitProcess(0);
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
#include "host/util.h"
|
#include "util.h"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
namespace
|
namespace
|
@ -1,7 +1,246 @@
|
|||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "types.h"
|
#include "defs.h"
|
||||||
|
#include "host.h"
|
||||||
|
#include <sstream>
|
||||||
#include <Psapi.h>
|
#include <Psapi.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::optional<HookParam> ParseRCode(std::wstring RCode)
|
||||||
|
{
|
||||||
|
std::wsmatch match;
|
||||||
|
HookParam hp = {};
|
||||||
|
hp.type |= DIRECT_READ;
|
||||||
|
|
||||||
|
// {S|Q|V}
|
||||||
|
switch (RCode[0])
|
||||||
|
{
|
||||||
|
case L'S':
|
||||||
|
break;
|
||||||
|
case L'Q':
|
||||||
|
hp.type |= USING_UNICODE;
|
||||||
|
break;
|
||||||
|
case L'V':
|
||||||
|
hp.type |= USING_UTF8;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
RCode.erase(0, 1);
|
||||||
|
|
||||||
|
// [codepage#]
|
||||||
|
if (std::regex_search(RCode, match, std::wregex(L"^([0-9]+)#")))
|
||||||
|
{
|
||||||
|
hp.codepage = std::stoi(match[1]);
|
||||||
|
RCode.erase(0, match[0].length());
|
||||||
|
}
|
||||||
|
|
||||||
|
// [*deref_offset]
|
||||||
|
if (RCode[0] == L'0') RCode.erase(0, 1); // Legacy
|
||||||
|
if (std::regex_search(RCode, match, std::wregex(L"^\\*(-?[[:xdigit:]]+)")))
|
||||||
|
{
|
||||||
|
hp.type |= DATA_INDIRECT;
|
||||||
|
hp.index = std::stoi(match[1], nullptr, 16);
|
||||||
|
RCode.erase(0, match[0].length());
|
||||||
|
}
|
||||||
|
|
||||||
|
// @addr
|
||||||
|
if (!std::regex_match(RCode, match, std::wregex(L"@([[:xdigit:]]+)"))) return {};
|
||||||
|
hp.address = std::stoull(match[1], nullptr, 16);
|
||||||
|
return hp;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<HookParam> ParseSCode(std::wstring SCode)
|
||||||
|
{
|
||||||
|
std::wsmatch match;
|
||||||
|
HookParam hp = {};
|
||||||
|
hp.type |= READ_SEARCH;
|
||||||
|
|
||||||
|
// [codepage#]
|
||||||
|
if (std::regex_search(SCode, match, std::wregex(L"^([0-9]+)#")))
|
||||||
|
{
|
||||||
|
hp.codepage = std::stoi(match[1]);
|
||||||
|
SCode.erase(0, match[0].length());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hp.codepage = Host::defaultCodepage;
|
||||||
|
}
|
||||||
|
|
||||||
|
wcscpy_s<MAX_MODULE_SIZE>(hp.text, SCode.c_str());
|
||||||
|
|
||||||
|
return hp;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<HookParam> ParseHCode(std::wstring HCode)
|
||||||
|
{
|
||||||
|
std::wsmatch match;
|
||||||
|
HookParam hp = {};
|
||||||
|
|
||||||
|
// {A|B|W|S|Q|V}
|
||||||
|
switch (HCode[0])
|
||||||
|
{
|
||||||
|
case L'S':
|
||||||
|
hp.type |= USING_STRING;
|
||||||
|
break;
|
||||||
|
case L'A':
|
||||||
|
hp.type |= BIG_ENDIAN;
|
||||||
|
hp.length_offset = 1;
|
||||||
|
break;
|
||||||
|
case L'B':
|
||||||
|
hp.length_offset = 1;
|
||||||
|
break;
|
||||||
|
case L'Q':
|
||||||
|
hp.type |= USING_STRING | USING_UNICODE;
|
||||||
|
break;
|
||||||
|
case L'W':
|
||||||
|
hp.type |= USING_UNICODE;
|
||||||
|
hp.length_offset = 1;
|
||||||
|
break;
|
||||||
|
case L'V':
|
||||||
|
hp.type |= USING_STRING | USING_UTF8;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
HCode.erase(0, 1);
|
||||||
|
|
||||||
|
// [N]
|
||||||
|
if (HCode[0] == L'N')
|
||||||
|
{
|
||||||
|
hp.type |= NO_CONTEXT;
|
||||||
|
HCode.erase(0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// [codepage#]
|
||||||
|
if (std::regex_search(HCode, match, std::wregex(L"^([0-9]+)#")))
|
||||||
|
{
|
||||||
|
hp.codepage = std::stoi(match[1]);
|
||||||
|
HCode.erase(0, match[0].length());
|
||||||
|
}
|
||||||
|
|
||||||
|
// data_offset
|
||||||
|
if (!std::regex_search(HCode, match, std::wregex(L"^-?[[:xdigit:]]+"))) return {};
|
||||||
|
hp.offset = std::stoi(match[0], nullptr, 16);
|
||||||
|
HCode.erase(0, match[0].length());
|
||||||
|
|
||||||
|
// [*deref_offset1]
|
||||||
|
if (std::regex_search(HCode, match, std::wregex(L"^\\*(-?[[:xdigit:]]+)")))
|
||||||
|
{
|
||||||
|
hp.type |= DATA_INDIRECT;
|
||||||
|
hp.index = std::stoi(match[1], nullptr, 16);
|
||||||
|
HCode.erase(0, match[0].length());
|
||||||
|
}
|
||||||
|
|
||||||
|
// [:split_offset[*deref_offset2]]
|
||||||
|
if (std::regex_search(HCode, match, std::wregex(L"^:(-?[[:xdigit:]]+)")))
|
||||||
|
{
|
||||||
|
hp.type |= USING_SPLIT;
|
||||||
|
hp.split = std::stoi(match[1], nullptr, 16);
|
||||||
|
HCode.erase(0, match[0].length());
|
||||||
|
|
||||||
|
if (std::regex_search(HCode, match, std::wregex(L"^\\*(-?[[:xdigit:]]+)")))
|
||||||
|
{
|
||||||
|
hp.type |= SPLIT_INDIRECT;
|
||||||
|
hp.split_index = std::stoi(match[1], nullptr, 16);
|
||||||
|
HCode.erase(0, match[0].length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @addr[:module[:func]]
|
||||||
|
if (!std::regex_match(HCode, match, std::wregex(L"@([[:xdigit:]]+)(:.+?)?(:.+)?"))) return {};
|
||||||
|
hp.address = std::stoull(match[1], nullptr, 16);
|
||||||
|
if (match[2].matched)
|
||||||
|
{
|
||||||
|
hp.type |= MODULE_OFFSET;
|
||||||
|
wcscpy_s<MAX_MODULE_SIZE>(hp.module, match[2].str().erase(0, 1).c_str());
|
||||||
|
}
|
||||||
|
if (match[3].matched)
|
||||||
|
{
|
||||||
|
hp.type |= FUNCTION_OFFSET;
|
||||||
|
std::wstring func = match[3];
|
||||||
|
strcpy_s<MAX_MODULE_SIZE>(hp.function, std::string(func.begin(), func.end()).erase(0, 1).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ITH has registers offset by 4 vs AGTH: need this to correct
|
||||||
|
if (hp.offset < 0) hp.offset -= 4;
|
||||||
|
if (hp.split < 0) hp.split -= 4;
|
||||||
|
|
||||||
|
return hp;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring GenerateRCode(HookParam hp)
|
||||||
|
{
|
||||||
|
std::wstringstream RCode(L"R");
|
||||||
|
|
||||||
|
if (hp.type & USING_UNICODE)
|
||||||
|
{
|
||||||
|
RCode << "Q";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RCode << "S";
|
||||||
|
if (hp.codepage != 0) RCode << hp.codepage << "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
RCode << std::uppercase << std::hex;
|
||||||
|
|
||||||
|
if (hp.type & DATA_INDIRECT) RCode << "*" << hp.index;
|
||||||
|
|
||||||
|
RCode << "@" << hp.address;
|
||||||
|
|
||||||
|
return RCode.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring GenerateHCode(HookParam hp, DWORD processId)
|
||||||
|
{
|
||||||
|
std::wstringstream HCode(L"H");
|
||||||
|
|
||||||
|
if (hp.type & USING_UNICODE)
|
||||||
|
{
|
||||||
|
if (hp.type & USING_STRING) HCode << "Q";
|
||||||
|
else HCode << "W";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (hp.type & USING_STRING) HCode << "S";
|
||||||
|
else if (hp.type & BIG_ENDIAN) HCode << "A";
|
||||||
|
else HCode << "B";
|
||||||
|
}
|
||||||
|
if (hp.type & NO_CONTEXT) HCode << "N";
|
||||||
|
if (hp.text_fun || hp.filter_fun || hp.hook_fun) HCode << "X"; // no AGTH equivalent
|
||||||
|
|
||||||
|
if (hp.codepage != 0 && !(hp.type & USING_UNICODE)) HCode << hp.codepage << "#";
|
||||||
|
|
||||||
|
HCode << std::uppercase << std::hex;
|
||||||
|
|
||||||
|
if (hp.offset < 0) hp.offset += 4;
|
||||||
|
if (hp.split < 0) hp.split += 4;
|
||||||
|
|
||||||
|
HCode << hp.offset;
|
||||||
|
if (hp.type & DATA_INDIRECT) HCode << "*" << hp.index;
|
||||||
|
if (hp.type & USING_SPLIT) HCode << ":" << hp.split;
|
||||||
|
if (hp.type & SPLIT_INDIRECT) HCode << "*" << hp.split_index;
|
||||||
|
|
||||||
|
// Attempt to make the address relative
|
||||||
|
if (!(hp.type & MODULE_OFFSET))
|
||||||
|
if (AutoHandle<> process = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId))
|
||||||
|
if (MEMORY_BASIC_INFORMATION info = {}; VirtualQueryEx(process, (LPCVOID)hp.address, &info, sizeof(info)))
|
||||||
|
if (auto moduleName = Util::GetModuleFilename(processId, (HMODULE)info.AllocationBase))
|
||||||
|
{
|
||||||
|
hp.type |= MODULE_OFFSET;
|
||||||
|
hp.address -= (uint64_t)info.AllocationBase;
|
||||||
|
wcscpy_s<MAX_MODULE_SIZE>(hp.module, moduleName->c_str() + moduleName->rfind(L'\\') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
HCode << "@" << hp.address;
|
||||||
|
if (hp.type & MODULE_OFFSET) HCode << ":" << hp.module;
|
||||||
|
if (hp.type & FUNCTION_OFFSET) HCode << ":" << hp.function;
|
||||||
|
|
||||||
|
return HCode.str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace Util
|
namespace Util
|
||||||
{
|
{
|
||||||
std::optional<std::wstring> GetModuleFilename(DWORD processId, HMODULE module)
|
std::optional<std::wstring> GetModuleFilename(DWORD processId, HMODULE module)
|
||||||
@ -45,4 +284,28 @@ namespace Util
|
|||||||
return RemoveRepetition(text = end - len), true;
|
return RemoveRepetition(text = end - len), true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<HookParam> ParseCode(std::wstring code)
|
||||||
|
{
|
||||||
|
if (code[0] == L'/') code.erase(0, 1); // legacy/AGTH compatibility
|
||||||
|
if (code[0] == L'R') return ParseRCode(code.erase(0, 1));
|
||||||
|
else if (code[0] == L'S') return ParseSCode(code.erase(0, 1));
|
||||||
|
else if (code[0] == L'H') return ParseHCode(code.erase(0, 1));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring GenerateCode(HookParam hp, DWORD processId)
|
||||||
|
{
|
||||||
|
return hp.type & DIRECT_READ ? GenerateRCode(hp) : GenerateHCode(hp, processId);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(
|
||||||
|
assert(StringToWideString(u8"こんにちは").value() == L"こんにちは"),
|
||||||
|
assert(ParseCode(L"/HQN936#-c*C:C*1C@4AA:gdi.dll:GetTextOutA")),
|
||||||
|
assert(ParseCode(L"HB4@0")),
|
||||||
|
assert(ParseCode(L"/RS*10@44")),
|
||||||
|
assert(!ParseCode(L"HQ@4")),
|
||||||
|
assert(!ParseCode(L"/RW@44")),
|
||||||
|
assert(!ParseCode(L"/HWG@33"))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
namespace Util
|
namespace Util
|
||||||
{
|
{
|
||||||
@ -10,4 +11,6 @@ namespace Util
|
|||||||
std::optional<std::wstring> StringToWideString(std::string text, UINT encoding = CP_UTF8);
|
std::optional<std::wstring> StringToWideString(std::string text, UINT encoding = CP_UTF8);
|
||||||
// return true if repetition found (see https://github.com/Artikash/Textractor/issues/40)
|
// return true if repetition found (see https://github.com/Artikash/Textractor/issues/40)
|
||||||
bool RemoveRepetition(std::wstring& text);
|
bool RemoveRepetition(std::wstring& text);
|
||||||
|
std::optional<HookParam> ParseCode(std::wstring code);
|
||||||
|
std::wstring GenerateCode(HookParam hp, DWORD processId);
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ void MainWindow::ProcessConnected(DWORD processId)
|
|||||||
auto hookList = std::find_if(allProcesses.rbegin(), allProcesses.rend(), [&](QString hookList) { return hookList.contains(process); });
|
auto hookList = std::find_if(allProcesses.rbegin(), allProcesses.rend(), [&](QString hookList) { return hookList.contains(process); });
|
||||||
if (hookList != allProcesses.rend())
|
if (hookList != allProcesses.rend())
|
||||||
for (auto hookCode : hookList->split(" , "))
|
for (auto hookCode : hookList->split(" , "))
|
||||||
if (auto hp = ParseCode(hookCode)) Host::InsertHook(processId, hp.value());
|
if (auto hp = Util::ParseCode(S(hookCode))) Host::InsertHook(processId, hp.value());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ void MainWindow::ProcessDisconnected(DWORD processId)
|
|||||||
|
|
||||||
void MainWindow::ThreadAdded(TextThread* thread)
|
void MainWindow::ThreadAdded(TextThread* thread)
|
||||||
{
|
{
|
||||||
QString ttString = TextThreadString(thread) + S(thread->name) + " (" + GenerateCode(thread->hp, thread->tp.processId) + ")";
|
QString ttString = TextThreadString(thread) + S(thread->name) + " (" + S(Util::GenerateCode(thread->hp, thread->tp.processId)) + ")";
|
||||||
QMetaObject::invokeMethod(this, [this, ttString]
|
QMetaObject::invokeMethod(this, [this, ttString]
|
||||||
{
|
{
|
||||||
ui->ttCombo->addItem(ttString);
|
ui->ttCombo->addItem(ttString);
|
||||||
@ -261,7 +261,7 @@ void MainWindow::DetachProcess()
|
|||||||
void MainWindow::AddHook()
|
void MainWindow::AddHook()
|
||||||
{
|
{
|
||||||
if (QString hookCode = QInputDialog::getText(this, ADD_HOOK, CODE_INFODUMP, QLineEdit::Normal, "", &ok, Qt::WindowCloseButtonHint); ok)
|
if (QString hookCode = QInputDialog::getText(this, ADD_HOOK, CODE_INFODUMP, QLineEdit::Normal, "", &ok, Qt::WindowCloseButtonHint); ok)
|
||||||
if (auto hp = ParseCode(hookCode)) Host::InsertHook(GetSelectedProcessId(), hp.value());
|
if (auto hp = Util::ParseCode(S(hookCode))) Host::InsertHook(GetSelectedProcessId(), hp.value());
|
||||||
else Host::AddConsoleOutput(INVALID_CODE);
|
else Host::AddConsoleOutput(INVALID_CODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,7 +276,7 @@ void MainWindow::SaveHooks()
|
|||||||
if (tp.processId == GetSelectedProcessId())
|
if (tp.processId == GetSelectedProcessId())
|
||||||
{
|
{
|
||||||
HookParam hp = Host::GetHookParam(tp);
|
HookParam hp = Host::GetHookParam(tp);
|
||||||
if (!(hp.type & HOOK_ENGINE)) hookCodes[tp.addr] = GenerateCode(hp, tp.processId);
|
if (!(hp.type & HOOK_ENGINE)) hookCodes[tp.addr] = S(Util::GenerateCode(hp, tp.processId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
QTextFile(HOOK_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Append).write((S(processName.value()) + " , " + QStringList(hookCodes.values()).join(" , ") + "\n").toUtf8());
|
QTextFile(HOOK_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Append).write((S(processName.value()) + " , " + QStringList(hookCodes.values()).join(" , ") + "\n").toUtf8());
|
||||||
|
286
GUI/misc.cpp
286
GUI/misc.cpp
@ -1,286 +0,0 @@
|
|||||||
#include "misc.h"
|
|
||||||
#include "const.h"
|
|
||||||
#include "defs.h"
|
|
||||||
#include "host/host.h"
|
|
||||||
#include "host/util.h"
|
|
||||||
#include <Psapi.h>
|
|
||||||
#include <QTextStream>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
std::optional<HookParam> ParseRCode(QString RCode)
|
|
||||||
{
|
|
||||||
HookParam hp = {};
|
|
||||||
hp.type |= DIRECT_READ;
|
|
||||||
|
|
||||||
// {S|Q|V}
|
|
||||||
switch (RCode.at(0).unicode())
|
|
||||||
{
|
|
||||||
case L'S':
|
|
||||||
break;
|
|
||||||
case L'Q':
|
|
||||||
hp.type |= USING_UNICODE;
|
|
||||||
break;
|
|
||||||
case L'V':
|
|
||||||
hp.type |= USING_UTF8;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
RCode.remove(0, 1);
|
|
||||||
|
|
||||||
// [codepage#]
|
|
||||||
QRegularExpressionMatch codepage = QRegularExpression("^([0-9]+)#").match(RCode);
|
|
||||||
if (codepage.hasMatch())
|
|
||||||
{
|
|
||||||
hp.codepage = codepage.captured(1).toInt();
|
|
||||||
RCode.remove(0, codepage.captured(0).length());
|
|
||||||
}
|
|
||||||
|
|
||||||
// [*deref_offset]
|
|
||||||
if (RCode.at(0).unicode() == L'0') RCode.remove(0, 1); // Legacy
|
|
||||||
QRegularExpressionMatch deref = QRegularExpression("^\\*(\\-?[[:xdigit:]]+)").match(RCode);
|
|
||||||
if (deref.hasMatch())
|
|
||||||
{
|
|
||||||
hp.type |= DATA_INDIRECT;
|
|
||||||
hp.index = deref.captured(1).toInt(nullptr, 16);
|
|
||||||
RCode.remove(0, deref.captured(0).length());
|
|
||||||
}
|
|
||||||
|
|
||||||
// @addr
|
|
||||||
QRegularExpressionMatch address = QRegularExpression("^@([[:xdigit:]]+)$").match(RCode);
|
|
||||||
if (!address.hasMatch()) return {};
|
|
||||||
hp.address = address.captured(1).toULongLong(nullptr, 16);
|
|
||||||
return hp;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<HookParam> ParseSCode(QString SCode)
|
|
||||||
{
|
|
||||||
HookParam hp = {};
|
|
||||||
hp.type |= READ_SEARCH;
|
|
||||||
|
|
||||||
// [codepage#]
|
|
||||||
QRegularExpressionMatch codepage = QRegularExpression("^([0-9]+)#").match(SCode);
|
|
||||||
if (codepage.hasMatch())
|
|
||||||
{
|
|
||||||
hp.codepage = codepage.captured(1).toInt();
|
|
||||||
SCode.remove(0, codepage.captured(0).length());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
hp.codepage = Host::defaultCodepage;
|
|
||||||
}
|
|
||||||
|
|
||||||
wcscpy_s<MAX_MODULE_SIZE>(hp.text, S(SCode).c_str());
|
|
||||||
|
|
||||||
return hp;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<HookParam> ParseHCode(QString HCode)
|
|
||||||
{
|
|
||||||
HookParam hp = {};
|
|
||||||
|
|
||||||
// {A|B|W|S|Q|V}
|
|
||||||
switch (HCode.at(0).unicode())
|
|
||||||
{
|
|
||||||
case L'S':
|
|
||||||
hp.type |= USING_STRING;
|
|
||||||
break;
|
|
||||||
case L'A':
|
|
||||||
hp.type |= BIG_ENDIAN;
|
|
||||||
hp.length_offset = 1;
|
|
||||||
break;
|
|
||||||
case L'B':
|
|
||||||
hp.length_offset = 1;
|
|
||||||
break;
|
|
||||||
case L'Q':
|
|
||||||
hp.type |= USING_STRING | USING_UNICODE;
|
|
||||||
break;
|
|
||||||
case L'W':
|
|
||||||
hp.type |= USING_UNICODE;
|
|
||||||
hp.length_offset = 1;
|
|
||||||
break;
|
|
||||||
case L'V':
|
|
||||||
hp.type |= USING_STRING | USING_UTF8;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
HCode.remove(0, 1);
|
|
||||||
|
|
||||||
// [N]
|
|
||||||
if (HCode.at(0).unicode() == L'N')
|
|
||||||
{
|
|
||||||
hp.type |= NO_CONTEXT;
|
|
||||||
HCode.remove(0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// [codepage#]
|
|
||||||
QRegularExpressionMatch codepage = QRegularExpression("^([0-9]+)#").match(HCode);
|
|
||||||
if (codepage.hasMatch())
|
|
||||||
{
|
|
||||||
hp.codepage = codepage.captured(1).toInt();
|
|
||||||
HCode.remove(0, codepage.captured(0).length());
|
|
||||||
}
|
|
||||||
|
|
||||||
// data_offset
|
|
||||||
QRegularExpressionMatch dataOffset = QRegularExpression("^\\-?[[:xdigit:]]+").match(HCode);
|
|
||||||
if (!dataOffset.hasMatch()) return {};
|
|
||||||
hp.offset = dataOffset.captured(0).toInt(nullptr, 16);
|
|
||||||
HCode.remove(0, dataOffset.captured(0).length());
|
|
||||||
|
|
||||||
// [*deref_offset1]
|
|
||||||
QRegularExpressionMatch deref1 = QRegularExpression("^\\*(\\-?[[:xdigit:]]+)").match(HCode);
|
|
||||||
if (deref1.hasMatch())
|
|
||||||
{
|
|
||||||
hp.type |= DATA_INDIRECT;
|
|
||||||
hp.index = deref1.captured(1).toInt(nullptr, 16);
|
|
||||||
HCode.remove(0, deref1.captured(0).length());
|
|
||||||
}
|
|
||||||
|
|
||||||
// [:split_offset[*deref_offset2]]
|
|
||||||
QRegularExpressionMatch splitOffset = QRegularExpression("^\\:(\\-?[[:xdigit:]]+)").match(HCode);
|
|
||||||
if (splitOffset.hasMatch())
|
|
||||||
{
|
|
||||||
hp.type |= USING_SPLIT;
|
|
||||||
hp.split = splitOffset.captured(1).toInt(nullptr, 16);
|
|
||||||
HCode.remove(0, splitOffset.captured(0).length());
|
|
||||||
|
|
||||||
QRegularExpressionMatch deref2 = QRegularExpression("^\\*(\\-?[[:xdigit:]]+)").match(HCode);
|
|
||||||
if (deref2.hasMatch())
|
|
||||||
{
|
|
||||||
hp.type |= SPLIT_INDIRECT;
|
|
||||||
hp.split_index = deref2.captured(1).toInt(nullptr, 16);
|
|
||||||
HCode.remove(0, deref2.captured(0).length());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @addr[:module[:func]]
|
|
||||||
QStringList addressPieces = HCode.split(":");
|
|
||||||
QRegularExpressionMatch address = QRegularExpression("^@([[:xdigit:]]+)$").match(addressPieces.at(0));
|
|
||||||
if (!address.hasMatch()) return {};
|
|
||||||
hp.address = address.captured(1).toULongLong(nullptr, 16);
|
|
||||||
if (addressPieces.size() > 1)
|
|
||||||
{
|
|
||||||
hp.type |= MODULE_OFFSET;
|
|
||||||
wcscpy_s<MAX_MODULE_SIZE>(hp.module, S(addressPieces.at(1)).c_str());
|
|
||||||
}
|
|
||||||
if (addressPieces.size() > 2)
|
|
||||||
{
|
|
||||||
hp.type |= FUNCTION_OFFSET;
|
|
||||||
strcpy_s<MAX_MODULE_SIZE>(hp.function, addressPieces.at(2).toStdString().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// ITH has registers offset by 4 vs AGTH: need this to correct
|
|
||||||
if (hp.offset < 0) hp.offset -= 4;
|
|
||||||
if (hp.split < 0) hp.split -= 4;
|
|
||||||
|
|
||||||
return hp;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString GenerateRCode(HookParam hp)
|
|
||||||
{
|
|
||||||
QString RCode = "R";
|
|
||||||
QTextStream codeBuilder(&RCode);
|
|
||||||
|
|
||||||
if (hp.type & USING_UNICODE)
|
|
||||||
{
|
|
||||||
codeBuilder << "Q";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
codeBuilder << "S";
|
|
||||||
if (hp.codepage != 0) codeBuilder << hp.codepage << "#";
|
|
||||||
}
|
|
||||||
|
|
||||||
codeBuilder.setIntegerBase(16);
|
|
||||||
codeBuilder.setNumberFlags(QTextStream::UppercaseDigits);
|
|
||||||
|
|
||||||
if (hp.type & DATA_INDIRECT) codeBuilder << "*" << hp.index;
|
|
||||||
|
|
||||||
codeBuilder << "@" << hp.address;
|
|
||||||
|
|
||||||
return RCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString GenerateHCode(HookParam hp, DWORD processId)
|
|
||||||
{
|
|
||||||
QString HCode = "H";
|
|
||||||
QTextStream codeBuilder(&HCode);
|
|
||||||
|
|
||||||
if (hp.type & USING_UNICODE)
|
|
||||||
{
|
|
||||||
if (hp.type & USING_STRING) codeBuilder << "Q";
|
|
||||||
else codeBuilder << "W";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (hp.type & USING_STRING) codeBuilder << "S";
|
|
||||||
else if (hp.type & BIG_ENDIAN) codeBuilder << "A";
|
|
||||||
else codeBuilder << "B";
|
|
||||||
}
|
|
||||||
if (hp.type & NO_CONTEXT) codeBuilder << "N";
|
|
||||||
if (hp.text_fun || hp.filter_fun || hp.hook_fun) codeBuilder << "X"; // no AGTH equivalent
|
|
||||||
|
|
||||||
if (hp.codepage != 0 && !(hp.type & USING_UNICODE)) codeBuilder << hp.codepage << "#";
|
|
||||||
|
|
||||||
codeBuilder.setIntegerBase(16);
|
|
||||||
codeBuilder.setNumberFlags(QTextStream::UppercaseDigits);
|
|
||||||
|
|
||||||
if (hp.offset < 0) hp.offset += 4;
|
|
||||||
if (hp.split < 0) hp.split += 4;
|
|
||||||
|
|
||||||
codeBuilder << hp.offset;
|
|
||||||
if (hp.type & DATA_INDIRECT) codeBuilder << "*" << hp.index;
|
|
||||||
if (hp.type & USING_SPLIT) codeBuilder << ":" << hp.split;
|
|
||||||
if (hp.type & SPLIT_INDIRECT) codeBuilder << "*" << hp.split_index;
|
|
||||||
|
|
||||||
// Attempt to make the address relative
|
|
||||||
if (!(hp.type & MODULE_OFFSET))
|
|
||||||
if (AutoHandle<> process = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId))
|
|
||||||
if (MEMORY_BASIC_INFORMATION info = {}; VirtualQueryEx(process, (LPCVOID)hp.address, &info, sizeof(info)))
|
|
||||||
if (auto moduleName = Util::GetModuleFilename(processId, (HMODULE)info.AllocationBase))
|
|
||||||
{
|
|
||||||
hp.type |= MODULE_OFFSET;
|
|
||||||
hp.address -= (uint64_t)info.AllocationBase;
|
|
||||||
wcscpy_s<MAX_MODULE_SIZE>(hp.module, moduleName->c_str() + moduleName->rfind(L'\\') + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
codeBuilder << "@" << hp.address;
|
|
||||||
if (hp.type & MODULE_OFFSET) codeBuilder << ":" << S(hp.module);
|
|
||||||
if (hp.type & FUNCTION_OFFSET) codeBuilder << ":" << hp.function;
|
|
||||||
|
|
||||||
return HCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<HookParam> ParseCode(QString code)
|
|
||||||
{
|
|
||||||
if (code.startsWith("/")) code.remove(0, 1); // legacy/AGTH compatibility
|
|
||||||
if (code.startsWith("R")) return ParseRCode(code.remove(0, 1));
|
|
||||||
else if (code.startsWith("S")) return ParseSCode(code.remove(0, 1));
|
|
||||||
else if (code.startsWith("H")) return ParseHCode(code.remove(0, 1));
|
|
||||||
else return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
QString GenerateCode(HookParam hp, DWORD processId)
|
|
||||||
{
|
|
||||||
if (hp.type & DIRECT_READ) return GenerateRCode(hp);
|
|
||||||
else return GenerateHCode(hp, processId);
|
|
||||||
}
|
|
||||||
|
|
||||||
HMODULE LoadLibraryOnce(std::wstring fileName)
|
|
||||||
{
|
|
||||||
HMODULE module = GetModuleHandleW(fileName.c_str());
|
|
||||||
if (!module) module = LoadLibraryW(fileName.c_str());
|
|
||||||
return module;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(
|
|
||||||
assert(ParseCode("/HQN936#-c*C:C*1C@4AA:gdi.dll:GetTextOutA")),
|
|
||||||
assert(ParseCode("HB4@0")),
|
|
||||||
assert(ParseCode("/RS*10@44")),
|
|
||||||
assert(!ParseCode("HQ@4")),
|
|
||||||
assert(!ParseCode("/RW@44")),
|
|
||||||
assert(!ParseCode("/HWG@33"))
|
|
||||||
);
|
|
@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "qtcommon.h"
|
#include "qtcommon.h"
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
struct QTextFile : QFile
|
struct QTextFile : QFile
|
||||||
{
|
{
|
||||||
@ -11,6 +10,4 @@ struct QTextFile : QFile
|
|||||||
|
|
||||||
inline std::wstring S(const QString& S) { return { S.toStdWString() }; }
|
inline std::wstring S(const QString& S) { return { S.toStdWString() }; }
|
||||||
inline QString S(const std::wstring& S) { return QString::fromStdWString(S); }
|
inline QString S(const std::wstring& S) { return QString::fromStdWString(S); }
|
||||||
std::optional<HookParam> ParseCode(QString HCode);
|
inline HMODULE LoadLibraryOnce(std::wstring fileName) { if (HMODULE module = GetModuleHandleW(fileName.c_str())) return module; return LoadLibraryW(fileName.c_str()); }
|
||||||
QString GenerateCode(HookParam hp, DWORD processId);
|
|
||||||
HMODULE LoadLibraryOnce(std::wstring fileName);
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user