Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix tracer return value parse error #483

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

commonuserlol
Copy link

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
Copy link
Owner

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
Copy link
Author

commonuserlol commented Feb 7, 2024

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
Copy link
Author

commonuserlol commented Feb 7, 2024

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
Copy link
Author

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
Copy link
Owner

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
Copy link
Author

commonuserlol commented Feb 8, 2024

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
Copy link
Author

commonuserlol commented Feb 8, 2024

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

@Flechaa
Copy link
Contributor

Flechaa commented Feb 8, 2024

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
Copy link
Author

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
Copy link
Author

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
Copy link
Owner

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
Copy link
Author

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

@commonuserlol
Copy link
Author

ping

@commonuserlol
Copy link
Author

one more ping

@commonuserlol
Copy link
Author

huh

@AsukaWhite
Copy link

Sorry for bothering. looks like you are familiar with this module. would you mind help my question in issue?
#506
it tortures me, thanks in advance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants