Home > IL2CPP > Il2CppInspector Tutorial: How to create, use and debug IL2CPP DLL injection projects

Il2CppInspector Tutorial: How to create, use and debug IL2CPP DLL injection projects

November 27, 2020 Leave a comment Go to comments

Il2CppInspector allows you to automatically create a Visual Studio solution containing a C++ DLL project targeted at the application you are reverse-engineering, which – when compiled – can be injected into the running application process to monitor or modify the application’s behaviour.

In the bad old days, you used to have to find every type, function, field etc. yourself. An excellent tutorial on this from an unknown author can be found here. Il2CppInspector’s C++ scaffolding projects render the need to perform this work obsolete. These projects give you complete access to all of the C#-equivalent types and methods in the IL2CPP application, plus all available IL2CPP APIs and exports. There is no need to determine any function pointers or add any type declarations.

In this article, we’ll briefly walk through how to create, edit, inject and debug these projects. Details on how to actually write useful code using the framework provided can be found in this tutorial.

Creating the project

First, let’s use Il2CppInspector to create a C++ scaffolding project for the application you are targeting. I’ll be using The Long Dark for this example but any application will work. Some applications will detect this kind of invasive action, so naturally, you inject DLLs and attach a debugger to any 3rd party application at your own risk.

First, create the project from the Il2CppInspector GUI (or CLI) like so:

Once you’ve done this, you should have a solution folder that looks as follows:

Double-click on the .sln file to open it. You may be asked to retarget the project, and you should accept this with the following (default) settings:

Note: It is strongly recommended to use Visual Studio 2019 and the MSVC++ Build Tools v142 with Windows 10 SDK when working with Il2CppInspector C++ scaffolding projects.

Compile the initial project to make sure that it succeeds. First-time compilation may take a few minutes as the pre-compiled headers are created. Subsequent re-compilations will be much faster.

Editing the code

Generally you should only edit the files in the user folder, and place any additional source files here. If the target app is patched or updated and you need to re-create the project, Il2CppInspector will overwrite the contents of appdata and framework but leave the user folder intact. In this way, you can re-create the project in-place without losing your work as long as you have only modified the user folder.

Tip: You can find out more about the files generated for the C++ scaffolding project in the related Il2CppInspector documentation section.

The entry point is in user/main.cpp and looks something like this:

// Generated C++ file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty
// Custom injected code entry point

#include "pch-il2cpp.h"

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <iostream>
#include "il2cpp-appdata.h"
#include "helpers.h"

using namespace app;

// Set the name of your log file here
extern const LPCWSTR LOG_FILE = L"il2cpp-log.txt";

// Custom injected code entry point
void Run()
{
    // If you would like to write to a log file, specify the name above and use il2cppi_log_write()
    // il2cppi_log_write("Startup");

    // If you would like to output to a new console window, use il2cppi_new_console() to open one and redirect stdout
    // il2cppi_new_console();

    // Place your custom code here
}

Your code will begin to execute from the Run() function as soon as the DLL is injected.

Let’s make a trivial example which simply creates a Vector3 and outputs its co-ordinates. This example can be injected into any Unity 3D IL2CPP application since Vector3 is always included. Here is the code:

void Run()
{
    // Vector3 example

    // (Create a console window using Il2CppInspector helper API)
    il2cppi_new_console();

    // (Call an IL2CPP API function)
    Vector3__Boxed* myVector3 = (Vector3__Boxed*) il2cpp_object_new((Il2CppClass*) *Vector3__TypeInfo);

    // (Call an instance constructor)
    Vector3__ctor(myVector3, 1.0f, 2.0f, 3.0f, nullptr);

    // (Access an instance field)
    std::cout <<   "x: " << std::to_string(myVector3->fields.x)
              << ", y: " << std::to_string(myVector3->fields.y)
              << ", z: " << std::to_string(myVector3->fields.z)
              << std::endl;
}

To create an object with IL2CPP, you must first allocate memory for it with il2cpp_object_new, which returns a pointer to the newly created object. You then pass this as the first argument to the object constructor (Vector3__ctor here). The final value of a C# method in IL2CPP is always a MethodInfo*, which is not needed here so we just use nullptr.

Notice how all of the instance fields of our Vector3 are automatically available via myVector3->fields. These fields mirror the equivalent C# object fields.

Compile the code, making sure to select the Debug configuration first. This will produce a file called in the project folder called x64\Debug\IL2CppDLL.dll. This is the file we will inject into the IL2CPP process.

Injecting the DLL

