Modding a Unity C++ Android Game

For those who ever wanted to create cheat for Android, they are many tutorials that explain how to patch the DLL of a classic Unity Game using libmono.so to load these C# DLL.

Here we will see how to mod a game when they’re is no C# DLL but all the game is packaged in a single library libil2cpp.so.

Here we will take the game ArcHero for example.

Recon

As usual when reversing Android applications, we can use jadx or apktool to see which are the libraries loaded, the assets, etc.

After extracting it, you can easily recognize a Unity game by looking at the Manifest.xml :

Then usually you can find DLLs in the folder : assets/bin/Data/Managed/ But in this case, they’re no DLLs.

Instead we have a new library :

What is il2cpp library ?

According to Unity’s documentation :

IL2CPP (Intermediate Language To C++) is a Unity-developed scripting backend
which you can use as an alternative to Mono when building projects for various platforms. When building a project using IL2CPP, Unity converts IL code from scripts
and assemblies to C++, before creating a native binary file (.exe, apk, .xap, for example) for your chosen platform. Some of the uses for IL2CPP include increasing the performance, security, and platform compatibility of your Unity projects. 

So it’s basically IL converted to C++, we have here a library that we’ll need to reverse using disassembler.

Extracting symbols

Instead of blindly looking at the library in IDA, we’ll use a tool called Il2CppDumper (Github repo).

The tools needsil2cpp.so and global-metadata.dat ( which is located in assets/bin/Data/Managed/Metadata).

It produce two files :

Then open IDA and do the following File -> Script File and load the file ida_with_structs.py.

It take a while to load all the symbols but now you are able to understand what the program do :

Modding the game

Now it’s time to mod the game.

First find the function you want to modify, here we’ll take the function EntityData__GetHPPercent that you can see above.

This function is called each time an entity (player or ennemies) is hit, then it update the life bar length. Seems a good function to hook if we want to become invincible.

Let’s build a hooking library, for that we’ll use : Android-Hooking-Template.

To use it you will need to download Android NDK build r16b at https://developer.android.com/ndk/downloads/older_releases

Then we will create our hooks in hook.cpp.

The EntityData__GetHPPercent function is located at 0x533518 in libil2cpp.so.

One interesting things to do is also extract the struct to import them in our hooks. In IDA do : File -> Produce File -> Create C headers file and the struct you want in it ( in our case EntityData_o) :

struct EntityData_o
{
  EntityData_c *klass;
  void *monitor;
  int32_t CharID;
  EntityBase_o *m_Entity;
  int32_t mDeadRecover;
  int64_t mHP2AttackSpeed;
  int64_t mHP2Miss;
  int32_t mHitCreate2;
  float mHitCreate2Percent;
  int32_t mFlyStoneCount;
  int32_t mFlyWaterCount;
  int32_t mBulletThroughCount;
  int32_t DizzyCount;
  float mDizzyTime;
  int32_t ExtraSkillCount;
  int64_t CurrentHP;
  int64_t MaxHP;
  
  ... 
  
};

We will not be able to include it if we keep other structures pointers so replace it with void*, for example :

EntityData_c *klass -> void *klass
EntityBase_o *m_Entity -> void *m_Entity

Now we can create our hooks :

/*
Pointer to the old function GetHPPercent
*/
float (*oldChangePlayerLife)(EntityData_o *entity);
float newChangePlayerLife(EntityData_o *entity){
    
    /*
    By launching the game a first time with logging
    we find that the player CharID is 1001 and we only want the player to be invicible
    */
    if ( entity->CharID == 1001 )
    {
        entity->CurrentHP = 35383773;
        entity->MaxHP = 35383773;
    }
    
    /*
        normal behaviour
    */
    return entity->CurrentHP / entity->MaxHP;
}


__attribute__((constructor))
void libhook_main() {

    while(libBase == 0) { 
        libBase = get_libBase(libName); 
        sleep(1); 
    }   
    
    __android_log_print(ANDROID_LOG_DEBUG,"Hook", "Loaded Cheat");

    // Replacing the function : EntityData__GetHPPercent by the new one
   MSHookFunction((void *)getRealOffset(0x533518),(void *) &newChangePlayerLife, (void**)&oldChangePlayerLife);
}

No we can build our library :

➜ Archero-Mod $ NDK_PROJECT_PATH=. NDK_APPLICATION_MK=jni/Application.mk /home/romain/Bureau/Archero-Mod/android-ndk-r16b/ndk-build
APP_OPTIM is release ...
[armeabi-v7a] Compile++ thumb: hook <= hook.cpp
[armeabi-v7a] SharedLibrary  : libhook.so
[armeabi-v7a] Install        : libhook.so => libs/armeabi-v7a/libhook.so
[x86] Compile++      : hook <= hook.cpp
[x86] SharedLibrary  : libhook.so
[x86] Install        : libhook.so => libs/x86/libhook.so

Now we want our hack to be loaded, so we do the following :

And add the following folder in the Smali sources :

In smali_classes/com/loadLib/libLoader$1.smali :

.class final Lcom/loadLib/libLoader$1;
.super Ljava/lang/Object;
.source "libLoader.java"

# interfaces
.implements Ljava/lang/Runnable;


# annotations
.annotation system Ldalvik/annotation/EnclosingMethod;
    value = Lcom/JvRuit/Ldr;->loadLib()V
.end annotation

.annotation system Ldalvik/annotation/InnerClass;
    accessFlags = 0x8
    name = null
.end annotation


# direct methods
.method constructor <init>()V
    .locals 0

    .prologue
    .line 14
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V

    return-void
.end method


# virtual methods
.method public run()V
    .locals 1

    .prologue
    .line 17
    const-string/jumbo v0, "hook"

    invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

    .line 18
    return-void
.end method

In smali_classes/com/loadLib/libLoader.smali :

.class public Lcom/loadLib/libLoader;
.super Landroid/app/Activity;
.source "libLoader.java"


# direct methods
.method public constructor <init>()V
    .locals 0

    .prologue
    .line 12
    invoke-direct {p0}, Landroid/app/Activity;-><init>()V

    return-void
.end method

.method public static loadLib()V
    .locals 4

    .prologue
    .line 14
    new-instance v0, Landroid/os/Handler;

    invoke-direct {v0}, Landroid/os/Handler;-><init>()V

    new-instance v1, Lcom/loadLib/libLoader$1;

    invoke-direct {v1}, Lcom/loadLib/libLoader$1;-><init>()V

    const-wide/16 v2, 0x3a98

    invoke-virtual {v0, v1, v2, v3}, Landroid/os/Handler;->postDelayed(Ljava/lang/Runnable;J)Z

    .line 20
    return-void
.end method

Then in the onCreate function in UnityPlayerActivity add the following so the librarie will be loaded at launch :

invoke-static {}, Lcom/loadLib/libLoader;->loadLib()V

Then build your apk with apktool and sign it.

Now you can execute it and have unlimited life points :