mirror of
https://github.com/tiennm99/time-mocker.git
synced 2026-10-15 16:15:27 +00:00
fix(mmf): fall back to Local\ namespace when controller is not elevated
Creating a `Global\TimeMocker_<pid>` mapping requires `SeCreateGlobalPrivilege`, which an unelevated token (e.g. debug `cargo run` where the UAC manifest is intentionally skipped) does not have, so `CreateFileMappingW` returned NULL with `ERROR_ACCESS_DENIED` and the auto-inject scanner spammed `create MMF Global\TimeMocker_<pid>` for every enumerated PID. The UI now tries `Global\` first and falls back to `Local\` on access denied, with a one-shot log warning so the auto-inject loop doesn't flood. The hook DLL probes both names on attach so the IPC pairs up symmetrically. Release builds keep their elevated `Global\` cross-session reach; debug builds work against same-session targets without admin. Also adds a dedicated `time-mocker-test-target` crate — a small console binary that prints all 5 hooked time APIs every second with its own PID banner, so dev verification can inject into a controlled harness instead of arbitrary running processes.
This commit is contained in:
1 parent
438d11fde7
commit
79d3b69dcd
8 files changed
+295
-24
No files matched your search
@@ -9,9 +9,10 @@ A Windows tool that injects fake time into running processes by hooking Win32 ti
|
||||
```
|
||||
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
|
||||
│ ├── 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-test-target/ — console binary that prints all 5 hooked APIs in a loop (dedicated dev target)
|
||||
│ └── time-mocker-ui/ — egui controller binary; injects via dll-syringe, writes delta per PID
|
||||
```
|
||||
|
||||
## Hooked APIs
|
||||
@@ -29,11 +30,18 @@ time-mocker-rs/
|
||||
Named Memory-Mapped File per injected process:
|
||||
|
||||
```
|
||||
Name: TimeMocker_<PID>
|
||||
Name: Global\TimeMocker_<PID> (preferred — requires elevation)
|
||||
Local\TimeMocker_<PID> (fallback — same-session, unelevated dev)
|
||||
Size: 8 bytes
|
||||
[0..7] DeltaTicks (i64 — 100-ns units, added to the real FILETIME)
|
||||
```
|
||||
|
||||
The UI tries `Global\` first and falls back to `Local\` on `ERROR_ACCESS_DENIED`
|
||||
(i.e. when the controller is not elevated and the token lacks
|
||||
`SeCreateGlobalPrivilege`). The hook DLL probes both names. Release builds
|
||||
embed a UAC manifest, so they always get `Global\`; debug builds run
|
||||
unelevated and use `Local\` for same-session targets.
|
||||
|
||||
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.
|
||||
@@ -54,7 +62,26 @@ cargo build --release
|
||||
|
||||
- Windows 10/11 x64
|
||||
- Rust nightly (pinned via `rust-toolchain.toml`)
|
||||
- Must run as Administrator (UAC manifest embedded)
|
||||
- Release build: must run as Administrator (UAC manifest embedded; needed for `Global\` MMF and cross-session injection)
|
||||
- Debug build (`cargo run`): runs unelevated; falls back to `Local\` namespace — same-session targets only
|
||||
|
||||
## Dev workflow: test target
|
||||
|
||||
Inject into a dedicated harness instead of arbitrary running processes:
|
||||
|
||||
```powershell
|
||||
# Terminal 1 — start the target, note the PID it prints
|
||||
cargo run -p time-mocker-test-target
|
||||
|
||||
# Terminal 2 — start the UI; type that PID into "Inject by PID"
|
||||
cargo build --workspace ; cargo run -p time-mocker-ui
|
||||
```
|
||||
|
||||
The target prints all 5 hooked APIs (`GetSystemTime`, `GetLocalTime`,
|
||||
`GetSystemTimeAsFileTime`, `GetSystemTimePreciseAsFileTime`,
|
||||
`NtQuerySystemTime`) every second. When the hook is loaded and you set a
|
||||
fake time in the UI, all five rows shift by the same delta — that's your
|
||||
proof the hook is live.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
Reference in new issue
Block a user