15 May 2018

Unity3D, Mono and invalid PE files

Some time ago, Reoto asked a very nice question on Black Storm forum:

Can someone fix the .dll (.net) pe header to MS DOS?
How can I do that?
If you know about protecting .net files for Android, please help me.
I have another question.
Can I fix dnspy to resolve .dll pe header isn't .net?

Obviously, English is not author's first language but it seemed like an interesting problem, so I decided to look into it.

Here is one of the files in question: https://mega.nz/#!0g4VHaIR!KmpQirte4_3lv8MSxyjETiufjFGb-CITpFGrXwxSgGY

TL;DR: Mono loader used by Unity3D accepts invalid PE files. It can be used to break most .NET decompilers. dnlib and tools based on dnlib (dnSpy, de4dot) were updated on 20-Apr-2018 but the rest of the tools still can't handle such files.

Quick background on Unity3D and Mono

I quickly checked file in CFF and it looked like the file doesn't have proper PE header.

I fixed that. But even then it was not recognized as a .NET file.

So, the file is clearly invalid, yet it works just fine in Android! How is that possible?

Well, Android has no clue about PE files or .NET Framework. When you build your program in Unity3D and deploy it to Android, it uses Mono to run your code. Mono is supposed to be open-source alternative of .NET Framework. And it is incredibly buggy.

I'm not Unity3D or Android wizard but the whole monstrosity works something like this:

To make matters worse, even current versions of Unity3D are using a very old Mono version. Like 3+ years old. You can easily tell it by looking at the PE loader error messages. This is how it looks in IDA:

The commit df51163 is clearly missing.

Therefore, for the rest of the article I'll be using Mono sources from commit 74be9b6, so that they would more or less match the code used by Unity3D.

Cause of the problem

A good place to start looking for bugs would be Mono implementation of PE loader:

