Unity3D protection in “AU2” dance games, part 2

kao

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. 🙂

The end result:

Have fun!

2 thoughts on “Unity3D protection in “AU2” dance games, part 2

Leave a Reply

  • Be nice to me and everyone else.
  • If you are reporting a problem in my tool, please upload the file which causes the problem.
    I can`t help you without seeing the file.
  • Links in comments are visible only to me. Other visitors cannot see them.

4  +   =  12