holy shit you can overload operator-> to do WHAT??

This commit is contained in:
Akash Mozumdar 2018-11-27 15:54:04 -05:00
parent 24e31247af
commit efa8d26ada
12 changed files with 224 additions and 240 deletions

View File

@ -1,7 +1,10 @@
#include "extenwindow.h" #include "extenwindow.h"
#include "ui_extenwindow.h" #include "ui_extenwindow.h"
#include "text.h" #include "text.h"
#include "defs.h"
#include "types.h" #include "types.h"
#include "misc.h"
#include <shared_mutex>
#include <QFileDialog> #include <QFileDialog>
#include <QMimeData> #include <QMimeData>
#include <QUrl> #include <QUrl>
@ -81,12 +84,7 @@ ExtenWindow::ExtenWindow(QWidget* parent) :
extenList = findChild<QListWidget*>("extenList"); extenList = findChild<QListWidget*>("extenList");
extenList->installEventFilter(this); extenList->installEventFilter(this);
if (extensions.empty()) for (auto extenName : QString(QAutoFile(EXTEN_SAVE_FILE, QIODevice::ReadOnly)->readAll()).split(">")) Load(extenName);
{
extenSaveFile.open(QIODevice::ReadOnly);
for (auto extenName : QString(extenSaveFile.readAll()).split(">")) Load(extenName);
extenSaveFile.close();
}
Sync(); Sync();
} }
@ -98,14 +96,13 @@ ExtenWindow::~ExtenWindow()
void ExtenWindow::Sync() void ExtenWindow::Sync()
{ {
extenList->clear(); extenList->clear();
extenSaveFile.open(QIODevice::WriteOnly | QIODevice::Truncate); QAutoFile extenSaveFile(EXTEN_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Truncate);
std::shared_lock sharedLock(extenMutex); std::shared_lock sharedLock(extenMutex);
for (auto extenName : extenNames) for (auto extenName : extenNames)
{ {
extenList->addItem(extenName); extenList->addItem(extenName);
extenSaveFile.write((extenName + ">").toUtf8()); extenSaveFile->write((extenName + ">").toUtf8());
} }
extenSaveFile.close();
} }
void ExtenWindow::Add(QString fileName) void ExtenWindow::Add(QString fileName)

View File

@ -2,8 +2,6 @@
#define EXTENSIONS_H #define EXTENSIONS_H
#include "qtcommon.h" #include "qtcommon.h"
#include "defs.h"
#include <shared_mutex>
#include <QListWidget> #include <QListWidget>
#include <QDragEnterEvent> #include <QDragEnterEvent>
#include <QDropEvent> #include <QDropEvent>
@ -35,7 +33,6 @@ private:
void dropEvent(QDropEvent* event); void dropEvent(QDropEvent* event);
Ui::ExtenWindow* ui; Ui::ExtenWindow* ui;
QFile extenSaveFile = QFile(EXTEN_SAVE_FILE);
QListWidget* extenList; QListWidget* extenList;
}; };

View File

