Defeating Denuvo from Ring -1

18 min read ·

A few months ago I wrote about BadUpdate, a software-only hypervisor exploit on the Xbox 360. It exploits an encrypted memory side channel to race the LZX decompressor and write forged data into hypervisor memory. The whole model rested on one assumption - that encrypted memory was unobservable from usermode. BadUpdate proved it wrong by turning the hypervisor's own update-processing path into an encryption oracle.

By early 2026 a similar architectural move had matured on PC, this time against Denuvo. Denuvo's runtime performance cost varies by title - undetectable in some, measurable in others - and the launches where it lands hardest tend to be the ones least able to absorb it. Watch Dogs: Legion is the case I keep coming back to: shipped with Denuvo in 2020 alongside a poor DX12 implementation, ran horrifically on hardware that exceeded its recommended specs, reception never recovered. Publishers routinely strip Denuvo from older releases once the launch window passes, and removal benchmarks across the current hypervisor-bypass wave show measurable FPS, VRAM, and memory footprint improvements on specific titles. Shipping protection that degrades the experience for paying customers, on a title that needs every advantage it can get, is a poor call.

Denuvo#

Denuvo is anti-tamper DRM owned by Irdeto, deployed on most AAA PC releases over the past decade. Its job is to make cracking expensive enough that publishers extract most of their revenue from the launch window before a working pirate copy exists. The protection works through code virtualisation - sections of the executable get translated into custom bytecode interpreted by a VM inside the binary - combined with hardware fingerprinting that ties activation to specific machine identifiers, and integrity checks that catch modifications to the executable.

Traditional cracking is slow. Removing Denuvo means reversing the VM, understanding the bytecode, identifying and patching every check, and avoiding integrity verification. Months of work per title. The first full traditional crack of Resident Evil: Requiem (released 27th February 2026) shipped 40 days after launch - the first 2026 Denuvo title to fall to a traditional crack at all. Forty days is fast. Forty days is also outside the launch window where most of the revenue lands.

The hypervisor bypass shipped within hours of the same game's release.

Ring -1#

Intel shipped the first VT-x silicon on 14th November 2005, in the Pentium 4 Model 662 and Model 672. AMD shipped AMD-V the following year. Both add a hardware execution mode that sits structurally below the four traditional privilege rings. Intel calls this VMX root mode; the hypervisor runs there. The operating system, including its kernel at ring 0, runs in VMX non-root mode.

Transitions between the two are called VM entry (root to non-root) and VM exit (non-root to root). A per-VCPU data structure called the VMCS - Virtual Machine Control Structure - manages the transitions and configures, among other things, which guest instructions cause a VM exit. The bitmap is granular: you can configure CPUID to trap, RDTSC to trap, specific MSR reads to trap, I/O port accesses to trap. Each configured instruction in the guest causes the CPU to save guest state, transfer control to the hypervisor's exit handler, and resume the guest only after the hypervisor returns. The hypervisor can modify register values during the exit. The guest sees those modified values as if they came from the silicon.

This is a hardware trap-and-emulate primitive. It exists for legitimate virtualisation - VMware, Hyper-V, KVM, and the Microsoft Virtualization-Based Security stack all use it. So does HyperDbg, an open-source hypervisor-based debugger. So does the Denuvo bypass.

The consequence: when something runs at VMX root mode, anything in the guest (the kernel, ring 0 drivers, PatchGuard, code integrity verification) is observable and modifiable from below. The guest cannot observe the host. CPUID has bits intended to indicate hypervisor presence, but those bits are set by software - a hypervisor that wants to hide can clear them. A hypervisor that wants to lie about CPU brand or timestamp counter values can return whatever it wants on the corresponding VM exits.

The hardware does not enforce truth about the hardware. This is the whole game.

The Bypass#

The technique has a discrete origin. In December 2025, the scene group MKDEV released a proof-of-concept hypervisor bypass against Persona 5 Royal, alongside documentation describing the architecture. Per coverage of the release, MKDEV claimed about two days of work and called the approach "10,000 times easier" than reversing Denuvo's VM directly. By early 2026, refined releases - principally from Kirigiri - had productized the technique into day-zero releases against Borderlands 4, Crimson Desert, and Resident Evil: Requiem. Other scene figures like 0xZeOn and sagerao applied the same approach to further titles.

The bypass loads a custom hypervisor under Windows at boot, configures it to trap every CPU instruction Denuvo uses to fingerprint the system, and returns forged values matching a pre-generated license token. No VM obfuscation reversal, no binary patching, no integrity check tampering. From inside Denuvo's threat model, the host is a normal PC that happens to own a copy of the game.

