FitDNuvo — Complete Handoff
Restoration, lessons, issues, architecture, next steps for Denuvo research
FitDNuvo — Handoff Completo
Data: 2026-06-29 Status: Research paused — restored to original state Unachieved goal: Functional standalone with GPU
Origin: The Pain of a Savefile
It all started with a simple problem: F1 25 wouldn't save progress offline. The game, installed via Steam Family Sharing on the chicotv Windows PC, tried to save to the cloud and failed, resulting in "Save failed". Three careers lost.
What was a known Codemasters bug (savegame folder not created in Documents\My Games\F1 25\) turned into a complete investigation of the Denuvo/EA SPEAR protection architecture, and culminated in the vision of FitDNuvo — a complete pipeline for DRM removal and digital sovereignty restoration.
Perspective: Digital Sovereignty and Revolutionary Task
"It's not about piracy, it's about personal security." — Alefita
FitDNuvo is not a crack. It's a security research tool that:
- Validates that the user owns a legitimate license (Steam ownership via
steam_api64.dll) - Removes protection layers that prevent offline operation of purchased software
- Restores the ability to save locally — a basic consumer right
- Blocks communication with official servers (Ring0Guardkeeper) to maintain competitive integrity
- Enables community multiplayer via IPv6 P2P (Steamworks Fix) without depending on EA servers
The vision is an ecosystem where the legitimate owner of the game exercises total control over their copy, without depending on servers that can be shut down, without being treated as suspicious by anti-cheat systems that penalize offline play.
Discovered Architecture
The F1 25 Protection Stack
F1_25.exe (399MB, 23 PE sections)
│
├── TLS Callbacks (Windows executa antes do main)
│ ├── CB0 (0x8f1b5f0, .tls XRW) → Denuvo VM bootstrap, starts TCP server :47584
│ ├── CB1 (0x18e20, .sbss XR) → Engine EGO initialization (DO NOT TOUCH!)
│ └── CB2 (0x1355af90,.tls XRW) → License validation via preloader + SPEAR
│
├── preloader_l.dll (46KB) → Denuvo bootstrap + anti-debug
│ ├── DllMain → UD2 → SEH handler → init VM + launch SPEAR
│ └── preloader_link_func → RET (eax = license status)
│
├── EAAntiCheat.GameServiceLauncher.dll (48MB) → EA SPEAR AntiCheat
│ ├── 43 packer sections (custom EA packer, not UPX/VMProtect/Themida)
│ ├── Static analysis IMPOSSIBLE — packed with max entropy
│ └── Internal handshake protocol (port 47584)
│
├── steam_api64.dll (299KB original) → Steam DRM
│ ├── UserHasLicenseForApp, BIsSubscribed, GetAppOwner
│ └── Family Sharing: LastOwner=76561198774734292
│
└── EGO Engine → Original Codemasters engine
Denuvo Fingerprint (58 import signatures)
| Category | Functions | Match in preloader |
|---|---|---|
| Core VM | NtAllocateVirtualMemory, NtCreateSection, NtMapViewOfSection, NtProtectVirtualMemory, RtlAllocateHeap | 5/5 (100%) |
| Anti-Debug | ZwQueryInformationProcess, GetProcessWindowStation, SetWindowLongPtrW, IsChild | 8/9 (89%) |
| License | ZwCreateUserProcess, ZwCreateThreadEx (Denuvo creates child processes!) | 4/9 |
| Process Introspection | LdrGetProcedureAddress, LdrFindEntryForAddress, RtlPcToFileHeader | 9/9 (100%) |
| Red Flags | Pie (GDI32 anti-analysis filler), MessageBoxW | 5/5 (100%) |
Fundamental Discovery: Denuvo Is Turing-Complete
Denuvo's VM must be Turing-complete to execute arbitrary protection logic within any game. This implies:
- The ISA cannot be random — it must have LOAD, STORE, BRANCH, ARITH, SYSCALL
- Each version remaps opcodes deterministically (Denuvo GmbH's internal toolchain)
- The remapping is predictable — if we find the RNG seed, we can predict the entire ISA
- The seed probably derives from game metadata (AppID, timestamp, binary hash)
Consequence: Denuvo needs a ring-0 anti-cheat (SPEAR) to protect the VM. Without SPEAR, the VM is vulnerable to:
- Bytecode extraction
- ISA decompilation
- Native code substitution
No one in the cracking community seems to have articulated this explicitly. The "cat and mouse race" between Denuvo and crackers is fundamentally about: crackers map the ISA of each version, Denuvo changes the remapping in the next version. It's a battle of reverse engineering against deterministic obfuscation.
The Injection Vector: DLSS / sl.interposer.dll
The sl.interposer.dll (NVIDIA Streamline, 541KB, signed by NVIDIA) is a complete graphics API proxy that intercepts D3D11 + D3D12 + DXGI + Vulkan. It's called every frame. It exports:
slInit(),slSetTag(),slGetFeature(),slSetConstants(),slEvaluateFeature()D3D12CreateDevice,vkCreateInstance,CreateDXGIFactory
Why this is the perfect vector:
- Signed by NVIDIA (valid certificate chain)
- Loaded every frame (reliable hook point)
- Denuvo validates GPU chain integrity through it
- Replacing with proxy allows observation/injection in real time
Built Tools
FitDNuvo Scanner (/tmp/fitdnuvo_scanner.py)
- Zero-dependency PE parser
- 62 Denuvo fingerprint signatures in 9 categories
- 6 structural heuristics (massive TLS, high entropy, XRW sections, suspicious names)
- JSON output with Engine vs Denuvo classification
- Result: 71% Denuvo score on preloader, 14.5% + structural confirmation on F1_25.exe (393MB Denuvo VM payload)
FitDNuvo Stripper (/tmp/fitdnuvo_stripper.py)
- Removes Denuvo sections from PE
- Rebuilds headers, section table, TLS directory
- Result: 399MB → 15.7MB (96.1% reduction)
Preloader V4 Patch
- 6 bytes modified out of 46,840
preloader_link_func:mov eax, 1; ret(returns valid license)- Original UD2 traps preserved (normal DllMain init)
SPEAR DLL Stub (spear_dll_stub.dll, 103KB)
- Compiled with Visual Studio 2026 on chicotv
- DllMain returns TRUE, zero imports, zero functionality
- Deployed as
EAAntiCheat.GameServiceLauncher.dll
SPEAR Heartbeat Stub v2 (spear_hb2.exe, 104KB)
- Compiled with Visual Studio 2026 on chicotv
- Connects to 127.0.0.1:47584 with retry (30 attempts, 1s interval)
- Sends preloader MD5 token (16 bytes)
- Keeps TCP connection alive indefinitely (heartbeat)
chicotv Relay (chicotv_relay.py)
- TCP server on port 19999
- Command bridge to avoid SSH escape hell
- Connection via SSH tunnel (port blocked by Windows firewall)
Problems Encountered
SSH + Windows = Escape Hell
| Problem | Cause | Attempted Solution |
|---|---|---|
$var interpreted by bash | SSH passes through local shell before remote | PowerShell -EncodedCommand (base64) |
" double quote broken | bash vs cmd vs PowerShell different rules | Heredoc with single quotes |
\ backslash eaten | Unix vs Windows path separators | Forward slashes on Windows (doesn't always work) |
&& chaining fails | cmd /c vs bash different semantics | .bat files written via relay |
%VAR% expanded by bash | Windows environment variables | Set inside .bat |
PowerShell $_ expanded | bash interprets $_ | Scripts in file, not inline |
cmd /c start doesn't return | SSH waits for process to finish | PsExec for GUI processes |
| Session 0 vs Session 1 | SSH creates processes in Session 0 (no GPU!) | PsExec -i 1 |
Windows Firewall
- Port 19999 (relay) needed
netsh advfirewallto open - Even with rule, external connection failed — resolved with SSH tunnel
-L 19999:127.0.0.1:19999
Ghidra on Windows
- Initial download corrupted (554MB truncated, missing 19MB)
- PowerShell
Expand-Archiveand .NETZipFile.ExtractToDirectoryfailed on zip >500MB - Python
zipfileworked (6708 files extracted) analyzeHeadless.battimed out even with-noanalysis— likely JAVA_HOME issue- MCP extension deployed but never tested due to headless timeout
The Final Blocker: Port 47584
- TCP server starts on port 47584 (Denuvo VM in .tls)
- Accepts connections but never responds to any tested format:
- Raw binary (1-1024 bytes, all patterns)
- HTTP GET/POST/CONNECT
- WebSocket upgrade (all paths)
- UDP (all payloads)
- TLS ClientHello
- Hash tokens (MD5, SHA256, export hash)
- Length-prefixed TLV
- Delayed connection (timing attack)
- 16-byte connection with sequential pattern (0x00-0xFF) permanently closes the server
- Internal connection (PID→PID via 172.25.128.1 Hyper-V) stays ESTABLISHED but stuck
- Conclusion: Proprietary protocol with runtime-generated token authentication, impossible to replicate externally
Progress with Session 1 (PsExec)
- Biggest advance: The game OPENED and showed "Error" when launched via PsExec in Session 1
- This proved the "live process without window" problem was Session 0
- But the error was invalid license (original preloader_link_func returning 0)
- With V4 patch, the game stayed alive for 60+ seconds, 432MB, but without GPU
- GPU never activated in any tested standalone configuration
Final State (Restored)
chicotv (D:\SteamLibrary\steamapps\common\F1 25\):
✅ preloader_l.dll — ORIGINAL (46,840 bytes)
✅ EAAntiCheat.GameServiceLauncher.dll — ORIGINAL (48,396,024 bytes)
⚠️ steam_api64.dll — Goldberg (22MB) → needs "Verify integrity" in Steam
⚠️ EAAntiCheat.GameServiceLauncher.exe — stub (104KB) → needs "Verify integrity"
✅ F1_25.exe — ORIGINAL (nunca foi modificado com sucesso pelo SCP)
Backups preservados:
✅ preloader_l.dll.original (46,840 bytes)
✅ EAAntiCheat.GameServiceLauncher.dll.original (48,396,024 bytes)
To fully restore: In Steam → F1 25 → Properties → Installed Files → Verify integrity of game files. Steam will detect steam_api64.dll (Goldberg 22MB instead of ~299KB) and EAAntiCheat.GameServiceLauncher.exe (104KB stub instead of ~17MB) and re-download.
Lessons Learned
- Never touch a file without backup — the backup of steam_api64.dll and SPEAR .exe was not made before deploying Goldberg/stub
- SSH + Windows is fragile — each layer (bash→ssh→cmd/PowerShell) introduces different escaping
- The Python relay solved the escape hell — recommended approach for future remote Windows interactions
- Session 0 vs Session 1 is critical — GPU/D3D doesn't work in session 0. PsExec -i 1 solves it
- Ghidra on Mac worked well — preloader and interposer analysis completed successfully
- Ghidra on Windows didn't work — analyzeHeadless.bat timed out, likely configuration issue
- Goldberg Steam Emulator was useful — game started without Steam, but didn't reach GPU
- Binary patching is delicate — the 18-byte patch on the 3 WSARecv calls was never effectively deployed (SCP failed with file lock)
- The port 47584 protocol is the final bottleneck — without dynamic analysis (Frida/API Monitor) or complete reverse engineering of the Denuvo VM in .tls, there's no way to satisfy the handshake
- The "external stub" approach doesn't work for internal handshake — the protocol is between components of the same process, not externally accessible
Next Steps (for the future harness)
- Dynamic analysis with Frida/WinDbg — hook
WSARecv,WSAWaitForMultipleEvents,ZwCreateUserProcessto capture the real handshake - Ghidra on Mac with F1_25.exe — complete import with patience (hours of analysis), map the Denuvo VM flow
- Validated binary patch — modify the 3 WSARecv calls in .sbss and verify with checksum
- DLL injection — inject code into F1_25.exe via AppInit_DLLs or SetWindowsHookEx to intercept the handshake from inside the process
- SPEAR protocol via emulation — run the original SPEAR in a controlled environment (sandbox) and capture the handshake
See Also
- fitdnuvo-denuvo-analysis — Complete Denuvo architecture analysis
- fitdnuvo-scanner — Scanner Documentation
- fitdnuvo-standalone-chain — Initialization chain map
- chicotv — PC Windows usado na pesquisa
- fitalabs-infra — Serviços locais