APEX: PROTON INTERNAL CHEAT: DRAUSEHOOK [RECAP]
I have previously been accused of being bad at picking names for my projects and honestly speaking, it’s kind of a hard truth. Today we will be dealing with something much more purposeful than worrying about names and how they’re perceived; We’ll talk about DrauseHook, an internal trainer i wrote. The only reason it exists is because i was very curious about how cheating was happening on the linux/proton platform. I also found it fascinating how Apex decided to approach anti-cheat on their proton build.
I haven’t put much effort into actual analysis of the Easy Anti-Cheat proton build, but what i did do was analysis of respawn’s in-house solution: R5AC. It is active to this day and receives an occasional update here and there. You should only use this project as a reference, at best. I didn’t have any quality assurance or code quality standards in place, because it was one of those projects you’d write while simulatenously learning about a new platform, and how to maneuver through it.
Certain parts of my DrauseHook code base have been very rough to retrospectively read, even to me – the original developer of this thing. So yeah, that already says a lot in my opinion. I still believe that it contains a lot of useful things, and some of it certainly applies to this day. Although i’d think probably not much at all, considering how often Apex Legends receives updates. I’d like to retrospectively take a peek at some code i wrote, design choices i made, and what i think of these nowdays.
1. Evasion of VMTP Swapping detection (R5AC)
Back when i still had worked on this project, R5AC had a shallow VMTP verification routine. All it cared about was that the VMTP was within the bounds of Apex’s main module within the LDR: r5apex.exe. If you would just allocate a new VMT and replace the VMTP of game interfaces/object instances, then this routine would get you reported to the game-server you were playing on.
My idea back then was to place it within .data, which sounds very stupid but it actually stopped R5AC to send out that violation packet for VMTP anomaly. I verified this by reversing R5AC networking, and figuring out a way to capture any telemetry packets that are sent. In earlier versions you’d have an encrypted C-String as a packet identifier, however they changed this to just be a quad (DWORD_PTR) so basically a numeric ID instead of a human readable one.
// Prefix:CS_, Meaning: Call Stack (stack walker anomaly)
0x20e9f0 CS_CEngineClient::Engine_SetViewAngles
0x26c540 CS_CNetChan::SetTimeout
0x26dfe0 CS_CNetChan::SendReliableMessages
// Prefix:VTP_, Virtual Method Table Pointer (VMTP) anomaly
0x22ee01 VTP_GetEngineTraceClient
0x22f271 VTP_GetEngineTraceClientDecals
0x479351 VTP_GetFilesystemInterface
It was highly debated whether accumulating any violations within R5AC would lead to account bans, and we ultimately don’t know how respawn handles all these violations/R5AC telemetry events when their backend processes them. We can’t do anything besides speculating here, which is a bad thing to do in this field. Which is why i decided to stop it there. Here is what R5AC does in 2026:
R5::AntiCheat::AddMessageToQueue(0x56C4AB4B2B73896Di64, 2, (__int64)v0, (__int64)v51);
R5::AntiCheat::AddMessageToQueue(0xFED07854FD9A8ED3ui64, 1, v55, (__int64)v217);
Notice how they started using numeric values (quad) to identify their telemetry packets? Yeah, no more auto-resolving critical game functions with the help of R5AC.
I would rate this as a pretty lazy approach, looking at it today. Which makes sense because again, my goal was to get something functional that wouldn’t lead to any obvious anomalies. Something interesting i witnessed during early R5AC days was, that despite other developers on certain cheating forums have complained of quick bans when triggering any violations within R5AC, for one of my accounts it took several months until they banned it.
Don’t get me wrong here, i’m not trying to say that i was undetectable, quite the opposite actually: I already knew from local debugging that my game client has sent the telemetry packet for VMTP anomaly. There is no way to coherently cope here, you’ll just have to accept reality sometimes. This happened during the season where R5AC was initially rolled out. So they could have tightened up their thresholds by now.
2. Leveraging R5AC pointer to a stack tracing API
If you have read my other posts or labs page, then you’ll already know that R5AC likes to build a stack trace, in order to then walk it + try find something that it views as odd. In earlier versions it made use of RtlCaptureStackBackTrace. It stored a reference to said API in a .data pointer within r5apex_dx12.exe. When i saw that it had no integrity check, like a simple bounds check or something more sophisticated, i immediately swapped it and used it to aid my dynamic analysis on the game.
Later i had the dubious idea to check who is the caller when something invokes this .data pointer, and i saw through actual execution logs + xrefs in static analysis that it was called from a lot of significant places. Including candidates like:
- C_Input::CreateMove: Used by cheaters to manipulate player commands.
- CalcMovementCompensation: The core of gamepad aim assist, often targeted by cheaters who seek a gameplay advantage while already playing on a controller.
This one actually allowed me to ditch one of my conventional hooks, which led me to write this piece of code:
unsigned short __fastcall hk_capture_stack_back_trace(unsigned long frames_to_skip, unsigned long frames_to_capture, void **trace, void *trace_hash)
{
auto retaddr = BASE_OF(_ReturnAddress()) - gctx->game_base;
if (retaddr == gctx->offsets.ret_addrs.create_move)
{
g.inputs.cmove.tick_begin();
features::on_anonymizer();
if (is_valid_session())
{
auto me = local_player();
if (me.is_alive())
{
// ...
features::on_movement(me);
features::on_trigger(me);
}
}
g.inputs.cmove.tick_end();
}
return 0; /* no traces are available */
}
I think that this was a solid strategy for the time being, however it could be neutralized by 1 single check from the anti-cheat.
3. Playlist Overrides System
DrauseHook also had functionality to mess with Apex’s playlist variable system. It placed a single hook for this:
g.vmts.client_state_vft3.place(HOOK_IDX_PROCESS_PLAYLIST_OVERRIDE, tp_process_playlist_override);
ghk_orig_process_playlist_override = g.vmts.client_state_vft3.original<uptr>(HOOK_IDX_PROCESS_PLAYLIST_OVERRIDE);
After ensuring that control flow is transferred to our hook handler during a server-triggered playlist variable override, we could parse the given parameters and get a C-Style String buffer with the raw playlist file. This file contained all playlist variables which controlled some key subsystems like gamepad aim assist, visuals (build watermark, etc) and similar stuff. I have primarily used this hook in order to mess with gamepad aim assist. I found the default one too weak for my skills, which is why i went with increasing it:
void __fastcall hk_process_playlist_override(void* inst, void* net_msg)
{
#if !defined(PROD)
char* data = reinterpret_cast<char*>(BASE_OF(net_msg) + 0x21);
respawn::hijack_playlist_data(data);
#endif
}
bool hijack_playlist_data(void *data)
{
#if !defined(PROD)
if (g.conf.override_controller_aa)
{
auto pdata = data;
auto pstr_assist_adspull_disable = find_substr((char *)pdata, "aimassist_adspull_disabled");
if (pstr_assist_adspull_disable)
{
auto toggle = find_substr(pstr_assist_adspull_disable, "1");
if (!toggle)
{
toggle = find_substr(pstr_assist_adspull_disable, "0");
}
if (toggle)
{
if (toggle[0] == '1')
{
toggle[0] = '0';
}
}
}
auto pstr_assist_magnet_pc = find_substr((char *)pdata, "aimassist_magnet_pc");
if (pstr_assist_magnet_pc)
{
auto value = find_substr(pstr_assist_magnet_pc, "0.");
if (value)
{
if (g.conf.override_controller_aa_value == 100)
{
value[0] = '1';
value[1] = '.';
value[2] = '0';
} else if(g.conf.override_controller_aa_value > 0)
{
value[0] = '0';
value[1] = '.';
int adjusted = g.conf.override_controller_aa_value / 10;
value[2] = tools::integer2char(adjusted);
}
}
}
}
#endif
return true;
}
3. Miscellaneous
I would say the rest of DrauseHook is pretty much rushed to an extent. My primary goal was to become more familiar with cheats on the proton/linux platform, not to fabricate a polished pay-cheat. I pretty much suck at understanding some source engine concepts, especially anything regarding deeper game physics and networking. However, this hasn’t prevented me from writing functional cheats and reasoning about many parts of Apex. I spent a lot of time verifying assumptions, but there’s so many things i still don’t know. I personally think that game security is a constantly evolving branch, and that is why it gets exhausting at times. It’s completely fine to take a break, reconsider your approach and spend time sharpening your skills. I always feared being stuck with having little to no technological advancement within my own life.
I’m very happy that it hasn’t ended like that, but at the same time, i feel like i have reached a skill ceiling after many years of fun. I personally think that i wouldn’t be able to compete with modern anti-cheat nowadays. My energy is also way too low to educate myself further on this topic, and i honestly find that cheating in video games is quite boring, especially if you consider the operational costs on modern anti-cheats. I can only speak for myself here and i’m aware that it might be easy to maintain for some people, all i can say to that is everybody’s got their own levels of capability.