
Reverse-engineered Easy Anti-Cheat kernel driver bypass that intercepts memory allocation to suppress violation packets, with report decryption routines and analysis notes.
Not my code. Only for saving https://www.unknowncheats.me/forum/anti-cheat-bypass/503052-easy-anti-cheat-kernel-packet-fucker.html
This is the Easy Anti-Cheat Kernel Packet Fucker (for short, EACKPFucker). What is this? Basically, packets via their kernel mode driver are not going to be sent to them, which means your pasta pasta 2023 kdmapper FUD bypasses can be used without any trouble.
Okay, you got me out of my pants. How the fuck does this work? By simply changing one address. Now, let's dive deep into how EAC actually works.
From the beginning, Easy Anti-Cheat has to actually get your data in order to ban you. These packets are sent over their Hydra channel and are cryptographically secure. That is all you need to know for this bypass, I will not go into more detail about this.
Let's take a look at how this works inside their kernel driver, with a random violation:

Doesn't this look vulnerable to you? Because it sure does to me.
Let's take a look at our first function: kalloc_rt
Hmm, okay. Let's jump into alloc_pool_with_tag
It dynamically imports ExAllocatePoolWithTag. Hmmmm... I wonder what would happen if someone were to modify that qword to their modified malloc function... (yeah, it works -- and since you're modifying a writable section, EAC is none the wiser)
Okay, now we have control over memory allocation. Cool! What can we do with this?
I'm glad you asked! Here's the thing: All packets from kernel mode are the size of 33096i64.. aaand previously, we saw that if the memory doesn't get allocated, EAC just.. ignores the violation.
Okay, say someone was to simply.. do this:



// report encryption looks like this (some parts may vary for each report, i believe they use key1, key2, key3 to use only 1 function for decryption)
static report_t* encrypt(uint8_t* data, uint64_t size) {
report_t* packet = (report_t*)malloc(sizeof(report_t));
if (!packet) return nullptr;
uint32_t seed = 0x80BE5ED5 * ((uint64_t)&data >> 2);
memset(&packet->key1, 0, 0x8200);
packet->raw_size = 0;
packet->key1 = 0x66259F86; // gives key4?
packet->key2 = 0x21EBA81; // gives key5?
packet->key3 = 0xACE987AF; // gives key6?
packet->key4 = 0x50BFC583; // gives seed
packet->key5 = 0x3C61A927; // gives dynamic_key (from end)
packet->key6 = 0x70881859; // gives actual payload size
uint8_t* raw_data = packet->raw;
uint64_t raw_size = 24;
uint8_t* payload_data = packet->payload;
uint64_t payload_size = 0;
uint32_t dynamic_key = seed ^ 0x6957FDB6;
while (payload_size < size && payload_size < 0x8000) {
uint32_t a = (dynamic_key << 0xD) ^ dynamic_key;
uint32_t b = (a >> 0x11) ^ a;
uint32_t c = (b << 0x5) ^ b;
uint32_t d = _rotr(c, 2);
uint8_t shift = 8 * (payload_size & 3);
payload_data[payload_size] = data[payload_size] ^ (d >> shift);
dynamic_key = data[payload_size] ^ d;
payload_size++;
raw_size++;
}
uint64_t aligned_size = (raw_size + 0xFF) & ~0xFF; // align up by 0x100
while (raw_size < aligned_size) {
dynamic_key *= 0x80BE5ED5;
raw_data[raw_size++] = dynamic_key;
}
packet->key4 ^= seed;
packet->key5 ^= dynamic_key;
packet->key6 ^= payload_size;
packet->raw_size = raw_size;
return packet;
}
// thus my decryption looks like this
static void decrypt(report_t* packet) {
uint32_t seed = packet->key4 ^ 0x50BFC583;
uint32_t dynamic_key = seed ^ 0x6957FDB6;
//uint32_t dynamic_key = packet->key5 ^ 0x3C61A927;
uint8_t* payload_data = packet->payload;
uint32_t payload_size = packet->key6 ^ 0x70881859;
for (uint32_t i = 0; i < payload_size; i++) {
uint32_t a = (dynamic_key << 0xD) ^ dynamic_key;
uint32_t b = (a >> 0x11) ^ a;
uint32_t c = (b << 0x5) ^ b;
uint32_t d = _rotr(c, 2);
uint8_t shift = 8 * (i & 3);
payload_data[i] ^= (d >> shift);
dynamic_key = payload_data[i] ^ d;
}
}
after dumping some reports and decrypting: