An educational anti-cheat system demonstrating Windows kernel programming, process monitoring, and cheat detection.
┌─────────────────────────────────────────────────────────────┐
│ Tauri GUI (Rust + Svelte) │
│ peregrine-tauri.exe (run elevated) │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌──────────────┐ │
│ │ Driver │ │ IPC Pipe │ │ Detection │ │ ETW-TI │ │
│ │ Polling │ │ Server │ │ Scans │ │ Consumer │ │
│ └────┬─────┘ └────┬─────┘ └───────────┘ └──────────────┘ │
│ │IOCTL │Named Pipe PPL+ETW │
├───────┼────────────┼────────────────────────────────────────┤
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Kernel Driver (Minifilter) │ │
│ │ PeregrineKernelComponent.sys │ │
│ │ • ObCallback (handle monitoring) │ │
│ │ • Thread/Image notify (protected PIDs) │ │
│ │ • APC DLL inject (Game + Sensor profiles) │ │
│ │ • PPL elevation (EPROCESS patch) │ │
│ │ • Minifilter (report + deny AC file tamper) │ │
│ │ • Driver/ObCallback scanning │ │
│ │ • HWID collection (disk serial, boot GUID) │ │
│ │ • VAD scan (executable private memory) │ │
│ └─────────┬───────────────────────────────────────────┘ │
│ │ APC Injection (per-role DLL path) │
│ ┌──────┴──────┐ │
│ ▼ ▼ │
│ Game DLL Sensor DLL │
│ (game.exe) (cheat.exe) │
│ • full hooks • external hooks │
│ • HWBP/VEH • callstack │
│ • auto-protect• no auto-protect │
└─────────────────────────────────────────────────────────────┘
| Component | Language | Role |
|---|---|---|
| Kernel Driver | C (Minifilter) | Ring-0: ObCallbacks, dual-profile APC injection, PPL, notify, file defense, scans, HWID, VAD |
| Game DLL | C++ (MinHook) | Inject into game: full API hooks + HWBP/VEH + callstack; inject success → auto StateAddPid |
| Sensor DLL | C++ (MinHook) | Inject into cheats/tools: external API hooks + callstack; no HWBP; no auto-protect |
| Tauri GUI | Rust + Svelte | Userland: IOCTL, IPC, scans, ETW-TI, dual inject target lists |
Communication flows:
- GUI ↔ Driver: IOCTL commands (PIDs, Game/Sensor inject config, scans) + event polling
- DLL → GUI: Named pipe
\\.\pipe\peregrine_ipc(hellowithrole, hooks, HWBP on Game) - Driver → target: APC at
kernel32load; Game success auto-protects PID; Sensor does not - GUI → ETW: PPL-protected real-time session for Threat Intelligence (start/stop in UI)
Provider: Microsoft-Windows-Threat-Intelligence ({f4e1897c-bb5d-5668-f1d8-040f4d8dd344}).
| Requirement | Detail |
|---|---|
| Elevation | GUI elevated |
| PPL | Kernel set_ppl on GUI PID before EnableTraceEx2 |
| Scope | Only events tied to protected game PIDs (external target, or internal interesting) |
Parsing (correct path): fields via TDH (TdhGetProperty / TdhGetPropertySize) by name (CallingProcessId, TargetProcessId, BaseAddress, RegionSize/ViewSize/BytesCopied, ProtectionMask, LastProtectionMask, …). No hardcoded UserData offsets (Version / FILL_VAD / build-safe). Classify with Keyword + Task; caller falls back to EventHeader.ProcessId. Keyword enable mask is u64::MAX.
UI policy (game-centric, VM-verified):
- Log only EXTERNAL ops where
target_pid ∈ protectedPids(cheat → game) - Drop all local (
caller == target) — Game DLL MinHook/HWBP is game→game noise (ALLOC 0x1000 RWX, PROTECT size≈5 RX↔RWX) - Drop system callers (csrss, powershell, explorer, svchost, …) — process-create chatter
- Severity:
!!critical /!high /[X]executable; READVM rate-limited ~1/2s
Ops: Toggle ETW-TI / ETW stop (re-start after stop works). Start ETW before running cheats.
VM smoke: Game inject + ETW-TI → test\cheat_etwti.exe <game_pid>
Expect clean lines only, e.g.:
!! WRITEVM_REMOTE cheat_etwti → game | addr size=0x13
!! PROTECTVM_REMOTE [X] cheat_etwti → game | addr size=0x1000 prot=0x40 was=0x4
! SUSPEND_THREAD / RESUME_THREAD cheat_etwti → game
| # | Detection | Technique |
|---|---|---|
| 1 | Module Integrity | .text section SHA-256: disk vs memory, relocation-aware; known PeregrineDLL MinHook sites excluded (logged as ok self-hooks) |
| 2 | IAT/EAT Hook Detection | PE import/export table scanning for entries outside known modules |
| 3 | External Memory Access | Sensor DLL (in cheat) and/or Game DLL hooks: RPM/WPM/NtRead/NtWrite/VirtualAllocEx/VirtualProtectEx/CreateRemoteThread/OpenProcess; also ObCallback + ETW-TI without any DLL |
| 3b | Call Stack Validation | RtlCaptureStackBackTrace on hooked APIs — flags callers in MEM_PRIVATE executable memory |
| 4 | Thread & Shellcode Detection | Kernel thread_create (start address) + userland scan: suspend → GetThreadContext → RIP/start vs modules |
| 5 | Handle Access Monitoring | Kernel ObCallback on handle create/duplicate with dangerous access flags |
| 6 | Process Blacklist | Keyword scan against running process paths |
| 7 | Driver Blacklist | Ring-0 enumeration of loaded kernel drivers |
| 8 | ObCallback Enumeration | Ring-0 scan of registered object callbacks |
| 9 | Manual-Map / VAD | Kernel ZwQueryVirtualMemory walk — executable private regions |
| 10 | Overlay Detection | EnumWindows for layered / transparent / topmost+near-fullscreen windows |
| 11 | System Integrity | Test-signing, HVCI, CPU vendor/hypervisor detection |
| 12 | ETW Threat Intelligence | PPL + TDH property parse; UI shows external→game only (WRITE/PROTECT/SUSPEND…); drops game→game MinHook noise and system callers |
| 13 | File Self-Defense | Minifilter reports and denies write/delete/rename on AC artifacts under \peregrine\ |
| 14 | HWID Collection | Hybrid kernel+userland fingerprinting |
| 15 | YARA Memory Scanning | yara-x over process address space (rules.yar) |
| 16 | HWBP / VEH Protection | Game DLL arms DR0 on VEH list; watchdog re-arms cleared DRs |
| Game | Sensor | |
|---|---|---|
| Deploy names | PeregrineGame_x64/x86.dll |
PeregrineSensor_x64/x86.dll |
| Inject targets | Protected game process names | Cheat / tool process names |
| Hooks | Full set (RPM/WPM/Nt*/VAEx/…/OpenProcess) + NtSetContextThread |
Same external hooks; no NtSetContextThread |
| HWBP / VEH | Yes | No |
| Callstack on hooks | Yes | Yes |
Auto StateAddPid |
Yes (on inject success) | No |
| IOCTL paths | 8 (x64), 9 (x86) | 15 (x64), 16 (x86) |
| IOCTL add target | 10 | 17 |
GUI: Connect loads both path pairs. Use Game / Sensor buttons for target basenames; Clear Inj clears both lists (IOCTL 14).
Verified flow (VM): Game inject game.exe → apc_inject role=game + HWBP + auto-protect; Sensor inject cheat.exe → role=sensor (not protected); cheat.exe RPM/WPM against game → external IPC from Sensor.
OpenEDR-style user-mode APC (no private shellcode):
- GUI sets Game and Sensor DLL paths and separate target name lists via IOCTL
- Driver matches on process create (Game list first, then Sensor); when
kernel32.dllmaps, queues a user APC with:NormalRoutine=KernelBase!LoadLibraryExW(real export — not thekernel32IAT thunk, which is unbound mid-map)NormalContext= full wide path for that role in target VA (PAGE_READWRITEonly)SystemArgument1=NULL,SystemArgument2= load flags (0for absolute paths underC:\Peregrine\)
- No
KeTestAlertThread— APC is delivered by the loader’s laterNtTestAlertafter static imports are initialized (forcing delivery inLoadImageNotifycrashes the process) - Path buffer is freed on process exit (never freed mid-load)
- On Game inject success, kernel calls
StateAddPidso ObCallback / thread / image notifies apply immediately. Sensor inject does not protect the host PID. - x64 + x86 WoW64 (
PsWrapApcWow64Threadin the APC kernel routine)
Approach adapted from OpenEDR injectengine (apcinjector / apcqueue). No RX inject staging (path buffer is RW only).
VAD after inject: A VAD scan on an injected game may still report a small private EXECUTE_READWRITE region. That is expected MinHook trampoline noise from the Game DLL (hooks need a short writable-exec stub), not the old inject shellcode page. The inject path itself no longer allocates executable staging.
Module integrity after inject: ntdll / KernelBase (and optionally kernel32) often fail a naive disk-vs-memory .text hash because the Game DLL patches API prologues. The checker masks those known export sites (same list as dllmain.cpp hooks) and re-hashes; only residual diffs are reported as tamper. UI shows [ok self-hooks] … (MinHook excluded) when the only diffs were self-hooks.
IOCTL notes: command 14 clears all injection targets (Game + Sensor) and disables injection.
| Surface | Policy |
|---|---|
Device \\.\Peregrine |
IoCreateDeviceSecure — SYSTEM + Administrators only |
| IPC pipe | SYSTEM + Administrators full; Authenticated Users write (injected medium-IL games can still send events). Not world-writable. |
| GUI | Must run elevated |
| Kernel JSON | Paths escaped in-kernel (JsonEscapeString); no userland \ massaging |
build_dll.bat # Game+Sensor DLLs + driver → Userland + C:\Peregrine\
cd src\peregrine-tauri && npx tauri build # use tauri build (not cargo alone) so the UI embeds
copy src\peregrine-tauri\src-tauri\target\release\peregrine-tauri.exe C:\Peregrine\Requirements: Windows 10/11 x64, VS2022 + WDK, Rust 1.70+, Node.js 18+, test signing enabled
Deploy layout (typical VM):
C:\Peregrine\
PeregrineKernelComponent.sys
PeregrineGame_x64.dll
PeregrineGame_x86.dll
PeregrineSensor_x64.dll
PeregrineSensor_x86.dll
peregrine-tauri.exe
rules.yar
sc.exe create Peregrine type= kernel binPath= "C:\Peregrine\PeregrineKernelComponent.sys"
reg add "HKLM\SYSTEM\CurrentControlSet\Services\Peregrine\Instances" /v "DefaultInstance" /d "Peregrine Instance" /f
reg add "HKLM\SYSTEM\CurrentControlSet\Services\Peregrine\Instances\Peregrine Instance" /v "Altitude" /d "370030" /f
reg add "HKLM\SYSTEM\CurrentControlSet\Services\Peregrine\Instances\Peregrine Instance" /v "Flags" /t REG_DWORD /d 0 /f
sc.exe start Peregrine
C:\Peregrine\peregrine-tauri.exe
- Connect (auto-finds Game + Sensor DLLs under
C:\Peregrineor next to the EXE) - Game button: e.g.
game.exe→ start game →apc_injectwithrole=game+ auto-protect - Sensor button (optional): e.g.
cheat.exe→ sensor hooks for outbound APIs (no protect) - Run cheats / scans (Threads, VAD, Overlay, ETW-TI, …)
Purpose-built tools in test/:
| Cheat | Technique | Detected by |
|---|---|---|
cheat.exe <PID> <addr> |
RPM/WPM | Sensor DLL hooks (if inject), ETW-TI, ObCallback |
cheat_inject.exe <PID> payload.dll |
Remote LoadLibrary | ObCallback, hooks, ETW-TI |
cheat_shellcode.exe <PID> |
RWX private + remote thread (CFG-safe spin loop) | Thread scan, VAD, ETW-TI, ObCallback |
cheat_patch.exe <PID> |
NOP in kernel32 .text |
Module integrity, ETW-TI |
cheat_manualmap.exe <PID> |
Private exec ± PE header | VAD |
cheat_yara.exe <PID> |
Marker strings / blob | YARA |
cheat_callstack.exe <PID> |
OpenProcess from private RWX | Callstack anomaly |
cheat_etwti.exe <PID> |
Alloc/WPM/RPM/ProtectEx + Suspend/Resume remote | ETW-TI (start ETW before running) |
CheatEngine.exe |
Exists | Blacklist |
Start game.exe first — it prints PID and a health address.
Note: Older shellcode demos that call Sleep from private memory can CFG FastFail the whole target process. The current cheat_shellcode uses a no-API spin loop so the game stays alive for Thread/VAD checks.
src/
├── PeregrineKernelComponent/ # Kernel minifilter driver (ring-0)
├── PeregrineDLL/ # Game + Sensor inject DLLs (x86 + x64, role builds)
└── peregrine-tauri/ # Rust/Tauri desktop GUI
test/ # Cheat test tools
Accompanying series on patchi.fyi:
- Anatomy of an Anti-Cheat
- ObCallbacks
- Kernel APC Injection
- MinHook API Interception
- Game vs Sensor Multi-DLL
- Relocation-Aware Hashing
- IAT/EAT Scanning
- Shellcode Thread Analysis
- PPL ETW Threat Intelligence
- Minifilter Self-Defense
- Kernel Scanning
Strictly for educational purposes. Use only in controlled environments with proper authorization.