For this to work, the hypervisor has to load. That's where the cost shows up.

Removing the Windows Hypervisor#

Modern Windows defends ring 0 with several layers wrapped under the umbrella of Virtualization-Based Security. PatchGuard verifies critical kernel structures at random intervals and bugchecks the system if it detects tampering. Driver Signature Enforcement (DSE) prevents the kernel from loading drivers that aren't signed by Microsoft or a trusted certificate authority. Hypervisor-Enforced Code Integrity (HVCI) runs code integrity checks in a Hyper-V partition that ring 0 can't reach. Credential Guard isolates authentication data. HyperGuard backs PatchGuard itself with VBS protection.

Most of these depend on the Windows hypervisor, which boots before the kernel using hardware virtualization extensions - VT-x on Intel, AMD-V on AMD. PatchGuard predates VBS and runs in the kernel on its own; the rest live above the hypervisor. The Windows hypervisor takes VMX root mode for itself and runs the kernel as a guest. VBS components live in a higher Virtual Trust Level (VTL1) the normal kernel can't reach.

A self-signed kernel driver from a scene release does not load on this configuration, and not because of DSE alone. The deeper architectural problem is that two bare-metal hypervisors cannot stack on x86 in the way the bypass needs. Nested virtualization exists - Hyper-V VMs can run their own Hyper-V instances - but Microsoft restricts it to its own hypervisor in Hyper-V guest configurations. The Windows hypervisor does not pass the hardware virtualization extensions through to its guest OS. Any third-party hypervisor that wants direct VT-x or AMD-V access has to replace the Windows hypervisor entirely. VMware Workstation hit this wall in 15.5.5 (May 2020) and added a User-Level Monitor mode that routes through the Windows Hypervisor Platform API instead of taking silicon when Hyper-V is active.

The Denuvo bypass faces the same constraint. It cannot run alongside the Windows hypervisor, only instead of it.

The user-side preparation reflects that. Scene releases ship a script (often named VBS.cmd) that toggles registry keys and bcdedit settings to disable VBS, HVCI, Credential Guard, Hyper-V, and the boot-time hypervisor launch (bcdedit /set hypervisorlaunchtype off). The user reboots and presses F7 at the Windows boot menu to disable DSE for that session - Microsoft's own boot-time option, intended for driver developers. With VBS torn down and DSE disabled for the boot, the bypass driver loads, claims VMX root mode for itself, and the game starts. After the session, running the script again reverts the changes and a reboot returns the system to its protected state.

EfiGuard by Mattiwatti is an alternative path that some users prefer. It's a UEFI bootkit that patches bootmgfw.efi, winload.efi and ntoskrnl.exe before ExitBootServices() is called, neutralising PatchGuard and DSE at boot. More surgical than the script-and-F7 approach, but only against PatchGuard and DSE. With HVCI enabled, EfiGuard's patches accomplish nothing useful: integrity checking moves to the secure kernel, and EfiGuard's SetVariable runtime DSE toggle bugchecks Windows on use. Users still have to disable VBS through Windows settings before a hypervisor bypass driver will load - which is most of the work.

A third path lives inside the bypass DLL itself. Independent reverse engineering of the KIRIGIRI release shows it can disable DSE at runtime by calling NtSetSystemEnvironmentValueEx with a magic value (0xDEADC0DE) that tunnels through a UEFI runtime variable into kernel memory. This is the same SetVariable backdoor technique EfiGuard exposes, invoked from usermode by the crack rather than from a UEFI bootkit. Either path produces the same result: g_CiEnabled flipped, unsigned drivers load.

Either way, after the disable step, Windows will load any unsigned ring 0 driver, and the silicon's virtualization extensions are available for the bypass to claim.

The DLL Hijack#

Once Windows is willing to load the driver, the bypass needs to bootstrap from inside the game process. The technique is a DLL proxy hijack against amd_ags_x64.dll, the AMD GPU Services SDK that AAA games statically import for GPU feature queries.

The release ships a patched proxy version of amd_ags_x64.dll that gets dropped into the game's directory, replacing the real one. The genuine AMD SDK is renamed to amd_ags_x64.org and shipped alongside it. At launch, the Windows loader walks the executable's PE imports, sees amd_ags_x64.dll listed, and resolves it to the patched proxy. The proxy's DllMain does two things: loads the original amd_ags_x64.org so the game's GPU calls still work, and pulls in the bypass DLL.

