vfsfitvnm / frida-il2cpp-bridge

A Frida module to dump, trace or hijack any Il2Cpp application at runtime, without needing the global-metadata.dat file.
https://github.com/vfsfitvnm/frida-il2cpp-bridge/wiki
MIT License
946 stars 194 forks source link

Add a way to get an array of generic types from a class / method #312

Closed Flechaa closed 1 year ago

Flechaa commented 1 year ago

Sometimes it might be useful to get all the generic types from a class and then inflate it into another (copying the generics).

I was able to achieve this by doing this:

Class.type.object.method<Il2Cpp.Array>("GetGenericArguments").invoke()

Based on https://github.com/vfsfitvnm/frida-il2cpp-bridge/blob/ae7ef9ea2ca0e33fe50994179b2a4751541549e4/src/il2cpp/structs/class.ts#L81

The only problem is that I am always assuming the generic is an object which I don't know for fact if this will always be true.

Here's a snippet that could be shortened:

const classes = [...Class.type.object.method<Il2Cpp.Array<Il2Cpp.Object>>("GetGenericArguments").invoke()].map(type => type.class);
const Action = Il2Cpp.corlib.class(`System.Action\`${types.length}`).inflate(...classes)
vfsfitvnm commented 1 year ago

It looks like a good idea.

Here's how you can convert a System.Array<System.Type> to a Il2Cpp.Class[]: https://github.com/vfsfitvnm/frida-il2cpp-bridge/blob/ae7ef9ea2ca0e33fe50994179b2a4751541549e4/src/il2cpp/structs/image.ts#L24

If you want to take a stab for a PR, go for it

Flechaa commented 1 year ago

It looks like a good idea.

Here's how you can convert a System.Array<System.Type> to a Il2Cpp.Class[]:

https://github.com/vfsfitvnm/frida-il2cpp-bridge/blob/ae7ef9ea2ca0e33fe50994179b2a4751541549e4/src/il2cpp/structs/image.ts#L24

If you want to take a stab for a PR, go for it

While doing it I discovered the following doesn't work as expected, at least in the android app I tested on.

const Action = Il2Cpp.corlib.class("System.Action`1").inflate(Il2Cpp.corlib.class("System.String"));
console.log(`genericParamCounter = ${Action.genericParameterCount}`);
console.log(`GetGenericArguments result = ${Array.from(Action.type.object.method<Il2Cpp.Array<Il2Cpp.Object>>("GetGenericArguments").invoke()).map(_ => new Il2Cpp.Class(Il2Cpp.api.classFromObject(_))).length}`);

Console:

genericParamCounter = 0
GetGenericArguments result = 1

It seems that it comes from this: https://github.com/vfsfitvnm/frida-il2cpp-bridge/blob/ae7ef9ea2ca0e33fe50994179b2a4751541549e4/src/il2cpp/structs/class.ts#L77

vfsfitvnm commented 1 year ago

Hmm good catch. If I recall correctly, I added that check because GetGenericArguments throws an error if the class isn't generic. However, I think that check should ne removed.

However, the method I use to determine whether a class is generic comes from IL2CPP, and they make a distinction between generics (e.g. System.Action<T>) and inflateds (e.g. System.Action<System.Object>), and thus inflateds are not generics for them.

I should take a look at the code base again to see what we could do about that.

Flechaa commented 1 year ago

When I tried to remove the check my script started to timeout for whatever reason, I was expecting different behavior. I didn't know generics and inflateds were different, so the correct name for the getter would be inflateds, right?

vfsfitvnm commented 1 year ago

generics is fine imho. Regarding the timeout, it means an exception has occurred. You can verifiy that by adding Il2Cpp.installExceptionListener("all");.

Another thing frida-il2cpp-bridge lacks of is a proper exception detection mechanism. Right now, invoke simply calls the RVA of that method; however, we could use il2cpp_runtime_invoke instead, which wraps the call in a try/catch block. That would solve a lot of problems, but it would introduce an additional overhead for every method call. I didn't iterate on this idea yet, but I think it would be a wise choice.

So yeah, the check is still required (for now). We could either use a C# method to determine if a method is generic (but only inside generics and not elsewhere) or use il2cpp_runtime_invoke to call GetGenericArguments.

PS: genericParametersCount can be entirely removed.

vfsfitvnm commented 1 year ago

I forgot the existence of Il2Cpp.Class::isInflated - generics could just check for this.isGeneric || this.isInflated