diff options
Diffstat (limited to '_posts/2020-12-11-mono-dot-net-injection.md')
-rw-r--r-- | _posts/2020-12-11-mono-dot-net-injection.md | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/_posts/2020-12-11-mono-dot-net-injection.md b/_posts/2020-12-11-mono-dot-net-injection.md new file mode 100644 index 0000000..bcde97b --- /dev/null +++ b/_posts/2020-12-11-mono-dot-net-injection.md @@ -0,0 +1,330 @@ +--- +layout: post +title: Mono/.NET Injection Under Linux +author: Dylan Müller +--- + +> Learning `mono` or `C#` library injection through a [Robocraft](https://robocraftgame.com/) exploit. The method used in +> this publication can be used to modify a wide range of Unity games. + +1. [Mono Overview](#mono-overview) +2. [Exploiting Robocraft](#exploiting-robocraft) +3. [Source Code](#source-code) + +# Mono Overview + +[Mono](https://en.wikipedia.org/wiki/Mono_%28software%29) is an open source port +of the `.NET` framework which runs on a variety of operating systems (including +Linux). + +The `mono` build chain compiles `C#` source code (`.cs` files) down to `IL` (immediate +language) spec'd byte code which is then executed by the `CLR` (Common Language +Runtime) layer provided by `mono`. + +Due to the translation down to `IL`, module decompilation as well as +modification/reverse engineering is relatively straightforward and a variety of +`C#` `IL` decompilers/recompilers already exist +([dnSpy](https://github.com/dnSpy/dnSpy/), +[ILSpy](https://github.com/icsharpcode/ILSpy)). + +The focus of this journal is on managed library injection, more specifically the +ability to inject `C#` code of our own and interact with/modify a target host. + +# Exploiting Robocraft + +[Robocraft](https://en.wikipedia.org/wiki/Robocraft) is an online `MMO` game +developed by `freejam` games. It features futuristic robotic battles and is an +example of an application we wish to tamper with. + + + +`Robocraft` uses the [Unity3D](https://unity3d.com/get-unity/download) engine, +which is a high level `C#` component based game engine. + +World entities in `Unity3D` derive from class `UnityEngine::GameObject` and may +have a number of components attached to them such as: rigidbodies, mesh +renderers, scripts, etc. + +`UnityEngine::GameObject` has many useful +[properties](https://docs.unity3d.com/ScriptReference/GameObject.html) such as a +name (string), tag, transform (position), etc. as well as static methods for +finding objects by name, tag, etc. These methods become useful when injecting +our own code as they provide a facility for interfacing with the game engine +from an external context (our `C#` script). + +Browsing the `Robocraft` root directory (installed via steam) revealed a few +directories that seemed interesting: + + - `Robocraft_Data` + - `lib64` + - `lib32` + - `EasyAntiCheat` + + + +Upon further inspection of the `Robocraft_Data` directory, we find the folders +containing the managed (`C#/mono`) portion of the application. In particular, the +Managed folder contains the `C#` libraries in `DLL` form of the `Unity3D` Engine as well +as other proprietary modules from the game developer. + + + +However at his point it's worth noting the presence of the `EasyAntiCheat` folder +in the root game directory which confirms the presence of an `anti-cheat` client. + +After some research I found out a few interesting details about the game's +`anti-cheat` client `EasyAntiCheat`: + + - The client computes hashes of all binary images during startup (including + managed libraries) and is cross-referenced to prevent modification to game + binaries. + - Uses a heartbeat mechanism to ensure presence of the `anti-cheat` client (To + mitigate `anti-cheat` removal). + - Works with an online service known as `RoboShield` to monitor server side + parameters such as position, velocity, damage, etc and assigns each user with + a trust score. The lower the score the higher the chance of getting kicked + from subsequent matches. This score seems to be persistent. + +Nonetheless, nothing seemed to prevent us from injecting our own `C#` library at +runtime and this was the vector employed with `Robocraft`. The advantage of this +method was that no modification to the game binaries would be required and +therefore any client side anti-tamper protection could be bypassed. + +In order to inject our own `C#` code we need to somehow force the client to load +our own `.NET/mono` library at runtime. This may be accomplished by a stager +payload which is essentially a shared library that makes internal calls to +`libmono.so`. + +Some interesting symbols found in `libmono.so` include: + + - `mono_get_root_domain` - get handle to primary domain. + - `mono_thread_attach` - attach to domain. + - `mono_assembly_open` - load assembly. + - `mono_assembly_get_image` - get assembly image. + - `mono_class_from_name` - get handle to class. + - `mono_class_get_method_from_name` - get handle to class method. + - `mono_runtime_invoke` - invoke class method. + +The function signatures for these symbols are shown below: + +``` +typedef void* (*mono_thread_attach)(void* domain); +typedef void* (*mono_get_root_domain)(); +typedef void* (*mono_assembly_open)(char* file, void* stat); +typedef void* (*mono_assembly_get_image)(void* assembly); +typedef void* (*mono_class_from_name)(void* image, char* namespacee, char* name); +typedef void* (*mono_class_get_method_from_name)(void* classs, char* name, DWORD param_count); +typedef void* (*mono_runtime_invoke)(void* method, void* instance, void* *params, void* exc); +``` + +In order to perform code injection, firstly a handle to the root application +domain must be retrieved using `mono_get_root_domain`. The primary application +thread must then be binded to the root domain using `mono_thread_attach` and the +assembly image loaded with `mono_assembly_open` and `mono_assembly_get_image`. + +Next the assembly class and class method to execute may be found by name using +`mono_class_from_name` and `mono_class_get_method_from_name`. + +Finally the class method may be executed using `mono_runtime_invoke`. It should +be noted that the class method to execute should be declared as static. + +The resulting stager payload is shown below: + +``` +#include <iostream> +#include <link.h> +#include <fstream> + +using namespace std; + +typedef unsigned long DWORD; + +typedef void* (*mono_thread_attach)(void* domain); +typedef void* (*mono_get_root_domain)(); +typedef void* (*mono_assembly_open)(char* file, void* stat); +typedef void* (*mono_assembly_get_image)(void* assembly); +typedef void* (*mono_class_from_name)(void* image, char* namespacee, char* name); +typedef void* (*mono_class_get_method_from_name)(void* classs, char* name, DWORD param_count); +typedef void* (*mono_runtime_invoke)(void* method, void* instance, void* *params, void* exc); + + +mono_get_root_domain do_mono_get_root_domain; +mono_assembly_open do_mono_assembly_open; +mono_assembly_get_image do_mono_assembly_get_image; +mono_class_from_name do_mono_class_from_name; +mono_class_get_method_from_name do_mono_class_get_method_from_name; +mono_runtime_invoke do_mono_runtime_invoke; +mono_thread_attach do_mono_thread_attach; + +int __attribute__((constructor)) init() +{ + void* library = dlopen("./Robocraft_Data/Mono/x86_64/libmono.so", RTLD_NOLOAD | RTLD_NOW); + + do_mono_thread_attach = (mono_thread_attach)(dlsym(library, "mono_thread_attach")); + do_mono_get_root_domain = (mono_get_root_domain)(dlsym(library, "mono_get_root_domain")); + do_mono_assembly_open = (mono_assembly_open)(dlsym(library, "mono_assembly_open")); + do_mono_assembly_get_image = (mono_assembly_get_image)(dlsym(library, "mono_assembly_get_image")); + do_mono_class_from_name = (mono_class_from_name)(dlsym(library, "mono_class_from_name")); + do_mono_class_get_method_from_name = (mono_class_get_method_from_name)(dlsym(library, "mono_class_get_method_from_name")); + do_mono_runtime_invoke = (mono_runtime_invoke)(dlsym(library, "mono_runtime_invoke")); + + + do_mono_thread_attach(do_mono_get_root_domain()); + void* assembly = do_mono_assembly_open("./Robocraft_Data/Managed/Client.dll", NULL); + + void* Image = do_mono_assembly_get_image(assembly); + void* MonoClass = do_mono_class_from_name(Image, "Test", "Test"); + void* MonoClassMethod = do_mono_class_get_method_from_name(MonoClass, "Load", 0); + + do_mono_runtime_invoke(MonoClassMethod, NULL, NULL, NULL); + + return 0; +} + +void __attribute__((destructor)) shutdown() +{ + +}; +``` + +The stager payload shown above loads the mono assembly located in +`<root>/Robocraft_Data/Managed/Client.dll` into memory and executes the class +method Load within the `namespace` `Test` and `class` `Test` (`Test::Test::Load`). + +Load has the following signature: `public static void Load()` The stager may +be compiled with: `gcc -fpic -shared stager.cpp -o stager.so`. + +In order to inject the stager into the target process you may use any standard +Linux shared library injector. + +With the capability of loading our own `mono` code into the target process, we +need to ensure that our injected `C#` code stays persistent, i.e to prevent +de-allocation due to garbage collection. + +For `Unity3D` this is typically achieved using the following pattern: + +``` + public class Exploit : MonoBehaviour + {...} + + public static class Test + { + private static GameObject loader; + public static void Load() + { + loader = new GameObject(); + loader.AddComponent<Exploit>(); + UnityEngine.Object.DontDestroyOnLoad(loader); + } + } +``` + +It is also worth keeping track of the `mono/.NET` assembly versions used in the +original application. Ideally you would want to use an identical `.NET` version as +compiling your `C#` exploit with the wrong `.NET` version can cause your exploit to +fail. + +For `Robocraft` `.NET` `v2.0` was required. Finding support for an older version of +`.NET` can be difficult as most modern `C#` `IDE's` do not support such an old target. +A simple solution to this problem is to download an older version of `mono`. + +At this point the second stage payload (our `C#` exploit) can be developed. I +chose to implement three simple functionalities: + + - Increase/decrease game speed. + +``` +if(Input.GetKeyDown(KeyCode.F2)){ + speedhack = !speedhack; + if(speedhack == true){ + Time.timeScale = 3; + }else{ + Time.timeScale = 1; + } + } +``` + + - Clip through walls/obstacles. + +``` +if(Input.GetKeyDown(KeyCode.F3)){ + collision = !collision; + GameObject obj = GameObject.Find("Player Machine Root"); + Rigidbody rb = obj.GetComponent<Rigidbody>(); + if(collision == true){ + rb.detectCollisions = false; + }else{ + rb.detectCollisions = true; + } + } +``` + + + + +- Place all network entites near player. + +``` +if(Input.GetKeyDown(KeyCode.F1)) +{ + salt = !salt; + GameObject obj = GameObject.Find("Player Machine Root"); + position = obj.transform.position; + + foreach(GameObject gameObj in GameObject.FindObjectsOfType<GameObject>()) + { + if(gameObj.name == "centerGameObject") + { + GameObject parent = gameObj.transform.parent.gameObject; + if(parent.name != "Player Machine Root"){ + MonoBehaviour[] comp = parent.GetComponents<MonoBehaviour>(); + foreach (MonoBehaviour c in comp){ + c.enabled = !salt; + + } + Vector3 myposition = position; + parent.transform.position = myposition; + + } + + } + } +} +``` + + + +In order to find the names of the game objects for the main player as well as +network players you can simply iterate through all the global game objects and +dump the corresponding names to a text file. + +# Source Code + +All source code for this `journal` is hosted at +[https://github.com/lunar-rf/robocraft](https://github.com/lunar-rf/robocraft) + +# Signature + +``` ++---------------------------------------+ +| .-. .-. .-. | +| / \ / \ / \ | +| / \ / \ / \ / | +| \ / \ / \ / | +| "_" "_" "_" | +| | +| _ _ _ _ _ _ ___ ___ _ _ | +| | | | | | | \| | /_\ | _ \ / __| || | | +| | |_| |_| | .` |/ _ \| /_\__ \ __ | | +| |____\___/|_|\_/_/ \_\_|_(_)___/_||_| | +| | +| | +| Lunar RF Labs | +| https://lunar.sh | +| | +| Research Laboratories | +| Copyright (C) 2022-2025 | +| | ++---------------------------------------+ +``` + |