do_mono_image_load calls function mono_verifier_verify_pe_data which should filter out any invalid PE file and stop loading it. For some reason (which I really don't care about), this function does nothing in Unity3D and returns success.

After that, buggy mono_image_load_pe_data takes over and uses PE parser to load PE structures and .NET metadata. It is followed by equally buggy processing of .NET metadata in both mono_verifier_verify_cli_data and mono_image_load_cli_data.

But first things first..

Invalid PE signature

First of the errors is inside do_load_header. It gets called indirectly from mono_image_load_pe_data:

It checks only first 2 bytes of PE signature, so you can change it to "PE\0\1" or "PEPE" and it will still work under Mono.

Invalid NumberOfRvasAndSizes value

Next bug is located get_data_dir used indirectly by mono_verifier_verify_cli_data.

and get_data_dir looks like this:

Both verify_cli_header and get_data_dir ignore NumberOfRvasAndSizes field in PE header and access "CLR Runtime Header Entry".

Identical bug is in load_cli_header called from mono_image_load_cli_data:

They do not use broken get_data_dir function but the check of NumberOfRvasAndSizes field is still missing. That's why our PE file can have NumberOfRvasAndSizes == 0xA and it still works under Mono.

Invalid metadata size

Mono does a very limited checking of .NET metadata size in load_metadata_ptrs.

However, this check is insufficient. For example, you can set .NET metadata size = 0, most of .NET reversing tools will break but Mono will happily accept the file.

Invalid number of .NET streams

.NET metadata streams are loaded by load_metadata_ptrs.

You can use an arbitrary large number of streams in .NET Metadata header, as Mono will ignore all the invalid data. It probably will spam Android log with "Unknown heap type" messages, but the file will still run.

Invalid number of rows in .NET tables

Final nail in the coffin is the incorrect processing on .NET metadata tables. Tables are loaded in load_tables method which seems to be correct on the first look:

However, the definition of .NET metadata table information (_MonoTableInfo) is wrong:

As a result of this definition, they ignore high-order byte in row count. You can set number of rows to, say, 0xCC000001, .NET metadata will be invalid but Mono happily accepts the file. WTF?

Extra protection by AndroidThaiMod

Game hacks created by AndroidThaiMod.com take the Unity3D/Mono abuse to the next level. They exploit all the bugs I already mentioned. In addition to that, they replace the original libmono.so with their own version. In their version there are few extra lines of code in the function mono_image_open_from_data_with_name:

This change allows AndroidThaiMod to distribute their DLLs encrypted with "extra-strong" XOR encryption. smile

Another fun fact - their cheats only hack ARM version of Unity3D runtime. x86 version, even if present in original APK, gets removed from the hacked APK. So, if you have an Android device with x86 CPU, you're out of luck - AndroidThaiMod cheats won't work there.

Few AndroidThaiMod files I was able to find on Google:
https://drive.google.com/file/d/1g2_43rP9LLrXSmGL-Lw36GTehSUmg3CJ/view
https://drive.google.com/file/d/0B7jRiqM-QmgUeUF2RldrRUFmcUk/view

Other possibilities of abuse

As I mentioned, Mono PE loader is extra buggy. Pretty much all fields in PE header are not validated properly. Plenty of fields in .NET structures are ignored. PE32+ header processing is broken beyond belief. File/section alignment is not enforced. You can have several streams named "#~" and Mono will happily use one of them. Or you could just search for a phrase "FIXME" in Mono sources - you'll find lots of other dirty hacks that can be exploited.

Can it be fixed?

Probably. But considering how broken and messy the entire Mono codebase is, I wouldn't bet on it.

For example, on 13-Apr-2018 Mono developers made this awful commit called "Verify all 4 bytes of PE signature.":

There was no explanation why it was done, what are the side effects and whether they plan to fix all the broken code or if this was just a one-off fix. What a surprise!

And, as I already mentioned, Unity3D is using 3+ years old version of Mono. So, you'll probably have to wait until year 2021 until Unity3D gets they necessary fixes. bigsmile

Workarounds

Even if Mono and Unity3D was fixed, it doesn't help us to analyze existing broken files.

One workaround was to update dnlib (commit c40e148) and reversing tools to support such broken files. It allows to analyze existing files using dnSpy and de4dot. But Reflector and other tools still won't work. So, I decided to make my own tool which will fix broken files and will allow you to use any .NET reversing tool you like.

I needed a PE/.NET reading library which is very simple and low-level. dnLib/Mono.Cecil is way too abstract. After a quick search, I decided to use parts of McCli - but any other library would do just fine.

The code is very straightforward. Read PE structure->validate->fix->repeat.. wink

Conclusion

Games and cheats made using Unity3D are great. It's possible to make very unique protections that will stop most Windows/.NET reversing tools but the program will still work on Android. Enjoy the fun times! smile

Unity3D fixer with source code:

17 thoughts on “Unity3D, Mono and invalid PE files

  1. Hello, I had a quick question. Disclaimer: I'm no expert so a lot of this post went over my head and I didn't have time to read it all. =P

    Is this post related to the v2018.1 changes Unity made to assemblies? Here is the link but I can't direct link to the section so Ctrl+F the phrase below the link since the page is long.
    {hidden link}
    Test assemblies defined by Assembly Definition

    As someone who only cares about reading the source code of Unity games with ILSpy, will decompiling assemblies made with the v2018.1 version of Unity still work or will there be any differences?

    Thanks for your tools btw. <3

  2. Hello, I used your tool on an already modded .dll file that had invalid coff header. Now I'm trying to see what was edited. I was wondering if you could help me?

  3. Wow you are such a big leecher, letting anyone crack our .dll and leech everything. Hope your website is down and get a god damn life.

  4. Mr. Kao why did the decrypted dll and obsfuscated dll not work when inserting back to managed folder . Does it need to rename all the obsfucasted parts in dll to make it work> ? or it affects the codes inside dll when renaming other parts. When you try to use obsfuscation tool to rename all obsfuscated codes and put it back to original managed folder and try to signing it the game crash.

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

      This. Really.

Comments are closed.