mirror of
https://github.com/tiennm99/time-mocker-cpp.git
synced 2026-06-08 14:13:44 +00:00
chore: init first version
This commit is contained in:
+15
-41
@@ -1,41 +1,15 @@
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Linker files
|
||||
*.ilk
|
||||
|
||||
# Debugger Files
|
||||
*.pdb
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
# debug information files
|
||||
*.dwo
|
||||
.vs/
|
||||
x64/
|
||||
x86/
|
||||
Debug/
|
||||
Release/
|
||||
*.user
|
||||
*.aps
|
||||
*.suo
|
||||
*.db
|
||||
*.opendb
|
||||
vcpkg/
|
||||
packages/detours/lib/
|
||||
packages/detours/include/
|
||||
build/
|
||||
out/
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(TimeMocker CXX)
|
||||
|
||||
# ── vcpkg toolchain (set via: cmake -DCMAKE_TOOLCHAIN_FILE=<vcpkg>/scripts/buildsystems/vcpkg.cmake)
|
||||
find_package(detours CONFIG REQUIRED)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Require Windows
|
||||
if (NOT WIN32)
|
||||
message(FATAL_ERROR "TimeMocker is Windows-only (uses Win32 API and MS Detours).")
|
||||
endif()
|
||||
|
||||
add_compile_definitions(WIN32_LEAN_AND_MEAN UNICODE _UNICODE)
|
||||
|
||||
# ── Shared headers ────────────────────────────────────────────────────────────
|
||||
set(SHARED_DIR ${CMAKE_SOURCE_DIR}/Shared)
|
||||
|
||||
# ── TimeMocker.Hook (DLL) ─────────────────────────────────────────────────────
|
||||
add_library(TimeMocker.Hook SHARED
|
||||
TimeMocker.Hook/dllmain.cpp
|
||||
TimeMocker.Hook/exports.cpp
|
||||
TimeMocker.Hook/TimeMocker.Hook.def
|
||||
)
|
||||
target_include_directories(TimeMocker.Hook PRIVATE ${SHARED_DIR})
|
||||
target_link_libraries(TimeMocker.Hook PRIVATE detours::detours)
|
||||
set_target_properties(TimeMocker.Hook PROPERTIES
|
||||
OUTPUT_NAME "TimeMocker.Hook"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||
)
|
||||
|
||||
# ── TimeMocker.Injector (static lib) ─────────────────────────────────────────
|
||||
add_library(TimeMocker.Injector STATIC
|
||||
TimeMocker.Injector/InjectionManager.cpp
|
||||
)
|
||||
target_include_directories(TimeMocker.Injector
|
||||
PUBLIC ${CMAKE_SOURCE_DIR}/TimeMocker.Injector
|
||||
PRIVATE ${SHARED_DIR}
|
||||
)
|
||||
target_link_libraries(TimeMocker.Injector PUBLIC detours::detours Psapi)
|
||||
|
||||
# ── TimeMocker.UI (console exe) ───────────────────────────────────────────────
|
||||
add_executable(TimeMocker.UI
|
||||
TimeMocker.UI/main.cpp
|
||||
)
|
||||
target_include_directories(TimeMocker.UI PRIVATE ${SHARED_DIR})
|
||||
target_link_libraries(TimeMocker.UI PRIVATE TimeMocker.Injector detours::detours)
|
||||
set_target_properties(TimeMocker.UI PROPERTIES
|
||||
OUTPUT_NAME "TimeMocker"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||
)
|
||||
|
||||
# Copy hook DLLs next to the UI executable after build
|
||||
add_custom_command(TARGET TimeMocker.UI POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
$<TARGET_FILE:TimeMocker.Hook>
|
||||
"${CMAKE_BINARY_DIR}/bin/TimeMocker.Hook.x64.dll"
|
||||
COMMENT "Copying Hook DLL (x64) → bin/"
|
||||
)
|
||||
|
||||
install(TARGETS TimeMocker.UI TimeMocker.Hook
|
||||
RUNTIME DESTINATION bin)
|
||||
@@ -1,2 +1,238 @@
|
||||
# time-mocker-cpp
|
||||
[time-mocker](https://github.com/tiennm99/time-mocker) but written in C++
|
||||
# TimeMocker — C++ / MS Detours Edition
|
||||
|
||||
A Windows tool that injects fake time into running processes by hooking Win32 time APIs using **Microsoft Detours**.
|
||||
|
||||
Port/rewrite of the original C# / EasyHook version with identical IPC design but in native C++.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
TimeMocker.sln
|
||||
├── Shared/
|
||||
│ └── MockTimeInfo.h ─ Named MMF layout (shared between UI and Hook)
|
||||
│
|
||||
├── TimeMocker.Hook/ ─ DLL injected into target processes
|
||||
│ ├── dllmain.cpp ─ DllMain: opens MMF, installs/removes Detours hooks
|
||||
│ └── exports.cpp ─ Sentinel export for version check
|
||||
│
|
||||
├── TimeMocker.Injector/ ─ Static library: injection + IPC management
|
||||
│ ├── InjectionManager.h/.cpp ─ LoadLibrary remote-thread injection + SharedMemoryHandle
|
||||
│ └── ProcessWatcher.h ─ Background thread: auto-inject via glob/regex rules
|
||||
│
|
||||
└── TimeMocker.UI/
|
||||
└── main.cpp ─ Interactive console controller
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hooked APIs
|
||||
|
||||
| API | DLL |
|
||||
|-----|-----|
|
||||
| `GetSystemTime` | kernel32 |
|
||||
| `GetLocalTime` | kernel32 |
|
||||
| `GetSystemTimeAsFileTime` | kernel32 |
|
||||
| `GetSystemTimePreciseAsFileTime` | kernel32 |
|
||||
| `NtQuerySystemTime` | ntdll |
|
||||
|
||||
All hooks read `DeltaTicks` from the named MMF on each call and return
|
||||
`real_utc_filetime_ticks + DeltaTicks`. When `DeltaTicks == 0` the real
|
||||
time passes through unchanged.
|
||||
|
||||
---
|
||||
|
||||
## IPC Design
|
||||
|
||||
```
|
||||
Named Memory-Mapped File: TimeMocker_<PID>
|
||||
Size: 8 bytes
|
||||
|
||||
[0..7] DeltaTicks (LONGLONG, 100-ns units, signed)
|
||||
= fakeUtcTicks - realUtcTicks at the moment the user clicks "Set"
|
||||
```
|
||||
|
||||
- The UI creates the MMF **before** injection so the hook can open it in `DllMain`.
|
||||
- Updates are written as atomic `InterlockedExchange64` — no locks on the hot path.
|
||||
- The hook reads with a single volatile 8-byte load (~4 ns, no syscall).
|
||||
- When the UI closes the MMF (on eject/exit), the hook's next read returns `0`,
|
||||
silently falling back to real time.
|
||||
|
||||
---
|
||||
|
||||
## How Detours Works Here
|
||||
|
||||
```
|
||||
Before injection:
|
||||
kernel32!GetSystemTime → [real implementation]
|
||||
|
||||
After injection:
|
||||
kernel32!GetSystemTime → Hook_GetSystemTime (our function)
|
||||
→ Real_GetSystemTime (trampoline, via DetourAttach)
|
||||
```
|
||||
|
||||
`DetourAttach` patches the first 5–14 bytes of the real function with a
|
||||
`JMP` to our hook, and saves the overwritten bytes + a JMP back in a
|
||||
trampoline so we can call the original.
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Windows 10/11 x64**
|
||||
- **Visual Studio 2022** (v143 toolset) or **CMake 3.20+**
|
||||
- **Microsoft Detours** (installed via `scripts/setup.ps1` → vcpkg)
|
||||
- Must run as **Administrator** (cross-process injection requires elevated privileges)
|
||||
|
||||
---
|
||||
|
||||
## Setup & Build
|
||||
|
||||
### Option A — Visual Studio 2022
|
||||
|
||||
```powershell
|
||||
# 1. Install Detours via vcpkg (one-time)
|
||||
powershell -ExecutionPolicy Bypass -File scripts\setup.ps1
|
||||
|
||||
# 2. Open solution
|
||||
start TimeMocker.sln
|
||||
|
||||
# 3. Build → Release | x64
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
x64\Release\TimeMocker.exe ← controller
|
||||
x64\Release\TimeMocker.Hook.x64.dll ← hook DLL (must be next to .exe)
|
||||
x64\Release\TimeMocker.Hook.x86.dll ← hook DLL for 32-bit targets
|
||||
```
|
||||
|
||||
### Option B — CMake + vcpkg
|
||||
|
||||
```powershell
|
||||
# Install vcpkg + detours
|
||||
git clone https://github.com/microsoft/vcpkg vcpkg
|
||||
.\vcpkg\bootstrap-vcpkg.bat -disableMetrics
|
||||
.\vcpkg\vcpkg install detours:x64-windows detours:x86-windows
|
||||
|
||||
# Configure + build
|
||||
cmake -B build -DCMAKE_TOOLCHAIN_FILE=vcpkg/scripts/buildsystems/vcpkg.cmake -A x64
|
||||
cmake --build build --config Release
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage (Console Controller)
|
||||
|
||||
```
|
||||
timemocker> help
|
||||
|
||||
list [filter] list running processes
|
||||
inject <pid> inject hook DLL into process
|
||||
eject <pid> remove hook from process
|
||||
time YYYY-MM-DD HH:MM:SS set fake time (local)
|
||||
time now reset to real time
|
||||
status show injected processes + delta
|
||||
rule add <pattern> add glob auto-inject rule
|
||||
rule add -r <pattern> add regex auto-inject rule
|
||||
rule list list rules
|
||||
rule del <index> remove rule by index
|
||||
watch start / stop control auto-inject watcher
|
||||
quit exit
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
# List all processes with "notepad" in name/path
|
||||
timemocker> list notepad
|
||||
|
||||
# Inject into Notepad (PID 12345) and set time to Jan 1 2020
|
||||
timemocker> time 2020-01-01 00:00:00
|
||||
timemocker> inject 12345
|
||||
|
||||
# Change time while injected (takes effect immediately)
|
||||
timemocker> time 2025-12-31 23:59:00
|
||||
|
||||
# Auto-inject any process matching a glob
|
||||
timemocker> rule add C:\Games\MyGame\*
|
||||
|
||||
# Auto-inject chrome.exe by name
|
||||
timemocker> rule add *chrome*
|
||||
|
||||
# Auto-inject using regex
|
||||
timemocker> rule add -r ^.*\\MyApp\.exe$
|
||||
|
||||
# Reset to real time and eject
|
||||
timemocker> time now
|
||||
timemocker> eject 12345
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Project Details
|
||||
|
||||
### DLL Injection Method
|
||||
|
||||
Uses the classic **LoadLibrary remote thread** technique:
|
||||
|
||||
1. `OpenProcess` with `PROCESS_CREATE_THREAD | PROCESS_VM_*` rights
|
||||
2. `VirtualAllocEx` → write DLL path string into target's memory
|
||||
3. `CreateRemoteThread(LoadLibraryW, dllPath)` → loads the DLL in-process
|
||||
4. `WaitForSingleObject` → confirm load completed
|
||||
5. On `DLL_PROCESS_ATTACH` the hook's `DllMain` opens the MMF and installs Detours
|
||||
|
||||
For **new processes** (before they start), `DetourCreateProcessWithDllEx()` can also
|
||||
be used — it's included in the Detours SDK and injects before the first instruction
|
||||
runs, before any DRM/anti-cheat initialises.
|
||||
|
||||
### 32-bit vs 64-bit
|
||||
|
||||
- **64-bit target**: `TimeMocker.Hook.x64.dll` is injected
|
||||
- **32-bit target** (WOW64): `TimeMocker.Hook.x86.dll` is injected
|
||||
- Both Hook DLLs are identical source; the platform is selected at build time
|
||||
- The UI (x64) determines which DLL to use by calling `IsWow64Process`
|
||||
|
||||
### Auto-Inject Watcher
|
||||
|
||||
`ProcessWatcher` runs a background thread that polls `CreateToolhelp32Snapshot`
|
||||
every 1.5 seconds. Any process whose full path or `.exe` name matches an enabled
|
||||
rule is automatically injected with the current fake time delta.
|
||||
|
||||
Previously-seen PIDs are tracked in a `HashSet` so they're only matched once,
|
||||
even if the process restarts with the same PID (very rare on Windows).
|
||||
|
||||
### Limitations
|
||||
|
||||
| Limitation | Notes |
|
||||
|-----------|-------|
|
||||
| **64-bit injector only** | The UI is x64-only; a separate x86 injector would be needed for 32-bit-only scenarios |
|
||||
| **Anti-cheat / protected processes** | EAC, BattlEye, and system PPL processes block `OpenProcess` |
|
||||
| **QueryPerformanceCounter** | Not affected — it reads a hardware MSR; EasyHook/Detours cannot intercept kernel MSR reads |
|
||||
| **GetTickCount / timeGetTime** | Not hooked in this version — easy to add following the same pattern |
|
||||
| **Hot eject** | DLL stays loaded but hooks are removed on unload (not forcible without `FreeLibrary` in a remote thread) |
|
||||
|
||||
---
|
||||
|
||||
## Adding a New Hook
|
||||
|
||||
1. Declare the real function pointer in `dllmain.cpp`:
|
||||
```cpp
|
||||
static DWORD (WINAPI* Real_GetTickCount)() = GetTickCount;
|
||||
```
|
||||
2. Write the hook function:
|
||||
```cpp
|
||||
static DWORD WINAPI Hook_GetTickCount()
|
||||
{
|
||||
// Convert fake UTC ticks to milliseconds since boot (approximate)
|
||||
return static_cast<DWORD>(GetFakeUtcTicks() / 10000);
|
||||
}
|
||||
```
|
||||
3. Add `DetourAttach` / `DetourDetach` calls in `InstallHooks` / `RemoveHooks`.
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0 (same as the original C# project).
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
// =============================================================================
|
||||
// TimeMocker Shared IPC
|
||||
// Named Memory-Mapped File layout shared between UI and Hook DLL.
|
||||
// One MMF per injected process, named: TimeMocker_<PID>
|
||||
// =============================================================================
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
|
||||
static const wchar_t* MMF_PREFIX = L"TimeMocker_";
|
||||
static const DWORD MMF_SIZE = sizeof(LONGLONG); // 8 bytes: DeltaTicks (Int64)
|
||||
|
||||
// The only field: signed tick offset to add to QueryUnbiasedInterruptTime / FILETIME.
|
||||
// A tick = 100 nanoseconds (same unit as FILETIME / SYSTEMTIME internals).
|
||||
// DeltaTicks == 0 → real time (pass-through)
|
||||
// DeltaTicks > 0 → future (clock is ahead)
|
||||
// DeltaTicks < 0 → past (clock is behind)
|
||||
#pragma pack(push, 1)
|
||||
struct MockTimeInfo
|
||||
{
|
||||
LONGLONG DeltaTicks; // Offset to add to the real UTC FILETIME ticks
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
// Helper: build the MMF name for a given PID
|
||||
inline void GetMmfName(DWORD pid, wchar_t* buf, size_t cchBuf)
|
||||
{
|
||||
swprintf_s(buf, cchBuf, L"%ls%lu", MMF_PREFIX, pid);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
LIBRARY TimeMocker.Hook
|
||||
|
||||
EXPORTS
|
||||
; No public exports required — Detours helper process detection uses
|
||||
; an internal mechanism. We export a sentinel for the injector to
|
||||
; verify the DLL loaded correctly.
|
||||
TimeMockerHookVersion @1
|
||||
@@ -0,0 +1,140 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{A1111111-1111-1111-1111-111111111111}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>TimeMockerHook</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
|
||||
<!-- ── Common compiler settings ── -->
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<!-- Include Detours via vcpkg or a local copy -->
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)packages\detours\include;$(SolutionDir)Shared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<!-- Link Detours and runtime imports -->
|
||||
<AdditionalDependencies>detours.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<ModuleDefinitionFile>TimeMocker.Hook.def</ModuleDefinitionFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<!-- ── Debug x86 ── -->
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_DEBUG;TIMEMOCKER_HOOK_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)packages\detours\lib\x86;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<!-- ── Release x86 ── -->
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>NDEBUG;TIMEMOCKER_HOOK_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)packages\detours\lib\x86;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<!-- ── Debug x64 ── -->
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_DEBUG;TIMEMOCKER_HOOK_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)packages\detours\lib\x64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<!-- ── Release x64 ── -->
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>NDEBUG;TIMEMOCKER_HOOK_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)packages\detours\lib\x64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="exports.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\Shared\MockTimeInfo.h" />
|
||||
<None Include="TimeMocker.Hook.def" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
</Project>
|
||||
@@ -0,0 +1,311 @@
|
||||
// =============================================================================
|
||||
// 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;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#include <Windows.h>
|
||||
|
||||
// Exported sentinel — lets the injector verify the DLL was built correctly
|
||||
extern "C" __declspec(dllexport) DWORD TimeMockerHookVersion()
|
||||
{
|
||||
return 0x0001'0000; // v1.0
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
// =============================================================================
|
||||
// TimeMocker.Injector — InjectionManager.cpp
|
||||
//
|
||||
// Uses the classic LoadLibrary remote-thread injection technique:
|
||||
// 1. Open the target process with sufficient rights
|
||||
// 2. Write the DLL path into the target's address space
|
||||
// 3. Create a remote thread that calls LoadLibraryW
|
||||
//
|
||||
// For new processes, DetourCreateProcessWithDllEx() can alternatively be used
|
||||
// (see TimeMocker.Injector.CLI for an example).
|
||||
// =============================================================================
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <TlHelp32.h>
|
||||
#include <Psapi.h>
|
||||
#include <detours.h>
|
||||
#include <cstdio>
|
||||
#include <cassert>
|
||||
#include <cwchar>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
||||
#include "InjectionManager.h"
|
||||
|
||||
#pragma comment(lib, "Psapi.lib")
|
||||
|
||||
// ============================================================================
|
||||
// SharedMemoryHandle
|
||||
// ============================================================================
|
||||
|
||||
SharedMemoryHandle::SharedMemoryHandle(DWORD pid)
|
||||
{
|
||||
wchar_t buf[64];
|
||||
GetMmfName(pid, buf, _countof(buf));
|
||||
m_name = buf;
|
||||
|
||||
m_hMap = CreateFileMappingW(
|
||||
INVALID_HANDLE_VALUE, nullptr,
|
||||
PAGE_READWRITE, 0, MMF_SIZE,
|
||||
m_name.c_str());
|
||||
|
||||
if (!m_hMap) return;
|
||||
|
||||
m_pView = MapViewOfFile(m_hMap, FILE_MAP_ALL_ACCESS, 0, 0, MMF_SIZE);
|
||||
if (!m_pView)
|
||||
{
|
||||
CloseHandle(m_hMap);
|
||||
m_hMap = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Zero-initialise (DeltaTicks = 0 → real time)
|
||||
ZeroMemory(m_pView, MMF_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
SharedMemoryHandle::~SharedMemoryHandle()
|
||||
{
|
||||
if (m_pView) UnmapViewOfFile(m_pView);
|
||||
if (m_hMap) CloseHandle(m_hMap);
|
||||
}
|
||||
|
||||
void SharedMemoryHandle::Write(const MockTimeInfo& info)
|
||||
{
|
||||
if (!m_pView) return;
|
||||
// Atomic write on aligned 8-byte address on x64/x86
|
||||
InterlockedExchange64(reinterpret_cast<LONGLONG*>(m_pView), info.DeltaTicks);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TimeUtil
|
||||
// ============================================================================
|
||||
|
||||
LONGLONG TimeUtil::RealUtcTicks()
|
||||
{
|
||||
FILETIME ft;
|
||||
GetSystemTimeAsFileTime(&ft);
|
||||
ULARGE_INTEGER ui;
|
||||
ui.LowPart = ft.dwLowDateTime;
|
||||
ui.HighPart = ft.dwHighDateTime;
|
||||
return static_cast<LONGLONG>(ui.QuadPart);
|
||||
}
|
||||
|
||||
LONGLONG TimeUtil::LocalSystemTimeToUtcTicks(const SYSTEMTIME& st)
|
||||
{
|
||||
FILETIME localFt, utcFt;
|
||||
SystemTimeToFileTime(&st, &localFt);
|
||||
LocalFileTimeToFileTime(&localFt, &utcFt);
|
||||
ULARGE_INTEGER ui;
|
||||
ui.LowPart = utcFt.dwLowDateTime;
|
||||
ui.HighPart = utcFt.dwHighDateTime;
|
||||
return static_cast<LONGLONG>(ui.QuadPart);
|
||||
}
|
||||
|
||||
LONGLONG TimeUtil::ComputeDelta(LONGLONG fakeUtcTicks)
|
||||
{
|
||||
return fakeUtcTicks - RealUtcTicks();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// InjectionManager helpers
|
||||
// ============================================================================
|
||||
|
||||
void InjectionManager::Log(const wchar_t* fmt, ...) const
|
||||
{
|
||||
if (!OnLog) return;
|
||||
wchar_t buf[1024];
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
vswprintf_s(buf, _countof(buf), fmt, va);
|
||||
va_end(va);
|
||||
OnLog(buf);
|
||||
}
|
||||
|
||||
std::wstring InjectionManager::ResolveHookDll(bool x64) const
|
||||
{
|
||||
// Prefer explicit directory; fall back to the injector's own directory
|
||||
std::wstring dir = m_hookDllDir;
|
||||
if (dir.empty())
|
||||
{
|
||||
wchar_t exe[MAX_PATH];
|
||||
GetModuleFileNameW(nullptr, exe, MAX_PATH);
|
||||
wchar_t* slash = wcsrchr(exe, L'\\');
|
||||
if (slash) { *(slash + 1) = L'\0'; dir = exe; }
|
||||
}
|
||||
return dir + (x64 ? L"TimeMocker.Hook.x64.dll" : L"TimeMocker.Hook.x86.dll");
|
||||
}
|
||||
|
||||
bool InjectionManager::IsProcess64Bit(HANDLE hProcess)
|
||||
{
|
||||
BOOL wow64 = FALSE;
|
||||
IsWow64Process(hProcess, &wow64);
|
||||
// If we are 64-bit and the target is NOT WOW64, target is 64-bit
|
||||
#ifdef _WIN64
|
||||
return !wow64;
|
||||
#else
|
||||
return false; // 32-bit injector can only inject x86
|
||||
#endif
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// InjectionManager
|
||||
// ============================================================================
|
||||
|
||||
InjectionManager::InjectionManager(const std::wstring& hookDllDir)
|
||||
: m_hookDllDir(hookDllDir)
|
||||
{
|
||||
}
|
||||
|
||||
InjectionManager::~InjectionManager()
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
for (auto& kv : m_injected)
|
||||
delete kv.second;
|
||||
m_injected.clear();
|
||||
}
|
||||
|
||||
bool InjectionManager::Inject(DWORD pid, LONGLONG fakeUtcTicks, std::wstring* pError)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
|
||||
if (m_injected.count(pid))
|
||||
{
|
||||
// Already injected — just update time
|
||||
m_injected[pid]->Shm->Write({ TimeUtil::ComputeDelta(fakeUtcTicks) });
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- Open target process -----------------------------------------------
|
||||
HANDLE hProcess = OpenProcess(
|
||||
PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION |
|
||||
PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
|
||||
FALSE, pid);
|
||||
|
||||
if (!hProcess)
|
||||
{
|
||||
std::wstring err = L"OpenProcess failed: " + std::to_wstring(GetLastError());
|
||||
Log(L"[Inject] %ls", err.c_str());
|
||||
if (pError) *pError = err;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is64 = IsProcess64Bit(hProcess);
|
||||
std::wstring dllPath = ResolveHookDll(is64);
|
||||
|
||||
// ---- Create shared memory (must exist BEFORE DLL is loaded) -------------
|
||||
auto* entry = new InjectedProcessInfo();
|
||||
entry->Pid = pid;
|
||||
entry->Shm = new SharedMemoryHandle(pid);
|
||||
|
||||
if (!entry->Shm->IsValid())
|
||||
{
|
||||
delete entry;
|
||||
CloseHandle(hProcess);
|
||||
std::wstring err = L"CreateFileMapping failed: " + std::to_wstring(GetLastError());
|
||||
Log(L"[Inject] %ls", err.c_str());
|
||||
if (pError) *pError = err;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write initial delta
|
||||
entry->Shm->Write({ TimeUtil::ComputeDelta(fakeUtcTicks) });
|
||||
|
||||
// ---- Collect process name / path ----------------------------------------
|
||||
wchar_t pathBuf[MAX_PATH] = {};
|
||||
DWORD pathLen = MAX_PATH;
|
||||
QueryFullProcessImageNameW(hProcess, 0, pathBuf, &pathLen);
|
||||
entry->ProcessPath = pathBuf;
|
||||
|
||||
const wchar_t* slash = wcsrchr(pathBuf, L'\\');
|
||||
entry->ProcessName = slash ? (slash + 1) : pathBuf;
|
||||
|
||||
// ---- Inject the DLL via LoadLibraryW remote thread ----------------------
|
||||
SIZE_T dllPathBytes = (dllPath.size() + 1) * sizeof(wchar_t);
|
||||
|
||||
LPVOID remoteStr = VirtualAllocEx(hProcess, nullptr, dllPathBytes,
|
||||
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!remoteStr)
|
||||
{
|
||||
delete entry;
|
||||
CloseHandle(hProcess);
|
||||
std::wstring err = L"VirtualAllocEx failed: " + std::to_wstring(GetLastError());
|
||||
Log(L"[Inject] %ls", err.c_str());
|
||||
if (pError) *pError = err;
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteProcessMemory(hProcess, remoteStr, dllPath.c_str(), dllPathBytes, nullptr);
|
||||
|
||||
HMODULE hKernel = GetModuleHandleW(L"kernel32.dll");
|
||||
FARPROC pLoadLib = GetProcAddress(hKernel, "LoadLibraryW");
|
||||
|
||||
HANDLE hThread = CreateRemoteThread(
|
||||
hProcess, nullptr, 0,
|
||||
reinterpret_cast<LPTHREAD_START_ROUTINE>(pLoadLib),
|
||||
remoteStr, 0, nullptr);
|
||||
|
||||
if (!hThread)
|
||||
{
|
||||
VirtualFreeEx(hProcess, remoteStr, 0, MEM_RELEASE);
|
||||
delete entry;
|
||||
CloseHandle(hProcess);
|
||||
std::wstring err = L"CreateRemoteThread failed: " + std::to_wstring(GetLastError());
|
||||
Log(L"[Inject] %ls", err.c_str());
|
||||
if (pError) *pError = err;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wait for LoadLibraryW to return (give it 5 seconds)
|
||||
WaitForSingleObject(hThread, 5000);
|
||||
|
||||
// Check the return value (hModule loaded)
|
||||
DWORD exitCode = 0;
|
||||
GetExitCodeThread(hThread, &exitCode);
|
||||
CloseHandle(hThread);
|
||||
VirtualFreeEx(hProcess, remoteStr, 0, MEM_RELEASE);
|
||||
CloseHandle(hProcess);
|
||||
|
||||
if (!exitCode)
|
||||
{
|
||||
delete entry;
|
||||
std::wstring err = L"LoadLibraryW in target returned NULL — DLL load failed";
|
||||
Log(L"[Inject] %ls", err.c_str());
|
||||
if (pError) *pError = err;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_injected[pid] = entry;
|
||||
Log(L"[Inject] pid=%lu '%ls' injected ('%ls')", pid, entry->ProcessName.c_str(), dllPath.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InjectionManager::SetFakeTime(DWORD pid, LONGLONG fakeUtcTicks)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
auto it = m_injected.find(pid);
|
||||
if (it == m_injected.end()) return false;
|
||||
it->second->Shm->Write({ TimeUtil::ComputeDelta(fakeUtcTicks) });
|
||||
return true;
|
||||
}
|
||||
|
||||
void InjectionManager::SetFakeTimeAll(LONGLONG fakeUtcTicks)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
LONGLONG delta = TimeUtil::ComputeDelta(fakeUtcTicks);
|
||||
for (auto& kv : m_injected)
|
||||
kv.second->Shm->Write({ delta });
|
||||
}
|
||||
|
||||
bool InjectionManager::Eject(DWORD pid)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
auto it = m_injected.find(pid);
|
||||
if (it == m_injected.end()) return false;
|
||||
|
||||
// Zero out the delta so the hook passes through real time before we unmap
|
||||
it->second->Shm->Write({ 0LL });
|
||||
Sleep(50); // let any in-flight hook calls complete
|
||||
|
||||
delete it->second;
|
||||
m_injected.erase(it);
|
||||
Log(L"[Eject] pid=%lu ejected (shared memory closed)", pid);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InjectionManager::IsInjected(DWORD pid) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
return m_injected.count(pid) != 0;
|
||||
}
|
||||
|
||||
void InjectionManager::ForEach(std::function<void(const InjectedProcessInfo&)> fn) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
for (auto& kv : m_injected)
|
||||
fn(*kv.second);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
// =============================================================================
|
||||
// TimeMocker.Injector — inject/eject Hook DLL + manage shared memory
|
||||
//
|
||||
// Usage:
|
||||
// InjectionManager mgr;
|
||||
// mgr.Inject(pid, fakeTimeUtc); // inject and set time
|
||||
// mgr.SetFakeTime(pid, fakeUtc); // update time while injected
|
||||
// mgr.Eject(pid); // detach hook
|
||||
// =============================================================================
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include "../Shared/MockTimeInfo.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SharedMemoryHandle
|
||||
// Wraps one named MMF per injected process.
|
||||
// ---------------------------------------------------------------------------
|
||||
class SharedMemoryHandle
|
||||
{
|
||||
public:
|
||||
explicit SharedMemoryHandle(DWORD pid);
|
||||
~SharedMemoryHandle();
|
||||
|
||||
SharedMemoryHandle(const SharedMemoryHandle&) = delete;
|
||||
SharedMemoryHandle& operator=(const SharedMemoryHandle&) = delete;
|
||||
|
||||
bool IsValid() const { return m_pView != nullptr; }
|
||||
void Write(const MockTimeInfo& info);
|
||||
const std::wstring& Name() const { return m_name; }
|
||||
|
||||
private:
|
||||
std::wstring m_name;
|
||||
HANDLE m_hMap = nullptr;
|
||||
LPVOID m_pView = nullptr;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// InjectedProcessInfo
|
||||
// ---------------------------------------------------------------------------
|
||||
struct InjectedProcessInfo
|
||||
{
|
||||
DWORD Pid = 0;
|
||||
std::wstring ProcessName;
|
||||
std::wstring ProcessPath;
|
||||
SharedMemoryHandle* Shm = nullptr;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// InjectionManager
|
||||
// ---------------------------------------------------------------------------
|
||||
class InjectionManager
|
||||
{
|
||||
public:
|
||||
explicit InjectionManager(const std::wstring& hookDllDir = L"");
|
||||
~InjectionManager();
|
||||
|
||||
// Inject hook DLL into process and set initial fake time (UTC FILETIME ticks)
|
||||
bool Inject(DWORD pid, LONGLONG fakeUtcTicks, std::wstring* pError = nullptr);
|
||||
|
||||
// Update fake time for an already-injected process
|
||||
bool SetFakeTime(DWORD pid, LONGLONG fakeUtcTicks);
|
||||
|
||||
// Set fake time for all injected processes
|
||||
void SetFakeTimeAll(LONGLONG fakeUtcTicks);
|
||||
|
||||
// Remove hook from process (best-effort — DLL stays loaded but hooks removed on next call)
|
||||
bool Eject(DWORD pid);
|
||||
|
||||
bool IsInjected(DWORD pid) const;
|
||||
|
||||
// Callback for log messages
|
||||
std::function<void(const std::wstring&)> OnLog;
|
||||
|
||||
// Iterate injected processes
|
||||
void ForEach(std::function<void(const InjectedProcessInfo&)> fn) const;
|
||||
|
||||
private:
|
||||
std::wstring ResolveHookDll(bool x64) const;
|
||||
static bool IsProcess64Bit(HANDLE hProcess);
|
||||
static LONGLONG RealUtcTicks();
|
||||
static LONGLONG ToFiletimeDelta(LONGLONG fakeUtcTicks);
|
||||
|
||||
void Log(const wchar_t* fmt, ...) const;
|
||||
|
||||
mutable std::mutex m_mutex;
|
||||
std::unordered_map<DWORD, InjectedProcessInfo*> m_injected;
|
||||
std::wstring m_hookDllDir; // directory where Hook DLLs live
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Utility: convert a DateTime-style local SYSTEMTIME to UTC FILETIME ticks
|
||||
// (helper for callers that work with wall-clock time)
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace TimeUtil
|
||||
{
|
||||
// Get the current real UTC as FILETIME ticks (100-ns units since Jan 1, 1601)
|
||||
LONGLONG RealUtcTicks();
|
||||
|
||||
// Convert a local SYSTEMTIME to UTC FILETIME ticks
|
||||
LONGLONG LocalSystemTimeToUtcTicks(const SYSTEMTIME& st);
|
||||
|
||||
// Build a DeltaTicks value: how many ticks ahead/behind of real time
|
||||
LONGLONG ComputeDelta(LONGLONG fakeUtcTicks);
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
#pragma once
|
||||
// =============================================================================
|
||||
// ProcessWatcher — polls running processes and auto-injects those matching
|
||||
// a set of glob/regex patterns.
|
||||
// =============================================================================
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <TlHelp32.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <regex>
|
||||
#include <algorithm>
|
||||
|
||||
#include "InjectionManager.h"
|
||||
|
||||
struct PatternRule
|
||||
{
|
||||
std::wstring Pattern;
|
||||
bool UseRegex = false;
|
||||
bool Enabled = true;
|
||||
|
||||
bool IsMatch(const std::wstring& path) const
|
||||
{
|
||||
if (path.empty()) return false;
|
||||
std::wstring regexStr;
|
||||
if (UseRegex)
|
||||
{
|
||||
regexStr = Pattern;
|
||||
}
|
||||
else
|
||||
{
|
||||
regexStr = L"^";
|
||||
for (wchar_t c : Pattern)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case L'*': regexStr += L".*"; break;
|
||||
case L'?': regexStr += L'.'; break;
|
||||
case L'.': regexStr += L"\\."; break;
|
||||
case L'\\': regexStr += L"\\\\"; break;
|
||||
default: regexStr += c; break;
|
||||
}
|
||||
}
|
||||
regexStr += L'$';
|
||||
}
|
||||
try
|
||||
{
|
||||
std::wregex re(regexStr, std::regex_constants::icase);
|
||||
return std::regex_match(path, re) || std::regex_search(path, re);
|
||||
}
|
||||
catch (...) { return false; }
|
||||
}
|
||||
};
|
||||
|
||||
class ProcessWatcher
|
||||
{
|
||||
public:
|
||||
explicit ProcessWatcher(InjectionManager& mgr)
|
||||
: m_mgr(mgr)
|
||||
{
|
||||
m_fakeUtcTicks = TimeUtil::RealUtcTicks();
|
||||
}
|
||||
|
||||
~ProcessWatcher() { Stop(); }
|
||||
|
||||
void AddRule(PatternRule rule)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_rulesMutex);
|
||||
m_rules.push_back(std::move(rule));
|
||||
}
|
||||
|
||||
void RemoveRule(const std::wstring& pattern)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_rulesMutex);
|
||||
m_rules.erase(
|
||||
std::remove_if(m_rules.begin(), m_rules.end(),
|
||||
[&](const PatternRule& r){ return r.Pattern == pattern; }),
|
||||
m_rules.end());
|
||||
}
|
||||
|
||||
void ClearRules()
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_rulesMutex);
|
||||
m_rules.clear();
|
||||
}
|
||||
|
||||
void SetFakeUtcTicks(LONGLONG ticks) { m_fakeUtcTicks.store(ticks); }
|
||||
|
||||
void Start(DWORD pollIntervalMs = 1500)
|
||||
{
|
||||
if (m_running.exchange(true)) return;
|
||||
m_thread = std::thread([this, pollIntervalMs]()
|
||||
{
|
||||
while (m_running.load())
|
||||
{
|
||||
Scan();
|
||||
for (DWORD e = 0; m_running.load() && e < pollIntervalMs; e += 100)
|
||||
Sleep(100);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Stop()
|
||||
{
|
||||
if (!m_running.exchange(false)) return;
|
||||
if (m_thread.joinable()) m_thread.join();
|
||||
}
|
||||
|
||||
// Callbacks
|
||||
std::function<void(DWORD pid, const std::wstring& name, const std::wstring& path)> OnAutoInjected;
|
||||
std::function<void(const std::wstring&)> OnLog;
|
||||
|
||||
private:
|
||||
void Scan()
|
||||
{
|
||||
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snap == INVALID_HANDLE_VALUE) return;
|
||||
|
||||
PROCESSENTRY32W pe; pe.dwSize = sizeof(pe);
|
||||
if (!Process32FirstW(snap, &pe)) { CloseHandle(snap); return; }
|
||||
|
||||
std::lock_guard<std::mutex> ruleLk(m_rulesMutex);
|
||||
|
||||
do
|
||||
{
|
||||
DWORD pid = pe.th32ProcessID;
|
||||
{ std::lock_guard<std::mutex> lk(m_seenMutex); if (m_seenPids.count(pid)) continue; }
|
||||
if (m_mgr.IsInjected(pid)) continue;
|
||||
|
||||
HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
|
||||
std::wstring fullPath;
|
||||
if (hProc)
|
||||
{
|
||||
wchar_t buf[MAX_PATH] = {}; DWORD len = MAX_PATH;
|
||||
QueryFullProcessImageNameW(hProc, 0, buf, &len);
|
||||
fullPath = buf;
|
||||
CloseHandle(hProc);
|
||||
}
|
||||
|
||||
std::wstring procName = pe.szExeFile;
|
||||
|
||||
for (auto& rule : m_rules)
|
||||
{
|
||||
if (!rule.Enabled) continue;
|
||||
if (!rule.IsMatch(fullPath) && !rule.IsMatch(procName)) continue;
|
||||
|
||||
{ std::lock_guard<std::mutex> lk(m_seenMutex); m_seenPids.insert(pid); }
|
||||
|
||||
std::wstring err;
|
||||
if (m_mgr.Inject(pid, m_fakeUtcTicks.load(), &err))
|
||||
{
|
||||
DoLog(L"[AutoInject] '%ls' → [%lu] %ls", rule.Pattern.c_str(), pid, procName.c_str());
|
||||
if (OnAutoInjected) OnAutoInjected(pid, procName, fullPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
DoLog(L"[AutoInject] FAIL [%lu] %ls: %ls", pid, procName.c_str(), err.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
} while (Process32NextW(snap, &pe));
|
||||
|
||||
CloseHandle(snap);
|
||||
}
|
||||
|
||||
void DoLog(const wchar_t* fmt, ...) const
|
||||
{
|
||||
if (!OnLog) return;
|
||||
wchar_t buf[1024]; va_list va; va_start(va, fmt);
|
||||
vswprintf_s(buf, _countof(buf), fmt, va); va_end(va);
|
||||
OnLog(buf);
|
||||
}
|
||||
|
||||
InjectionManager& m_mgr;
|
||||
mutable std::mutex m_rulesMutex;
|
||||
std::vector<PatternRule> m_rules;
|
||||
std::mutex m_seenMutex;
|
||||
std::unordered_set<DWORD> m_seenPids;
|
||||
std::atomic<LONGLONG> m_fakeUtcTicks{ 0 };
|
||||
std::atomic<bool> m_running{ false };
|
||||
std::thread m_thread;
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32"> <Configuration>Debug</Configuration> <Platform>Win32</Platform> </ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32"><Configuration>Release</Configuration><Platform>Win32</Platform> </ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64"> <Configuration>Debug</Configuration> <Platform>x64</Platform> </ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64"> <Configuration>Release</Configuration><Platform>x64</Platform> </ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{B2222222-2222-2222-2222-222222222222}</ProjectGuid>
|
||||
<RootNamespace>TimeMockerInjector</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType><UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset><CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType><UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset><WholeProgramOptimization>true</WholeProgramOptimization><CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType><UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset><CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType><UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset><WholeProgramOptimization>true</WholeProgramOptimization><CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)packages\detours\include;$(SolutionDir)Shared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile><Optimization>Disabled</Optimization><RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary></ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile><Optimization>MaxSpeed</Optimization><RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary></ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile><Optimization>Disabled</Optimization><RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary></ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile><Optimization>MaxSpeed</Optimization><RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary></ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ClCompile Include="InjectionManager.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="InjectionManager.h" />
|
||||
<ClInclude Include="ProcessWatcher.h" />
|
||||
<ClInclude Include="..\Shared\MockTimeInfo.h" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
</Project>
|
||||
@@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64"> <Configuration>Debug</Configuration> <Platform>x64</Platform> </ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64"><Configuration>Release</Configuration><Platform>x64</Platform> </ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{C3333333-3333-3333-3333-333333333333}</ProjectGuid>
|
||||
<RootNamespace>TimeMockerUI</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>
|
||||
$(SolutionDir)packages\detours\include;
|
||||
$(SolutionDir)Shared;
|
||||
%(AdditionalIncludeDirectories)
|
||||
</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<AdditionalDependencies>detours.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalLibraryDirectories>
|
||||
$(SolutionDir)packages\detours\lib\x64;
|
||||
$(OutDir);
|
||||
%(AdditionalLibraryDirectories)
|
||||
</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>TimeMocker.Injector.lib;detours.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalLibraryDirectories>
|
||||
$(SolutionDir)packages\detours\lib\x64;
|
||||
$(OutDir);
|
||||
%(AdditionalLibraryDirectories)
|
||||
</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>TimeMocker.Injector.lib;detours.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp" />
|
||||
<!-- Compile injector source directly (alternative to linking static lib) -->
|
||||
<ClCompile Include="..\TimeMocker.Injector\InjectionManager.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\TimeMocker.Injector\InjectionManager.h" />
|
||||
<ClInclude Include="..\TimeMocker.Injector\ProcessWatcher.h" />
|
||||
<ClInclude Include="..\Shared\MockTimeInfo.h" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
</Project>
|
||||
@@ -0,0 +1,461 @@
|
||||
// =============================================================================
|
||||
// TimeMocker.UI — Console controller
|
||||
//
|
||||
// Commands:
|
||||
// list — list running processes (filtered to accessible ones)
|
||||
// inject <pid> — inject into process and apply current fake time
|
||||
// eject <pid> — remove hook from process
|
||||
// time <datetime> — set fake time e.g. time "2024-06-15 14:30:00"
|
||||
// time now — reset fake time to real time
|
||||
// status — show injected processes and current time offset
|
||||
// rule add <glob> — add auto-inject glob rule
|
||||
// rule add -r <rx> — add auto-inject regex rule
|
||||
// rule list — list auto-inject rules
|
||||
// rule del <n> — remove rule by index
|
||||
// watch start — start auto-inject watcher (default: on startup)
|
||||
// watch stop — stop auto-inject watcher
|
||||
// quit / exit — exit
|
||||
// =============================================================================
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <TlHelp32.h>
|
||||
#include <Psapi.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cwchar>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "../TimeMocker.Injector/InjectionManager.h"
|
||||
#include "../TimeMocker.Injector/ProcessWatcher.h"
|
||||
|
||||
#pragma comment(lib, "Psapi.lib")
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void EnableAnsi()
|
||||
{
|
||||
HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
DWORD mode = 0;
|
||||
GetConsoleMode(h, &mode);
|
||||
SetConsoleMode(h, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
|
||||
}
|
||||
|
||||
static const char* C_RESET = "\033[0m";
|
||||
static const char* C_CYAN = "\033[36m";
|
||||
static const char* C_GREEN = "\033[32m";
|
||||
static const char* C_YELLOW = "\033[33m";
|
||||
static const char* C_RED = "\033[31m";
|
||||
static const char* C_GRAY = "\033[90m";
|
||||
|
||||
static void Log(const wchar_t* msg)
|
||||
{
|
||||
// Convert wide to UTF-8 for console output
|
||||
char buf[1024];
|
||||
WideCharToMultiByte(CP_UTF8, 0, msg, -1, buf, sizeof(buf), nullptr, nullptr);
|
||||
printf("%s[LOG]%s %s\n", C_GRAY, C_RESET, buf);
|
||||
}
|
||||
|
||||
// Parse "YYYY-MM-DD HH:MM:SS" → SYSTEMTIME (local)
|
||||
static bool ParseDateTime(const std::string& s, SYSTEMTIME& st)
|
||||
{
|
||||
memset(&st, 0, sizeof(st));
|
||||
int Y, M, D, h, m, sec;
|
||||
if (sscanf_s(s.c_str(), "%d-%d-%d %d:%d:%d", &Y, &M, &D, &h, &m, &sec) == 6 ||
|
||||
sscanf_s(s.c_str(), "%d/%d/%d %d:%d:%d", &Y, &M, &D, &h, &m, &sec) == 6)
|
||||
{
|
||||
st.wYear = static_cast<WORD>(Y);
|
||||
st.wMonth = static_cast<WORD>(M);
|
||||
st.wDay = static_cast<WORD>(D);
|
||||
st.wHour = static_cast<WORD>(h);
|
||||
st.wMinute = static_cast<WORD>(m);
|
||||
st.wSecond = static_cast<WORD>(sec);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::string FormatDelta(LONGLONG deltaTicks)
|
||||
{
|
||||
// deltaTicks: 100-ns units
|
||||
bool neg = deltaTicks < 0;
|
||||
LONGLONG abs = neg ? -deltaTicks : deltaTicks;
|
||||
|
||||
LONGLONG secs = abs / 10000000LL;
|
||||
LONGLONG mins = secs / 60; secs %= 60;
|
||||
LONGLONG hours = mins / 60; mins %= 60;
|
||||
LONGLONG days = hours / 24; hours %= 24;
|
||||
|
||||
char buf[128] = {};
|
||||
if (days) sprintf_s(buf, "%s%lldd%02lldh%02lldm%02llds", neg?"-":"+", days, hours, mins, secs);
|
||||
else if (hours) sprintf_s(buf, "%s%lldh%02lldm%02llds", neg?"-":"+", hours, mins, secs);
|
||||
else if (mins) sprintf_s(buf, "%s%lldm%02llds", neg?"-":"+", mins, secs);
|
||||
else sprintf_s(buf, "%s%llds", neg?"-":"+", secs);
|
||||
return buf;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Enumerate accessible processes
|
||||
// ---------------------------------------------------------------------------
|
||||
struct ProcInfo { DWORD pid; std::wstring name, path; };
|
||||
|
||||
static std::vector<ProcInfo> EnumProcesses(const std::wstring& filter = L"")
|
||||
{
|
||||
std::vector<ProcInfo> result;
|
||||
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snap == INVALID_HANDLE_VALUE) return result;
|
||||
|
||||
PROCESSENTRY32W pe; pe.dwSize = sizeof(pe);
|
||||
if (!Process32FirstW(snap, &pe)) { CloseHandle(snap); return result; }
|
||||
|
||||
do
|
||||
{
|
||||
HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe.th32ProcessID);
|
||||
if (!hProc) continue;
|
||||
|
||||
wchar_t pathBuf[MAX_PATH] = {}; DWORD len = MAX_PATH;
|
||||
QueryFullProcessImageNameW(hProc, 0, pathBuf, &len);
|
||||
CloseHandle(hProc);
|
||||
|
||||
ProcInfo info;
|
||||
info.pid = pe.th32ProcessID;
|
||||
info.name = pe.szExeFile;
|
||||
info.path = pathBuf;
|
||||
|
||||
if (!filter.empty())
|
||||
{
|
||||
auto contains = [&](const std::wstring& hay, const std::wstring& needle)
|
||||
{
|
||||
std::wstring lh = hay, ln = needle;
|
||||
std::transform(lh.begin(), lh.end(), lh.begin(), ::towlower);
|
||||
std::transform(ln.begin(), ln.end(), ln.begin(), ::towlower);
|
||||
return lh.find(ln) != std::wstring::npos;
|
||||
};
|
||||
if (!contains(info.name, filter) && !contains(info.path, filter)) continue;
|
||||
}
|
||||
|
||||
result.push_back(info);
|
||||
|
||||
} while (Process32NextW(snap, &pe));
|
||||
|
||||
CloseHandle(snap);
|
||||
std::sort(result.begin(), result.end(), [](const ProcInfo& a, const ProcInfo& b)
|
||||
{
|
||||
return _wcsicmp(a.name.c_str(), b.name.c_str()) < 0;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// ---------------------------------------------------------------------------
|
||||
int main()
|
||||
{
|
||||
// Switch console to UTF-8
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
_setmode(_fileno(stdout), _O_U8TEXT); // redirect wide to UTF-8
|
||||
_setmode(_fileno(stdout), _O_TEXT); // reset (we'll use printf)
|
||||
EnableAnsi();
|
||||
|
||||
printf("%s╔══════════════════════════════════════════════╗%s\n", C_CYAN, C_RESET);
|
||||
printf("%s║ TimeMocker — C++ / MS Detours ║%s\n", C_CYAN, C_RESET);
|
||||
printf("%s╚══════════════════════════════════════════════╝%s\n", C_CYAN, C_RESET);
|
||||
printf("Type %shelp%s for command list.\n\n", C_YELLOW, C_RESET);
|
||||
|
||||
// Check elevation
|
||||
{
|
||||
HANDLE hToken;
|
||||
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken);
|
||||
TOKEN_ELEVATION elev;
|
||||
DWORD sz = sizeof(elev);
|
||||
GetTokenInformation(hToken, TokenElevation, &elev, sz, &sz);
|
||||
CloseHandle(hToken);
|
||||
if (!elev.TokenIsElevated)
|
||||
{
|
||||
printf("%s[WARN] Not running as Administrator. Injection into protected processes will fail.%s\n\n", C_YELLOW, C_RESET);
|
||||
}
|
||||
}
|
||||
|
||||
InjectionManager mgr;
|
||||
mgr.OnLog = [](const std::wstring& msg) { Log(msg.c_str()); };
|
||||
|
||||
ProcessWatcher watcher(mgr);
|
||||
watcher.OnLog = [](const std::wstring& msg) { Log(msg.c_str()); };
|
||||
watcher.OnAutoInjected = [&](DWORD pid, const std::wstring& name, const std::wstring& /*path*/)
|
||||
{
|
||||
char buf[256]; WideCharToMultiByte(CP_UTF8, 0, name.c_str(), -1, buf, sizeof(buf), nullptr, nullptr);
|
||||
printf("%s[AutoInject]%s [%lu] %s\n", C_GREEN, C_RESET, pid, buf);
|
||||
};
|
||||
|
||||
LONGLONG fakeUtcTicks = TimeUtil::RealUtcTicks(); // start at real time (delta = 0)
|
||||
std::vector<PatternRule> ruleList; // local copy for display
|
||||
|
||||
watcher.SetFakeUtcTicks(fakeUtcTicks);
|
||||
watcher.Start(1500);
|
||||
printf("%s[Watcher]%s Auto-inject watcher started.\n\n", C_GREEN, C_RESET);
|
||||
|
||||
std::string line;
|
||||
while (true)
|
||||
{
|
||||
printf("%stimemocker>%s ", C_CYAN, C_RESET);
|
||||
fflush(stdout);
|
||||
if (!std::getline(std::cin, line)) break;
|
||||
|
||||
// Tokenize
|
||||
std::istringstream iss(line);
|
||||
std::vector<std::string> tokens;
|
||||
{
|
||||
std::string tok;
|
||||
// Handle quoted tokens
|
||||
bool inQ = false; std::string cur;
|
||||
for (char c : line)
|
||||
{
|
||||
if (c == '"') { inQ = !inQ; }
|
||||
else if (c == ' ' && !inQ && !cur.empty()) { tokens.push_back(cur); cur.clear(); }
|
||||
else { cur += c; }
|
||||
}
|
||||
if (!cur.empty()) tokens.push_back(cur);
|
||||
}
|
||||
if (tokens.empty()) continue;
|
||||
|
||||
std::string cmd = tokens[0];
|
||||
std::transform(cmd.begin(), cmd.end(), cmd.begin(), ::tolower);
|
||||
|
||||
// ---- quit -----------------------------------------------------------
|
||||
if (cmd == "quit" || cmd == "exit") break;
|
||||
|
||||
// ---- help -----------------------------------------------------------
|
||||
else if (cmd == "help")
|
||||
{
|
||||
printf(
|
||||
" %slist%s [filter] list running processes\n"
|
||||
" %sinject%s <pid> inject hook DLL into process\n"
|
||||
" %seject%s <pid> remove hook from process\n"
|
||||
" %stime%s YYYY-MM-DD HH:MM:SS set fake time (local)\n"
|
||||
" %stime%s now reset to real time\n"
|
||||
" %sstatus%s show injected processes + delta\n"
|
||||
" %srule add%s <pattern> add glob auto-inject rule\n"
|
||||
" %srule add%s -r <pattern> add regex auto-inject rule\n"
|
||||
" %srule list%s list rules\n"
|
||||
" %srule del%s <index> remove rule by index\n"
|
||||
" %swatch start%s / %sstop%s control auto-inject watcher\n"
|
||||
" %squit%s exit\n",
|
||||
C_YELLOW,C_RESET, C_YELLOW,C_RESET, C_YELLOW,C_RESET,
|
||||
C_YELLOW,C_RESET, C_YELLOW,C_RESET, C_YELLOW,C_RESET,
|
||||
C_YELLOW,C_RESET, C_YELLOW,C_RESET, C_YELLOW,C_RESET,
|
||||
C_YELLOW,C_RESET, C_YELLOW,C_RESET, C_YELLOW,C_RESET,
|
||||
C_YELLOW,C_RESET, C_YELLOW,C_RESET);
|
||||
}
|
||||
|
||||
// ---- list -----------------------------------------------------------
|
||||
else if (cmd == "list")
|
||||
{
|
||||
std::wstring filter;
|
||||
if (tokens.size() > 1)
|
||||
{
|
||||
std::string fs = tokens[1];
|
||||
filter = std::wstring(fs.begin(), fs.end());
|
||||
}
|
||||
|
||||
auto procs = EnumProcesses(filter);
|
||||
printf("%s%-8s %-28s %s%s\n", C_GRAY, "PID", "Name", "Path", C_RESET);
|
||||
for (auto& p : procs)
|
||||
{
|
||||
char name[128] = {}, path[512] = {};
|
||||
WideCharToMultiByte(CP_UTF8, 0, p.name.c_str(), -1, name, sizeof(name), nullptr, nullptr);
|
||||
WideCharToMultiByte(CP_UTF8, 0, p.path.c_str(), -1, path, sizeof(path), nullptr, nullptr);
|
||||
bool inj = mgr.IsInjected(p.pid);
|
||||
printf("%-8lu %-28s %s%s\n", p.pid, name, path, inj ? " ✓" : "");
|
||||
}
|
||||
printf(" %lu processes shown\n", (DWORD)procs.size());
|
||||
}
|
||||
|
||||
// ---- inject ---------------------------------------------------------
|
||||
else if (cmd == "inject")
|
||||
{
|
||||
if (tokens.size() < 2) { printf("%sUsage: inject <pid>%s\n", C_RED, C_RESET); continue; }
|
||||
DWORD pid = (DWORD)atoul(tokens[1].c_str());
|
||||
std::wstring err;
|
||||
if (mgr.Inject(pid, fakeUtcTicks, &err))
|
||||
{
|
||||
auto delta = TimeUtil::ComputeDelta(fakeUtcTicks);
|
||||
printf("%s[OK]%s Injected pid=%lu delta=%s\n",
|
||||
C_GREEN, C_RESET, pid, FormatDelta(delta).c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
char ebuf[512]; WideCharToMultiByte(CP_UTF8,0,err.c_str(),-1,ebuf,sizeof(ebuf),nullptr,nullptr);
|
||||
printf("%s[FAIL]%s %s\n", C_RED, C_RESET, ebuf);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- eject ----------------------------------------------------------
|
||||
else if (cmd == "eject")
|
||||
{
|
||||
if (tokens.size() < 2) { printf("%sUsage: eject <pid>%s\n", C_RED, C_RESET); continue; }
|
||||
DWORD pid = (DWORD)atoul(tokens[1].c_str());
|
||||
if (mgr.Eject(pid))
|
||||
printf("%s[OK]%s Ejected pid=%lu\n", C_GREEN, C_RESET, pid);
|
||||
else
|
||||
printf("%s[FAIL]%s pid=%lu not injected\n", C_RED, C_RESET, pid);
|
||||
}
|
||||
|
||||
// ---- time -----------------------------------------------------------
|
||||
else if (cmd == "time")
|
||||
{
|
||||
if (tokens.size() < 2) { printf("%sUsage: time YYYY-MM-DD HH:MM:SS | time now%s\n", C_RED, C_RESET); continue; }
|
||||
|
||||
std::string arg = tokens[1];
|
||||
if (tokens.size() >= 3) arg += " " + tokens[2]; // join date and time
|
||||
|
||||
if (arg == "now")
|
||||
{
|
||||
fakeUtcTicks = TimeUtil::RealUtcTicks();
|
||||
}
|
||||
else
|
||||
{
|
||||
SYSTEMTIME st;
|
||||
if (!ParseDateTime(arg, st))
|
||||
{
|
||||
printf("%sInvalid format. Use: YYYY-MM-DD HH:MM:SS%s\n", C_RED, C_RESET);
|
||||
continue;
|
||||
}
|
||||
fakeUtcTicks = TimeUtil::LocalSystemTimeToUtcTicks(st);
|
||||
}
|
||||
|
||||
mgr.SetFakeTimeAll(fakeUtcTicks);
|
||||
watcher.SetFakeUtcTicks(fakeUtcTicks);
|
||||
|
||||
auto delta = TimeUtil::ComputeDelta(fakeUtcTicks);
|
||||
// Display local time
|
||||
SYSTEMTIME disp;
|
||||
FILETIME ft;
|
||||
ULARGE_INTEGER ui; ui.QuadPart = (ULONGLONG)fakeUtcTicks;
|
||||
ft.dwLowDateTime = ui.LowPart; ft.dwHighDateTime = ui.HighPart;
|
||||
FILETIME lft; FileTimeToLocalFileTime(&ft, &lft);
|
||||
FileTimeToSystemTime(&lft, &disp);
|
||||
|
||||
printf("%s[Time]%s Fake time set to %04d-%02d-%02d %02d:%02d:%02d (local) delta=%s\n",
|
||||
C_GREEN, C_RESET,
|
||||
disp.wYear, disp.wMonth, disp.wDay,
|
||||
disp.wHour, disp.wMinute, disp.wSecond,
|
||||
FormatDelta(delta).c_str());
|
||||
}
|
||||
|
||||
// ---- status ---------------------------------------------------------
|
||||
else if (cmd == "status")
|
||||
{
|
||||
LONGLONG delta = TimeUtil::ComputeDelta(fakeUtcTicks);
|
||||
|
||||
// Show local fake time
|
||||
FILETIME ft; ULARGE_INTEGER ui; ui.QuadPart = (ULONGLONG)fakeUtcTicks;
|
||||
ft.dwLowDateTime = ui.LowPart; ft.dwHighDateTime = ui.HighPart;
|
||||
FILETIME lft; FileTimeToLocalFileTime(&ft, &lft);
|
||||
SYSTEMTIME st; FileTimeToSystemTime(&lft, &st);
|
||||
|
||||
printf("Fake time : %04d-%02d-%02d %02d:%02d:%02d (local) delta=%s\n",
|
||||
st.wYear, st.wMonth, st.wDay,
|
||||
st.wHour, st.wMinute, st.wSecond,
|
||||
FormatDelta(delta).c_str());
|
||||
|
||||
printf("Injected processes:\n");
|
||||
bool any = false;
|
||||
mgr.ForEach([&](const InjectedProcessInfo& p)
|
||||
{
|
||||
any = true;
|
||||
char name[256] = {};
|
||||
WideCharToMultiByte(CP_UTF8, 0, p.ProcessName.c_str(), -1, name, sizeof(name), nullptr, nullptr);
|
||||
printf(" %s[%lu]%s %s\n", C_GREEN, p.Pid, C_RESET, name);
|
||||
});
|
||||
if (!any) printf(" (none)\n");
|
||||
}
|
||||
|
||||
// ---- rule -----------------------------------------------------------
|
||||
else if (cmd == "rule")
|
||||
{
|
||||
if (tokens.size() < 2) { printf("Subcommands: add, list, del\n"); continue; }
|
||||
std::string sub = tokens[1];
|
||||
std::transform(sub.begin(), sub.end(), sub.begin(), ::tolower);
|
||||
|
||||
if (sub == "list")
|
||||
{
|
||||
if (ruleList.empty()) { printf(" (no rules)\n"); continue; }
|
||||
for (size_t i = 0; i < ruleList.size(); i++)
|
||||
{
|
||||
char pat[512]; WideCharToMultiByte(CP_UTF8, 0, ruleList[i].Pattern.c_str(), -1, pat, sizeof(pat), nullptr, nullptr);
|
||||
printf(" [%zu] %s%s%s (%s)\n",
|
||||
i, C_YELLOW, pat, C_RESET,
|
||||
ruleList[i].UseRegex ? "regex" : "glob");
|
||||
}
|
||||
}
|
||||
else if (sub == "add")
|
||||
{
|
||||
if (tokens.size() < 3) { printf("Usage: rule add [-r] <pattern>\n"); continue; }
|
||||
bool isRegex = false;
|
||||
std::string patStr;
|
||||
if (tokens[2] == "-r" || tokens[2] == "--regex")
|
||||
{
|
||||
isRegex = true;
|
||||
if (tokens.size() < 4) { printf("Missing pattern after -r\n"); continue; }
|
||||
patStr = tokens[3];
|
||||
}
|
||||
else
|
||||
{
|
||||
patStr = tokens[2];
|
||||
}
|
||||
|
||||
PatternRule rule;
|
||||
rule.Pattern = std::wstring(patStr.begin(), patStr.end());
|
||||
rule.UseRegex = isRegex;
|
||||
rule.Enabled = true;
|
||||
|
||||
watcher.AddRule(rule);
|
||||
ruleList.push_back(rule);
|
||||
|
||||
printf("%s[OK]%s Rule added: '%s' (%s)\n",
|
||||
C_GREEN, C_RESET, patStr.c_str(), isRegex ? "regex" : "glob");
|
||||
}
|
||||
else if (sub == "del")
|
||||
{
|
||||
if (tokens.size() < 3) { printf("Usage: rule del <index>\n"); continue; }
|
||||
size_t idx = (size_t)atoi(tokens[2].c_str());
|
||||
if (idx >= ruleList.size()) { printf("%sIndex out of range%s\n", C_RED, C_RESET); continue; }
|
||||
|
||||
watcher.RemoveRule(ruleList[idx].Pattern);
|
||||
ruleList.erase(ruleList.begin() + idx);
|
||||
printf("%s[OK]%s Rule removed\n", C_GREEN, C_RESET);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Unknown rule subcommand: %s\n", sub.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// ---- watch ----------------------------------------------------------
|
||||
else if (cmd == "watch")
|
||||
{
|
||||
if (tokens.size() < 2) { printf("Usage: watch start|stop\n"); continue; }
|
||||
std::string sub = tokens[1];
|
||||
if (sub == "start") { watcher.Start(); printf("%s[Watcher]%s Started\n", C_GREEN, C_RESET); }
|
||||
else if (sub == "stop") { watcher.Stop(); printf("%s[Watcher]%s Stopped\n", C_YELLOW, C_RESET); }
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
printf("%sUnknown command: %s%s (type help)\n", C_RED, cmd.c_str(), C_RESET);
|
||||
}
|
||||
}
|
||||
|
||||
watcher.Stop();
|
||||
printf("\nGoodbye.\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.11.35222.181
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TimeMocker.Hook", "TimeMocker.Hook\TimeMocker.Hook.vcxproj", "{A1111111-1111-1111-1111-111111111111}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TimeMocker.Injector", "TimeMocker.Injector\TimeMocker.Injector.vcxproj", "{B2222222-2222-2222-2222-222222222222}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TimeMocker.UI", "TimeMocker.UI\TimeMocker.UI.vcxproj", "{C3333333-3333-3333-3333-333333333333}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{A1111111-1111-1111-1111-111111111111} = {A1111111-1111-1111-1111-111111111111}
|
||||
{B2222222-2222-2222-2222-222222222222} = {B2222222-2222-2222-2222-222222222222}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Release|x64 = Release|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A1111111-1111-1111-1111-111111111111}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A1111111-1111-1111-1111-111111111111}.Debug|x64.Build.0 = Debug|x64
|
||||
{A1111111-1111-1111-1111-111111111111}.Release|x64.ActiveCfg = Release|x64
|
||||
{A1111111-1111-1111-1111-111111111111}.Release|x64.Build.0 = Release|x64
|
||||
{A1111111-1111-1111-1111-111111111111}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{A1111111-1111-1111-1111-111111111111}.Debug|x86.Build.0 = Debug|Win32
|
||||
{A1111111-1111-1111-1111-111111111111}.Release|x86.ActiveCfg = Release|Win32
|
||||
{A1111111-1111-1111-1111-111111111111}.Release|x86.Build.0 = Release|Win32
|
||||
{B2222222-2222-2222-2222-222222222222}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{B2222222-2222-2222-2222-222222222222}.Debug|x64.Build.0 = Debug|x64
|
||||
{B2222222-2222-2222-2222-222222222222}.Release|x64.ActiveCfg = Release|x64
|
||||
{B2222222-2222-2222-2222-222222222222}.Release|x64.Build.0 = Release|x64
|
||||
{C3333333-3333-3333-3333-333333333333}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{C3333333-3333-3333-3333-333333333333}.Debug|x64.Build.0 = Debug|x64
|
||||
{C3333333-3333-3333-3333-333333333333}.Release|x64.ActiveCfg = Release|x64
|
||||
{C3333333-3333-3333-3333-333333333333}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,64 @@
|
||||
# =============================================================================
|
||||
# setup.ps1 — Bootstrap MS Detours for TimeMockerCpp
|
||||
#
|
||||
# Run once before opening the solution in Visual Studio:
|
||||
# powershell -ExecutionPolicy Bypass -File scripts\setup.ps1
|
||||
#
|
||||
# What it does:
|
||||
# 1. Clones / updates vcpkg (if not already present at $env:VCPKG_ROOT or ./vcpkg)
|
||||
# 2. Installs detours:x64-windows and detours:x86-windows
|
||||
# 3. Copies the resulting headers + libs into packages\detours\
|
||||
# so the vcxproj files can find them without requiring vcpkg integration.
|
||||
# =============================================================================
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$scriptDir = $PSScriptRoot
|
||||
$repoRoot = Split-Path $scriptDir -Parent
|
||||
$pkgDir = Join-Path $repoRoot "packages\detours"
|
||||
$vcpkgRoot = if ($env:VCPKG_ROOT) { $env:VCPKG_ROOT } else { Join-Path $repoRoot "vcpkg" }
|
||||
|
||||
# ── 1. Ensure vcpkg ──────────────────────────────────────────────────────────
|
||||
if (!(Test-Path (Join-Path $vcpkgRoot "vcpkg.exe")))
|
||||
{
|
||||
Write-Host "Cloning vcpkg into $vcpkgRoot ..." -ForegroundColor Cyan
|
||||
git clone https://github.com/microsoft/vcpkg.git $vcpkgRoot
|
||||
& (Join-Path $vcpkgRoot "bootstrap-vcpkg.bat") -disableMetrics
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host "vcpkg found at $vcpkgRoot" -ForegroundColor Green
|
||||
}
|
||||
|
||||
$vcpkg = Join-Path $vcpkgRoot "vcpkg.exe"
|
||||
|
||||
# ── 2. Install Detours ───────────────────────────────────────────────────────
|
||||
Write-Host "Installing detours:x64-windows ..." -ForegroundColor Cyan
|
||||
& $vcpkg install "detours:x64-windows"
|
||||
|
||||
Write-Host "Installing detours:x86-windows ..." -ForegroundColor Cyan
|
||||
& $vcpkg install "detours:x86-windows"
|
||||
|
||||
# ── 3. Copy headers + libs into packages\detours\ ───────────────────────────
|
||||
$x64installed = Join-Path $vcpkgRoot "installed\x64-windows"
|
||||
$x86installed = Join-Path $vcpkgRoot "installed\x86-windows"
|
||||
|
||||
$incSrc = Join-Path $x64installed "include"
|
||||
$incDst = Join-Path $pkgDir "include"
|
||||
|
||||
Write-Host "Copying headers → $incDst" -ForegroundColor Cyan
|
||||
New-Item -ItemType Directory -Force -Path $incDst | Out-Null
|
||||
Copy-Item -Path (Join-Path $incSrc "detours.h") -Destination $incDst -Force
|
||||
|
||||
foreach ($triplet in @("x64", "x86"))
|
||||
{
|
||||
$libSrc = Join-Path (Join-Path $vcpkgRoot "installed\$triplet-windows") "lib\detours.lib"
|
||||
$libDst = Join-Path $pkgDir "lib\$triplet"
|
||||
New-Item -ItemType Directory -Force -Path $libDst | Out-Null
|
||||
Copy-Item -Path $libSrc -Destination (Join-Path $libDst "detours.lib") -Force
|
||||
Write-Host "Copied $triplet detours.lib → $libDst" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Setup complete! Open TimeMocker.sln in Visual Studio 2022." -ForegroundColor Green
|
||||
Write-Host "Build configuration: Debug|x64 or Release|x64" -ForegroundColor Green
|
||||
Reference in New Issue
Block a user