Last December I wrote a blog post explaining how some of the AU2 dance games were protected. Apparently, the protection author read the blog post and updated his protection. 🙂 This blog post will explain how the protection was changed and suggest several ways of dealing with the new features.
This analysis covers:
Please read the original a blog post before continuing. 95% of the protection has not changed, so I'll cover just the new bits.
Additional "encryption" in .NET metadata
There's a very small change in how encryption key is calculated. In the old version, calculation was as follows:
protectedModule->key[0] = heap_tables->reserved0; protectedModule->key[1] = BYTE1(heap_tables->reserved0); protectedModule->key[2] = BYTE2(heap_tables->reserved0); protectedModule->key[3] = HIBYTE(heap_tables->reserved0); ... protectedModule->key[4] = heap_tables->valid[0]; protectedModule->key[8] = heap_tables->valid[4]; protectedModule->key[6] = heap_tables->valid[2]; protectedModule->key[9] = heap_tables->valid[5]; protectedModule->key[11] = heap_tables->valid[7]; protectedModule->key[5] = heap_tables->valid[1]; protectedModule->key[7] = heap_tables->valid[3]; protectedModule->key[10] = heap_tables->valid[6];
New version adds one additional XOR operation:
protectedModule->key[0] = heap_tables->reserved0; protectedModule->key[1] = BYTE1(heap_tables->reserved0); protectedModule->key[2] = BYTE2(heap_tables->reserved0); protectedModule->key[3] = HIBYTE(heap_tables->reserved0); ... heap_tables->valid ^= heap_tables->sorted; // this is new! :) ... protectedModule->key[4] = heap_tables->valid[0]; protectedModule->key[8] = heap_tables->valid[4]; protectedModule->key[6] = heap_tables->valid[2]; protectedModule->key[9] = heap_tables->valid[5]; protectedModule->key[11] = heap_tables->valid[7]; protectedModule->key[5] = heap_tables->valid[1]; protectedModule->key[7] = heap_tables->valid[3]; protectedModule->key[10] = heap_tables->valid[6];
I'm really not sure what's the reason behind the change, as it doesn't add any additional security.
Swapped IL opcodes
The most interesting part of AU2 protection was change of several IL opcodes. In the old version, only 9 trivial opcodes were changed:
AU2 opcode | Real opcode | Mnemonic |
---|---|---|
0x02 | 0x05 | ldarg.3 |
0x03 | 0x04 | ldarg.2 |
0x04 | 0x03 | ldarg.1 |
0x04 | 0x02 | ldarg.0 |
0x05 | 0x08 | ldloc.3 |
0x06 | 0x07 | ldloc.2 |
0x07 | 0x06 | ldloc.1 |
0x08 | 0x05 | ldloc.0 |
0x24 | 0x28 | call |
In the new version, almost 50 opcodes have been changed. That's not an easy thing to analyze and fix.
I came up with 2 possible solutions to the problem.
Solution 1: compare assemblies
This is the quick and dirty way to deal with the problem. Take assembly you just unpacked. Take assembly from another AU2 version where IL opcodes are correct. Then compare the methods which have the same name and parameters.
A picture is worth a thousand words:
On the left you can see Assembly-CSharp.dll with AU2 opcodes, on the right - older version with proper IL opcodes. It's easy to notice that AU2 opcode 0xD6 is actually 0x02 - ldarg.0. Further down you can see that AU2 opcode 0x86 is actually 0x18 - ldc.i4.2. AU2 opcode 0x49 is 0x16, and so on..
Repeat same process for tens or hundreds of methods and you'll have a full list of changes.
Pros:
- You don't need to unpack libmono.so
- It's easy to compare methods, you can do it even with pen and paper
Cons:
- You might miss some opcodes that are not used in your assembly
- You need older unprotected version of the assembly
Solution 2: compare IL handlers
As explained in previous post, libmono.so must support both protected and unprotected assemblies. So, they have 2 implementations of mono_method_to_ri method - one using proper IL opcodes, another using the custom AU2 ones. You can take each AU2 opcode handler, compare it to the proper ones and find which one matches.
Here on the left is handler for AU2 opcode 0x0A. On the right is handler for original opcode 0x15. You can see that they are identical.
Pros:
- You will definitely find all the opcodes
- You don't need an older unprotected version of the assembly
Cons:
- You need unpacked libmono.so
- x86 and ARM compiler sometimes optimizes the code, so the handler disassembly might not match.
- It's much harder to implement. IDA is not really suitable for the purpose, most binary diffing tools compare 2 files against each other, so you'll have to make your own tool
For now I wrote a tool that compares 2 assemblies and it worked just fine. 🙂
Have fun!
Liking these weirdly new protections, nice write ups!
please hack box app packer is file .bxpck