Discussion Frida not detecting libil2cpp.so

athenegg

Platinian
Original poster
Dec 20, 2023
18
3
3
33
Rooted, frida-server running, everything works. However, it can't detect the libil2cpp.so in the game.
I'm trying to get the base address of libil2cpp.so

I can see that libil2cpp.so is loaded in GameGuardian

1703182357126.png


I can successfully check all loaded module of the package and list it all out as seen below.
1703182362432.png

I enumerated all modules with this script.
// frida-enumerate-modules.js

// Wait for the script to attach to the process
Java.perform(function () {
// Function to enumerate and print information about all modules
function enumerateModules() {
console.log('Enumerating modules...');

// Introduce a short delay
setTimeout(function () {
// Enumerate loaded modules
Process.enumerateModules({
onMatch: function (module) {
console.log(`Module: ${module.name} - Base: ${module.base} - Size: ${module.size}`);
},
onComplete: function () {
console.log('Module enumeration completed.');
},
});
}, 1000); // Adjust the delay as needed
}

// Run the enumerateModules function when the script is loaded
enumerateModules();
});
However, libil2cpp.so isn't listed in there.
1703182375020.png

Is there something I'm doing wrong?
This is my code that I use to search for libil2cpp.so and it's base address.
// frida-search-libil2cpp.js

// Wait for the script to attach to the process
Java.perform(function () {
// Function to search for libil2cpp.so and print its base address
function searchForLibil2cpp() {
console.log('Searching for libil2cpp.so...');

// Introduce a short delay
setTimeout(function () {
// Flag to check if libil2cpp.so is found
var libil2cppFound = false;

// Enumerate loaded modules
Process.enumerateModules({
onMatch: function (module) {
if (module.name.toLowerCase().includes('libil2cpp.so')) {
console.log(`Found libil2cpp.so at base address: ${module.base}`);
libil2cppFound = true;
}
},
onComplete: function () {
if (!libil2cppFound) {
console.log('libil2cpp not found or base address not found.');
} else {
console.log('Module search completed.');
}
},
});
}, 1000); // Adjust the delay as needed
}

// Run the searchForLibil2cpp function when the script is loaded
searchForLibil2cpp();
});
 

Backshift

Solid & Active Platinian
Oct 10, 2023
53
35
18
32
What device are you running this on?
I dont see any libs that look like game libs in that screenshot they all look like standard android libs, are you on an emulator?
Is that all the libs that get logged or only the bottom part of the log?
 

athenegg

Platinian
Original poster
Dec 20, 2023
18
3
3
33
What device are you running this on?
I dont see any libs that look like game libs in that screenshot they all look like standard android libs, are you on an emulator?
Is that all the libs that get logged or only the bottom part of the log?
Yes, it's on an emulator. LDPlayer. And it's just bottom part of the log.
Are you saying that Frida fails at properly attaching to the game to get proper libs?

It is able to detect if the game is running or not tho.
Am I missing a parameter to be able to use it properly with an emulator?
 

athenegg

Platinian
Original poster
Dec 20, 2023
18
3
3
33
I can see through the Module.findExportByName function of Frida, that libil2cpp.so indeed gets opened by the game.
However, it is not shown in Process.enumerateModules.
findBaseAddress('libil2cpp.so') also doesn't work.
 

Backshift

Solid & Active Platinian
Oct 10, 2023
53
35
18
32
My question about being on an emulator was due to Frida not working with emulators originally.

Frida has since been updated and has support for emu's now but you have to tell frida about it

If using command line to launch add the argument: --realm emulated
If using frida with python to launch you can set realm there too: device.attach(process, realm="emulated")

I would imagine this is the cause? If you are already using realm = emulated, its less obvious why its happening.
 

mIsmanXP

Approved Modder
Approved Modder
Feb 20, 2022
205
9,222
193
Republic of Indonesia
JavaScript:
const doit = () => {
  const lib = Process.findModuleByName("libil2cpp.so");
  if (!lib) {
    setTimeout(doit, 10);
  } else {
    //libil2cpp.so found
  }
};

doit();
Simple as that
 

athenegg

Platinian
Original poster
Dec 20, 2023
18
3
3
33
Yeah it seems like using --realm emulated got me a different problem than usual, but it's the right direction.
I'm getting Failed to attach: the connection is closed.

Read the github on issues with emulators, and it seems like Frida does not go well with emulators. Even with the
--realm emulated
arguments.

