07. August 2022
I actually worked on this quite a while ago. I was playing this game Escape From Tarkov for a few months. But I primarily use Linux and this is a Windows only game. Therefore, I did what every linux user does: spin up a virtual machine. I installed windows and the game. I passed through my beefy host GPU (and my linux host remained to use the intel integrated GPU). This whole setup is called PCI passthrough via OVMF.
I started the game, joined an offline game to see if it works. Everything was fine. The performance was good too. I stepped things up and joined an online game. After exactly 3 minutes I was forced out of the game with a message that a hypervisor was present. Shortly after that I got permanently banned from the game (Even though I likely violated their terms by trying to run the game in a virtual machine, I am not amused).
Then I heard about this guy... He shared his guide to beating VALORANT's shady anticheat. I knew he was up to something! I wanted to achieve what this man has achieved: obscuring the presence of a hypervisor to play Windows games in your virtual machine on linux!
After fiddling around with my vm configuration I figured that was not enough. I kept googling and I found out that the anticheat (Battleye) was also utilizing different technologies to detect the presence of a hypervisor. To be more precise, it uses RDTSC timing checks. Basically there's an instruction that gives you a cpu timestamp. The timestamp is usually passed down the virtual machine. Now, there are a few tricks to force a vmexit, specifically by invoking KVM handlers. vmexit is called, when the virtual machine was intercepted and some KVM logic takes over. Eventually it returns to the virtual machine execution with vmenter. Here comes the trick: by measuring RDTSC before and after triggering vmexit, you can find out if a hypervisor is present or not.
What I needed was to offset the RDTSC timing values by the time passed between vmexit and vmenter. Luckily some people have already done that. This patch alone wasn't the holy grail for a Battleye VM detection bypass. After sleepless hours and days spent into looking at possible clues for a hypervisor detection I found some nasty things in arch/x86/kvm/cpuid.c. The cpuid instruction is intercepted by KVM and it handles some custom leafs (which will give away the presence of a hypervisor easily). More information about that is available here.
I did a wild attempt to just comment out these handlers in the switch statement, recompiled my kernel and tried entering an online game again. The relief after 5 minutes without getting kicked out of the game was insane. I made it. I bypassed Battleye virtual machine detection on my own.
This journey taught me how to compile my own linux kernel. I learned a lot about the intrinsics of the KVM. It's been very informative and fun to attempt beating an anti cheat company, and I challenge everyone out there to try the same.
You can check out my slides deck for pleasant memes and some more details.