commit mostly nonworking code

This commit is contained in:
Devin Zuczek
2026-08-18 15:53:45 -04:00
parent 54c97ef6d9
commit b15039ea08
29 changed files with 2797 additions and 43 deletions
+11
View File
@@ -0,0 +1,11 @@
#ifndef ANTITAMPER_PATCH_H
#define ANTITAMPER_PATCH_H
// Suppresses the client's anti-tamper report funnel (build 2025-04-29). Every tamper detection --
// ImageSignature (signed CDN URL check), Inject, UnknownDll, Memory_Hash_Mismatch, etc. -- routes
// through one static method that creates a "Hile" warning (POST api/PlayerReporting/v1/hile) and can
// force-quit. Neutralizing it stops the ~30s lockup/exit our own patches + recflare's placeholder URL
// signatures would otherwise trigger.
void PatchAntiTamper(void);
#endif
+41
View File
@@ -21,4 +21,45 @@ extern char *rewrite_from[MAX_REWRITES];
extern char *rewrite_to[MAX_REWRITES];
extern int rewrite_count;
// Photon Cloud app IDs injected into AppSettings at connect time (a self-hosted server can't supply
// real Photon Cloud app IDs). Empty string = not configured (that field is left untouched).
extern char photon_realtime_appid[64];
extern char photon_chat_appid[64];
extern char photon_voice_appid[64];
// Per-hook enable flags (all default 1/on). Lets a hook be disabled from redirector.json without a
// rebuild -- used to bisect which hook the anti-cheat's periodic runtime check is reacting to (the
// ~13-30s poison/crash -- see memory note unstable-build-identity-rvas.md). enable_spoof toggles
// whether SpoofCall4 (retspoof.c) actually uses the scanned gadget or always falls back to a plain
// call, so the return-address-spoofing change itself can be A/B tested the same way.
extern int enable_ssl;
extern int enable_http;
extern int enable_photon;
extern int enable_spoof;
// When set, the Application.Quit tracer (quit_trace.c) suppresses the shutdown instead of only
// logging its caller. Default 0 (log only) so a diagnostic build never silently blocks a legitimate
// exit -- set "blockQuit": 1 in redirector.json to keep the client alive through a spurious quit.
extern int block_quit;
// Use hardware breakpoints (debug registers, zero bytes written) instead of inline detours for the
// three GameAssembly.dll hooks, so a native integrity check hashing .text cannot see them. Default 1.
// Set "useHwbp": 0 in redirector.json to A/B against the old inline detours without a rebuild.
extern int use_hwbp;
// Install the ws2_32!getaddrinfo detour. Once the SendRequest host rewrite is active the client asks
// for real *.recflare.net names, which resolve on their own, so the DNS hook is only a safety net --
// turning it off ("enableDns": false) leaves ZERO inline byte patches anywhere in the process.
extern int enable_dns;
// Diagnostics: the vectored AV logger, the hang probe (which SUSPENDS the main thread every 3s) and
// the hardware-breakpoint self-test. All three are observers, but a first-in-chain VEH and periodic
// thread suspension are exactly the sort of thing that can perturb Themida's exception-driven code
// decryption -- so they must be switchable to keep them out of a measurement.
extern int enable_diag;
// Unlink redirector.dll from the PEB loader lists after load so a module-walking anti-tamper scan
// can't see our injected DLL. Default 1 (current best guess at what Themida's ~35s check flags).
extern int hide_module;
void LoadConfig(void);
+13
View File
@@ -0,0 +1,13 @@
#pragma once
// Vectored AV logger: logs access violations as module+RVA (GA/UP/RR) with registers and a walk of
// the FAULTING thread's stack. First-chance and non-intrusive.
void InstallCrashHandler(void);
// Periodically samples Unity's main thread (RIP + stack) so a stall shows up as a repeating sample.
// See the comment block in src/debug/crash_handler.c.
void StartHangProbe(void);
// Unity's main thread id, published by the SendRequest hook the first time it runs (that hook
// executes on the main thread). Read by the hang probe; 0 until the first request.
extern volatile DWORD g_mainThreadId;
+5
View File
@@ -0,0 +1,5 @@
#pragma once
// Neutralises the file-signature-check P/Invoke that calls through an unresolved native pointer and
// eventually kills the process. See src/unity/filesig_patch.c.
void PatchFileSigCheck(void);
+71
View File
@@ -0,0 +1,71 @@
#pragma once
//
// Hardware-breakpoint hooks: function interception that writes ZERO bytes.
//
// Every hook in this project so far is an inline detour -- 14+ bytes overwritten at the target's
// entry. Any integrity check that hashes GameAssembly.dll's .text sees those edits. On the
// recflare-client-unstable build (2025-04-29) the session dies ~35s in with a hard 0xC0000005 inside
// the Themida-wrapped RecRoom.exe.dll, which is very likely a NATIVE integrity check reacting to
// exactly that (see memory note unstable-build-identity-rvas.md).
//
// A hardware breakpoint lives in the CPU's debug registers instead of in the code, so the target's
// bytes stay pristine and no memory scan -- managed or native -- can see the hook.
//
// Cost/limits (why this isn't the default everywhere):
// * There are only DR0-DR3, so a hard cap of 4 hooks. We spend 3 on the GameAssembly.dll targets
// and leave ws2_32!getaddrinfo as a normal inline detour (a system DLL the game's own integrity
// check has no reason to hash), keeping one slot free.
// * Debug registers are PER-THREAD, so they must be applied to every thread that could reach the
// target -- including threads created later (BestHTTP spins up its own). HwbpInit starts a
// watcher that applies the current register set to any thread it hasn't seen yet.
//
// -------------------------------------------------------------------------------------------
// STATUS: WORKS. Verified on recflare-client-unstable (2025-04-29) with `"useHwbp": true` --
// 176 traps and 77 URL rewrites in one session, and the self-test (arming a function inside this
// DLL) passes. Default is nonetheless OFF (`use_hwbp = 0`): it makes no behavioural difference
// versus inline detours (the crash was proven NOT to be tamper detection), and it costs a thread
// suspend/resume storm, so the simpler path wins.
//
// Two things had to be right, both of which cost a lot of debugging:
// * Arming: Dr0 reads back as the target VA and Dr7 as 0x415 (that is our 0x15 plus bit 10,
// which always reads as 1 -- not a bug).
// * The protector inside RecRoom.exe.dll ZEROES Dr0-Dr3 a few seconds in (Dr0 -> 0, Dr7 left
// alone). Re-arming EVERY thread EVERY pass -- not just newly seen ones -- defeats that.
// * Ordering: HwbpInit must run EARLY (it is kicked off from HwbpSelfTest right after the crash
// handler). Starting the engine lazily at first-hook time was the real reason for a long
// stretch of "armed but never fires" results -- threads were created before the watcher ran.
//
// Always HwbpDisarm() a slot you are done with, or the watcher keeps suspending every thread in the
// process five times a second forever.
// -------------------------------------------------------------------------------------------
#define HWBP_MAX 4
// Slot assignment (one per DR register).
#define HWBP_SLOT_SSL 0
#define HWBP_SLOT_HTTP 1
#define HWBP_SLOT_PHOTON 2
// slot 3 intentionally free
// Install the VEH and start the per-thread applier. Safe to call more than once.
BOOL HwbpInit(void);
// Point `slot` at `target`; when any thread executes `target`, control transfers to `hook` with the
// register state (and therefore the arguments and return address) untouched. Returns FALSE if the
// slot is out of range. Applying to already-running threads happens here; new threads are picked up
// by the watcher.
BOOL HwbpAdd(int slot, void *target, void *hook);
// Let THIS thread execute `slot`'s target once without trapping. A call-through hook must call this
// immediately before invoking the original, otherwise the call re-triggers the breakpoint and
// recurses forever. Per-thread, one-shot, and consumed by the next hit.
void HwbpSkipOnce(int slot);
// Arm the spare slot on a function inside this DLL and call it from a fresh thread, to establish
// whether hardware breakpoints function in this process at all. Logs SELF-TEST PASSED/FAILED.
// Runs regardless of use_hwbp -- it only touches our own code, never the game's.
void HwbpSelfTest(void);
// Release a slot and push the cleared debug registers to every thread. With no slots left armed the
// watcher stops suspending threads entirely, so always disarm what you no longer need.
void HwbpDisarm(int slot);
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include <windows.h>
// Unlink `self` from the PEB loader's three module lists so anything that walks the loaded-module
// list (GetModuleHandle, CreateToolhelp32Snapshot's module snapshot, and -- the point here -- a
// protector's periodic anti-tamper scan) no longer sees redirector.dll. The DLL stays mapped and all
// its hooks/threads keep running; only its visibility in the loader lists is removed. See
// src/memory/module_hide.c.
void HideModuleFromPeb(HMODULE self);
+8
View File
@@ -0,0 +1,8 @@
#ifndef PHOTON_PATCH_H
#define PHOTON_PATCH_H
// Injects the operator's Photon Cloud app IDs into the AppSettings the client hands to Photon at
// connect time (the self-hosted server can't supply real Photon Cloud app IDs). Build 2025-04-29.
void PatchPhotonAppId(void);
#endif
+9
View File
@@ -0,0 +1,9 @@
#pragma once
// Detours UnityEngine.Application.Quit() / Quit(int) to log the managed caller and (optionally)
// suppress the shutdown. See src/unity/quit_trace.c.
void PatchQuitTrace(void);
// Call-through hooks on kernel32!ExitProcess / !TerminateProcess that log who tore the process
// down. Installed by PatchQuitTrace; separate entry point so it can be used standalone.
void HookProcessExit(void);
+38
View File
@@ -0,0 +1,38 @@
#ifndef RETSPOOF_H
#define RETSPOOF_H
#include "common.h"
//
// Return-address spoofing for il2cpp utility calls (il2cpp_string_new, il2cpp_object_new, Uri..ctor,
// get_Uri/set_Uri, ...). These are called from inside our hooks (http_rewrite.c, photon_patch.c), which
// means the CPU pushes a return address inside redirector.dll before jumping to them -- so for the
// duration of that call, any stack walk over the calling thread sees an unrecognized module
// (redirector.dll) on the stack. This is exactly the kind of signal an anti-tamper stack-walk check
// looks for (see the ~30s freeze/crash: GameAssembly.dll rewrites hooked methods into stack-exhaustion
// poison once it detects us -- memory note unstable-build-identity-rvas.md).
//
// SpoofCall4 calls a function with the return address the CPU sees replaced by a "jmp qword ptr [rbx]"
// gadget scanned out of the target's own module, so the call looks -- from the stack's perspective --
// like it originated from inside that module instead of from us. See retspoof.asm for the mechanics.
//
// Scans [mod's image] for a usable gadget and caches it. Safe to call once GameAssembly's code has
// decrypted (this build's packer XOR-decrypts .text shortly after mapping -- see CLAUDE.md). Logs the
// found address, or logs a failure and leaves spoofing unavailable (SpoofCall4 falls back to a direct
// call in that case). Returns 1 on success, 0 otherwise.
int InitRetSpoof(HMODULE mod);
// True once InitRetSpoof has found a usable gadget.
int RetSpoofReady(void);
// Waits for GameAssembly.dll to appear and its code to decrypt, then resolves the gadget. Intended to
// run on its own thread (mirrors the WaitForCode pattern in ssl_patch.c / http_rewrite.c), started
// early so the gadget is ready well before real traffic starts flowing through the hooks that use it.
DWORD WINAPI PatchRetSpoof(LPVOID param);
// Calls target(a1, a2, a3, a4) with a spoofed return address (see above). Pad unused trailing
// arguments with 0. Falls back to a direct call if no gadget has been resolved yet.
uint64_t SpoofCall4(void *target, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4);
#endif