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 :
il2cpp.h
which basically contains headers used by the programmscript.json
which contains information to load the headers in IDA ( or Ghidra )
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 :
- Extract the game with
apktool
- Delete the folder
arm64-v8a
because we have compiled our hook onarmv7
and we don’t want our device to load thearm64-v8a
where the hook will not be executed - Add
libhook.so
toarmv7
libraries
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 :