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
974 stars 199 forks source link

add filter start methods to Tracer #271

Closed WankkoRee closed 1 year ago

WankkoRee commented 1 year ago

use it can know that what methods called by special methods.

just like: A call B, B call C, C call D. And all of them are in same assembly or class, so I can attach the assembly or class and specify A as start method, then I can know A->B->C->D.

e.g. :

AssemblyCSharp=Il2Cpp.Domain.tryAssembly("Assembly-CSharp")
ShopModel=AssemblyCSharp.image.tryClass("ShopModel")
ShopModel_Save=ShopModel.tryMethod("Save")

Il2Cpp.trace().assemblies(AssemblyCSharp).and().methods(ShopModel_Save).start().attach()

then I can know what methods called by ShopModel.Save.

image

In the past:


Because not familiar with this project, there may be some bugs in the code. I just verified this way is feasible.

vfsfitvnm commented 1 year ago

Thanks for contributing!

If I got this correctly, you want to log the call trees that have a certain method(s) as root (can we name it foretracer?). This is handy if you want to log the methods that are called by a designated method.

This is an interesting use case for sure :smiley:

However, I think there are a better API and implementation that can address this use case (checking if a given entry is member of an array on every call is a nightmare for performance).

We should deep dive into this, i.e. probably Stalker is the right tool for this.

WankkoRee commented 1 year ago

yes, it would make the performance degradation if check at every time, so may it need to separate into a class (may named FrontTrace?🤔) or just try optimize the judgment way?

WankkoRee commented 1 year ago

is it right way to use Stalker?

axhlzy / Il2CppHookScripts - Il2cppHook/agent/base/extends.ts#L456-L508

I have try to use it, but it will be segmentation fault after log one time.

vfsfitvnm commented 1 year ago

so may it need to separate into a class (may named FrontTrace?

This is a possibility. However, I don't know if it makes sense to keep the current AbstractTracer as it is.

is it right way to use Stalker?

Err, I never used Stalker once! That's why I need to investigate.

350030173 commented 1 year ago

frida stalker https://github.com/iGio90/Hooah-Trace

vfsfitvnm commented 1 year ago

I played with Stalker a little bit, but I couldn't figure out a way to effectively use it to replace the current tracing system: it looks like it misses few method calls...

vfsfitvnm commented 1 year ago

I'm leaving this work in progress snippet here for housekeeping purposes.

const map = new Map<string, Il2Cpp.Method>();

Il2Cpp.domain.assemblies.forEach(_ => {
    _.image.classes.forEach(_ => {
        _.methods.forEach(_ => {
            map.set(_.virtualAddress.toString(), _);
        });
    });
});

function isInModule(address: NativePointer) {
    return address.compare(Il2Cpp.module.base) > 0 && address.compare(Il2Cpp.module.base.add(Il2Cpp.module.size)) < 0;
}

function callAddress({ type, value }: X86Operand, context: X64CpuContext): NativePointer {
    switch (type) {
        case "imm":
            return ptr(value.toString());
        case "reg":
            return context[value as keyof X64CpuContext];
        case "mem":
            return context[value.base as keyof X64CpuContext].add(value.disp).readPointer();
        default:
            return NULL;
    }
}

const events: string[] = [];

Stalker.follow(Il2Cpp.mainThread.id, {
    transform(iterator: StalkerX86Iterator) {
        let instruction: X86Instruction | null;

        while ((instruction = iterator.next()) != null) {
            if (isInModule(instruction.address) && (instruction.mnemonic == "jmp" || instruction.mnemonic == "call")) {
                const operand = instruction.operands[0];

                iterator.putCallout(_ => {
                    const method = map.get(callAddress(operand, _ as X64CpuContext).toString());
                    if (method) {
                        events.push(`-> \x1B[3m\x1B[2m${method.class.type.name}::\x1B[0m${method.name}`);
                    }

                    if (events.length == 128) {
                        console.log(events.join("\n"));
                        events.length = 0;
                    }
                });
            }

            iterator.keep();
        }
    }
});
vfsfitvnm commented 1 year ago

Hey, I took some spare time to figure out how this feature should be implemented. Unfortunately it doesn't look like this is a common use case so that it should be part of the code base (especially considering the amount of lines of code!).

However, v0.8.1 lets you pass a custom applier to Il2Cpp::Tracer, so that you don't need to modify the source code:

function foretrace(...methods: Il2Cpp.Method[]): Il2Cpp.Tracer {
    const starters = new Set<number>(methods.map(_ => _.virtualAddress.toUInt32()));

    const applier = (): Il2Cpp.Tracer.Apply => (method, state, threadId) => {
        const paddedVirtualAddress = method.relativeVirtualAddress.toString(16).padStart(8, "0");

        Interceptor.attach(method.virtualAddress, {
            onEnter() {
                this.flag = this.threadId == threadId && (state.depth > 0 || starters.has(method.virtualAddress.toInt32()));
                if (this.flag) {
                    // prettier-ignore
                    state.buffer.push(`\x1b[2m0x${paddedVirtualAddress}\x1b[0m ${`│ `.repeat(state.depth++)}┌─\x1b[35m${method.class.type.name}::\x1b[1m${method.name}\x1b[0m\x1b[0m`);
                }
            },
            onLeave() {
                if (this.flag) {
                    // 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`);
                    state.flush();
                }
            }
        });
    };

    return new Il2Cpp.Tracer(applier());
}

Hopefully, I can iterate on the API so that it doesn't take much code, possibly:

const starters = new Set<number>(methods.map(_ => _.virtualAddress.toUInt32()));
Il2Cpp.trace()
    .logCondition((state, method) => state.depth > 0 || starters.has(method.virtualAddress.toInt32())
    // ...