ngoalong123 / frida-il2cpp-bridge

Frida module to debug, dump, manipulate or hijack any IL2CPP application at runtime with a high level of abstraction, without needing the global-metadata.dat file.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

frida-il2cpp-bridge

Frida NPM

Frida module to dump, manipulate and hijack any IL2CPP application at runtime with a high level of abstraction, without needing the global-metadata.dat file.

import "frida-il2cpp-bridge";

async function main() {
    await Il2Cpp.initialize();
    
    const TestAssembly = Il2Cpp.Domain.reference.assemblies["Test.Assembly"].image;
    
    TestAssembly.classes.TestClass.methods.testMethod.intercept({
        onLeave(returnValue) { 
            const testObject = returnValue.value as Il2Cpp.Object;
            testObject.fields.testField.value = 100;
            testObject.methods.newTestMethod.invoke(false, Il2Cpp.String.from("testString"));
        }
    });
    
    TestAssembly.classes.NewTestClass.trace();
}

main().catch(error => console.log(error.stack));

Version support

Thanks to Il2CppInspector for providing the headers.

Platform support

Project setup

Please take a look at the example folder. You can download it here.

Add to an existing project

npm install --save-dev frida-il2cpp-bridge

You may need to include "moduleResolution": "node" in your tsconfig.json.

Known limitations

  • Lack of support for reference types (e.g. System.Boolean&)
  • Absent generic classes or methods utilities
  • Missing traceback system
  • Probably a lot more

Snippets

Initialization
import "frida-il2cpp-bridge";

async function main() {
    await Il2Cpp.initialize();
}

main().catch(error => console.log(error.stack));
Dump

Make sure the target has write-permissions to the destination.

Il2Cpp.dump("/full/path/to/file.cs");

If you don't provide a path, it will be automatically detected. For instance, this will be /storage/emulated/0/Android/data/com.example.application/files/com.example.application_1.2.3.cs on Android.

Il2Cpp.dump();

This will produce something like:

// mscorlib.dll
class <Module>
{
}

// mscorlib.dll
class Locale : System.Object
{
    static System.String GetText(System.String msg); // 0x01fbb020
    static System.String GetText(System.String fmt, System.Object[] args); // 0x01803a38
}
Find instances
const mscorlib = Il2Cpp.Domain.reference.assemblies.mscorlib.image;
const SystemType = mscorlib.classes["System.Type"];

Il2Cpp.GC.choose(SystemType).forEach(instance => {
    // Do whatever you want
    assert(instance.class.type.name == "System.Type");
});

Alternatively

const mscorlib = Il2Cpp.Domain.reference.assemblies.mscorlib.image;
const SystemString = mscorlib.classes["System.String"];

new Il2Cpp.MemorySnapshot().objects.filter(Il2Cpp.Filtering.IsExactly(SystemString)).forEach(instance => {
   // Do whatever you want
   assert(instance.class.type.name == "System.Type[]");
});
Class tracing
const mscorlib = Il2Cpp.Domain.reference.assemblies.mscorlib.image;
const SystemString = mscorlib.classes["System.String"];

SystemString.trace();

It will log something like:

[il2cpp] 0x015ed550 get_Chars
[il2cpp] 0x005602f0 FastAllocateString
[il2cpp] 0x00ab497c wstrcpy
[il2cpp] 0x01a62bc0 IsNullOrEmpty
[il2cpp] 0x015ed550 get_Chars
[il2cpp] 0x015ed550 get_Chars
Method tracing
const mscorlib = Il2Cpp.Domain.reference.assemblies.mscorlib.image;
const IsNullOrEmpty = mscorlib.classes["System.String"].methods.IsNullOrEmpty;

IsNullOrEmpty.trace();

It will log something like:

[il2cpp] 0x01a62bc0 IsNullOrEmpty
[il2cpp] 0x01a62bc0 IsNullOrEmpty
[il2cpp] 0x01a62bc0 IsNullOrEmpty
Method replacement
const mscorlib = Il2Cpp.Domain.reference.assemblies.mscorlib.image;
const IsNullOrEmpty = mscorlib.classes["System.String"].methods.IsNullOrEmpty;

IsNullOrEmpty.implementation = (instance, parameters) => {
    parameters.value.value = Il2Cpp.String.from("Hello!");
    return 0;
};

Later on, to revert the replacement:

IsNullOrEmpty.implementation = null;
Method interception

You can replace any of the parameters and the return value by reassigning them.

const mscorlib = Il2Cpp.Domain.reference.assemblies.mscorlib.image;
const SystemString = mscorlib.classes["System.String"];

SystemString.methods.IsNullOrEmpty.intercept({
    onEnter(instance, parameters) {
        parameters.value = Il2Cpp.String.from("Replaced!");
        assert((parameters.value.value as Il2Cpp.String).content == "Replaced!");
    },
    onLeave(returnValue) {
        returnValue.value = true;
    }
});

