// Host_p.cc // 10/15/2011 jichi #include "texthook/ihf_p.h" #include "texthook/ith_p.h" #include "texthook/textthread_p.h" #include "host/host.h" #include "vnrhook/include/types.h" #include "ithsys/ithsys.h" #include "wintimer/wintimer.h" #include #ifdef WITH_LIB_WINMAKER # include "winmaker/winmaker.h" #endif // WITH_LIB_WINMAKER //#define ITH_RUNNING_EVENT L"ITH_PIPE_EXIST" //#define ITH_RUNNING_MUTEX L"ITH_RUNNING" //#define ITH_MUTEX_NAME L"ITH_MAIN_RUNNING" //#define DEBUG "ihf_p.cc" #include "sakurakit/skdebug.h" //#define ITH_WITH_LINK // - Construction - //bool Ihf::debug_ = true; bool Ihf::enabled_ = true; //Settings *Ihf::settings_; HookManager *Ihf::hookManager_; qint64 Ihf::messageInterval_ = 250; // 0.25 secs by default, larger than the split_time (0.2sec) in ITH::setman WId Ihf::parentWindow_; QHash Ihf::threadDelegates_; //QHash Ihf::linkedDelegates_; QHash Ihf::hookAddresses_; char Ihf::keptThreadName_[ITH_THREAD_NAME_CAPACITY]; bool Ihf::whitelistEnabled_; qint32 Ihf::whitelist_[Ihf::WhitelistSize]; // Debugging output //void Ihf::consoleOutput(const char *text) //{ if (debug_) qDebug() << "texthook:console:" << text; } //void Ihf::consoleOutputW(const wchar_t *text) //{ if (debug_) qDebug() << "texthook:console:" << QString::fromWCharArray(text); } void Ihf::init() { IthInitSystemService(); Host_Init(); } void Ihf::destroy() { Host_Destroy(); IthCloseSystemService(); } // See also: HelloITH/main.cpp bool Ihf::load() { // 12/20/2013: This would crash the error of failure to create QTimer //if (!parentWindow_) //::wm_register_hidden_class("vnrtexthook.class"); //parentWindow_ = (WId)::wm_create_hidden_window("vnrtexthook.class", "vnrtexthook"); DOUT("enter"); if (hookManager_) { DOUT("leave: already loaded"); return true; } // Single instance protection //HANDLE hMutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, ITH_MUTEX_NAME); // in kernel32.dll //if (hMutex != 0 || ::GetLastError() != ERROR_FILE_NOT_FOUND) { // ::CloseHandle(hMutex); // return false; //} // See: ITH/main.cpp //if (!IthInitSystemService()) { // DOUT("leave: error: failed to init system service"); // return false; //} if (::Host_Open()) { #ifdef WITH_LIB_WINMAKER if (!parentWindow_) parentWindow_ = (WId)::wm_create_hidden_window("vnrtexthook"); #endif // WITH_LIB_WINMAKER WinTimer::setGlobalWindow(parentWindow_); ::Host_GetHookManager(&hookManager_); if (hookManager_) { //::Host_GetSettings(&settings_); //settings_->debug = debug_; //hookManager_->RegisterConsoleCallback(consoleOutput); //hookManager_->RegisterConsoleWCallback(consoleOutputW); //hookManager_->RegisterProcessAttachCallback(processAttach); //hookManager_->RegisterProcessDetachCallback(processDetach); //hookManager_->RegisterProcessNewHookCallback(processNewHook); //hookManager_->RegisterThreadResetCallback(threadReset); hookManager_->RegisterThreadCreateCallback(threadCreate); hookManager_->RegisterThreadRemoveCallback(threadRemove); ::Host_Start(); } } else ::Host_Close(); DOUT("leave: hook manager =" << hookManager_); return hookManager_; } void Ihf::unload() { DOUT("enter: hook manager =" << hookManager_); if (hookManager_) { //hookManager_->RegisterProcessAttachCallback(nullptr); //hookManager_->RegisterProcessDetachCallback(nullptr); //hookManager_->RegisterProcessNewHookCallback(nullptr); //hookManager_->RegisterThreadResetCallback(nullptr); hookManager_->RegisterThreadCreateCallback(nullptr); hookManager_->RegisterThreadRemoveCallback(nullptr); // Console output is not unregisterd to avoid segmentation fault //hookManager_->RegisterConsoleCallback(nullptr); ::Host_Close(); hookManager_ = nullptr; //settings_ = nullptr; #ifdef WITH_LIB_WINMAKER if (parentWindow_) { wm_destroy_window(parentWindow_); parentWindow_ = nullptr; } #endif // WITH_LIB_WINMAKER } //if (parentWindow_) { // wm_destroy_window(parentWindow_); // parentWindow_ = nullptr; //} DOUT("leave"); } // - Callbacks - //DWORD Ihf::processAttach(DWORD pid) //{ // DOUT("enter"); // Q_UNUSED(pid); // DOUT("leave"); // return 0; //} //DWORD Ihf::processDetach(DWORD pid) //{ // DOUT("enter"); // Q_UNUSED(pid); // DOUT("leave"); // return 0; //} //DWORD Ihf::processNewHook(DWORD pid) //{ // DOUT("enter"); // Q_UNUSED(pid); // DOUT("leave"); // return 0; //} // See: HelloITH/main.cpp // See: ThreadCreate in ITH/window.cpp DWORD Ihf::threadCreate(TextThread *t) { Q_ASSERT(t); DOUT("enter: pid =" << t->PID()); Q_ASSERT(hookManager_); // Propagate UNICODE // See: ThreadCreate in ITH/window.cpp //if (ProcessRecord *pr = hookManager_->GetProcessRecord(t->PID())) { // NtWaitForSingleObject(pr->hookman_mutex, 0, 0); // Hook *hk = static_cast(pr->hookman_map); // Q_ASSERT(!hk&&!MAX_HOOK || hk&&MAX_HOOK); // for (int i = 0; i < MAX_HOOK; i++) { // if (hk[i].Address() == t->Addr()) { // if (hk[i].Type() & USING_UNICODE) // t->Status() |= USING_UNICODE; // break; // } // } // NtReleaseMutant(pr->hookman_mutex, 0); //} auto d = new TextThreadDelegate(t); bool init = true; foreach (TextThreadDelegate *it, threadDelegates_) if (d->signature() == it->signature()) { TextThreadDelegate::release(d); d = it; d->retain(); init = false; break; } if (init) { d->setInterval(messageInterval_); d->setParentWindow(parentWindow_); updateLinkedDelegate(d); } threadDelegates_[t] = d; t->RegisterOutputCallBack(threadOutput, d); //t->RegisterFilterCallBack(threadFilter, d); DOUT("leave"); return 0; } // See also: HelloITH/main.cpp DWORD Ihf::threadRemove(TextThread *t) { DOUT("enter"); Q_ASSERT(t); auto p = threadDelegates_.find(t); if (p != threadDelegates_.end()) { auto d = p.value(); //if (!linkedDelegates_.isEmpty()) { // linkedDelegates_.remove(d); // while (auto k = linkedDelegates_.key(d)) // linkedDelegates_.remove(k); //} threadDelegates_.erase(p); TextThreadDelegate::release(d); } #ifdef ITH_WITH_LINK ::Host_UnLinkAll(t->Number()); #endif // ITH_WITH_LINK DOUT("leave"); return 0; } // See: HelloITH/main.cpp DWORD Ihf::threadOutput(TextThread *t, BYTE *data, DWORD dataLength, DWORD newLine, PVOID pUserData, bool space) { DOUT("newLine =" << newLine << ", dataLength =" << dataLength << ", space =" << space); Q_UNUSED(t) Q_ASSERT(data); Q_ASSERT(pUserData); auto d = static_cast(pUserData); //if (TextThreadDelegate *link = findLinkedDelegate(d)) // d = link; Q_ASSERT(d); if (!enabled_ || whitelistEnabled_ && !whitelistContains(d->signature()) && !(keptThreadName_[0] && d->nameEquals(keptThreadName_))) { DOUT("leave: ignored"); return dataLength; } if (newLine) d->touch(); //d->flush(); // new line data are ignored else if (dataLength || space) d->append(reinterpret_cast(data), dataLength, space); //QString text = QString::fromLocal8Bit(reinterpret_cast(data), len); DOUT("leave"); return dataLength; } //TextThreadDelegate *Ihf::findLinkedDelegate(TextThreadDelegate *d) //{ // Q_ASSERT(d); // if (!linkedDelegates_.isEmpty()) { // auto p = linkedDelegates_.find(d); // if (p != linkedDelegates_.end()) // return p.value(); // } // return nullptr; //} void Ihf::updateLinkedDelegate(TextThreadDelegate *d) { #ifdef ITH_WITH_LINK Q_ASSERT(t); foreach (TextThreadDelegate *it, threadDelegates_) if (it->delegateOf(d)) ::Host_AddLink(d->threadNumber(), it->threadNumber()); else if (d->delegateOf(it)) ::Host_AddLink(it->threadNumber(), d->threadNumber()); #else Q_UNUSED(d); #endif // ITH_WITH_LINK } // - Injection - // See: Host_InjectByPID in IHF/main.cpp // See: InjectThread in ITH/profile.cpp bool Ihf::attachProcess(DWORD pid) { DOUT("enter: pid =" << pid); bool ok = ::Host_InjectByPID(pid); //enum { AttachDelay = 500 }; // in msec //::Sleep(AttachDelay); DOUT("leave: ret =" << ok); return ok; } // See: Host_ActiveDetachProcess in IHF/main.cpp bool Ihf::detachProcess(DWORD pid) { return ::Host_ActiveDetachProcess(pid); } bool Ihf::hijackProcess(DWORD pid) { return ::Host_HijackProcess(pid); } // - Hook - // See: Host_ModifyHook in IHF/main.cpp bool Ihf::updateHook(ulong pid, const QString &code) { DOUT("enter: pid =" << pid << ", code =" << code); Q_ASSERT(pid); HookParam hp = {}; if (!Ith::parseHookCode(code, &hp)) { DOUT("leave: failed to parse hook code"); return false; } DWORD hh = ::Host_ModifyHook(pid, &hp); bool ok = ~hh; DOUT("leave: ret =" << ok); return ok; } // See: Host_InsertHook in IHF/main.cpp bool Ihf::addHook(ulong pid, const QString &code, const QString &name, bool verbose) { DOUT("enter: pid =" << pid << ", name =" << name << ", code =" << code); Q_ASSERT(pid); if (hookAddresses_.contains(code)) { DOUT("leave: already added"); return false; } HookParam hp = {}; if (!Ith::parseHookCode(code, &hp, verbose)) { DOUT("leave: failed to parse hook code"); return false; } DWORD hh = ::Host_InsertHook(pid, &hp, name.toAscii()); //DWORD hh = ::NewHook(hp, nameBuf); bool ok = ~hh; if (ok && hp.address) { DOUT("hook address =" << hp.address); hookAddresses_[code] = hp.address; } DOUT("leave: ok =" << ok); return ok; } // See: Host_RemoveHook in IHF/main.cpp bool Ihf::removeHook(ulong pid, const QString &code) { DOUT("enter: pid =" << pid << ", code =" << code); Q_ASSERT(pid); auto p = hookAddresses_.find(code); if (p == hookAddresses_.end()) { DOUT("leave: hook not added"); return false; } DWORD addr = p.value(); Q_ASSERT(addr); hookAddresses_.erase(p); DWORD hh = ::Host_RemoveHook(pid, addr); bool ok = ~hh; DOUT("leave: ret =" << ok); return ok; } bool Ihf::verifyHookCode(const QString &code) { return Ith::verifyHookCode(code); } // - Whitelist - QList Ihf::whitelist() { QList ret; const qint32 *p = whitelist_; while (*p) ret.append(*p++); return ret; } void Ihf::clearWhitelist() { *whitelist_= 0; } void Ihf::setWhitelist(const QList &l) { qint32 *p = whitelist_; if (!l.isEmpty()) foreach (qint32 it, l) { *p++ = it; if (p >= whitelist_ + WhitelistSize) break; } whitelist_[qMin(l.size(), WhitelistSize -1)] = 0; } bool Ihf::whitelistContains(qint32 signature) { const qint32 *p = whitelist_; while (*p) if (signature == *p++) return true; return false; } // EOF /* BYTE LeadByteTable[0x100] = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1 }; DWORD Ihf::threadFilter(TextThread *thread, BYTE *out, DWORD len, DWORD new_line, PVOID data) { DWORD status = thread->Status(); if (!new_line && thread->Number() != 0) { if (status & USING_UNICODE) { DWORD i, j; len >>= 1; WCHAR c, *str = (LPWSTR)out; for (i = 0, j = 0; i < len; i++) { c = str[i]; //if (!uni_filter->Check(c)) str[j++] = c; } memset(str + j, 0, (len - j) << 1); len = j << 1; } else { WORD c; DWORD i, j; for (i = 0, j = 0; i < len; i++) { c = out[i]; if (LeadByteTable[c] == 1) { //if (!mb_filter->Check(c)) out[j++] = c & 0xFF; } else if (i + 1 < len) { c = out[i + 1]; c <<= 8; c |= out[i]; //if (!mb_filter->Check(c)) { out[j++] = c & 0xFF; out[j++] = c >> 8; } i++; } } memset(out + j, 0, len - j); len = j; } } return len; } */ /* // jichi: 10/15/2011: FIXME: This overload will infect the entire program, // even source files that exclude this header, which is unexpected. // No idea what is the trade off of this behavior on performance and liability. // Lots of Qt stuff doesn't work such as QString::toStdString. // I have to use dynamic linkage to avoid being polluted by this module. // // original author: HEAP_ZERO_MEMORY flag is critical. All new object are assumed with zero initialized. // jichi: 10/20/2011: I think the only reason to use Rtl heap here is to ensure HEAP_ZERO_MEMORY, // which is really a bad programming style and incur unstability on heap memory allocation. // ::RtlFreeHeap crash on DLL debug mode. Replace it with standard malloc/free. // ::hHeap handle is also removed from ith/sys.c.cc inline void * __cdecl operator new(size_t lSize) { return ::RtlAllocateHeap(::hHeap, HEAP_ZERO_MEMORY, lSize); } inline void * __cdecl operator new[](size_t lSize) { return ::RtlAllocateHeap(::hHeap, HEAP_ZERO_MEMORY, lSize); } inline void __cdecl operator delete(void *pBlock) { ::RtlFreeHeap(::hHeap, 0, pBlock); } inline void __cdecl operator delete[](void* pBlock) { ::RtlFreeHeap(::hHeap, 0, pBlock); } #include #include inline void * __cdecl operator new(size_t size) throw() { if (!size) // When the value of the expression in a direct-new-declarator is zero, size = 4; // the allocation function is called to allocatean array with no elements.(ISO) void *p = malloc(size); if (p) memset(p, 0, size); return p; } inline void * __cdecl operator new[](size_t size) throw() { if (!size) // When the value of the expression in a direct-new-declarator is zero, size = 4; // the allocation function is called to allocatean array with no elements.(ISO) void *p = malloc(size); if (p) memset(p, 0, size); return p; } inline void __cdecl operator delete(void *p) throw() { free(p); } inline void __cdecl operator delete[](void *p) throw() { free(p); } */ //QString //Ihf::getHookNameById(ulong hookId) //{ // QString ret; // if (hookId) { // auto p = reinterpret_cast(hookId); // if (p->good()) // ret = p->name(); // } // return ret; //} //DWORD ProcessAttach(DWORD pid) //{ // DOUT("process attached, pid =" << pid); // return 0; //} //DWORD ProcessDetach(DWORD pid) //{ // DOUT("process detached, pid =" << pid); // return 0; //} //DWORD ProcessNewHook(DWORD pid) //{ // DOUT("process has new hook inserted, pid =" << pid); // return 0; //}