Unity Reverse engineering, part 2: Mono shenanigans

If you need help installing the correct tools for this task, refer to Part 1
References#
First, we need to understand what’s mono and how it works.
For a complete Mono embedding C code, see teste.c
Optional If you really want to understand how mono works, see here: www.mono-project.com/docs/advanced/runtime/docs/.
What’s mono#
Mono, the open source development platform based on the .NET Framework, allows developers to build cross-platform applications with improved developer productivity. Mono’s .NET implementation is based on the ECMA standards for C# and the Common Language Infrastructure. From mono-project.com
What we are really interested in here is the Mono Runtime.
Mono Runtime - The runtime implements the ECMA Common Language Infrastructure (CLI). The runtime provides a Just-in-Time (JIT) compiler, an Ahead-of-Time compiler (AOT), a library loader, the garbage collector, a threading system and interoperability functionality. From mono-project.com
In short: The Mono Runtime is what allows us to execute C# code
C & C##
From the docs
The Mono runtime provides two mechanisms to expose C code to the CIL universe: internal calls and native C code. Internal calls are tightly integrated with the runtime, and provide no support for marshalling between runtime types and C types.
P/Invoke#
Basically, a P/Invoke looks like this.
using System.Runtime.InteropServices;
[DllImport ("__Internal", EntryPoint="DoSomething")]
static extern void DoSomething ();
Now, we know how to identify P/Invoke in a C# function. Let’s try it.
Back to Unity, let’s open up a bunch of .dll to check for Object. In Unity, Object is the base of all interesting objects.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using UnityEngine.Bindings;
using UnityEngine.Internal;
using UnityEngine.Scripting;
using UnityEngineInternal;
#nullable disable
namespace UnityEngine
{
[NativeHeader("Runtime/SceneManager/SceneManager.h")]
[NativeHeader("Runtime/GameCode/CloneObject.h")]
[RequiredByNativeCode(GenerateProxy = true)]
[NativeHeader("Runtime/Export/Scripting/UnityEngineObject.bindings.h")]
[StructLayout(LayoutKind.Sequential)]
public class Object
// Snip
[FreeFunction("UnityEngineObjectBindings::GetName")]
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string GetName(Object obj);
// Snip
}
Not quite the same.
Internal call#
If we scroll down a little further in Mono doc, we get to:
mono_add_internal_call ("Hello::Sample", sample);
using System;
using System.Runtime.CompilerServices;
class Hello {
[MethodImplAttribute(MethodImplOptions.InternalCall)]
extern static string Sample ();
}
(Note, MethodImpl
is MethodImplAttribute
. DotPeek, as all decompilers/disassemblers/whatever.exe can’t generate 100% accurate code.)
So now, we know that Unity uses internal calls ! But can we find it’s definition ?
Not a 100% match, but given the proximity with other functions and name proximity, that looks like it.
When we search for reference in Ghidra, we find:
🎉 A small victory.
So now, we have a way to find the definition of functions we encounter in the Unity assembly.
Thought process: Find class in C# > Find it’s internal name from it’s attributes > Find where it’s passed on to Mono for registering > We now have the function pointer
Now, we know that anything that’s an unity Object, is a C# object. Thus, it is managed by Mono.
We have to understand Objects.
How objects are born.#
First, we need to understand how mono creates those objects, and what they look like in memory.
My approach is to see how they are created when in the C universe.
From the docs
/* we usually get the class we need during initialization */
MonoImage *image = mono_assembly_get_image (assembly); // Load the Image from the assembly
MonoClass *my_class = mono_class_from_name (image, "MyNamespace", "MyClass"); // Get the MonoClass from the assembly, located by it's namespace and class name
...
/* allocate memory for the object */
MonoObject *my_class_instance = mono_object_new (domain, my_class); // Instanciate the class
/* execute the default argument-less constructor */
mono_runtime_object_init (my_class_instance); // Call the ctor.
Now we know 3 structs:
MonoImage
: The Assembly, with a memory representationMonoClass
: The class definition. Contains the infosMonoObject
: The class instance
So, I can assume that MonoObject*
holds the instance data.
Let’s have a look at how mono_object_new
is implemented: mono/mono/metadata/object.c
It’s a wrapper around mono_object_new_checked
. Memory safety is for the weak. True programmers SEGFaults like real man /s
The process:
- Initializes (or fetches) a vtable with
mono_class_vtable_checked
- Checks if the class has a
MonoClassRuntimeInfo
andruntime_info->max_domain >= domain->domain_id && runtime_info->domain_vtables [domain->domain_id])
.
If I were to make a guess, I think it’s some kind of cache where we cache the vtable for each domain- If it has, then return the runtime class’ vtable for
domain->domain_id
- Else, ``mono_class_create_runtime_vtable`
- If it has, then return the runtime class’ vtable for
- Deep down the vtable process,
mono_class_init_internal
is called. I’ll explain later what are vtables
- Checks if the class has a
- calls
mono_object_new_alloc_specific_checked
with the class’ vtable and returns theMonoObject*
created- calls
mono_gc_alloc_obj
which allocates the necessary memory
- calls
Used functions:
mono_class_vtable_checked
mono/metadata/object.c#L1927mono_class_create_runtime_vtable
mono/metadata/object.c#L1995mono_class_init_internal
mono/metadata/class-init.c#L2695mono_object_new_alloc_specific_checked
mono/metadata/object.c#L6021mono_object_new_alloc_specific_checked
mono/metadata/object.c#L6021mono_gc_alloc_obj
mono/metadata/sgen-mono.c#L937
Basically, that’s a memory-safe malloc.
Anyway, we have our MonoObject* which is a base for any C# object. It contains a pointer to the vtable, which contains function pointers. It’s size is variable, and is a base for any C# object.
23 years old documentation: docs/object-layout