Note: This analysis was performed approximately two months ago when I was significantly less experienced, particularly in cryptographic analysis, and even in how EAC communicates. Because of this, information may be slightly wrong. This post is partly from assumptions and static analysis as the AES logic is mostly unobfuscated and I didn't confirm the AES logic dynamically, hence the IOCTL codes and buffers. EasyAntiCheat rotates their encryption keys frequently as part of their update cycle, so some specifics may have changed in current builds. Diagrams are hosted on Imgur - if you're in the UK you may need a VPN to view them.
Over the past few days, I went deep into EasyAntiCheat's kernel driver to understand how their cryptographic protocol actually works. What started as basic kernel analysis turned into a full breakdown of their AES-256-CBC + ECDSA-P256 implementation and extracting keys from obfuscated code.
Before I go further, I need to mention something about EAC's actual detection capabilities. I have not even reversed half the driver yet, and I have already found things that genuinely blew me away. There is one function I will not detail here because it deserves its own dedicated writeup given how deep it goes, but it essentially builds a graph of all reachable code paths so if execution ever happens outside of that reachable set, they can flag it. Think along the lines of a custom CFI implementation, but the way they have done it is different enough from standard kCFG that it warrants a full breakdown on its own. I have been doing this for a while now and that is something I never even thought of before. It is actually innovative. To anyone just starting out in this space, please understand that their detection surface is massive. They have hundreds of things you will not think of for the next couple years, no exaggeration.
This is also part one of a bigger series I am working on. My end goal is to build a complete EAC emulator, not to bypass it, but to understand the system inside and out. I have spent months on this already but most of that has been theory. The emulator is a tool for me to learn, to make it easier to find vulnerabilities over time, and to really test how deep EAC's detection surface goes. People hype up EAC as this untouchable thing and honestly? It kind of is. But I want to see for myself just how difficult it really is, and document the whole process along the way. Part two is coming.
I am not trying to inflate EAC's reputation or look good for them. It is just genuinely well built, and there are several other functions I found that I am saving for future posts because they deserve their own writeups.
Analyzing the Cryptographic System
Starting with static analysis in IDA Pro, I traced the IOCTL handler to function sub_14007224C which processes incoming messages. The code showed a clear two-stage validation: message deserialization and structure validation, then cryptographic signature verification.
The signature verification function sub_140070ED8 was using Windows BCrypt APIs, but every single parameter was obfuscated. Algorithm names, key material, all of it encrypted to prevent static extraction. The function clearly took a message buffer, size, and signature as parameters, then performed hash computation and ECDSA verification.
Decrypting Obfuscated Strings
EAC uses custom PRNG-based XOR encryption to hide all their cryptographic algorithm names and parameters. Each string is encrypted with a different seed and algorithm variant, so I had to reverse multiple decryption functions one by one.
The SHA256 algorithm name was stored as 8 encrypted bytes at address 0x1401A9160. The decryption used a specific xorshift variant with seed -1125228787. Running the decryption gave me "SHA256" in UTF-16LE encoding.
The ECDSA algorithm name was 16 bytes at 0x1401A9120, encrypted with an LCG using seed -1721366523 and a byteswap operation. This decrypted to "ECDSA_P256", confirming they are on the P-256 elliptic curve.
Public Key Extraction
The ECDSA public key was stored at 0x1401A9170 as 72 encrypted bytes in BCRYPT_ECDSA_PUBLIC_BLOB format. The encryption used another xorshift variant with seed -551316663. After decryption I got the complete public key with magic value 0x45435331 ("ECS1"), key size 32 bytes for P-256, and both X and Y coordinates of the public point.
This public key is what the driver uses to verify signatures from usermode, proving that usermode has not been hooked or tampered with. The driver has the public key, usermode has the private key, so only legitimate usermode components can produce valid signatures.
Symmetric Encryption Discovery
The actual message decryption happens in function sub_1400715C8. This uses AES in CBC mode, again with everything obfuscated through custom PRNG encryption.
The algorithm name "AES" was 8 bytes at 0x1401A90F8, encrypted with an LCG using seed 1611801797 and the formula (-1189061 - 24595 * seed).
The 256-bit AES key itself was 32 bytes at 0x1401A90B8. This used an xorshift variant with seed -1479485563 (unsigned 0xA7F4CE05). After working through the decryption I pulled the complete key:
6a2e87818307d0021f34eaa7e3d61a48759862e85871e95d783c54180c1da3fb
The chaining mode was 32 bytes at 0x1401A9098, encrypted with yet another xorshift variant. Decrypted to "ChainingModeCBC", confirming CBC mode.
Additional properties at 0x1401A9038 and 0x1401A9058 provided the IV parameter name and other BCrypt config values, all following the same obfuscation pattern.
Complete Protocol Architecture
Here is the full cryptographic protocol as it emerged from analysis:
Encryption Layer:
- Algorithm: AES-256-CBC
- 256-bit key extracted from driver binary
- IV passed with each message
- Standard BCrypt implementation
Authentication Layer:
- Algorithm: ECDSA with P-256 curve
- Public key embedded in driver
- Signature verification on all messages
- SHA256 hash before signing
Message Flow:
- Usermode encrypts message with AES-256-CBC using hardcoded key
- Usermode computes SHA256 hash of ciphertext
- Usermode signs hash with ECDSA private key
- Complete message structure sent via IOCTL
- Driver verifies ECDSA signature using public key
- If signature valid, driver decrypts with AES-256-CBC
- Driver processes plaintext message
Signature verification ensures usermode has not been hooked. Encryption handles confidentiality. It is a solid two-layer setup.
One thing to clarify: this crypto protocol applies specifically to the usermode-to-kernel communication path, where usermode sends commands and the kernel verifies they are legitimate. Driver enumeration data flows the other direction, kernel-to-usermode, and appears to use a separate mechanism entirely. These are two distinct communication channels with different security models, not one unified system.
The Private Key Question
Obvious question: if the ECDSA private key lives in usermode, can someone just extract it and sign their own packets, completely defeating this? Yes, absolutely. This is not to say it is simple and easy, but if you are asking a yes or no question then there is your answer.
Certificate Thumbprint Collection
During analysis, I found that EAC collects extensive system fingerprinting data. The driver systematically enumerates every Microsoft-signed driver loaded on the system, collecting SHA1 certificate thumbprints, GPU vendor information, and certificate chain details.
I wrote a PowerShell script to enumerate all running drivers and check their code signing certificates. Hash 3B77DB29AC72AA6B5880ECB2ED5EC1EC6601D847 matched literally every single Microsoft-signed driver on my system. ACPI.sys, disk.sys, Ntfs.sys, Tcpip.sys, all the core Windows kernel drivers. Every one signed with certificate subject "CN=Microsoft Windows, O=Microsoft Corporation" and the same thumbprint.
EAC is not collecting random certificate info but rather systematically going through every Microsoft-signed driver loaded on the system and collecting a detailed system fingerprint including GPU vendor, certificates, and full driver inventory, then sending all of it to Epic's servers with every heartbeat.
The Anti-Spoofing Technique
This is where everything clicked. It is about catching stub drivers pretending to be EasyAntiCheat. An attacker creates a fake EAC driver that responds to game queries, but it needs to handle IOCTL requests from the game and servers. The fake driver has to respond with believable data to not get flagged, and if it does not return proper system information, it is done.
EAC collects a detailed system fingerprint including GPU vendor, certificates, and full driver inventory, then sends all of it to Epic's servers with every heartbeat. Servers validate that the fingerprint matches the expected baseline for that Windows version, so any discrepancy points to emulation.
The count fields in the packet structure track stuff like total Microsoft-signed drivers loaded, total third-party drivers, certificate counts. A legitimate Windows 11 22H2 system has something like 87 Microsoft-signed drivers. If a fake driver reports only 30, or reports drivers that do not exist for that Windows version, the mismatch is obvious server-side.
The technique validates that EAC is running in a real environment, not that the environment is free of cheats. It is anti-tampering and anti-spoofing, making sure that what claims to be EasyAntiCheat actually is EasyAntiCheat.
What's Next
This is part one. The crypto reversal gives me the foundation, but there is way more to cover. Usermode protection, private key extraction, the emulator itself, and some of those functions I mentioned earlier that I am saving for dedicated posts. The code reachability graph thing alone deserves its own writeup because it genuinely changed how I think about detection design.
I have been at this for months and I have only reversed about 19% of the driver's functions which is barely any progress compared to how much the driver truly contains. That should tell you something about the scale of what EAC has built. Every time I think I have mapped out the important stuff, I find another layer that makes me rethink things.
If you are working in anti-cheat or security research, feel free to reach out. Always down to talk about this stuff, whether it is offensive or defensive.