mirror of
https://github.com/tiennm99/time-mocker-cpp.git
synced 2026-06-08 18:17:14 +00:00
312 lines
11 KiB
C++
312 lines
11 KiB
C++
// =============================================================================
|
|
// TimeMocker.Hook — MS Detours-based time hooking DLL
|
|
//
|
|
// Injected into a target process by TimeMocker.Injector.
|
|
// Opens a named Memory-Mapped File created by the UI:
|
|
// Name: TimeMocker_<PID>
|
|
// Data: MockTimeInfo { LONGLONG DeltaTicks }
|
|
//
|
|
// Hooks 5 Win32 time APIs:
|
|
// kernel32!GetSystemTime
|
|
// kernel32!GetLocalTime
|
|
// kernel32!GetSystemTimeAsFileTime
|
|
// kernel32!GetSystemTimePreciseAsFileTime
|
|
// ntdll!NtQuerySystemTime
|
|
//
|
|
// The delta (DeltaTicks) is added to the real UTC FILETIME on every call.
|
|
// When DeltaTicks == 0 the real time passes through unchanged.
|
|
// =============================================================================
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <Windows.h>
|
|
#include <detours.h>
|
|
#include <cstdio>
|
|
#include <cstdint>
|
|
|
|
#include "../Shared/MockTimeInfo.h"
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Module-level state
|
|
// ---------------------------------------------------------------------------
|
|
static HANDLE g_hMapFile = nullptr;
|
|
static LPVOID g_pView = nullptr;
|
|
static wchar_t g_logPath[MAX_PATH];
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Logging (writes to %TEMP%\TimeMocker.Hook.log)
|
|
// ---------------------------------------------------------------------------
|
|
static void Log(const char* fmt, ...)
|
|
{
|
|
FILE* f = nullptr;
|
|
if (_wfopen_s(&f, g_logPath, L"a") == 0 && f)
|
|
{
|
|
SYSTEMTIME st;
|
|
GetLocalTime(&st); // NOTE: intentional real time call before hooks install
|
|
fprintf(f, "[%02d:%02d:%02d] ", st.wHour, st.wMinute, st.wSecond);
|
|
va_list va;
|
|
va_start(va, fmt);
|
|
vfprintf(f, fmt, va);
|
|
va_end(va);
|
|
fprintf(f, "\n");
|
|
fclose(f);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Read the delta from shared memory (inline, hot path)
|
|
// ---------------------------------------------------------------------------
|
|
static inline LONGLONG ReadDeltaTicks()
|
|
{
|
|
if (!g_pView) return 0LL;
|
|
// Atomic-friendly: LONGLONG is 8-byte aligned, single read is atomic on x86/x64
|
|
return *reinterpret_cast<volatile LONGLONG*>(g_pView);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// FILETIME helpers
|
|
// ---------------------------------------------------------------------------
|
|
static const LONGLONG FILETIME_EPOCH_BIAS = 116444736000000000LL; // Jan 1, 1601 → Jan 1, 1970
|
|
|
|
// Convert a FILETIME (100-ns ticks since Jan 1, 1601 UTC) to a SYSTEMTIME UTC
|
|
static void FileTimeToSystemTimeLocal(LONGLONG ticks, SYSTEMTIME* pSt, bool localTime)
|
|
{
|
|
FILETIME ft;
|
|
ft.dwLowDateTime = static_cast<DWORD>(ticks & 0xFFFFFFFF);
|
|
ft.dwHighDateTime = static_cast<DWORD>(ticks >> 32);
|
|
|
|
if (localTime)
|
|
{
|
|
FILETIME localFt;
|
|
FileTimeToLocalFileTime(&ft, &localFt);
|
|
FileTimeToSystemTime(&localFt, pSt);
|
|
}
|
|
else
|
|
{
|
|
FileTimeToSystemTime(&ft, pSt);
|
|
}
|
|
}
|
|
|
|
// Get real UTC as 100-ns ticks (FILETIME ticks)
|
|
static inline LONGLONG GetRealUtcTicks()
|
|
{
|
|
FILETIME ft;
|
|
// Call the REAL GetSystemTimeAsFileTime (trampoline, set up after DetourAttach)
|
|
// We use a raw read of the real function pointer stored in the trampoline.
|
|
// To avoid recursion during hook installation we use a flag.
|
|
ULARGE_INTEGER ui;
|
|
GetSystemTimeAsFileTime(&ft); // will be redirected after hooks are live; see note below
|
|
ui.LowPart = ft.dwLowDateTime;
|
|
ui.HighPart = ft.dwHighDateTime;
|
|
return static_cast<LONGLONG>(ui.QuadPart);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Original function pointers (Detours trampolines)
|
|
// ---------------------------------------------------------------------------
|
|
static void (WINAPI* Real_GetSystemTime)(LPSYSTEMTIME) = GetSystemTime;
|
|
static void (WINAPI* Real_GetLocalTime)(LPSYSTEMTIME) = GetLocalTime;
|
|
static void (WINAPI* Real_GetSystemTimeAsFileTime)(LPFILETIME) = GetSystemTimeAsFileTime;
|
|
static void (WINAPI* Real_GetSystemTimePreciseAsFileTime)(LPFILETIME) = GetSystemTimePreciseAsFileTime;
|
|
|
|
typedef NTSTATUS (NTAPI* NtQuerySystemTimeFn)(PLARGE_INTEGER SystemTime);
|
|
static NtQuerySystemTimeFn Real_NtQuerySystemTime = nullptr;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helper: get fake UTC ticks
|
|
// ---------------------------------------------------------------------------
|
|
static LONGLONG GetFakeUtcTicks()
|
|
{
|
|
// Get real time via the trampoline (not the hook)
|
|
FILETIME ft;
|
|
Real_GetSystemTimeAsFileTime(&ft);
|
|
ULARGE_INTEGER ui;
|
|
ui.LowPart = ft.dwLowDateTime;
|
|
ui.HighPart = ft.dwHighDateTime;
|
|
return static_cast<LONGLONG>(ui.QuadPart) + ReadDeltaTicks();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Hook implementations
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static void WINAPI Hook_GetSystemTime(LPSYSTEMTIME lpSystemTime)
|
|
{
|
|
if (!lpSystemTime) return;
|
|
LONGLONG ticks = GetFakeUtcTicks();
|
|
FileTimeToSystemTimeLocal(ticks, lpSystemTime, false);
|
|
}
|
|
|
|
static void WINAPI Hook_GetLocalTime(LPSYSTEMTIME lpLocalTime)
|
|
{
|
|
if (!lpLocalTime) return;
|
|
LONGLONG ticks = GetFakeUtcTicks();
|
|
FileTimeToSystemTimeLocal(ticks, lpLocalTime, true);
|
|
}
|
|
|
|
static void WINAPI Hook_GetSystemTimeAsFileTime(LPFILETIME lpFileTime)
|
|
{
|
|
if (!lpFileTime) return;
|
|
LONGLONG ticks = GetFakeUtcTicks();
|
|
lpFileTime->dwLowDateTime = static_cast<DWORD>(ticks & 0xFFFFFFFF);
|
|
lpFileTime->dwHighDateTime = static_cast<DWORD>(ticks >> 32);
|
|
}
|
|
|
|
static void WINAPI Hook_GetSystemTimePreciseAsFileTime(LPFILETIME lpFileTime)
|
|
{
|
|
// Same as GetSystemTimeAsFileTime — we can't improve precision beyond real time
|
|
Hook_GetSystemTimeAsFileTime(lpFileTime);
|
|
}
|
|
|
|
static NTSTATUS NTAPI Hook_NtQuerySystemTime(PLARGE_INTEGER SystemTime)
|
|
{
|
|
if (!SystemTime) return STATUS_ACCESS_VIOLATION;
|
|
SystemTime->QuadPart = GetFakeUtcTicks();
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Install / remove hooks
|
|
// ---------------------------------------------------------------------------
|
|
static bool InstallHooks()
|
|
{
|
|
// Resolve NtQuerySystemTime from ntdll
|
|
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
|
|
if (!hNtdll)
|
|
{
|
|
Log("ERROR: ntdll.dll not found");
|
|
return false;
|
|
}
|
|
Real_NtQuerySystemTime = reinterpret_cast<NtQuerySystemTimeFn>(
|
|
GetProcAddress(hNtdll, "NtQuerySystemTime"));
|
|
if (!Real_NtQuerySystemTime)
|
|
{
|
|
Log("ERROR: NtQuerySystemTime not found in ntdll");
|
|
return false;
|
|
}
|
|
|
|
DetourTransactionBegin();
|
|
DetourUpdateThread(GetCurrentThread());
|
|
|
|
DetourAttach(reinterpret_cast<PVOID*>(&Real_GetSystemTime),
|
|
reinterpret_cast<PVOID>(Hook_GetSystemTime));
|
|
DetourAttach(reinterpret_cast<PVOID*>(&Real_GetLocalTime),
|
|
reinterpret_cast<PVOID>(Hook_GetLocalTime));
|
|
DetourAttach(reinterpret_cast<PVOID*>(&Real_GetSystemTimeAsFileTime),
|
|
reinterpret_cast<PVOID>(Hook_GetSystemTimeAsFileTime));
|
|
DetourAttach(reinterpret_cast<PVOID*>(&Real_GetSystemTimePreciseAsFileTime),
|
|
reinterpret_cast<PVOID>(Hook_GetSystemTimePreciseAsFileTime));
|
|
DetourAttach(reinterpret_cast<PVOID*>(&Real_NtQuerySystemTime),
|
|
reinterpret_cast<PVOID>(Hook_NtQuerySystemTime));
|
|
|
|
LONG err = DetourTransactionCommit();
|
|
if (err != NO_ERROR)
|
|
{
|
|
Log("ERROR: DetourTransactionCommit failed: %ld", err);
|
|
return false;
|
|
}
|
|
|
|
Log("Hooks installed successfully (delta=%lld ticks)", ReadDeltaTicks());
|
|
return true;
|
|
}
|
|
|
|
static void RemoveHooks()
|
|
{
|
|
DetourTransactionBegin();
|
|
DetourUpdateThread(GetCurrentThread());
|
|
|
|
DetourDetach(reinterpret_cast<PVOID*>(&Real_GetSystemTime),
|
|
reinterpret_cast<PVOID>(Hook_GetSystemTime));
|
|
DetourDetach(reinterpret_cast<PVOID*>(&Real_GetLocalTime),
|
|
reinterpret_cast<PVOID>(Hook_GetLocalTime));
|
|
DetourDetach(reinterpret_cast<PVOID*>(&Real_GetSystemTimeAsFileTime),
|
|
reinterpret_cast<PVOID>(Hook_GetSystemTimeAsFileTime));
|
|
DetourDetach(reinterpret_cast<PVOID*>(&Real_GetSystemTimePreciseAsFileTime),
|
|
reinterpret_cast<PVOID>(Hook_GetSystemTimePreciseAsFileTime));
|
|
|
|
if (Real_NtQuerySystemTime)
|
|
DetourDetach(reinterpret_cast<PVOID*>(&Real_NtQuerySystemTime),
|
|
reinterpret_cast<PVOID>(Hook_NtQuerySystemTime));
|
|
|
|
DetourTransactionCommit();
|
|
Log("Hooks removed");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Open the shared memory created by the UI
|
|
// ---------------------------------------------------------------------------
|
|
static bool OpenSharedMemory(DWORD pid)
|
|
{
|
|
wchar_t mmfName[64];
|
|
GetMmfName(pid, mmfName, _countof(mmfName));
|
|
|
|
g_hMapFile = OpenFileMappingW(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, mmfName);
|
|
if (!g_hMapFile)
|
|
{
|
|
Log("ERROR: OpenFileMapping('%ls') failed: %lu", mmfName, GetLastError());
|
|
return false;
|
|
}
|
|
|
|
g_pView = MapViewOfFile(g_hMapFile, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, MMF_SIZE);
|
|
if (!g_pView)
|
|
{
|
|
Log("ERROR: MapViewOfFile failed: %lu", GetLastError());
|
|
CloseHandle(g_hMapFile);
|
|
g_hMapFile = nullptr;
|
|
return false;
|
|
}
|
|
|
|
Log("Shared memory '%ls' opened (delta=%lld)", mmfName, ReadDeltaTicks());
|
|
return true;
|
|
}
|
|
|
|
static void CloseSharedMemory()
|
|
{
|
|
if (g_pView) { UnmapViewOfFile(g_pView); g_pView = nullptr; }
|
|
if (g_hMapFile) { CloseHandle(g_hMapFile); g_hMapFile = nullptr; }
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// DllMain
|
|
// ---------------------------------------------------------------------------
|
|
BOOL WINAPI DllMain(HINSTANCE /*hInst*/, DWORD reason, LPVOID /*reserved*/)
|
|
{
|
|
if (DetourIsHelperProcess()) return TRUE;
|
|
|
|
switch (reason)
|
|
{
|
|
case DLL_PROCESS_ATTACH:
|
|
{
|
|
// Build log path: %TEMP%\TimeMocker.Hook.log
|
|
wchar_t tempDir[MAX_PATH];
|
|
GetTempPathW(MAX_PATH, tempDir);
|
|
swprintf_s(g_logPath, _countof(g_logPath), L"%lsTimeMocker.Hook.log", tempDir);
|
|
|
|
DWORD pid = GetCurrentProcessId();
|
|
Log("DLL_PROCESS_ATTACH pid=%lu", pid);
|
|
|
|
if (!OpenSharedMemory(pid))
|
|
{
|
|
// UI hasn't created the MMF yet — this is fatal for injection
|
|
return FALSE;
|
|
}
|
|
|
|
DisableThreadLibraryCalls(/*hInst*/ GetModuleHandleW(nullptr));
|
|
|
|
if (!InstallHooks())
|
|
{
|
|
CloseSharedMemory();
|
|
return FALSE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case DLL_PROCESS_DETACH:
|
|
RemoveHooks();
|
|
CloseSharedMemory();
|
|
Log("DLL_PROCESS_DETACH");
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|