Do you have any alternative to be able do live testing?
My goal is to be able to hook onto methods or fields and just check for the values given to whatever parameters that they receive.
Patching simple hex codes isn't my goal.

If you have an alternative, I would really appreciate it @Backshift
 

athenegg

Platinian
Original poster
Dec 20, 2023
18
3
3
33
JavaScript:
const doit = () => {
  const lib = Process.findModuleByName("libil2cpp.so");
  if (!lib) {
    setTimeout(doit, 10);
  } else {
    //libil2cpp.so found
  }
};

doit();
Simple as that
Yeah I think Frida does not go well with emulators. I've tried Nox, Memu and LDPlayer. Both highly modified and also the default unchanged downloaded ones.
 

Backshift

Solid & Active Platinian
Oct 10, 2023
53
35
18
32
I was talking to @RedMundo in PM a couple weeks ago about a lua game, RedMundo was using LDPlayer too and once I said about realm emulated it was fine, it "should" work on LDPlayer.

Looking at the patch notes for Frida 14.2, I did notice this bit:
One important caveat when using this on Android is that you will need to apply your Java-level instrumentation in the native realm.
Looking at your script above I do notice you have the code wrapped in Java.perform(function () { }, I am wondering if thats confusing Frida into running the rest of your code in native and not emualted realm.

Can you try removing the java.perform part? like this:

JavaScript:
function enumerateModules() {
    console.log('Enumerating modules...');

    setTimeout(function () {
        Process.enumerateModules({
            onMatch: function (module) {
                console.log(`Module: ${module.name} - Base: ${module.base} - Size: ${module.size}`);
            },
            onComplete: function () {
                console.log('Module enumeration completed.');
            },
        });
    }, 1000);
}

enumerateModules();
 

Backshift

Solid & Active Platinian
Oct 10, 2023
53
35
18
32
Ok ye, I tried it myself with frida both frida-server-16.1.8-android-x86_64 and frida-server-16.1.9-android-x86_64 and yes logcat is reporting crashes and the connection is closed quickly.

This was using the default LDPlayer installed files which by default installs Android9.0 (64-bit), I then used LDMultiPlayer to create a new instance, this time with Android7.0 (64-bit) and retried. This time frida with realm=emulated does not crash on either version of frida-server, using the code I pasted above though doesnt log libil2cpp,so when I tested with a simple games (Subway Surfers). It appears that frida launches the script and instantly enumeratesModules() before the Unity libs are even loaded.

A simple way around this is is to launch the game and once the game has loaded for a few seconds (or more) you can then attach your frida script using:
frida -U -f "com.kiloo.subwaysurf" --realm emulated -l "script.js"
This should be fine since the game libs should have been loaded, my log showed this:
Code:
[...]
Module: libil2cpp.so - Base: 0x7ffef75a0000 - Size: 60784640
Module: libunity.so - Base: 0x7ffefaf98000 - Size: 19300352
[...]
 
  • Like
Reactions: athenegg

athenegg

Platinian
Original poster
Dec 20, 2023
18
3
3
33
Ok ye, I tried it myself with frida both frida-server-16.1.8-android-x86_64 and frida-server-16.1.9-android-x86_64 and yes logcat is reporting crashes and the connection is closed quickly.

This was using the default LDPlayer installed files which by default installs Android9.0 (64-bit), I then used LDMultiPlayer to create a new instance, this time with Android7.0 (64-bit) and retried. This time frida with realm=emulated does not crash on either version of frida-server, using the code I pasted above though doesnt log libil2cpp,so when I tested with a simple games (Subway Surfers). It appears that frida launches the script and instantly enumeratesModules() before the Unity libs are even loaded.

A simple way around this is is to launch the game and once the game has loaded for a few seconds (or more) you can then attach your frida script using:
frida -U -f "com.kiloo.subwaysurf" --realm emulated -l "script.js"
This should be fine since the game libs should have been loaded, my log showed this:
Code:
[...]
Module: libil2cpp.so - Base: 0x7ffef75a0000 - Size: 60784640
Module: libunity.so - Base: 0x7ffefaf98000 - Size: 19300352
[...]
This worked! Changing the instance to Android 7.0 64bit was definitely the move.
Thank you so much! You have been really really helpful. I have no idea how to thank you <3

I finally can mess around with Subway Surfers! And yes, that was the game I'm messing with from the beginning :)
 
  • Like
Reactions: CodeJutsu

athenegg

Platinian
Original poster
Dec 20, 2023
18
3
3
33
@Backshift btw, do you have any experience with the dump generated by Zygisk-Il2cppdumper?

All of the classes has no offsets in the dump.cs.
For example, a generic/standard class in dump.cs.
// Dll : System.Xml.dll
// Namespace: System.Xml.Schema
public class XmlSchemaAnnotated : XmlSchemaObject
{
// Fields
private String id; // 0x38
private XmlSchemaAnnotation annotation; // 0x40
private XmlAttribute[] moreAttributes; // 0x48

// Properties
public String Id { get; set; }
public XmlSchemaAnnotation Annotation { get; set; }
public XmlAttribute[] UnhandledAttributes { get; set; }
internal override String IdAttribute { get; set; }

// Methods
// RVA: 0x272979c VA: 0x7fff3e52979c
public String get_Id() { }
 

Backshift

Solid & Active Platinian
Oct 10, 2023
53
35
18
32
@Backshift btw, do you have any experience with the dump generated by Zygisk-Il2cppdumper?

All of the classes has no offsets in the dump.cs.
For example, a generic/standard class in dump.cs.
Any reason you are using the Zygisk version of il2cppdumper for Subway Surfers? Zygisk's better use case is for games with protection mechanisms in place.

As for offsets, using public class RunSessionData in // Namespace: SYBO.Subway as an example.

The fields do have offsets, for example
private SafeFloat _distance; // 0xC
private SafeInt _availableKeysForUse;// 0x14
The offsets are small since they are the offset from the class instance (the instance of the class in memory at runtime), not the base address of the lib.so in memory.

The properties aren't really needed for modding, they are in use but not through the properties listed like this: public int TotalCoins { get; }
Notice in this example public int TotalCoins says it has a "get". if you look further down the dump of the class you will see the getter method that corresponds to the property along with the method offset:
C++:
// RVA: 0x1199BE8 Offset: 0x1199BE8 VA: 0x1199BE8
public int get_TotalCoins() { }
Notice you wont see a set_TotalCoins, since the property only mentions a "getter". Compare this to public float TotalTime { get; set; }, this describes a "get" and a "set", and likewise in the methods you will see there is a set_TotalTime method and a get_TotalTime method:
C++:
[CompilerGenerated]
// RVA: 0x1199DDC Offset: 0x1199DDC VA: 0x1199DDC
public float get_TotalTime() { }

[CompilerGenerated]
// RVA: 0x1199DE4 Offset: 0x1199DE4 VA: 0x1199DE4
private void set_TotalTime(float value) { }
The primary thing you want is to hook the methods you are interested in, using the offset from the dump (which would be il2cpp.so base address + function offset) then you can do your desired stuff in the context of that method, which in general also give you the ability to access the fields.

Also note, the offsets stuff is more in the realm of general programming/memory concepts, that game hacking/cheating/modding specifics, if your interested in getting to be able to do "cool"/"advanced" stuff, more knowledge in general programming etc is only going to benefit a potential modder.

Anyway, I gotta go for now, will reply later if you have anymore questions.
 
  • Like
Reactions: athenegg

athenegg

Platinian
Original poster
Dec 20, 2023
18
3
3
33
Any reason you are using the Zygisk version of il2cppdumper for Subway Surfers? Zygisk's better use case is for games with protection mechanisms in place.

As for offsets, using public class RunSessionData in // Namespace: SYBO.Subway as an example.

The fields do have offsets, for example
private SafeFloat _distance; // 0xC
private SafeInt _availableKeysForUse;// 0x14
The offsets are small since they are the offset from the class instance (the instance of the class in memory at runtime), not the base address of the lib.so in memory.

The properties aren't really needed for modding, they are in use but not through the properties listed like this: public int TotalCoins { get; }
Notice in this example public int TotalCoins says it has a "get". if you look further down the dump of the class you will see the getter method that corresponds to the property along with the method offset:
C++:
// RVA: 0x1199BE8 Offset: 0x1199BE8 VA: 0x1199BE8
public int get_TotalCoins() { }
Notice you wont see a set_TotalCoins, since the property only mentions a "getter". Compare this to public float TotalTime { get; set; }, this describes a "get" and a "set", and likewise in the methods you will see there is a set_TotalTime method and a get_TotalTime method:
C++:
[CompilerGenerated]
// RVA: 0x1199DDC Offset: 0x1199DDC VA: 0x1199DDC
public float get_TotalTime() { }

[CompilerGenerated]
// RVA: 0x1199DE4 Offset: 0x1199DE4 VA: 0x1199DE4
private void set_TotalTime(float value) { }
The primary thing you want is to hook the methods you are interested in, using the offset from the dump (which would be il2cpp.so base address + function offset) then you can do your desired stuff in the context of that method, which in general also give you the ability to access the fields.

Also note, the offsets stuff is more in the realm of general programming/memory concepts, that game hacking/cheating/modding specifics, if your interested in getting to be able to do "cool"/"advanced" stuff, more knowledge in general programming etc is only going to benefit a potential modder.

Anyway, I gotta go for now, will reply later if you have anymore questions.
I am using the Zygisk version just to get used to it's format for dump.cs.
Just in case I'd like to mess with protected games.

I noticed how for these offsets, they're unique. Meaning there's no duplicates.
Code:
    // RVA: 0x14bc5fc VA: 0x88a05fc
    public Void AddKeys(Int32 keys) { }
    // RVA: 0x14bc620 VA: 0x88a0620
    public Void SetHoverboardActive(Boolean active) { }
    // RVA: 0x14bc6e0 VA: 0x88a06e0
    public Int32 ConsumeKeysAndGetRemaining(Int32 keys) { }
I'm dumping a few random games, and I noticed how some of them have the same offsets spread across multiple methods.
Have you seen these type before?
Code:
// Methods
    // RVA: 0xd68c88 VA: 0x7fff3cb68c88
    protected Int32 get_cd_value() { }
    // RVA: 0xd6dbd4 VA: 0x7fff3cb6dbd4
    public Boolean get_is_ready() { }
    // RVA: 0xd68c88 VA: 0x7fff3cb68c88
    public Int32 get_this_mana() { }
    // RVA: 0xd68c88 VA: 0x7fff3cb68c88
    public Int32 get_UsedWeaponSpeed() { }
    // RVA: 0xd6dbd4 VA: 0x7fff3cb6dbd4
    public Boolean get_IsDrag() { }
    // RVA: 0xd6f9d4 VA: 0x7fff3cb6f9d4
    public Vector2 get_DragEndPosition() { }
    // RVA: 0xd704ac VA: 0x7fff3cb704ac
    protected override Void AfterSetUpSkill() { }
    // RVA: 0xd6a51c VA: 0x7fff3cb6a51c
    public override String GetIntroduce(Int32 f_level) { }
As you see, for public Int32 get_this_mana, the offset is (libil2cpp.so base address+ 0xd6dbd4) correct?
But protected Int32 get_cd_value() { }
and Int32 get_UsedWeaponSpeed() { } has the same offsets.

If I search the whole dump.cs, there are 800+ occurences of 0xd68c88.
 

Backshift

Solid & Active Platinian
Oct 10, 2023
53
35
18
32
yea, my assumption would be the same as mIsmanXP said.

If I search the whole dump.cs, there are 800+ occurences of 0xd68c88.
Although 800+ does seem quite high... What game is this if you dont mind me asking?
 

athenegg

Platinian
Original poster
Dec 20, 2023
18
3
3
33
yea, my assumption would be the same as mIsmanXP said.


Although 800+ does seem quite high... What game is this if you dont mind me asking?
Soul Knight Prequel, you can try dumping. Honestly, it's my first time seeing occurences of offsets this much. And the offsets of the methods are even the same through other classes. Very weird indeed.

10-20 occurences, and you'd assume that they use the same function after optimization.
But getting to 800-1000 occurences, I'd say something weird is definitely going on.
 

athenegg

Platinian
Original poster
Dec 20, 2023
18
3
3
33
@Backshift if you're using zygisk dumper, I'd recommend adding a 1 minute delay before dumping. Insta-dumping would result in an incomplete dump.
 

Backshift

Solid & Active Platinian
Oct 10, 2023
53
35
18
32
Soul Knight Prequel
Had a little look at it, looks like tencent anticheat, honestly, from my experience with tencent in the passed and considering there are 800+ reults I wouldnt be surprised if they were doing this stuff on purpose as a countermeasure to memory based il2cppdumps lol
 

athenegg

Platinian
Original poster
Dec 20, 2023
18
3
3
33
Had a little look at it, looks like tencent anticheat, honestly, from my experience with tencent in the passed and considering there are 800+ reults I wouldnt be surprised if they were doing this stuff on purpose as a countermeasure to memory based il2cppdumps lol
Ahhh, no wonder.
Do you have an alternative to memory based il2cpp dumps?