To combat masses of video game hackers, anti cheat systems need to collect and process a lot of information from clients. This is usually usually done by sending everything to the servers for further analysis, which allows the attackers to circumvent these systems through interesting means, one of them being hijack of the communication routine.
If an anti cheat is trying to detect a certain cheat by, for example, the name of the process that hosts the cheat code, it will usually parse the entire process list and send it to the server. This way of outsourcing the processing prevents cheaters from reverse engineering the blacklisted process names, as all they can see is that the *entire* process list is sent to the anti cheat server. This is actually becoming more and more prevalent in the anti cheat community, which raises some serious privacy concerns, simply due to the sheer amount of information being sent to a foreign server.
BattlEye, one of the world’s most installed anti cheats, uses such a routine to send data to their master server over UDP. This function is usually referred to as battleye::send or battleye::report (as in my previous articles). It takes two parameters: buffer and size. Every single piece of information sent to the BattlEye servers is passed through this function, making it very lucrative for hackers to intercept, possibly circumventing every single protection as the game can’t report the anomalies if a hacker is the middleman of communcations. Few cheat developers are actively using this method, as most of them lack the technical skills to reverse engineer and deobfuscate the dynamically streamed modules that BattlEye heavily relies on, but in this post i will shed some light on how this communication routine is being actively exploited, and how BattlEye has tried to mitigate it.
Abuse
BattlEye’s communication routine resides in the module BEClient, which is loaded dynamically by the protected game process. This routine takes “packets” with a two byte header and varying content length, encrypts and afterwards transmits them to the BattlEye server over UDP. Detection routines such as the timing detection or the single stepper rely on this communication, as the results of these tests are sent unfiltered to the BattlEye server for processing. What would happen if you were to hook this function, and simply modify the raw data being sent to prevent the server from banning you? Absolutely nothing, for many years up until last year, when BattlEye added integrity checks of battleye::report to the obfuscated module BEClient2. This integrity check is described in the following section.
Mitigation
It seems to have been desperate times for the BattlEye developers, as they discovered the prevalence of battleye::report hooking in private, highly exclusive cheats in games such as Rainbow Six: Siege and PUBG. Ideally, you would run complete integrity checks of the respective module to ensure fair play, but BattlEye has a very long history of low effort, band-aid fixes, and this is no exception. If you are willing to spend hours on end to devirtualize the module BEClient2, you will notice this integrity check:
*(std::uint64_t*)&report_table[0x3A] = battleye::report;
if ( *(std::uint32_t*)(battleye::report + 5) == 0xCCCCCCCC &&
*(std::uint32_t*)(battleye::report + 0x1506CA) == 0xFFF3BF25 &&
(*(std::uint32_t*)(battleye::report + 1) != 0x1506C9 || *((std::uint8_t*)battleye::report + 0x1506CE) != 0x68) )
{
report_table[0x43] = 1;
}
Instead of doing a full integrity check by, for example running hash comparisons of the module’s code, they’ve decided to simply test certain portions of the function and set a boolean if the conditions are met. If we ignore the poor attempt at integrity checks for a second, how is report_table transmitted to BattlEye’s servers?
battleye::report(report_table, sizeof(report_table)); return;
Holy shit! Not only are the integrity checks very primitive, it relies on its own integrity! This means that any hacker that is already hooking the communication routine and triggering the integrity check, can control the result of the same exact check *facepalm*. This is actually really trivial to bypass:
Circumvention
As you can see from the integrity check, the result is stored in the report data array + 0x43. With a hook, this can be trivially bypassed as nothing stops you from setting the integrity check result to fit the bill:
void battleye_report_hook(const std::uint8_t* buffer, const std::size_t size)
{
// ? BECLIENT2 PACKET ?
if (buffer[1] == PACKET_BECLIENT2) // 0x39
{
// SET INTEGRITY CHECK RESULT
buffer[0x43] = true;
}
// SEND THE INFORMATION TO BATTLEYE SERVERS
original_fn(buffer, size);
}
Exercise for the reader: the buffer is actually encrypted with a simple xor key (literally the tick count of when the packet was generated, it’s stored in plaintext at (std::uint32_t)report_table[0x1FC] 🙂
26 thoughts on “BattlEye communication hook”
Good stuff, probably some be dev already reading and patching this though.
We’re doing their jobs for them. They’re welcome. This is why companies should interview for the job, not just accept anyone with a CS degree. You get stuff like this – this is a problem that’s trivial to avoid.
Surprises me how much these companies fancy the curriculum guys 🙂 never seen one worth shit
@mickwsecurity, CS degree is about learning the theory behind it. I don’t understand why some people have to talk shit about anyone with a CS degree when they make a mistake or do a bad job.
So you think are so smart and people will clap for you ?
thanks idiot now it will be patched ,
The OP openly welcomes them to do so. This is a sport to them and the entire point is competition; statements like “most of them lack the technical skills to reverse engineer and deobfuscate the dynamically streamed modules that BattlEye heavily relies on” and “poor attempt at integrity checks” should not be read as pure arrogance but statements of fact.
Also let’s not forget that this individual is a ‘bored high school student’; immaturity should be assumed.
“Also let’s not forget that this individual is a ‘bored high school student’; immaturity should be assumed”
Little kid tries to get some attention
you explain my feelings
Ad-hominem then calling someone immature. The irony.
It’s unfortunate that it will get patched because all those freeloading p2c owners will suffer. /s
More stuff should be released. Wash out the weak minded and freeloaders. Of course, the statements can be seen as arrogant, but let’s not forget that the founder of BE (Bastian Suter) is the king of copy-pasting from well known sources to slap together an otherwise shitty anti-cheat. He makes money off of it, and should be viewed as a complete dimwit if he thinks any of these protection schemes are production ready. The anti-cheat works for catching large sites, and private cheats that were glued together with Github and prayers. The AC engineering team (if there even is one) should be degraded so they either step it up or move the hell out of the way for a better solution to take its place.
A lot of it can be disappointment as well. These are pathetic attempts at integrity checks by a company that calls themselves the “Gold Standard.” They should be called out for their bad engineering.
His tone could be changed to be more objective, but those statements still hold some truth to them. This anti-cheat is piss poor comparatively.
Screw you
Good.
Found a user, or developer of private cheats! 😉
This isn’t medium you retard, no one claps here
GOOD I HOPE IT GETS PATCHED U CHEATERS NEED TOBE STOPPED YOU RUIN THE GAME FOR US NORMAL AND GOOD PLAYERS AND HAVE NO LIFE YOU JUST SIT IN YOUR BASEMENT EATING MACARONI AND CHEESE WITH CUT UP HOT DOGS GET A LIVE LOOSERS
Ahahahahahaha Douggem 😀
OMFG even cuckold infiSTAR is here. Still running backdoors in your Arma anticheat, m8 ?
It’s crazy how every anti-cheat developer makes the same mistakes over time. I remember PunkBuster having the same issues with self-reporting back in the Call of Duty 1 era.
The thing is often that they are not stupid, just lazy.
the would have to write a new func on client and server side i guess, which would proab cost alot effort and hours in their puzzle system
I was giving battle eye my penis in it’s eye, and now you’re telling them to get protective goggles. lame
Except that the code you posted doesn’t even work.
https://gist.github.com/Sen66/87d206c5050f0ad1316bd8d4a93e7570#file-beclient2-cpp-L722
I actually went and checked the code. Encryption is not just simple linear XOR as you claimed based on this leak posted on a popular site. Yet the entire point of this post is that just because you can hook this function you can magically control all of its content with ease. That’s only true if you get the encryption right.
But I get these posts are aimed at bashing Battleeye, so why do I even mention this?
Your snippet has not been deobfuscated correctly 🙂
nice shares 🙂 create a donation button so people can support your work.
I have updated my gist with the latest code. I probably won’t deobfuscate it more but you are right. The packet is encrypted multiple times, everything but the timestamp and 2 bytes(last 6 bytes) at the end and the packet header(first 2 bytes) gets encrypted.
The first pass starts here: https://gist.github.com/Sen66/87d206c5050f0ad1316bd8d4a93e7570#file-beclient2-cpp-L467
v50 = PACKET->TimeStamp ^ a1 ^ 0x378E5979;
it takes the first argument(RCX) passed onto the module, xor’d with a static DWORD and the timestamp. It encrypts DWORDs of the packet, the key changes after every encrypted DWORD:
v50 *= ~v50;
Some crap instructions are below, I haven’t figured out if they are also used actually. Then begins pass 2 at line 516, again taking first argument passed and this time it encrypts everythign, how it encrypts the packet starts at line 576, now it encrypts BYTEs. It might be missing some instructions as I heavily modified the the instructions to generate a nice-looking pseudocode
any one have the sig of SendPacket(Or battleye::report)??
be的dll文件签名检测也很傻,直接本地修改内存就可以pass,你应该也写篇文章
Hopefully a little bit about the PUBG call API aimbot Or the aimbot. Relevant information.