Cracking the uncrackables – Reverse engineering – Supercell – part 7

C

From 0 knowledge to 0.1 knowledge and a lucky win on the new protections shipped on Brawl Stars.

Hello folks!!!! Welcome to the, damn…, 7th chapter of this awesome journey. The subtitle already gives a hint about the topic, whose in the Supercell reversing / gaming scene may know that:

In the late 2018

Supercell released the new game Brawl Stars, protected with a new unknown compiler from an unknown company. Our mobile team at Overwolf got an application which runs on top of reverse engineered Brawl Stars api in the meantime official api from Supercell see the lights. After about 20 hours of intense debugging and instrumentation it got cracked, with luck and 0.1 knowledge of what the compiler does in the deep, but still… cracked.

Again… let’s get back

and start from scratch. We know Supercell used to use the same protections (Arxan) on all the games. We know ways to attach to the process and that we can run arbitrary code with some problems, but at least, we can. We open the library with a decompiler… and we know we can kill everything we knew.

The library is now “packed” with what, at me, looks more like a malware packer. We have no way to attach a debugger to the process. We have no way to get code execution inside the library unless we step the behavior and find spots to do so.

So… let’s find those damn spots

Or yeah… we can do usual waste-of-time stuffs like checking the apk and notice that there is one file more in the assets with encrypted binary data or keep watching those subs on IDA without understanding 0 of what it is doing.

The decompiler is just not useful at all but instead, figuring out a way to get code execution into the process and dump the real library would be the first step…

For achieve this and crack whatever is this named, I used:

  • frida
  • brain
  • modded Android kernel + ftrace + strace + kprobes later to partially understand the behavior

Crack of contents:

  1. Control the spawn
  2. Stepping until the crash
  3. Control the spawn of the second process
  4. Prevent the third process to ptrace the game process
  5. Prevent the main process to do anything it does further

What i will say later, could be true or not.. most of the following things are supposition because I’m sure only about the stuffs which prevent me to gain code execution, I still have no idea how the decryption of the library happens in example.

Control the spawn

Frida can spawn the process and inject the agent just after the process got forked. This is impossible to prevent for any company which develop security tools to protect apps from cracking unless a modification of the system happens either through root or kernel.

Once the game is started we know that after about a second it quit itself and frida dies.

Stepping until the crash

We then want to step through initializations functions of the game library. (The segment code is visible on IDA).

We will discover later that everything die in the first function of initialization array and the address of the function is held by a register.

At this point we are totally blind because this address point to a dynamically mapped region.

I’m really sorry that I don’t have any of the “debug” code I used but instead I only have the “final crack” code to show.

Those are some Q&A i made to my self and how i figured out the reply:

  • What’s happening?
  • Something is mapping something around the globe and the code jumps there
  • Where are those maps happenings?
  • There is no mem map calls around…. inline syscalls?
  • How do we know?
  • Let’s search on IDA for SVC or… let’s scan the library space in runtime and check for the ARM instruction SVC, attach to any of them and see what happens
  • We hit them… it’s mapping memory, writing code there and jumping there… what’s this code?
  • After dumping that memory region we are still in a “phase” before everything dies and we can still control everything, so… It’s doing something… (we don’t know what and it wasn’t necessary to know what. We can assume it’s decrypting the library and writing into it’s own space or whatever). What was necessary to know is that this things happens in a loop. This code will do something, map another region, write code and jumps there and so on, all of this using inline syscalls. How I know this is simple, I scanned and attached all SVC syscalls found in any new mapped memory region after it got written. All of this can be easily achieved by using strace or kernel ftrace, but I was still unsure about a lot of stuffs.
  • So… when does the crash happens?
  • The crash… which is not a crash.. happens because the first process, after doing all the stuffs it’s meant to do (which we don’t really know), use syscall system with (“am start –user 0 -f 0x10200000 -a android.intent.action.MAIN -c android.intent.category.HOME”) followed by syscall exit group (which logically stop the pid and frida attached to that pid).

Control the spawn of the second process

I said at the beginning this is a lucky win because, I have no idea how and why this works, eventually, the company which developed this will fix it and will makes me going deeper on understanding why this worked or why not, will send me an email with more details!

The final crack came out by testing things in the purpose to understand how the app react to my changes. We can say that the crack is coming from a bug of the compiler? I don’t know. Let’s go again with the Q&A

  • What we do now?
  • We have tons of way to follow… but the first one that pop on my mind is trying to attach to that second process. So.. let’s setup something on the local machine, which receive a “ping” from the device when the syscall system is used. Let’s start a brutal loop of “adb shell ps | grep brawlstars” until the vfork() in the system syscall is invoked and try to attach to the new process. Also, when the ping happens, let’s detach frida from the first process.
  • It partially worked. The stuffs are not going to die anymore but we can’t attach to the other process because there is, WTF, a third process which is PTRACEing it.
  • If we attach to the process which is ptracing, everything die. We can go through kernel and hijack ptrace or eventually, figuring out a way to delay the second process startup execution.

I went the second way to complete the crack with frida code. Delaying the startup of the game process can be done in 2 ways showed in the crack code:

Interceptor.attach(Module.findExportByName('libc.so', 'execve'), function () {
                        console.log('[+] done. starting stage 2');
                        Memory.writeUtf8String(Memory.readPointer(this.context.r1.add(8)),
                            //'am start --user 0 -f 0x10200000 -a android.intent.action.MAIN -c android.intent.category.HOME -D'
                            'fuck -!-'
                        )
                    });

execve, which is happening inside system syscall, can be replaced either with trash data, or by adding -D flag (debug) to the am start command.

As I said.. I can understand the execution delay caming from the -D, but adding trash data will makes the game process spawn the exact way as adding -D (some fallback happenings later maybe.. I didn’t checked but what’s sure is that this gave us a little time window to attach with frida to the game process before the spawn of the ptracer process happens.

The code injected in the second process (the game one) is as simple as:

console.log('[+] stage 2 cracked. injecting into ' + Process.id);
var m = Process.findModuleByName('libg.so');
var base = m.base;

// do the stuffs we want to do... it's lifetime code execution block here

while (true) {
    send('keepalive');
    Thread.sleep(1 / 100000);
}

Once again… If we doesn’t sleep the main thread and keep the connection with our frida server active, for some reason everything dies. One of this reason could be that the ptracer try to attach on an already traced process, it fail and send a sigusr1/2 to notify the other process, still… everything can get a reply by stracing or to be totally stealth in the program space, by ftracing in the kernel space. But… I have code execution and the game library is decrypted in place so finally I can get my public server key and check if the encryption is changed.

Did you forget about Arxan?

Once code execution is achieved, there is still Arxan to fight as usual… was quite expected, so I’ve figured out an abstract solution which was working on all of the games before the update that happened today, have no clue if this is still working or not. The logic is once again quite simple, I’m basically bruteforcing the memory for leftovers. This means, the moment the login is sent, the public client key is appended in the first 32 bytes of the message. We can scan all the ranges for those 32 bytes to find structures holding this key and read previous and further bytes to match later. This way, you can get public server key and client private key to achieve proxy and decryption/encryption of packets. (gisted here)

Conclusion

I think I gave enough logic for people which are really intended to go deeper and achieve a crack with a similar approach. Thanks for the bounty and…. se y’all the next episode!

About the author

GiovanniRocca

Add comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

By GiovanniRocca