You can use any DLL injection software to do this, however my preference is to use Cheat Engine due to its ease of use and rich feature set.

First, start the IL2CPP application and Cheat Engine (you can do this in any order).

In Cheat Engine, attach to the IL2CPP process by clicking the top-left toolbar icon and selecting the process as shown below:

Now click the Memory View button to pop up the disassembler, and select Tools -> Inject DLL from the menu or press Ctrl+I. Select the DLL compiled above and choose No when asked if you wish to execute a specific function in the DLL.

If all has gone as planned, Cheat Engine will report that the DLL was injected, and a new console window should appear with the contents of our Vector3 object:

x: 1.000000, y: 2.000000, z: 3.000000

Nice! Now we have successfully injected a DLL, and created and accessed a C# object! You can now start to actually work on your real code.

Tip: When you modify your project, you will typically have to close and re-open the target application before re-injecting the newly compiled DLL. You can however leave Cheat Engine open between runs.

Debugging the code

Once you’ve fleshed out your code, debugging the DLL can be a tricky business: typically if there is a bug, the app will just freeze or crash and you won’t have any idea what’s wrong. We can make the work of debugging less painful by attaching the Visual Studio debugger to the injected process, allowing us to set breakpoints in our DLL and step through it line by line in the normal fashion. To enable this, we’ll edit the project properties in Visual Studio.

Right-click on the project in Solution Explorer and choose Properties, then select the Debugging page in the left-hand pane.

We’ll change two options: first, change the Command to the actual path of the IL2CPP application. Secondly, change Attach from No to Yes. Click OK to save the changes.

Set an initial breakpoint in your code by clicking to the left of the code window to create a red circle marking the breakpoint:

Now, start the target application as normal. Once it is running, press F5 (or your defined keyboard shortcut) in Visual Studio to spin up the debugger. This may take a few moments, then you should see a window like this:

The debugger is now attached to the target application.

Finally, use Cheat Engine to inject the DLL as before, and this time execution should immediately stop at the previously set breakpoint. You can now hover over variables to see their contents, step through the code line by line with F10, see the call stack with Alt+7, set watchpoints, resume execution with F5 and perform all of the other tasks normally associated with using the debugger. For example, hovering over myVector3 in our example above shows us the value of every field:

…and that’s it!

Conclusion

The days of hunting through binaries for function pointers and object instances are over – as are the days of needing to update them with every released patch of your target application. Every class and method is now available automatically, requiring only that you regenerate the scaffolding project when the target application is updated. Hopefully this will help speed up the development of new projects, reducing the need to figure out the binary layout and allowing you to focus on writing useful code.

At the time of writing, the C++ scaffolding project generator is still in its infancy and there are many major quality-of-life improvements still to be made. These will trickle out over time, but for now, I hope this tutorial helps set you on the path to more efficient IL2CPP reverse engineering. Happy hacking!

RaaS – Ranting as a Service

I play video games badly and complain about the state of the video games industry regularly on my livestream: https://trovo.live/djkaty.

I don’t usually code or taking coding questions on the stream so please keep those to the comments below, but if you like games and banter I’d love to meet you! (Warning: very frequent use of mature language and mature themes)

Categories: IL2CPP Tags:
  1. Иван Васильев
    November 27, 2020 at 15:02

    Уважаю женщин, знающих C++!

  2. 0u0
    December 16, 2020 at 04:22

    Is is possible to create a class inherits monobehaviour, and inject to make its magic methods called by engine automatically? For now I use threads to do things, but I thought an Update() is better.

  3. Nattou
    December 20, 2020 at 15:04

    I tried to use
    Renderer* renderer = (Renderer*) il2cpp_object_new((Il2CppClass*)*Renderer__TypeInfo)
    to create a Renderer object, but it crash with 0xC0000005: Access violation reading location 0x00000000, I had checked Renderer__TypeInfo and *Renderer__TypeInfo is not null, and I got same error while creating String object via il2cpp_object_new((Il2CppClass*)*String__TypeInfo).
    But il2cpp_object_new((Il2CppClass*)*Vector3__TypeInfo) works well. Is it possible to help find out the problem?

    • January 10, 2021 at 13:52

      You may need to add il2cpp_thread_attach(il2cpp_domain_get()) to the start of your Run() function. The latest commit of Il2CppInspector includes this automatically.

  1. January 14, 2021 at 03:39

Share your thoughts! Note: to post source code, enclose it in [code lang=...] [/code] tags. Valid values for 'lang' are cpp, csharp, xml, javascript, php etc. To post compiler errors or other text that is best read monospaced, use 'text' as the value for lang.

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: