Files
skidfire fe3468c65d yikes,
2026-09-06 19:59:27 -04:00

237 lines
10 KiB
C#

using System;
using System.IO;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using VanadiumGuard.Protocol;
namespace VanadiumGuard.Server.Policy
{
/// <summary>
/// Serves the policy handed to clients at session open, reloading it from disk when the
/// file changes so rules can be adjusted without a deploy.
/// </summary>
public sealed class PolicyProvider
{
private readonly ILogger<PolicyProvider> _logger;
private readonly string _path;
private GuardPolicy _policy;
private DateTime _loadedStamp;
public PolicyProvider(ILogger<PolicyProvider> logger, string policyDirectory)
{
_logger = logger;
_path = Path.Combine(policyDirectory, "policy.json");
_policy = Load();
}
public GuardPolicy Current
{
get
{
ReloadIfChanged();
return _policy;
}
}
private void ReloadIfChanged()
{
try
{
if (!File.Exists(_path))
return;
DateTime stamp = File.GetLastWriteTimeUtc(_path);
if (stamp <= _loadedStamp)
return;
_policy = Load();
}
catch (Exception ex)
{
// Keep serving the last good policy. A malformed edit must not take the
// whole fleet offline.
_logger.LogError(ex, "Policy reload failed; continuing with the previous policy");
}
}
private GuardPolicy Load()
{
if (!File.Exists(_path))
{
_logger.LogWarning("No policy at {Path}; writing the built-in default", _path);
GuardPolicy fallback = Default();
try
{
Directory.CreateDirectory(Path.GetDirectoryName(_path)!);
File.WriteAllText(_path, JsonSerializer.Serialize(
fallback, new JsonSerializerOptions { WriteIndented = true }));
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Could not write the default policy to {Path}", _path);
}
_loadedStamp = DateTime.UtcNow;
return fallback;
}
try
{
GuardPolicy? loaded = JsonSerializer.Deserialize<GuardPolicy>(
File.ReadAllText(_path),
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (loaded == null)
throw new InvalidOperationException("policy.json deserialised to null");
_loadedStamp = File.GetLastWriteTimeUtc(_path);
_logger.LogInformation("Loaded policy version {Version}", loaded.PolicyVersion);
return loaded;
}
catch (Exception ex)
{
_logger.LogError(ex, "Policy at {Path} is invalid; using the built-in default", _path);
_loadedStamp = DateTime.UtcNow;
return Default();
}
}
/// <summary>
/// The shipped starting point.
/// <para>
/// Note what is missing relative to the old hard-coded lists. There is no filesystem
/// scanner, so nothing looks at what a player keeps in Documents or Downloads; that
/// was a large privacy cost for a signal that only ever proved someone had once
/// downloaded a tool. Generic process names are gone. Reverse-engineering tools that
/// developers use every day are present but weighted low, because owning IDA is not
/// evidence of cheating in a match.
/// </para>
/// </summary>
public static GuardPolicy Default()
{
return new GuardPolicy
{
PolicyVersion = "2024.09.05-1",
ReportIntervalMs = 5000,
HeartbeatIntervalMs = 15000,
Detectors = new[]
{
new DetectorSettings { Name = "module", Enabled = true, MaxConfidence = 100 },
new DetectorSettings { Name = "process", Enabled = true, MaxConfidence = 60 },
new DetectorSettings { Name = "window", Enabled = true, MaxConfidence = 60 },
new DetectorSettings { Name = "debugger", Enabled = true, MaxConfidence = 100 },
new DetectorSettings { Name = "nativehook", Enabled = true, MaxConfidence = 100 },
// Starts in observe-only mode. Every player with a second mod installed
// trips this, so it reports for a while and earns its confidence later.
new DetectorSettings { Name = "managedpatch", Enabled = true, MaxConfidence = 0 },
new DetectorSettings { Name = "il2cpp", Enabled = true, MaxConfidence = 100 },
},
ModuleAllowPrefixes = new[]
{
"vanadiumguard", "melonloader", "0harmony", "il2cpp", "unityplayer", "unityengine",
"gameassembly", "baselib", "unitycrashhandler", "recroom", "easyanticheat",
"system.", "microsoft.", "mono", "netstandard", "mscor", "clr", "coreclr",
"hostfxr", "hostpolicy", "ucrtbase", "vcruntime", "msvcp", "msvcr",
"ntdll", "kernel32", "kernelbase", "user32", "gdi32", "win32u", "advapi32",
"sechost", "rpcrt4", "combase", "ole32", "oleaut32", "shell32", "shlwapi",
"psapi", "winhttp", "wininet", "crypt32", "bcrypt", "ncrypt", "schannel",
"iphlpapi", "ws2_32", "mswsock", "dnsapi", "setupapi", "cfgmgr32", "devobj",
"powrprof", "secur32", "sspicli", "userenv", "profapi", "winmm", "dsound",
"xaudio", "x3daudio", "xinput", "dinput", "openal", "physx", "steam",
"nvapi", "nvcuda", "nvoglv", "cudart", "amdxc", "atiadl", "igd", "dxgi",
"d3d", "d3dcompiler", "dcomp", "dwmapi", "uxtheme", "propsys", "version",
"dbghelp", "dbgcore", "symsrv", "textinputframework", "coreuicomponents",
"coremessaging", "windows.storage", "wintypes", "apphelp", "avrt", "wtsapi32",
},
TrustedPathPrefixes = new[]
{
"\\windows\\system32\\",
"\\windows\\winsxs\\",
"\\program files\\",
"\\program files (x86)\\",
"\\steam\\steamapps\\",
"\\dotnet\\",
},
ModuleDenyList = new[]
{
"minhook", "easyhook", "blackbone", "detours", "polyhook",
"scyllahide", "scyllahook",
"frida-agent", "frida-gadget",
"cheatengine", "ceserver", "vehdebug", "speedhack",
"unityexplorer", "monoinjector", "melonsampler",
"vmthook", "internalhack", "externalhack",
},
// Exact image names only. Anything that is also a normal program name has
// been left off entirely rather than matched loosely.
ProcessDenyList = new[]
{
"cheatengine-x86_64.exe", "cheatengine-i386.exe",
"cheatengine-x86_64-SSE4-AVX2.exe", "ceserver.exe",
"x64dbg.exe", "x32dbg.exe", "ollydbg.exe", "immunitydebugger.exe",
"windbg.exe", "windbgx.exe",
"ida.exe", "ida64.exe", "idaq.exe", "idaq64.exe",
"ghidraRun.exe",
"dnSpy.exe", "dnSpy-x86.exe", "dnSpyEx.exe", "ILSpy.exe", "dotPeek64.exe",
"scylla.exe", "scylla_x64.exe", "ScyllaHide.exe",
"frida.exe", "frida-server.exe", "frida-trace.exe",
"artmoney.exe", "squalr.exe",
"HTTPDebuggerUI.exe", "HTTPDebuggerSvc.exe",
"extremeinjector.exe", "processhacker.exe", "SystemInformer.exe",
},
// A class alone never counts; the detector requires a title match to treat
// one of these as more than informational.
WindowClassDenyList = new[]
{
"TFormCheatEngine", "TMemoryBrowser", "Qt5152QWindowIcon", "OLLYDBG",
"ID Window", "WinDbgFrameClass",
},
WindowTitleDenyList = new[]
{
"Cheat Engine", "Memory Viewer", "Add Address Manually",
"x64dbg", "x32dbg", "OllyDbg", "Immunity Debugger",
"IDA -", "Ghidra:", "dnSpy", "ILSpy",
"HTTP Debugger", "Extreme Injector",
},
WatchedExports = new[]
{
new WatchedExport { Module = "ntdll.dll", Export = "NtProtectVirtualMemory" },
new WatchedExport { Module = "ntdll.dll", Export = "NtWriteVirtualMemory" },
new WatchedExport { Module = "ntdll.dll", Export = "NtReadVirtualMemory" },
new WatchedExport { Module = "ntdll.dll", Export = "NtCreateThreadEx" },
new WatchedExport { Module = "ntdll.dll", Export = "NtOpenProcess" },
new WatchedExport { Module = "ntdll.dll", Export = "LdrLoadDll" },
},
// Replace these with the real signatures for your build. They are examples,
// and a target that does not resolve is skipped rather than treated as a fault.
Il2CppTargets = new[]
{
new Il2CppTarget { TypeName = "UnityEngine.Camera", MethodName = "set_fieldOfView", ParameterCount = 1 },
},
AllowedPatchOwners = new[]
{
"vanadiumguard", "vanadium.patch", "melonloader",
},
ChallengeableModules = new[]
{
"GameAssembly.dll", "UnityPlayer.dll", "VanadiumGuard.dll",
},
};
}
}
}