API

Il2Cpp.Array

It's not possible to add or remove an array element at the moment.

const mscorlib = Il2Cpp.Domain.reference.assemblies.mscorlib.image;
const SystemString = mscorlib.classes["System.String"];

const arr = Il2Cpp.Array.from<Il2Cpp.String>(SystemString, [
    Il2Cpp.String.from("One"), Il2Cpp.String.from("Two"), Il2Cpp.String.from("Three")
]);

assert(arr.elementSize == StringClass.arrayElementSize);

assert(arr.length == 3);

assert(arr.object.class.type.name == "System.String[]");

assert(arr.elementType.name == "System.String");

assert(Array.from(arr).join(",") == "One,Two,Three");

assert(arr.get(0).content == "One");
arr.set(0, Il2Cpp.String.from("Replaced"));
assert(arr.get(0).content == "Replaced");
Il2Cpp.Assembly
const mscorlib = Il2Cpp.Domain.reference.assemblies.mscorlib.image;

assert(mscorlib.name == "mscorlib");
Il2Cpp.Class
const mscorlib = Il2Cpp.Domain.reference.assemblies.mscorlib.image;

const BooleanClass = mscorlib.classes["System.Boolean"];
const Int32Class = mscorlib.classes["System.Int32"];
const Int64Class = mscorlib.classes["System.Int64"];
const ObjectClass = mscorlib.classes["System.Object"];
const DayOfWeekClass = mscorlib.classes["System.DayOfWeek"];
const MathClass = mscorlib.classes["System.Math"];
const IFormattableClass = mscorlib.classes["System.IFormattable"];
const ExecutionContextClass = mscorlib.classes["System.Threading.ExecutionContext"];
const ExecutionContextFlagsClass = mscorlib.classes["System.Threading.ExecutionContext.Flags"];

assert(BooleanClass.arrayClass.name == "Boolean[]");

assert(Int32Class.arrayElementSize == 4);
assert(Int64Class.arrayElementSize == 8);
assert(ObjectClass.arrayElementSize == Process.pointerSize);

assert(BooleanClass.arrayClass.elementClass?.name == "Boolean");

assert(ExecutionContextFlagsClass.declaringClass!.handle.equals(ExecutionContextClass.handle));

assert(Int32Class.hasStaticConstructor == ".cctor" in Int32Class.methods);

assert(Int32Class.image.name == "mscorlib.dll");

assert(DayOfWeekClass.isEnum);
assert(!ObjectClass.isEnum);

assert(IFormattableClass.isInterface);
assert(!ObjectClass.isInterface);

if (!MathClass.isStaticConstructorFinished) {
 MathClass.initialize();
 assert(MathClass.isStaticConstructorFinished);
}

assert(Int32Class.isValueType);
assert(!ObjectClass.isValueType);

assert(BooleanClass.name == "Boolean");

assert(BooleanClass.namespace == "System");

assert(BooleanClass.parent!.type.name == "System.ValueType");
assert(ObjectClass.parent == null);

assert(BooleanClass.type.name == "System.Boolean");

Il2Cpp.Domain

assert(Il2Cpp.Domain.reference.name == "IL2CPP Root Domain");

Il2Cpp.Field

const mscorlib = Il2Cpp.Domain.reference.assemblies.mscorlib.image;
const coreModule = Il2Cpp.Domain.reference.assemblies["UnityEngine.CoreModule"].image;

const BooleanClass = mscorlib.classes["System.Boolean"];
const MathClass = mscorlib.classes["System.Math"];
const ThreadClass = mscorlib.classes["System.Threading.Thread"];
const Vector2Class = coreModule.classes["UnityEngine.Vector2"];

assert(MathClass.fields.PI.class.handle.equals(MathClass.handle));

assert(!Vector2Class.fields.x.isStatic);
assert(Vector2Class.fields.oneVector.isStatic);

assert(MathClass.fields.PI.isLiteral);

assert(ThreadClass.fields.current_thread.isThreadStatic);
assert(!ThreadClass.fields.m_Delegate.isThreadStatic);

assert(BooleanClass.fields.TrueLiteral.name == "TrueLiteral");

assert(MathClass.fields.PI.type.name == "System.Double");

const vec = Vector2Class.fields.oneVector.value as Il2Cpp.ValueType;
assert(vec.fields.x.value == 1);
assert(vec.fields.y.value == 1);

vec.fields.x.value = 42;
assert(vec.fields.x.value == 42);

Il2Cpp.Image

const mscorlib = Il2Cpp.Domain.reference.assemblies.mscorlib.image;

assert(mscorlib.name == "mscorlib.dll");

Il2Cpp.Method

