Hey everyone,
recently I had to do some small tool for someone and needed a quick way to hook into the main thread.
I was too lazy to search for a function that I could use as a "pulse" and import a hook lib, so I did some magic with VEH's and WoWs ClientSetTimer function.
It does not use any memory patches or HW-breakpoints, it's just triggering a single step exception in the main thread and catching it with a simple VEH.
I figured this method was not widely used so I decided to make a thread about it.
Here's what I came up with (behold, it's very very simple):
Code:
uint32_t hooks::get_main_thread_id() const {
DWORD main_thread_id = 0;
DWORD process_id = GetCurrentProcessId();
ULONGLONG min_create_time = MAXULONGLONG;
HANDLE thread_snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (thread_snap != INVALID_HANDLE_VALUE) {
THREADENTRY32 th32;
th32.dwSize = sizeof(THREADENTRY32);
BOOL ok = TRUE;
for (ok = Thread32First(thread_snap, &th32); ok;
ok = Thread32Next(thread_snap, &th32))
{
if (th32.th32OwnerProcessID != process_id)
continue;
if (HANDLE thread_handle = OpenThread(
THREAD_QUERY_INFORMATION,
FALSE, th32.th32ThreadID))
{
FILETIME times[4] = { 0 };
if (GetThreadTimes(thread_handle, ×[0],
×[1], ×[2], ×[3]))
{
ULONGLONG time = MAKEULONGLONG(
times[0].dwLowDateTime,
times[0].dwHighDateTime);
if (time && time < min_create_time) {
min_create_time = time;
main_thread_id = th32.th32ThreadID;
}
}
CloseHandle(thread_handle);
}
}
CloseHandle(thread_snap);
}
return (uint32_t) main_thread_id;
}
bool hooks::hook_thread(uint32_t thread_id) {
HANDLE handle = OpenThread(THREAD_SUSPEND_RESUME
| THREAD_SET_CONTEXT | THREAD_GET_CONTEXT
| THREAD_QUERY_INFORMATION, FALSE, thread_id);
if (handle == NULL)
return false;
CONTEXT ctx;
ZeroMemory(&ctx, sizeof(ctx));
ctx.ContextFlags = CONTEXT_CONTROL;
SuspendThread(handle);
BOOL ret = FALSE;
if (GetThreadContext(handle, &ctx)) {
ctx.EFlags |= 0x100;
if (ret = SetThreadContext(handle, &ctx)) {
this->ve_handler = AddVectoredExceptionHandler(
TRUE, vectored_handler);
}
}
ResumeThread(handle);
CloseHandle(handle);
return ret != FALSE;
}
LONG CALLBACK hooks::vectored_handler(PEXCEPTION_POINTERS exp) {
if (exp->ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP ||
GetCurrentThreadId() != hooks::instance()->main_thread_id)
return EXCEPTION_CONTINUE_SEARCH;
if (RemoveVectoredExceptionHandler(hooks::instance()->ve_handler))
hooks::instance()->ve_handler = nullptr;
auto delegates = hooks::instance()->hook_delegates;
delegates->client_set_timer(0, timer_callback, nullptr);
return EXCEPTION_CONTINUE_EXECUTION;
}
bool __cdecl hooks::timer_callback(const void*, void*) {
engine::instance()->on_pulse();
if (engine::instance()->is_shutting_down()) {
engine::instance()->on_unload();
return true;
}
auto delegates = hooks::instance()->hook_delegates;
delegates->client_set_timer(100, timer_callback, nullptr);
return true;
}
ClientSetTimer = 0x1F52 (19802 - rebased)
Have fun