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
918 stars 191 forks source link

Fix tracer return value parse error #483

Open commonuserlol opened 5 months ago

commonuserlol commented 5 months ago

When a method definition does not include an null value (example: MyClass getMyClass();) but something went wrong (or null check was stripped by il2cpp compiler) it can return NULL so Error: abort was called will be thrown (I think because NativeFunction expected pointer return type but got null) One more note, "real" IL2CPP code checks if the value is null (at least ghidra decompiler shows that), so nothing bad should happen

Before:

il2cpp: 
0x00d42bc0 ┌─MyClass::.ctor(this =  (MyClass))
0x00d42bc0 └─MyClass::.ctor

Error: abort was called
    at callback (/home/commonuserlol/index.js:1316)

After:

il2cpp: 
0x00d42bc0 ┌─MyClass::.ctor(this =  (MyClass))
0x00d42bc0 └─MyClass::.ctor

il2cpp: 
0x00d41af4 ┌─MyClass::OnEnable(this = SOME_VIEW(Clone) (MyClass))
0x00d41e48 │ ┌─MyClass::GetNextReward(this = SOME_VIEW(Clone) (MyClass))
0x00d41e48 │ └─MyClass::GetNextReward = null
0x00d422c8 │ ┌─MyClass::ParseReward(this = SOME_VIEW(Clone) (MyClass), reward = null)
0x00d421f0 │ │ ┌─MyClass::OnOkButton(this = null)
0x00d421f0 │ │ └─MyClass::OnOkButton
0x00d422c8 │ └─MyClass::ParseReward
0x00d41af4 └─MyClass::OnEnable
vfsfitvnm commented 5 months ago

Thanks, I'm fine with that. But perhaps it would be more meaningful if we reported it, even a simple message is fine something like:

0x00d41e48 │ ┌─MyClass::GetNextReward(this = SOME_VIEW(Clone) (MyClass))
0x00d41e48 │ └─MyClass::GetNextReward = null [native IL2CPP excpetion occurred]

Bonus points if you make it red!

What do you think?

commonuserlol commented 5 months ago

I haven’t seen something similar here, how would you implement a value check??

  1. ret.equals(NULL) ? "err" : (ret == undefined ? "" : "formatted ret")
  2. if (ret.equals(NULL)) ...; else if (...); else ...
commonuserlol commented 5 months ago

Okay, now it looks like

0x00d41e48 │ ┌─MyClass::GetNextReward(this = SOME_VIEW(Clone) (MyClass))
0x00d41e48 │ └─MyClass::GetNextReward = null [native IL2CPP excpetion occurred]

Also colored like error from console.ts/raise image

I did with 1st variant from my previous message (not commited yet)

                    const result = returnValue == undefined ? ""
                        : (returnValue.equals(NULL) ?
                        " = \x1b[0m\x1b[38;5;9mnull [native IL2CPP excpetion occurred]\x1b[0m"
                        : ` = \x1b[36m${fromFridaValue(returnValue, method.returnType)}\x1b[0m`);

If you prefer other style let me know

commonuserlol commented 5 months ago

Correct impl is

                    const result = returnValue == undefined ? "" :
                        returnValue instanceof NativePointer ?
                            returnValue.equals(NULL)
                            ? " = \x1b[0m\x1b[38;5;9mnull [native IL2CPP excpetion occurred]\x1b[0m"
                            : ` = \x1b[36m${fromFridaValue(returnValue, method.returnType)}\x1b[0m`
                        : ` = \x1b[36m${fromFridaValue(returnValue, method.returnType)}\x1b[0m`;

now tracer works (in prev piece of code it died when returnValue was non-native pointer) Still waiting for answer about codestyle

vfsfitvnm commented 5 months ago

Hmm, this is how I would do it:


                let returnValue;
                let isError = false;
                try {
                    returnValue = method.nativeFunction(...args);
                } catch (_) {
                    isError =  true;
                }

                if ((this as InvocationContext).threadId == threadId) {
                    // prettier-ignore
                    state.buffer.push(`\x1b[2m0x${paddedVirtualAddress}\x1b[0m ${`│ `.repeat(--state.depth)}└─\x1b[33m${method.class.type.name}::\x1b[1m${method.name}\x1b[0m\x1b[0m${returnValue == undefined ? "" : ` = \x1b[36m${fromFridaValue(returnValue, method.returnType)}`}\x1b[0m${isError ? " \x1b[38;5;9m[native IL2CPP excpetion occurred]\x1b[0m" : ""}`);
                    state.flush();
                }

                return isError ? NULL : returnValue;

However, I'm wondering what happens in case Frida expect us to return a number (let's say an exception occurs within System.In32 Foo();) - I believe we can't always return NULL (a NativePointer), can we?

