tiennm99 10f73fc32c refactor: harden IPC, hook install, and injection safety
Workspace-wide refactor that fixes correctness, safety, and concurrency
issues across the three crates while preserving the public CLI/UX surface.

core:
- Split SharedDelta into SharedDeltaReader / SharedDeltaWriter so the
  access-mode (FILE_MAP_READ vs ALL_ACCESS) is encoded in the type.
- Use AtomicI64::from_ptr on the page-aligned mapped view instead of a
  raw *mut AtomicI64 cast.
- Detect ERROR_ALREADY_EXISTS via CreateOutcome and surface it.
- Move MMF name to the Global\ namespace so cross-session injection is
  no longer silently scoped to the controller's session.
- Add tick-math round-trip tests (i64 + SYSTEMTIME boundary cases).

hook:
- install_hook! macro collapses the five hook installs to a table;
  per-hook failures are collected into InstallReport instead of
  aborting mid-chain and leaving a partial state armed.
- Unify fake_filetime helpers behind a single trampoline-parameterised fn.
- Fix GetLocalTime: previously returned UTC; now goes
  FILETIME(UTC) -> SYSTEMTIME -> SystemTimeToTzSpecificLocalTime so DST
  is resolved against the source date, matching real GetLocalTime.
- Replace thread::spawn from DllMain with raw CreateThread and
  DisableThreadLibraryCalls(hinst) to avoid loader-lock deadlocks;
  close the returned thread handle to plug a per-injection kernel leak.
- Propagate the real NtQuerySystemTime NTSTATUS instead of always
  returning STATUS_SUCCESS.
- Return STATUS_ACCESS_VIOLATION on null out-pointer.
- Pipe InstallReport + MMF-open failures to OutputDebugStringW for
  DbgView visibility in the target process.

ui:
- New win32_process_info module: pe_machine reads up to 64 KiB so PEs
  with large e_lfanew values parse cleanly; query_full_image_name and
  is_native_x64 (IsWow64Process2) gate inject against PID reuse and
  WoW64 / non-AMD64 targets.
- Drop for InjectionManager zeroes every injected process's delta so
  targets return to real time on UI exit.
- inject() reorders checks so the system-process guard runs against the
  filename derived from the live image path, not the stale watcher
  snapshot; failures are pushed to the ring-buffer log so auto-inject
  loops are no longer silent.
- Refuse to inject critical Windows processes (csrss, smss, lsass,
  services, svchost, MsMpEng, ...) explicitly.
- Switch the log from unbounded Vec to a VecDeque ring buffer (cap 1000).
- Rename eject -> disable to match what it actually does (zero delta,
  keep DLL loaded).
- Unicode-aware paths_equivalent via to_lowercase comparison so non-ASCII
  case differences don't yield false-positive PID-reuse errors.
- app.rs date/time picker: switch to DragValue so mid-typing keystrokes
  don't rewrite the date; clamp year 1970-2200; checked arithmetic on
  unix_micros -> FILETIME so far-future inputs don't silently overflow;
  surface DST-gap status to the user; "Now" auto-applies.
- Add 51 unit tests across mmf, PE parser, system-process guards, time
  math; expose helpers via pub(crate) for test access.
- Drop unused serde_json and thiserror deps.

Total: 56/56 tests passing, cargo clippy clean, cargo build --release
produces time_mocker_ui.exe and time_mocker_hook.dll.
2026-05-20 17:08:43 +07:00
2026-05-20 17:08:09 +07:00
2026-05-20 17:08:09 +07:00
2026-05-20 17:08:09 +07:00

TimeMocker

A Windows tool that injects fake time into running processes by hooking Win32 time APIs. Written in Rust.

Status: Active development. This is the canonical implementation. Language-specific ports are available in C# (EasyHook) and C++ (MS Detours).

Architecture

time-mocker-rs/
├── crates/
│   ├── time-mocker-core/   — shared types (MockTimeInfo) + named MMF helper + tick conversions
│   ├── time-mocker-hook/   — cdylib injected into target processes; hooks 5 time APIs via retour
│   └── time-mocker-ui/     — egui controller binary; injects via dll-syringe, writes delta per PID

Hooked APIs

API DLL
GetSystemTime kernel32
GetLocalTime kernel32
GetSystemTimeAsFileTime kernel32
GetSystemTimePreciseAsFileTime kernel32
NtQuerySystemTime ntdll

IPC Design

Named Memory-Mapped File per injected process:

Name: TimeMocker_<PID>
Size: 8 bytes
  [0..7]  DeltaTicks (i64 — 100-ns units, added to the real FILETIME)

The hook reads the delta on every time API call and returns real_filetime + delta. The controller writes the delta whenever the user picks a new fake time.

Tick epoch difference vs the C# version: The C# version stores a delta against DateTime.UtcNow.Ticks (epoch 0001-01-01 UTC). The Rust version stores a delta in raw FILETIME units (epoch 1601-01-01 UTC). The two IPC contracts are not interoperable — the Rust UI and Rust hook DLL only talk to each other.

Build

# Nightly Rust (required by retour for inline x64 detours)
# A `rust-toolchain.toml` at the repo root pins the channel automatically.
cargo build --release

# Outputs:
#   target/release/time_mocker_ui.exe
#   target/release/time_mocker_hook.dll   (must be next to the UI exe)

Requirements

  • Windows 10/11 x64
  • Rust nightly (pinned via rust-toolchain.toml)
  • Must run as Administrator (UAC manifest embedded)

License

Apache-2.0 — see LICENSE.

S
Description
Windows tool that injects fake time into running processes by hooking Win32 time APIs. Rust.
Readme Apache-2.0
522 KiB
Languages
Rust 99.4%
Java 0.6%