Security News

Cybersecurity news aggregator

🪟
HIGH Vulnerabilities Reddit r/netsec

I reversed Tower of Fantasy's kernel anti-cheat driver while waiting for the game to install. It's a full BYOVD toolkit that's never even loaded.

A security researcher discovered that Tower of Fantasy installs a vulnerable kernel anti-cheat driver (GameDriverX64.sys) that acts as a full Bring Your Own Vulnerable Driver (BYOVD) toolkit, even though it's never loaded. The driver lacks obfuscation and proper authentication, making it an easily exploitable attack surface for malicious actors to gain elevated privileges. The lack of obfuscation is likely due to Hypervisor-Protected Code Integrity (HVCI) preventing the use of VMProtect. There is no information about affected versions, fixed versions, or workarounds.
Read Full Article →

This all started because I wanted to delete my Tower of Fantasy account from over 4 years ago. For the life of me, I couldn’t find a way to do it without having the game installed. There was no web portal and no obvious support route. Eventually I gave up and decided to just download it. Tower of Fantasy is over 100 GB so it would be a long install. I already knew the game shipped with an anti-cheat driver from past experience, so while the download crawled along I started poking around the launcher directory. That’s when I noticed GameDriverX64.sys . Note Kernel drivers run with the highest privileges on your machine. Anti-cheat drivers use this power to protect games from cheaters, but when they’re poorly written, attackers can abuse that same power against you. I opened the driver in IDA expecting a wall of virtualized code, probably VMProtect. Instead I got clean, readable functions with no obfuscation or virtualization at all. By now, the install was at 9%. I had time to dig in. Important There’s a lot of noise online about kernel anti-cheats being “spyware” or inherently privacy-invasive. Most of it misidentifies the actual risk. A usermode game client can already steal your browser cookies, log keystrokes, and exfiltrate files without ever touching the kernel. The real concern with kernel anti-cheats isn’t surveillance, it’s that they are security-critical code running at the highest privilege level. When they’re poorly written, they become attack surface, and when they fail, they can take your entire system down with them (e.g the CrowdStrike incident). For a thorough, level-headed breakdown of the privacy and security tradeoffs, I’d recommend this post by Bevan Philip . Why Isn’t This Obfuscated? The previous version of this driver ( KSophon_x64.sys ) was VMProtect’d to hell, so I was curious why they’d strip protection from a security-critical kernel component. The reason is due to HVCI. Definition HVCI (Hypervisor-Protected Code Integrity) is a Windows security feature that uses Hyper-V to enforce code integrity above the NT kernel, enabled by default on clean Windows 11 installs. The key constraint: W^X (Write XOR Execute) enforcement means code pages can’t be both writable and executable. VMProtect’s packing and import protection both violate this, so the driver fails integrity checks on HVCI-enabled systems. VMProtect can still work under HVCI if you stick to mutation and virtualization macros while avoiding the features that break W^X. They could still protect their imports manually and virtualize the bulk of their code. For some reason, they did neither. Even with VMProtect, these vulnerabilities would still exist. The IOCTLs still do what they do and the authentication is essentially nonexistent. Obfuscation makes reversing harder, not impossible. What the Driver Actually Does It registers the following device: 1 Device Name: \Device\HtAntiCheatDriver 2 Symbolic Link: \\.\HtAntiCheatDriver Device Access Control The IRP_MJ_CREATE handler checks whether the calling process has loaded one of three specific DLLs: 1 NTSTATUS CreateHandler (PDEVICE_OBJECT DeviceObject , PIRP Irp ) { 2 NTSTATUS Status = STATUS_UNSUCCESSFUL; 3 4 if ( VerifyRequiredModules ()) 5 Status = STATUS_SUCCESS; 6 7 Irp->IoStatus.Status = Status; 8 Irp->IoStatus.Information = 0 ; 9 10 IoCompleteRequest (Irp, IO_NO_INCREMENT); 11 12 return Status; 13 } 14 15 BOOLEAN VerifyRequiredModules () { 16 UNICODE_STRING QmGUI4, QmGUI, GameUI{}; 17 18 RtlInitUnicodeString ( & QmGUI4, L "QmGUI4.dll" ); 19 RtlInitUnicodeString ( & QmGUI, L "QmGUI.dll" ); 20 RtlInitUnicodeString ( & GameUI, L "gameuirender.dll" ); 21 22 // Get the PEB of the calling process 23 PEPROCESS Process = IoGetCurrentProcess (); 24 PPEB Peb = PsGetProcessPeb (Process); 25 PPEB_LDR_DATA Ldr = Peb->Ldr; 26 27 PLIST_ENTRY Head = & Ldr->InLoadOrderModuleList; 28 PLIST_ENTRY Entry = Head->Flink; 29 30 // Walk the loaded module list and check each DLL name 31 while (Entry != Head) { 32 PLDR_DATA_TABLE_ENTRY Module = CONTAINING_RECORD (Entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); 33 34 if ( RtlCompareUnicodeString ( & Module->BaseDllName, & QmGUI4, TRUE ) == 0 || 35 RtlCompareUnicodeString ( & Module->BaseDllName, & QmGUI, TRUE ) == 0 || 36 RtlCompareUnicodeString ( & Module->BaseDllName, & GameUI, TRUE ) == 0 ) { 37 return TRUE ; 38 } 39 40 Entry = Entry->Flink; 41 } 42 43 return FALSE ; 44 } In practice, you don’t even need the real DLLs. The check only looks at module names in your PEB, so you can rename literally any DLL to QmGUI.dll for example and load it. My PoC just copies version.dll from System32 and renames it. Handle Protection The driver registers ObRegisterCallbacks to intercept handle operations on protected processes and strip dangerous access rights. The callbacks look like this: 1 NTSTATUS ProcessPreCallback (PVOID RegistrationContext , POB_PRE_OPERATION_INFO OperationInfo ) { 2 PEPROCESS TargetProcess = OperationInfo->Object; 3 PEPROCESS CurrentProce...

Share this article