The proxy declares the bypass DLL as a static PE import. The Windows loader resolves it during normal import resolution - no LoadLibrary call from scene code, no thread injection, no shellcode trampoline. The bypass loads because the loader follows the standard rules for a DLL it has no reason to suspect. From an anti-cheat perspective, the only signal is a static import on a DLL the game already loads.

Many AAA titles ship amd_ags_x64.dll as a static PE import - particularly those with AMD partner integrations - which makes the same proxy viable across multiple games without per-game work. AMD also distributes AGS as a static library that links into the executable directly, in which case there is no separate DLL on disk and the proxy hijack doesn't apply.

Backend Selection#

The first runtime decision inside the bypass DLL is which hypervisor backend to load. The DLL reads the CPU vendor string via CPUID leaf 0 and dispatches based on the result:

Vendor stringDriver loaded
AuthenticAMDSimpleSvm.sys
GenuineIntelhyperkd.sys

These are different implementations because Intel VT-x and AMD-V are different ISAs. They have similar capabilities but different control structures - VMCS on Intel, VMCB on AMD - different instructions for entering and exiting virtualisation mode, and different MSRs to configure. A bypass that wants to support both vendors has to ship two backends.

The selected driver registers as a Windows service via CreateServiceW under the name denuvo_kirigiri and starts. Hardware virtualisation is exclusive: only one root-mode hypervisor can hold VT-x or AMD-V at a time. The bypass logic checks for an existing hypervisor; if Hyper-V or another VMM is still present, it stops it before the chosen driver initialises. With VBS disabled by the earlier preparation step, the slot is free.

HyperDbg#

The Intel-side hypervisor in current bypass releases is built on HyperDbg, an open-source hypervisor-based debugger by Mohammad Sina Karvandi. HyperDbg uses Intel VT-x and Extended Page Tables, and avoids standard Windows debugging APIs by design so that anti-debug protections aimed at conventional debuggers can't see it. Its purpose is to debug software that resists being debugged - malware, packers, anti-cheat engines. The architectural symmetry with the Denuvo case is direct: "invisible to anti-cheat" and "invisible to Denuvo" are the same property, and HyperDbg's design produces both.

The two driver names aren't a single project. hyperkd is HyperDbg's kernel-mode shim; the real VMX engine lives in hyperhv.dll, a modified build of upstream HyperDbg. SimpleSvm is unrelated - an independent AMD-V hypervisor by Satoshi Tanda. HyperDbg's own AMD codebase is RedDbg, which the bypass doesn't use. Both drivers are repackaged under the same Windows service name, denuvo_kirigiri; the Intel primitives come from the upstream debugger, the AMD primitives don't.

HyperDbg has two operating modes. Default mode advertises itself as a hypervisor: CPUID leaf 1 sets the hypervisor present bit, leaf 0x40000000 returns 'HyperDbg' as the vendor string (encoded little-endian as 'epyH gbDr' across EBX and ECX, with EDX zeroed - unlike the 12-byte GenuineIntel/AuthenticAMD form that uses EBX/EDX/ECX), and leaf 0x40000001 returns 'Hv#0' to indicate non-Microsoft. Transparent mode, gated on a flag the source calls g_CheckForFootprints, hides all of this. The hypervisor present bit gets cleared. Leaves in the 0x40000000 range return constant 0x40000000 in all four registers - no usable vendor or interface data. The bypass enables transparent mode and adds a custom handler for the CPU brand string at leaves 0x80000002-0x80000004, which transparent mode itself doesn't touch. That's where DenuvOWO CPU @ 1337 GHz lives. The string is in the bypass author's custom layer, not in upstream HyperDbg.

On initialisation, the driver allocates page-aligned VMXON and VMCS regions, enters VMX root mode via VMXON, and configures the VMCS for Windows as the guest. The default VMCS configuration in HyperDbg is minimal by design: IO bitmaps, MSR bitmaps, EPT, RDTSCP, INVPCID, XSAVES, and VPID. CPUID is intercepted because VMX always traps CPUID. RDTSC exiting is not enabled. The trapping surface is small because every trap is a potential timing-detection signature. Minimum trapping surface, minimum detection surface.