commonuserlol commented 5 months ago

Il2Cpp compiled code already have checks for NativePointer but for number it can be UB (undefined behavior) as c++ have it. Frida can interpret it as 0 afaik. Probably easiest way to check is replacing some method with Int32 return type (NativeFunction provides exceptions option which stops generating JS exception and app should handle it by itself) and throw some exception in it. But lemme test this tomorrow (here is 0:45)

commonuserlol commented 5 months ago

Actually we can shift responsibility to the app (it will handle NULL), what do think about this? upd: nvm it crashes with this

Flechaa commented 5 months ago

Hmm, this is how I would do it:

                let returnValue;
                let isError = false;
                try {
                    returnValue = method.nativeFunction(...args);
                } catch (_) {
                    isError =  true;
                }

                if ((this as InvocationContext).threadId == threadId) {
                    // prettier-ignore
                    state.buffer.push(`\x1b[2m0x${paddedVirtualAddress}\x1b[0m ${`│ `.repeat(--state.depth)}└─\x1b[33m${method.class.type.name}::\x1b[1m${method.name}\x1b[0m\x1b[0m${returnValue == undefined ? "" : ` = \x1b[36m${fromFridaValue(returnValue, method.returnType)}`}\x1b[0m${isError ? " \x1b[38;5;9m[native IL2CPP excpetion occurred]\x1b[0m" : ""}`);
                    state.flush();
                }

                return isError ? NULL : returnValue;

However, I'm wondering what happens in case Frida expect us to return a number (let's say an exception occurs within System.In32 Foo();) - I believe we can't always return NULL (a NativePointer), can we?

By the way, there's a typo here, it should be exception not excpetion.

commonuserlol commented 5 months ago

I found method which already throws error (but target still works)

il2cpp: System.NullReferenceException: Object reference not set to an instance of an object.
  at User.GetBalance (System.String a) [0x00000] in <00000000000000000000000000000000>:0
il2cpp:
0x00ee1ad4 ┌─User::GetBalance(this = name, a = "remove_ads")
0x00ee1ad4 └─User::GetBalance = null [native IL2CPP excpetion occurred]

Error: expected an integer // due returning NULL

after commit:

il2cpp:
0x00ee1ad4 ┌─User::GetBalance(this = name, a = "remove_ads")
0x00ee1ad4 └─User::GetBalance = 0 [native IL2CPP exception occurred]
// No Error: expected an integer

For other types it should work

commonuserlol commented 5 months ago

Note: void is fine with both NULL and 0 (not sure it's your code or frida default behavior), tested with:

    Backend.method("HandleResponse").implementation = function (req) {
        this.method<void>("HandleResponse").invoke(req);
        // let's imagine that an exception was caught (abort was called), we returning null
        console.log("test: return NULL for System.Void HandleResponse(HttpRequest request);");
        console.log(`isPrimitive for original ret (void): ${this.method<void>("HandleResponse").returnType.isPrimitive}`)
        return NULL;
    }

output:

test: return NULL for System.Void HandleResponse(HttpRequest request);
isPrimitive for original ret (void): false
// No error, 0 is ok too
vfsfitvnm commented 5 months ago
const cm = ((globalThis as any).cm = new CModule(`int lol(void) { return 1; }`));
Interceptor.replace(cm.lol, new NativeCallback(() => NULL as any, "int", []));
console.log(new NativeFunction(cm.lol, "int", [])());

I get Error: expected an integer

commonuserlol commented 5 months ago

Look, i pushed one more commit which does method.returnValue.isPrimitive ? 0 : NULL (int/System.Int32 is primitive)

commonuserlol commented 5 months ago

ping

commonuserlol commented 4 months ago

one more ping

commonuserlol commented 3 months ago

huh

AsukaWhite commented 2 months ago

Sorry for bothering. looks like you are familiar with this module. would you mind help my question in issue? https://github.com/vfsfitvnm/frida-il2cpp-bridge/issues/506 it tortures me, thanks in advance.