frida-il2cpp-bridge icon indicating copy to clipboard operation
frida-il2cpp-bridge copied to clipboard

Fix tracer return value parse error

Open commonuserlol opened this issue 1 year ago • 16 comments

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

commonuserlol avatar Feb 06 '24 19:02 commonuserlol

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?

vfsfitvnm avatar Feb 07 '24 20:02 vfsfitvnm

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 avatar Feb 07 '24 20:02 commonuserlol

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 avatar Feb 07 '24 21:02 commonuserlol

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

commonuserlol avatar Feb 08 '24 22:02 commonuserlol

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?

vfsfitvnm avatar Feb 08 '24 22:02 vfsfitvnm

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 avatar Feb 08 '24 22:02 commonuserlol

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

commonuserlol avatar Feb 08 '24 22:02 commonuserlol

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.

Flechaa avatar Feb 08 '24 23:02 Flechaa

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 avatar Feb 09 '24 09:02 commonuserlol

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

commonuserlol avatar Feb 09 '24 10:02 commonuserlol

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

vfsfitvnm avatar Feb 11 '24 21:02 vfsfitvnm

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

commonuserlol avatar Feb 11 '24 21:02 commonuserlol

ping

commonuserlol avatar Feb 15 '24 19:02 commonuserlol

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.

AsukaWhite avatar Apr 19 '24 18:04 AsukaWhite

@vfsfitvnm please review this pr

commonuserlol avatar Jul 21 '24 12:07 commonuserlol

@vfsfitvnm ping

Vladik01-11 avatar Aug 07 '24 18:08 Vladik01-11

@commonuserlol Hi, I think I'm having the same issue here. I've tried building your fork and there were 4 errors before, but now there are only 2 left. Could you please take a look? I'd really appreciate it.

- Error: abort was called
-   at invokeRaw (structs/method.ts:234)
-   at toString (structs/object.ts:58)  
-   at concat (native)
-   at <anonymous> (tracer.ts:297)      
-   at map (native)
-   at callback (tracer.ts:297)
- Error: access violation accessing 0x10  
-   at get length (structs/string.ts:26)
-   at get content (structs/string.ts:5)
-   at toString (structs/object.ts:58)  
-   at concat (native)
-   at <anonymous> (tracer.ts:297)      
-   at map (native)
-   at callback (tracer.ts:297)

fakekey avatar Aug 10 '24 18:08 fakekey

@fakekey Hi, I haven't these error when tested, can you tell game name (or send link)

commonuserlol avatar Aug 10 '24 19:08 commonuserlol

@fakekey Hi, I haven't these error when tested, can you tell game name (or send link)

I can suggest you take a look at games.flexus.trainminer, in fact there are the same mistakes that @fakekey said

Vladik01-11 avatar Aug 10 '24 19:08 Vladik01-11

I see. I think we shouldn't flood there. Please create issue on my repo or you can DM me at discord (same name as on github) to track it. I'll look into tomorrow

commonuserlol avatar Aug 10 '24 19:08 commonuserlol

Hi @fakekey, @Vladik01-11 - reproduced, pushed and tested on games.flexus.trainminer. Should be fine now

commonuserlol avatar Aug 11 '24 10:08 commonuserlol

Confirmed by fakekey (via discord)

commonuserlol avatar Aug 11 '24 18:08 commonuserlol

Reverted due return value parsed incorrectly.

commonuserlol avatar Aug 17 '24 12:08 commonuserlol