What Gets Spoofed (And What Doesn't)#

The CPUID interception described above handles hypervisor presence and the CPU brand string. License token contents come back from a separate handler the bypass installs, fed by a pre-generated tokens.bin written on first launch (more on this below).

CPUID also serves as a hyper-call channel from usermode. Custom magic-value leaves at 0x69696969, 0x1337, 0x336933, and 0x41414141 register the game's CR3 with the hypervisor, pass the target PID for KUSER_SHARED_DATA spoofing, store per-game configuration, and trigger hypervisor teardown respectively. Using CPUID rather than VMCALL is the quieter choice; guests issue CPUID constantly, while a guest-side VMCALL is rare enough to draw heuristic attention.

RDTSC and RDTSCP detection deserves a separate treatment because the mechanism isn't what most writeups claim. Anti-VM detection issues RDTSC before and after a known-fast operation, then checks the delta - VM-EXIT latency leaks through as elevated cycle counts. The standard countermove is to hook RDTSC and return values that don't reveal the exit overhead.

HyperDbg doesn't do that. Its default VMCS leaves RDTSC exiting disabled, which means RDTSC and RDTSCP run natively at hardware speed inside the guest. There is no VM-EXIT, no latency, nothing to detect. The mechanism is "don't trap the instruction" rather than "trap it and lie about elapsed cycles." If RDTSC trapping is enabled (for explicit tracing commands), the emulation is a straight __rdtsc() passthrough, with proper TSC offsetting noted as a future improvement blocked by PatchGuard interactions.

The timing-spoofing layer the bypass adds sits one level higher than the instruction. Independent reverse engineering of the bypass shows a kernel thread named CounterUpdater that runs in a loop and writes spoofed tick count and timestamp values into KUSER_SHARED_DATA, the page Windows maps at 0x7FFE0000 containing system time, tick count, and various OS state. Most Windows API timing calls (GetTickCount, QueryPerformanceCounter and friends) read from KUSER_SHARED_DATA rather than issuing RDTSC themselves, so spoofing the page spoofs all of them. The instruction stays a passthrough at the silicon. The page above it lies.

MSR spoofing in HyperDbg's transparent mode is inactive. The handler functions exist (TransparentCheckAndModifyMsrRead/Write) but return without touching register values. Injecting #GP on the reserved hypervisor MSR range crashes Windows on Meteor Lake processors, because the OS expects synthetic timer MSRs to function, so the obvious approach is closed in the upstream code.

The bypass stacks the brand string spoofing in the hypervisor, IAT hooks and pre-generated license tokens in usermode, the proxy DLL chain, and the Goldberg Steam integration on top of HyperDbg. The hypervisor is upstream; the rest is what the scene wrote.

The load-bearing observation: every CPUID query in the validation chain is a CPU instruction the guest believes goes to silicon. The guest has no mechanism to verify that the CPU it's talking to is the real CPU. The hardware that exists for legitimate virtualisation does not give the guest a way to escape virtualisation, by design - that would defeat the point of running a hypervisor in the first place.

Tokens and Goldberg#

Denuvo's license validation isn't only hardware fingerprinting. There's also a license token - a cryptographic blob that binds an activation to a specific machine and an authenticated Steam account. The token has to come from somewhere.

The bypass DLL ships two pre-generated tokens, one per supported hypervisor backend. On first launch, the bypass writes the appropriate token to a .bin file in the game directory. Delivery runs through usermode hooking rather than through the hypervisor: the bypass creates writable shadow copies of ntdll.dll, kernel32.dll, kernelbase.dll and user32.dll, patches the Import Address Tables in those copies, and replaces the entries in the PEB's three module lists so loader-walking detection sees the shadow copies, not the originals. Selected calls from the game then route through bypass DLL handlers before reaching the real implementations. The hook on CreateFileW redirects opens of Denuvo's license file to the fake .bin. Denuvo reads what looks like a valid license blob and proceeds. The scene pre-generated both tokens against a known set of forged hardware values - the same forged values the hypervisor returns on CPUID queries - so validation across the file-side and the hardware-side checks lines up by construction.

The IAT hooks also catch other checks that bottom out in user-mode APIs the hypervisor doesn't intercept (because the call doesn't translate to a trapping CPU instruction). The bypass needs that second layer of interception in usermode for those.

The token-to-environment binding is the part of the bypass that's fragile. If Irdeto rotates the cryptographic scheme used for token generation in a future Denuvo version, every existing pre-generated token becomes invalid overnight, and the scene has to regenerate them. A soft kill, not a hard one. The technique still works, but every release needs new tokens.

The Steam side is handled separately by a fork of the Goldberg emulator (GBE), which replaces steam_api64.dll with a proxy that emulates the Steam platform interface locally. Goldberg is unrelated to Denuvo. It's been around for years and exists for legitimate reasons - offline play, LAN multiplayer for older games, modding - but it's a necessary component because Denuvo's license validation cross-references with Steam ticket APIs, and those have to return something coherent.