const mscorlib = Il2Cpp.Domain.reference.assemblies.mscorlib.image;

const BooleanClass = mscorlib.classes["System.Boolean"];
const ArrayClass = mscorlib.classes["System.Array"];
const MathClass = mscorlib.classes["System.Math"];

assert(MathClass.methods.Sqrt.class.handle.equals(MathClass.handle));

assert(ArrayClass.methods.Empty.isGeneric);

assert(!BooleanClass.methods.ToString.isStatic);
assert(!BooleanClass.methods.Parse.isStatic);

assert(MathClass.methods.Sqrt.name == "Sqrt");

assert(MathClass.methods[".cctor"].parameterCount == 0);
assert(MathClass.methods.Abs.parameterCount == 1);
assert(MathClass.methods.Max.parameterCount == 2);

assert(BooleanClass.methods.Parse.invoke<boolean>(Il2Cpp.String.from("true")));

MathClass.methods.Max.implementation = (_instance, parameters) => {
 const val1 = parameters.val1.value as number;
 const val2 = parameters.val2.value as number;
 return val1 > val2 ? val2 : val1;
};
assert(MathClass.methods.Max.invoke<number>(1, 2) == 1);

MathClass.methods.Max.implementation = null;
assert(MathClass.methods.Max.invoke<number>(1, 2) == 2);

MathClass.methods.Max.intercept({
 onEnter(_instance, parameters) {
  parameters.val1.value = 10;
 }
});
assert(MathClass.methods.Max.invoke<number>(1, 2) == 10);

Il2Cpp.Object

const mscorlib = Il2Cpp.Domain.reference.assemblies.mscorlib.image;
const coreModule = Il2Cpp.Domain.reference.assemblies["UnityEngine.CoreModule"].image;

const OrdinalComparerClass = mscorlib.classes["System.OrdinalComparer"];
const Vector2Class = coreModule.classes["UnityEngine.Vector2"];

const ordinalComparer = Il2Cpp.Object.from(OrdinalComparerClass);
assert(ordinalComparer.class.name == "OrdinalComparer");
assert(ordinalComparer.base.class.name == "StringComparer");

const vec = Il2Cpp.Object.from(Vector2Class);
vec.methods[".ctor"].invoke(36, 4);

const vecUnboxed = vec.unbox();
assert(vec.fields.x.value == vecUnboxed.fields.x.value);
assert(vec.fields.y.value == vecUnboxed.fields.y.value);

const vecBoxed = vecUnboxed.box();
assert(vecBoxed.fields.x.value == vecUnboxed.fields.x.value);
assert(vecBoxed.fields.y.value == vecUnboxed.fields.y.value);

assert(!vecBoxed.handle.equals(vec.handle));

Il2Cpp.Parameter

const mscorlib = Il2Cpp.Domain.reference.assemblies.mscorlib.image;

const dParameter = mscorlib.classes["System.Math"].methods.Sqrt.parameters.d;

assert(dParameter.name == "d");

assert(dParameter.position == 0);

assert(dParameter.type.name == "System.Double");

Il2Cpp.String

const str = Il2Cpp.String.from("Hello!");

assert(str.content == "Hello!");

str.content = "Bye";
assert(str.content == "Bye");

assert(str.length == 3);
assert(str.content?.length == 3);

assert(str.object.class.type.name == "System.String");
assert(str.object.class.type.typeEnum == "string");

Il2Cpp.Type

const mscorlib = Il2Cpp.Domain.reference.assemblies.mscorlib.image;

const Int32Class = mscorlib.classes["System.Int32"];
const StringClass = mscorlib.classes["System.String"];
const ObjectClass = mscorlib.classes["System.Object"];

assert(StringClass.type.class.handle.equals(StringClass.handle));

const array = Il2Cpp.Array.from<number>(Int32Class, [0, 1, 2, 3, 4]);
assert(array.object.class.type.name == "System.Int32[]");
assert(array.object.class.type.dataType?.name == "System.Int32");

assert(StringClass.type.name == "System.String");

assert(Int32Class.type.typeEnum == "i4");
assert(ObjectClass.type.typeEnum == "object");

Il2Cpp.ValueType

const coreModule = Il2Cpp.Domain.reference.assemblies["UnityEngine.CoreModule"].image;

const Vector2Class = coreModule.classes["UnityEngine.Vector2"];

const vec = Vector2Class.fields.positiveInfinityVector.value as Il2Cpp.ValueType;

assert(vec.class.type.name == "UnityEngine.Vector2");

assert(vec.fields.x.value == Infinity);
assert(vec.fields.y.value == Infinity);

About

Frida module to debug, dump, manipulate or hijack any IL2CPP application at runtime with a high level of abstraction, without needing the global-metadata.dat file.

License:MIT License


Languages

Language:TypeScript 100.0%