In this paper I’m going to speak about what I’ve did to reverse engineer the new encryption, using Clash of Clans as base (The logic used to break CoC could be replicated as well on Boom Beach and HayDay that are already shipped with the new encryption).
I’ll skip whatever that’s already known (the previous encryption), as it can be read and understood here: https://github.com/clugh/cocdp/wiki/Protocol (SuperCell shares the same protocol on all the games) and I’m going to focus on what really changes on this new encryption and how the crack has been done and figured out.
The goal, of course, was to break the encryption. Meanings, beeing able to decrypt and encrypt both server and client side to understand and reproduce payloads. You can find the patcher and the proxy server on github.
- Unicorn emulator
- Python for the patcher
- Node JS for the proxy server
- ARM (ARM / Thumb) Assembly to reverse and understand the logic
What’s changed in details and why:
The old encryption was cracked by patching the public server key, that’s hardcoded in the libg and that’s used to feed blake2b update, to build the nonce, and crypto_box beforenm to build the shared key used later to encrypt/decrypt the payload.
First static analysis shown clearly that both the methods (blake2b update and cryptobox / cryptobox_open) was hard modified. Most of the edits done was only on purpose to obfuscate the whole encryption/decryption logic. What really metter can be found on top of what now are 2 big subroutines wrapping most of the encryption/decryption logic. The 2 subroutines involved are now generiting a new key during runtime (the function that build the key is reversed as well, and its mostly obfuscated with control flow flattening) but still feeded with the hardcoded public key as evidenced during debugging.
Hardcoded CoC key:
Key built int runtime:
Following, this new key built in runtime is used to actually generate the nonce and to build the shared key with cryptobox_beforenm.
What follow, was dumping that new key, still static, and use it to reproduce and encrypt valid requests but of course that wasn’t enough to decrypt what the client send, why?
To build the login message, the official client, build the shared key from the private key generated during encryption logic and that pair with the key generated in runtime. What we know server side is the public key, that’s sent during login and that we can’t use to build the shared key.
So TLDR, patching the hardcoded key as we were used to, will just result in nothing done.
The actual logic
Starting from saying that I’m aware of another very easy method, that has been used by other guys and kept private and that i never ever tought to check since the amount of attention made to keep the things hard, my logic will patch 2 bytes. These 2 offsets has been found by digging in the memory at the entrance of the 2 involed sodium encryption and decryption subs (crypto_box and crypto_box_open). The library is loadint into R11 a struct holding our keys (public and private + the public server key) In more simple words, what the patch is doing, is replacing what’s used to build the shared key and instead of using the private key (that we don’t like because we don’t have on our server side) is using the hardcoded server key, that’s the pointer in memory just after the private one. This way, the shared key would be static and used always for any server proxy encryption and decryption.
- Note please, if you read how the proxy server has been modded to made it work, you have noticed that is now using a “magic key” as shared key. This magic key was originally built in runtime while i was playing and messing with some values over there. Its basically the result of a beforenm with the 32 bytes key (the 0x72 one) that we were used to patch and the new server public key that’s build in runtime, that we already know.
This explain why my patcher is still patching the hardcoded public server key with the 0x72 one (that is now mandatory to let the client generate the magic key as well)
The question that will problably jump on the mind is why I’m still patching the hardcoded key. Simple. The 0x72 was patched inside the client during my works and the first magic key that was built was using it. I see no reason to skip patching 32 bytes that will make life harder to baby leechers. Let’s see that magic key as a little signature from me.
The runtime key generation
Some screenshots while working
More to come…
If you are interested in the function that pack the new public server key, you can find my java port here.