@ -10,86 +10,58 @@ namespace
class ProcessRecord class ProcessRecord
{ {
public: public:
ProcessRecord(DWORD processId, HANDLE hostPipe) : inline static Host::ProcessEventCallback OnConnect, OnDisconnect;
hostPipe(hostPipe),
section(OpenFileMappingW(FILE_MAP_READ, FALSE, (ITH_SECTION_ + std::to_wstring(processId)).c_str())),
sectionMap(MapViewOfFile(section, FILE_MAP_READ, 0, 0, HOOK_SECTION_SIZE / 2)), // jichi 1/16/2015: Changed to half to hook section size
sectionMutex(ITH_HOOKMAN_MUTEX_ + std::to_wstring(processId))
{}
ProcessRecord(ProcessRecord&) = delete; ProcessRecord(DWORD processId, HANDLE pipe) :
ProcessRecord& operator=(ProcessRecord) = delete; processId(processId),
pipe(pipe),
fileMapping(OpenFileMappingW(FILE_MAP_READ, FALSE, (ITH_SECTION_ + std::to_wstring(processId)).c_str())),
mappedView(MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, HOOK_SECTION_SIZE / 2)), // jichi 1/16/2015: Changed to half to hook section size
sectionMutex(ITH_HOOKMAN_MUTEX_ + std::to_wstring(processId))
{
OnConnect(processId);
}
~ProcessRecord() ~ProcessRecord()
{ {
UnmapViewOfFile(sectionMap); OnDisconnect(processId);
CloseHandle(section); UnmapViewOfFile(mappedView);
} }
TextHook GetHook(uint64_t addr) TextHook GetHook(uint64_t addr)
{ {
if (sectionMap == nullptr) return {}; if (mappedView == nullptr) return {};
LOCK(sectionMutex); LOCK(sectionMutex);
auto hooks = (const TextHook*)sectionMap; auto hooks = (const TextHook*)mappedView;
for (int i = 0; i < MAX_HOOK; ++i) for (int i = 0; i < MAX_HOOK; ++i)
if (hooks[i].hp.insertion_address == addr) return hooks[i]; if (hooks[i].hp.insertion_address == addr) return hooks[i];
return {}; return {};
} }
HANDLE hostPipe; template <typename T>
void Send(T data)
{
DWORD DUMMY;
WriteFile(pipe, &data, sizeof(data), &DUMMY, nullptr);
}
private: private:
HANDLE section; DWORD processId;
LPVOID sectionMap; HANDLE pipe;
AutoHandle<> fileMapping;
LPCVOID mappedView;
WinMutex sectionMutex; WinMutex sectionMutex;
}; };
ThreadEventCallback OnCreate, OnDestroy; ThreadSafePtr<std::unordered_map<ThreadParam, std::shared_ptr<TextThread>>> textThreadsByParams;
ProcessEventCallback OnAttach, OnDetach; ThreadSafePtr<std::unordered_map<DWORD, std::unique_ptr<ProcessRecord>>> processRecordsByIds;
std::unordered_map<ThreadParam, std::shared_ptr<TextThread>> textThreadsByParams;
std::unordered_map<DWORD, std::unique_ptr<ProcessRecord>> processRecordsByIds;
std::recursive_mutex hostMutex;
DWORD DUMMY;
ThreadParam CONSOLE{ 0, -1ULL, -1ULL, -1ULL }, CLIPBOARD{ 0, 0, -1ULL, -1ULL }; ThreadParam CONSOLE{ 0, -1ULL, -1ULL, -1ULL }, CLIPBOARD{ 0, 0, -1ULL, -1ULL };
void DispatchText(ThreadParam tp, const BYTE* text, int len)
{
LOCK(hostMutex);
if (textThreadsByParams[tp] == nullptr)
{
if (textThreadsByParams.size() > MAX_THREAD_COUNT) return Host::AddConsoleOutput(TOO_MANY_THREADS);
OnCreate(textThreadsByParams[tp] = std::make_shared<TextThread>(tp, Host::GetHookParam(tp), Host::GetHookName(tp)));
}
textThreadsByParams[tp]->Push(text, len);
}
void RemoveThreads(std::function<bool(ThreadParam)> removeIf) void RemoveThreads(std::function<bool(ThreadParam)> removeIf)
{ {
LOCK(hostMutex); auto lockedTextThreadsByParams = textThreadsByParams.operator->();
for (auto it = textThreadsByParams.begin(); it != textThreadsByParams.end();) for (auto it = lockedTextThreadsByParams->begin(); it != lockedTextThreadsByParams->end(); removeIf(it->first) ? it = lockedTextThreadsByParams->erase(it) : ++it);
if (auto curr = it++; removeIf(curr->first))
{
OnDestroy(curr->second);
textThreadsByParams.erase(curr->first);
}
}
void RegisterProcess(DWORD processId, HANDLE hostPipe)
{
LOCK(hostMutex);
processRecordsByIds.insert({ processId, std::make_unique<ProcessRecord>(processId, hostPipe) });
OnAttach(processId);
}
void UnregisterProcess(DWORD processId)
{
OnDetach(processId);
LOCK(hostMutex);
processRecordsByIds.erase(processId);
RemoveThreads([&](ThreadParam tp) { return tp.processId == processId; });
} }
void CreatePipe() void CreatePipe()
@ -100,14 +72,15 @@ namespace
InitializeSecurityDescriptor(&pipeSD, SECURITY_DESCRIPTOR_REVISION); InitializeSecurityDescriptor(&pipeSD, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&pipeSD, TRUE, NULL, FALSE); // Allow non-admin processes to connect to pipe created by admin host SetSecurityDescriptorDacl(&pipeSD, TRUE, NULL, FALSE); // Allow non-admin processes to connect to pipe created by admin host
SECURITY_ATTRIBUTES pipeSA = { sizeof(SECURITY_ATTRIBUTES), &pipeSD, FALSE }; SECURITY_ATTRIBUTES pipeSA = { sizeof(SECURITY_ATTRIBUTES), &pipeSD, FALSE };
HANDLE hookPipe = CreateNamedPipeW(HOOK_PIPE, PIPE_ACCESS_INBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, PIPE_UNLIMITED_INSTANCES, 0, PIPE_BUFFER_SIZE, MAXDWORD, &pipeSA); AutoHandle<Util::NamedPipeHandleCloser>
HANDLE hostPipe = CreateNamedPipeW(HOST_PIPE, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, PIPE_UNLIMITED_INSTANCES, PIPE_BUFFER_SIZE, 0, MAXDWORD, &pipeSA); hookPipe = CreateNamedPipeW(HOOK_PIPE, PIPE_ACCESS_INBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, PIPE_UNLIMITED_INSTANCES, 0, PIPE_BUFFER_SIZE, MAXDWORD, &pipeSA),
hostPipe = CreateNamedPipeW(HOST_PIPE, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, PIPE_UNLIMITED_INSTANCES, PIPE_BUFFER_SIZE, 0, MAXDWORD, &pipeSA);
ConnectNamedPipe(hookPipe, nullptr); ConnectNamedPipe(hookPipe, nullptr);
BYTE buffer[PIPE_BUFFER_SIZE] = {}; BYTE buffer[PIPE_BUFFER_SIZE] = {};
DWORD bytesRead, processId; DWORD bytesRead, processId;
ReadFile(hookPipe, &processId, sizeof(processId), &bytesRead, nullptr); ReadFile(hookPipe, &processId, sizeof(processId), &bytesRead, nullptr);
RegisterProcess(processId, hostPipe); processRecordsByIds->insert({ processId, std::make_unique<ProcessRecord>(processId, hostPipe) });
CreatePipe(); CreatePipe();
@ -129,16 +102,19 @@ namespace
default: default:
{ {
auto tp = *(ThreadParam*)buffer; auto tp = *(ThreadParam*)buffer;
DispatchText(tp, buffer + sizeof(tp), bytesRead - sizeof(tp)); if (textThreadsByParams->count(tp) == 0)
{
auto textThread = textThreadsByParams->insert({ tp, std::make_shared<TextThread>(tp, Host::GetHookParam(tp), Host::GetHookName(tp)) }).first->second;
if (textThreadsByParams->size() > MAX_THREAD_COUNT) Host::AddConsoleOutput(TOO_MANY_THREADS);
else textThread->Start();
}
textThreadsByParams->at(tp)->Push(buffer + sizeof(tp), bytesRead - sizeof(tp));
} }
break; break;
} }
UnregisterProcess(processId); RemoveThreads([&](ThreadParam tp) { return tp.processId == processId; });
DisconnectNamedPipe(hookPipe); processRecordsByIds->erase(processId);
DisconnectNamedPipe(hostPipe);
CloseHandle(hookPipe);
CloseHandle(hostPipe);
}).detach(); }).detach();
} }
@ -156,12 +132,16 @@ namespace
namespace Host namespace Host
{ {
void Start(ProcessEventCallback onAttach, ProcessEventCallback onDetach, ThreadEventCallback onCreate, ThreadEventCallback onDestroy, TextThread::OutputCallback output) void Start(ProcessEventCallback OnConnect, ProcessEventCallback OnDisconnect, TextThread::EventCallback OnCreate, TextThread::EventCallback OnDestroy, TextThread::OutputCallback Output)
{ {
OnAttach = onAttach; OnDetach = onDetach; OnCreate = onCreate; OnDestroy = onDestroy; TextThread::Output = output; ProcessRecord::OnConnect = OnConnect;
RegisterProcess(CONSOLE.processId, INVALID_HANDLE_VALUE); ProcessRecord::OnDisconnect = OnDisconnect;
OnCreate(textThreadsByParams[CONSOLE] = std::make_shared<TextThread>(CONSOLE, HookParam{}, L"Console")); TextThread::OnCreate = OnCreate;
OnCreate(textThreadsByParams[CLIPBOARD] = std::make_shared<TextThread>(CLIPBOARD, HookParam{}, L"Clipboard")); TextThread::OnDestroy = OnDestroy;
TextThread::Output = Output;
processRecordsByIds->insert({ CONSOLE.processId, std::make_unique<ProcessRecord>(CONSOLE.processId, INVALID_HANDLE_VALUE) });
textThreadsByParams->insert({ CONSOLE, std::make_shared<TextThread>(CONSOLE, HookParam{}, L"Console") });
textThreadsByParams->insert({ CLIPBOARD, std::make_shared<TextThread>(CLIPBOARD, HookParam{}, L"Clipboard") });
StartCapturingClipboard(); StartCapturingClipboard();
CreatePipe(); CreatePipe();
} }
@ -170,9 +150,10 @@ namespace Host
{ {
// Artikash 7/25/2018: This is only called when Textractor is closed, at which point Windows should free everything itself...right? // Artikash 7/25/2018: This is only called when Textractor is closed, at which point Windows should free everything itself...right?
#ifdef _DEBUG // Check memory leaks #ifdef _DEBUG // Check memory leaks
LOCK(hostMutex); ProcessRecord::OnConnect = ProcessRecord::OnDisconnect = [](auto) {};
processRecordsByIds.clear(); TextThread::OnCreate = TextThread::OnDestroy = [](auto) {};
textThreadsByParams.clear(); processRecordsByIds->clear();
textThreadsByParams->clear();
#endif #endif
} }
@ -180,43 +161,37 @@ namespace Host
{ {
if (processId == GetCurrentProcessId()) return false; if (processId == GetCurrentProcessId()) return false;
CloseHandle(CreateMutexW(nullptr, FALSE, (ITH_HOOKMAN_MUTEX_ + std::to_wstring(processId)).c_str())); WinMutex(ITH_HOOKMAN_MUTEX_ + std::to_wstring(processId));
if (GetLastError() == ERROR_ALREADY_EXISTS) if (GetLastError() == ERROR_ALREADY_EXISTS)
{ {
AddConsoleOutput(ALREADY_INJECTED); AddConsoleOutput(ALREADY_INJECTED);
return false; return false;
} }
HMODULE textHooker = LoadLibraryExW(ITH_DLL, nullptr, DONT_RESOLVE_DLL_REFERENCES); static HMODULE vnrhook = LoadLibraryExW(ITH_DLL, nullptr, DONT_RESOLVE_DLL_REFERENCES);
wchar_t textHookerPath[MAX_PATH]; static std::wstring location = Util::GetModuleFileName(vnrhook).value();
DWORD textHookerPathSize = GetModuleFileNameW(textHooker, textHookerPath, MAX_PATH) * 2 + 2;
FreeLibrary(textHooker);
if (HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId)) if (AutoHandle<> process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId))
{ {
#ifdef _WIN64 #ifdef _WIN64
BOOL invalidProcess = FALSE; BOOL invalidProcess = FALSE;
IsWow64Process(processHandle, &invalidProcess); IsWow64Process(process, &invalidProcess);
if (invalidProcess) if (invalidProcess)
{ {
AddConsoleOutput(ARCHITECTURE_MISMATCH); AddConsoleOutput(ARCHITECTURE_MISMATCH);
CloseHandle(processHandle);
return false; return false;
} }
#endif #endif
if (LPVOID remoteData = VirtualAllocEx(processHandle, nullptr, textHookerPathSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)) if (LPVOID remoteData = VirtualAllocEx(process, nullptr, location.size() * 2 + 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE))
{ {
WriteProcessMemory(processHandle, remoteData, textHookerPath, textHookerPathSize, nullptr); WriteProcessMemory(process, remoteData, location.c_str(), location.size() * 2 + 2, nullptr);
if (HANDLE thread = CreateRemoteThread(processHandle, nullptr, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, remoteData, 0, nullptr)) if (AutoHandle<> thread = CreateRemoteThread(process, nullptr, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, remoteData, 0, nullptr))
{ {
WaitForSingleObject(thread, timeout); WaitForSingleObject(thread, timeout);
CloseHandle(thread); VirtualFreeEx(process, remoteData, 0, MEM_RELEASE);
VirtualFreeEx(processHandle, remoteData, 0, MEM_RELEASE);
CloseHandle(processHandle);
return true; return true;
} }
VirtualFreeEx(processHandle, remoteData, 0, MEM_RELEASE); VirtualFreeEx(process, remoteData, 0, MEM_RELEASE);
CloseHandle(processHandle);
} }
} }
@ -226,41 +201,32 @@ namespace Host
void DetachProcess(DWORD processId) void DetachProcess(DWORD processId)
{ {
LOCK(hostMutex); processRecordsByIds->at(processId)->Send(HostCommandType(HOST_COMMAND_DETACH));
HostCommandType buffer(HOST_COMMAND_DETACH);
WriteFile(processRecordsByIds.at(processId)->hostPipe, &buffer, sizeof(buffer), &DUMMY, nullptr);
} }
void InsertHook(DWORD processId, HookParam hp, std::string name) void InsertHook(DWORD processId, HookParam hp, std::string name)
{ {
LOCK(hostMutex); processRecordsByIds->at(processId)->Send(InsertHookCmd(hp, name));
InsertHookCmd buffer(hp, name);
WriteFile(processRecordsByIds.at(processId)->hostPipe, &buffer, sizeof(buffer), &DUMMY, nullptr);
} }
void RemoveHook(DWORD processId, uint64_t addr) void RemoveHook(DWORD processId, uint64_t addr)
{ {
LOCK(hostMutex); processRecordsByIds->at(processId)->Send(RemoveHookCmd(addr));
RemoveHookCmd buffer(addr);
WriteFile(processRecordsByIds.at(processId)->hostPipe, &buffer, sizeof(buffer), &DUMMY, nullptr);
} }
HookParam GetHookParam(DWORD processId, uint64_t addr) HookParam GetHookParam(DWORD processId, uint64_t addr)
{ {
LOCK(hostMutex); return processRecordsByIds->at(processId)->GetHook(addr).hp;
return processRecordsByIds.at(processId)->GetHook(addr).hp;
} }
std::wstring GetHookName(DWORD processId, uint64_t addr) std::wstring GetHookName(DWORD processId, uint64_t addr)
{ {
LOCK(hostMutex); return Util::StringToWideString(processRecordsByIds->at(processId)->GetHook(addr).hookName).value();
return Util::StringToWideString(processRecordsByIds.at(processId)->GetHook(addr).hookName).value();
} }
std::shared_ptr<TextThread> GetThread(ThreadParam tp) std::shared_ptr<TextThread> GetThread(ThreadParam tp)
{ {
LOCK(hostMutex); return textThreadsByParams->at(tp);
return textThreadsByParams[tp];
} }
void AddConsoleOutput(std::wstring text) void AddConsoleOutput(std::wstring text)

View File

@ -3,12 +3,10 @@
#include "common.h" #include "common.h"
#include "textthread.h" #include "textthread.h"
typedef std::function<void(DWORD)> ProcessEventCallback;
typedef std::function<void(std::shared_ptr<TextThread>)> ThreadEventCallback;
namespace Host namespace Host
{ {
void Start(ProcessEventCallback onAttach, ProcessEventCallback onDetach, ThreadEventCallback onCreate, ThreadEventCallback onDestroy, TextThread::OutputCallback output); typedef std::function<void(DWORD)> ProcessEventCallback;
void Start(ProcessEventCallback OnConnect, ProcessEventCallback OnDisconnect, TextThread::EventCallback OnCreate, TextThread::EventCallback OnDestroy, TextThread::OutputCallback Output);
void Close(); void Close();
bool InjectProcess(DWORD processId, DWORD timeout = 5000); bool InjectProcess(DWORD processId, DWORD timeout = 5000);

View File

@ -4,33 +4,37 @@
#include "host.h" #include "host.h"
#include "util.h" #include "util.h"
TextThread::TextThread(ThreadParam tp, HookParam hp, std::wstring name) : handle(threadCounter++), name(name), tp(tp), hp(hp) {} TextThread::TextThread(ThreadParam tp, HookParam hp, std::wstring name) : handle(threadCounter++), name(name), tp(tp), hp(hp)
{
OnCreate(this);
}
TextThread::~TextThread() TextThread::~TextThread()
{ {
SetEvent(deletionEvent); SetEvent(deletionEvent);
flushThread.join(); flushThread.join();
CloseHandle(deletionEvent); OnDestroy(this);
} }
std::wstring TextThread::GetStorage() std::wstring TextThread::GetStorage()
{ {
LOCK(storageMutex); return storage->c_str();
return storage; }
void TextThread::Start()
{
deletionEvent = CreateEventW(nullptr, FALSE, FALSE, NULL);
flushThread = std::thread([&] { while (WaitForSingleObject(deletionEvent, 10) == WAIT_TIMEOUT) Flush(); });
} }
void TextThread::AddSentence(std::wstring sentence) void TextThread::AddSentence(std::wstring sentence)
{ {
if (Output(this, sentence)) if (Output(this, sentence)) storage->append(sentence);
{
LOCK(storageMutex);
storage += sentence;
}
} }
void TextThread::Push(const BYTE* data, int len) void TextThread::Push(const BYTE* data, int len)
{ {
if (len < 0) return; if (!flushThread.joinable() || len < 0) return;
LOCK(bufferMutex); LOCK(bufferMutex);
if (hp.type & USING_UNICODE) buffer += std::wstring((wchar_t*)data, len / 2); if (hp.type & USING_UNICODE) buffer += std::wstring((wchar_t*)data, len / 2);
else if (auto converted = Util::StringToWideString(std::string((char*)data, len), hp.codepage ? hp.codepage : defaultCodepage)) buffer += converted.value(); else if (auto converted = Util::StringToWideString(std::string((char*)data, len), hp.codepage ? hp.codepage : defaultCodepage)) buffer += converted.value();

View File

@ -6,8 +6,9 @@
class TextThread class TextThread
{ {
public: public:
typedef std::function<void(TextThread*)> EventCallback;
typedef std::function<bool(TextThread*, std::wstring&)> OutputCallback; typedef std::function<bool(TextThread*, std::wstring&)> OutputCallback;
inline static EventCallback OnCreate, OnDestroy;
inline static OutputCallback Output; inline static OutputCallback Output;
inline static int flushDelay = 400; // flush every 400ms by default inline static int flushDelay = 400; // flush every 400ms by default
@ -21,6 +22,7 @@ public:
~TextThread(); ~TextThread();
std::wstring GetStorage(); std::wstring GetStorage();
void Start();
void AddSentence(std::wstring sentence); void AddSentence(std::wstring sentence);
void Push(const BYTE* data, int len); void Push(const BYTE* data, int len);
@ -32,13 +34,11 @@ public:
private: private:
void Flush(); void Flush();
ThreadSafePtr<std::wstring> storage;
std::wstring buffer; std::wstring buffer;
std::unordered_set<wchar_t> repeatingChars; std::unordered_set<wchar_t> repeatingChars;
std::mutex bufferMutex; std::mutex bufferMutex;
std::wstring storage; AutoHandle<> deletionEvent = NULL;
std::mutex storageMutex; std::thread flushThread;
DWORD lastPushTime;
HANDLE deletionEvent = CreateEventW(nullptr, FALSE, FALSE, NULL);
std::thread flushThread = std::thread([&] { while (WaitForSingleObject(deletionEvent, 10) == WAIT_TIMEOUT) Flush(); });
DWORD lastPushTime = GetTickCount();
}; };

View File

@ -1,16 +1,36 @@
#include "util.h" #include "util.h"
#include "types.h"
#include <Psapi.h>
namespace Util namespace Util
{ {
std::optional<std::wstring> GetModuleFileName(DWORD processId, HMODULE module)
{
if (AutoHandle<> process = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId))
{
std::vector<wchar_t> buffer(MAX_PATH);
if (GetModuleFileNameExW(process, module, buffer.data(), MAX_PATH)) return buffer.data();
else return {};
}
return {};
}
std::optional<std::wstring> GetModuleFileName(HMODULE module)
{
std::vector<wchar_t> buffer(MAX_PATH);
if (::GetModuleFileNameW(module, buffer.data(), MAX_PATH)) return buffer.data();
else return {};
}
std::optional<std::wstring> GetClipboardText() std::optional<std::wstring> GetClipboardText()
{ {
if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) return {}; if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) return {};
if (!OpenClipboard(NULL)) return {}; if (!OpenClipboard(NULL)) return {};
if (HANDLE clipboardHandle = GetClipboardData(CF_UNICODETEXT)) if (HANDLE clipboard = GetClipboardData(CF_UNICODETEXT))
{ {
std::wstring ret = (wchar_t*)GlobalLock(clipboardHandle); std::wstring ret = (wchar_t*)GlobalLock(clipboard);
GlobalUnlock(clipboardHandle); GlobalUnlock(clipboard);
CloseClipboard(); CloseClipboard();
return ret; return ret;
} }

View File

@ -4,6 +4,9 @@
namespace Util namespace Util
{ {
struct NamedPipeHandleCloser { void operator()(void* h) { DisconnectNamedPipe(h); CloseHandle(h); } };
std::optional<std::wstring> GetModuleFileName(DWORD processId, HMODULE module = NULL);
std::optional<std::wstring> GetModuleFileName(HMODULE module = NULL);
std::optional<std::wstring> GetClipboardText(); std::optional<std::wstring> GetClipboardText();
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)

View File

@ -1,5 +1,5 @@
#include "mainwindow.h" #include "mainwindow.h"
#include "misc.h" #include "host/util.h"
#include <sstream> #include <sstream>
#include <QApplication> #include <QApplication>
@ -19,17 +19,19 @@ namespace
{ {
MEMORY_BASIC_INFORMATION info = {}; MEMORY_BASIC_INFORMATION info = {};
VirtualQuery(exception->ExceptionRecord->ExceptionAddress, &info, sizeof(info)); VirtualQuery(exception->ExceptionRecord->ExceptionAddress, &info, sizeof(info));
wchar_t moduleName[MAX_PATH] = {};
GetModuleFileNameW((HMODULE)info.AllocationBase, moduleName, MAX_PATH);
std::wstringstream errorMsg; std::wstringstream errorMsg;
errorMsg << std::uppercase << std::hex << errorMsg << std::uppercase << std::hex <<
L"Error code: " << exception->ExceptionRecord->ExceptionCode << std::endl << L"Error code: " << exception->ExceptionRecord->ExceptionCode << std::endl <<
L"Error address: " << (uint64_t)exception->ExceptionRecord->ExceptionAddress << std::endl << L"Error address: " << exception->ExceptionRecord->ExceptionAddress << std::endl <<
L"Error in module: " << moduleName << std::endl; L"Error in module: " << Util::GetModuleFileName((HMODULE)info.AllocationBase).value_or(L"Could not find") << std::endl <<
L"Additional info: " << info.AllocationBase << std::endl;
if (exception->ExceptionRecord->ExceptionCode == 0xE06D7363) if (exception->ExceptionRecord->ExceptionCode == 0xE06D7363)
{
errorMsg << L"Additional info: " << GetCppExceptionInfo(exception) << std::endl; errorMsg << L"Additional info: " << GetCppExceptionInfo(exception) << std::endl;
if (errorMsg.str().find(L"exception")) errorMsg << ((std::exception*)exception->ExceptionRecord->ExceptionInformation[1])->what();
}
for (int i = 0; i < exception->ExceptionRecord->NumberParameters; ++i) for (int i = 0; i < exception->ExceptionRecord->NumberParameters; ++i)
errorMsg << L"Additional info: " << exception->ExceptionRecord->ExceptionInformation[i] << std::endl; errorMsg << L"Additional info: " << exception->ExceptionRecord->ExceptionInformation[i] << std::endl;
@ -38,7 +40,7 @@ namespace
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
} }
void Terminate() __declspec(noreturn) void Terminate()
{ {
MessageBoxW(NULL, lastError.c_str(), L"Textractor ERROR", MB_ICONERROR); MessageBoxW(NULL, lastError.c_str(), L"Textractor ERROR", MB_ICONERROR);
std::abort(); std::abort();
@ -50,9 +52,12 @@ namespace
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
AddVectoredExceptionHandler(FALSE, ExceptionLogger); AddVectoredExceptionHandler(FALSE, ExceptionLogger);
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)Terminate); SetUnhandledExceptionFilter([](auto) -> LONG { Terminate(); });
QString exe = GetFullModuleName(GetCurrentProcessId());
SetCurrentDirectoryW(exe.left(exe.lastIndexOf("\\")).toStdWString().c_str()); std::wstring exe = Util::GetModuleFileName().value();
while (exe.back() != L'\\') exe.pop_back();
SetCurrentDirectoryW(exe.c_str());
QApplication a(argc, argv); QApplication a(argc, argv);
MainWindow w; MainWindow w;
w.show(); w.show();

View File

@ -4,6 +4,7 @@
#include "extenwindow.h" #include "extenwindow.h"
#include "setdialog.h" #include "setdialog.h"
#include "misc.h" #include "misc.h"
#include <QTimer>
#include <QInputDialog> #include <QInputDialog>
MainWindow::MainWindow(QWidget *parent) : MainWindow::MainWindow(QWidget *parent) :
@ -22,20 +23,12 @@ MainWindow::MainWindow(QWidget *parent) :
if (settings.contains(MAX_BUFFER_SIZE)) TextThread::maxBufferSize = settings.value(MAX_BUFFER_SIZE).toInt(); if (settings.contains(MAX_BUFFER_SIZE)) TextThread::maxBufferSize = settings.value(MAX_BUFFER_SIZE).toInt();
if (settings.contains(DEFAULT_CODEPAGE)) TextThread::defaultCodepage = settings.value(DEFAULT_CODEPAGE).toInt(); if (settings.contains(DEFAULT_CODEPAGE)) TextThread::defaultCodepage = settings.value(DEFAULT_CODEPAGE).toInt();
qRegisterMetaType<std::shared_ptr<TextThread>>();
connect(this, &MainWindow::SigAddProcess, this, &MainWindow::AddProcess);
connect(this, &MainWindow::SigRemoveProcess, this, &MainWindow::RemoveProcess);
connect(this, &MainWindow::SigAddThread, this, &MainWindow::AddThread);
connect(this, &MainWindow::SigRemoveThread, this, &MainWindow::RemoveThread);
connect(this, &MainWindow::SigThreadOutput, this, &MainWindow::ThreadOutput);
Host::Start( Host::Start(
[&](DWORD processId) { emit SigAddProcess(processId); }, [&](DWORD processId) { ProcessConnected(processId); },
[&](DWORD processId) { emit SigRemoveProcess(processId); }, [&](DWORD processId) { ProcessDisconnected(processId); },
[&](std::shared_ptr<TextThread> thread) { emit SigAddThread(thread); }, [&](TextThread* thread) { ThreadAdded(thread); },
[&](std::shared_ptr<TextThread> thread) { emit SigRemoveThread(thread); }, [&](TextThread* thread) { ThreadRemoved(thread); },
[&](TextThread* thread, std::wstring& output) { return ProcessThreadOutput(thread, output); } [&](TextThread* thread, std::wstring& output) { return SentenceReceived(thread, output); }
); );
Host::AddConsoleOutput(ABOUT); Host::AddConsoleOutput(ABOUT);
} }
@ -44,8 +37,8 @@ MainWindow::~MainWindow()
{ {
settings.setValue(WINDOW, geometry()); settings.setValue(WINDOW, geometry());
settings.sync(); settings.sync();
delete ui;
Host::Close(); Host::Close();
delete ui;
} }
void MainWindow::closeEvent(QCloseEvent*) void MainWindow::closeEvent(QCloseEvent*)
@ -53,14 +46,20 @@ void MainWindow::closeEvent(QCloseEvent*)
QCoreApplication::quit(); // Need to do this to kill any windows that might've been made by extensions QCoreApplication::quit(); // Need to do this to kill any windows that might've been made by extensions
} }
void MainWindow::AddProcess(unsigned processId)
void MainWindow::InvokeOnMainThread(std::function<void()>&& f)
{
QMetaObject::invokeMethod(this, f);
}
void MainWindow::ProcessConnected(DWORD processId)
{ {
if (processId == 0) return; if (processId == 0) return;
InvokeOnMainThread([&, processId]
{
processCombo->addItem(QString::number(processId, 16).toUpper() + ": " + GetModuleName(processId)); processCombo->addItem(QString::number(processId, 16).toUpper() + ": " + GetModuleName(processId));
QFile file(HOOK_SAVE_FILE);
file.open(QIODevice::ReadOnly);
QString processName = GetFullModuleName(processId); QString processName = GetFullModuleName(processId);
QStringList allProcesses = QString(file.readAll()).split("\r", QString::SkipEmptyParts); QStringList allProcesses = QString(QAutoFile(HOOK_SAVE_FILE, QIODevice::ReadOnly)->readAll()).split("\r", QString::SkipEmptyParts);
for (auto hooks = allProcesses.rbegin(); hooks != allProcesses.rend(); ++hooks) for (auto hooks = allProcesses.rbegin(); hooks != allProcesses.rend(); ++hooks)
if (hooks->contains(processName)) if (hooks->contains(processName))
{ {
@ -68,51 +67,50 @@ void MainWindow::AddProcess(unsigned processId)
if (auto hp = ParseCode(hook)) Host::InsertHook(processId, hp.value()); if (auto hp = ParseCode(hook)) Host::InsertHook(processId, hp.value());
return; return;
} }
});
} }
void MainWindow::RemoveProcess(unsigned processId) void MainWindow::ProcessDisconnected(DWORD processId)
{ {
processCombo->removeItem(processCombo->findText(QString::number(processId, 16).toUpper() + ":", Qt::MatchStartsWith)); InvokeOnMainThread([&, processId] { processCombo->removeItem(processCombo->findText(QString::number(processId, 16).toUpper() + ":", Qt::MatchStartsWith)); });
} }
void MainWindow::AddThread(std::shared_ptr<TextThread> thread) void MainWindow::ThreadAdded(TextThread* thread)
{ {
ttCombo->addItem( QString ttString = TextThreadString(thread) + QString::fromStdWString(thread->name) + " (" + GenerateCode(thread->hp, thread->tp.processId) + ")";
TextThreadString(thread.get()) + InvokeOnMainThread([&, ttString] { ttCombo->addItem(ttString); });
QString::fromStdWString(thread->name) +
" (" +
GenerateCode(thread->hp, thread->tp.processId) +
")"
);
} }
void MainWindow::RemoveThread(std::shared_ptr<TextThread> thread) void MainWindow::ThreadRemoved(TextThread* thread)
{ {
int threadIndex = ttCombo->findText(TextThreadString(thread.get()), Qt::MatchStartsWith); QString ttString = TextThreadString(thread);
InvokeOnMainThread([&, ttString]
{
int threadIndex = ttCombo->findText(ttString, Qt::MatchStartsWith);
if (threadIndex == ttCombo->currentIndex()) if (threadIndex == ttCombo->currentIndex())
{ {
ttCombo->setCurrentIndex(0); ttCombo->setCurrentIndex(0);
on_ttCombo_activated(0); on_ttCombo_activated(0);
} }
ttCombo->removeItem(threadIndex); ttCombo->removeItem(threadIndex);
});
} }
void MainWindow::ThreadOutput(QString threadString, QString output) bool MainWindow::SentenceReceived(TextThread* thread, std::wstring& sentence)
{ {
if (ttCombo->currentText().startsWith(threadString)) if (DispatchSentenceToExtensions(sentence, GetMiscInfo(thread)))
{
sentence += L"\r\n";
QString ttString = TextThreadString(thread);
InvokeOnMainThread([&, ttString, sentence]
{
if (ttCombo->currentText().startsWith(ttString))
{ {
textOutput->moveCursor(QTextCursor::End); textOutput->moveCursor(QTextCursor::End);
textOutput->insertPlainText(output); textOutput->insertPlainText(QString::fromStdWString(sentence));
textOutput->moveCursor(QTextCursor::End); textOutput->moveCursor(QTextCursor::End);
} }
} });
bool MainWindow::ProcessThreadOutput(TextThread* thread, std::wstring& output)
{
if (DispatchSentenceToExtensions(output, GetMiscInfo(thread)))
{
output += L"\r\n";
emit SigThreadOutput(TextThreadString(thread), QString::fromStdWString(output));
return true; return true;
} }
return false; return false;
@ -130,9 +128,9 @@ QString MainWindow::TextThreadString(TextThread* thread)
).toUpper(); ).toUpper();
} }
ThreadParam MainWindow::ParseTextThreadString(QString textThreadString) ThreadParam MainWindow::ParseTextThreadString(QString ttString)
{ {
QStringList threadParam = textThreadString.split(":"); QStringList threadParam = ttString.split(":");
return { threadParam[1].toUInt(nullptr, 16), threadParam[2].toULongLong(nullptr, 16), threadParam[3].toULongLong(nullptr, 16), threadParam[4].toULongLong(nullptr, 16) }; return { threadParam[1].toUInt(nullptr, 16), threadParam[2].toULongLong(nullptr, 16), threadParam[3].toULongLong(nullptr, 16), threadParam[4].toULongLong(nullptr, 16) };
} }
@ -214,14 +212,10 @@ void MainWindow::on_unhookButton_clicked()
void MainWindow::on_saveButton_clicked() void MainWindow::on_saveButton_clicked()
{ {
auto hooks = GetAllHooks(GetSelectedProcessId());
QString hookList = GetFullModuleName(GetSelectedProcessId()); QString hookList = GetFullModuleName(GetSelectedProcessId());
for (auto hp : hooks) for (auto hp : GetAllHooks(GetSelectedProcessId()))
if (!(hp.type & HOOK_ENGINE)) if (!(hp.type & HOOK_ENGINE)) hookList += " , " + GenerateCode(hp, GetSelectedProcessId());
hookList += " , " + GenerateCode(hp, GetSelectedProcessId()); QAutoFile(HOOK_SAVE_FILE, QIODevice::Append)->write((hookList + "\r\n").toUtf8());
QFile file(HOOK_SAVE_FILE);
file.open(QIODevice::Append);
file.write((hookList + "\r\n").toUtf8());
} }
void MainWindow::on_setButton_clicked() void MainWindow::on_setButton_clicked()

View File

@ -13,8 +13,6 @@ namespace Ui
class MainWindow; class MainWindow;
} }
Q_DECLARE_METATYPE(std::shared_ptr<TextThread>);
class MainWindow : public QMainWindow class MainWindow : public QMainWindow
{ {
Q_OBJECT Q_OBJECT
@ -23,19 +21,7 @@ public:
explicit MainWindow(QWidget *parent = nullptr); explicit MainWindow(QWidget *parent = nullptr);
~MainWindow(); ~MainWindow();
signals:
void SigAddProcess(unsigned processId);
void SigRemoveProcess(unsigned processId);
void SigAddThread(std::shared_ptr<TextThread>);
void SigRemoveThread(std::shared_ptr<TextThread>);
void SigThreadOutput(QString threadString, QString output);
private slots: private slots:
void AddProcess(unsigned processId);
void RemoveProcess(unsigned processId);
void AddThread(std::shared_ptr<TextThread> thread);
void RemoveThread(std::shared_ptr<TextThread> thread);
void ThreadOutput(QString threadString, QString output); // this function doesn't take TextThread* because it might be destroyed on pipe thread
void on_attachButton_clicked(); void on_attachButton_clicked();
void on_detachButton_clicked(); void on_detachButton_clicked();
void on_unhookButton_clicked(); void on_unhookButton_clicked();
@ -46,9 +32,14 @@ private slots:
void on_ttCombo_activated(int index); void on_ttCombo_activated(int index);
private: private:
bool ProcessThreadOutput(TextThread* thread, std::wstring& output); void InvokeOnMainThread(std::function<void()>&& f);
void ProcessConnected(DWORD processId);
void ProcessDisconnected(DWORD processId);
void ThreadAdded(TextThread* thread);
void ThreadRemoved(TextThread* thread);
bool SentenceReceived(TextThread* thread, std::wstring& sentence);
QString TextThreadString(TextThread* thread); QString TextThreadString(TextThread* thread);
ThreadParam ParseTextThreadString(QString textThreadString); ThreadParam ParseTextThreadString(QString ttString);
DWORD GetSelectedProcessId(); DWORD GetSelectedProcessId();
std::unordered_map<std::string, int64_t> GetMiscInfo(TextThread* thread); std::unordered_map<std::string, int64_t> GetMiscInfo(TextThread* thread);
QVector<HookParam> GetAllHooks(DWORD processId); QVector<HookParam> GetAllHooks(DWORD processId);

View File

@ -4,6 +4,15 @@
#include "qtcommon.h" #include "qtcommon.h"
#include "types.h" #include "types.h"
class QAutoFile
{
public:
QAutoFile(QString name, QIODevice::OpenMode mode) : f(name) { f.open(mode); }
QFile* operator->() { return &f; }
private:
QFile f;
};
QString GetFullModuleName(DWORD processId, HMODULE module = NULL); QString GetFullModuleName(DWORD processId, HMODULE module = NULL);
QString GetModuleName(DWORD processId, HMODULE module = NULL); QString GetModuleName(DWORD processId, HMODULE module = NULL);
QMultiHash<QString, DWORD> GetAllProcesses(); QMultiHash<QString, DWORD> GetAllProcesses();