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
1k stars 200 forks source link

Unable to properly hook or call virtual methods #73

Closed RealIndica closed 2 years ago

RealIndica commented 2 years ago

I have method A :

public virtual void AddDialog(UIDialog screen)
{
      doStuff(screen);
}

And attempting to hook it does work, but the result of the hook does not change the outcome. The implementation below will still be called, but it will not change what the function does and will always return the original result from the hooked function.

targetClass.methods.AddDialog.implementation = function (dialog: IL2CPP.Object) {
      //do nothing
}

Attempting to change the methods outcome I use this code:

targetClass.methods.AddDialog.implementation = function (dialog: IL2CPP.Object) {
     var Instance = new IL2CPP.Object(this.handle);
     var newDialog = myDialog;
     return Instance.methods.AddDialog.invoke(myDialog);
}

That will then throw an error like so:

Error: Couldn't find property "AddDialog", did you mean ".ctor"?
    at get (node_modules/frida-il2cpp-bridge/dist/utils/utils.js:29)
    at <anonymous> (agent/index.ts:187)
    at call (native)
    at replaceCallback (node_modules/frida-il2cpp-bridge/dist/il2cpp/structs/method.js:124)

Any idea how I can go about this?

Thank you and merry christmas! (If you're into the xmas stuff. If not... Happy holidays!)

vfsfitvnm commented 2 years ago

Hi, would you attach the relevant classes (the ones you get from Il2Cpp.dump().classes().build()?

RealIndica commented 2 years ago

Hi, would you attach the relevant classes (the ones you get from Il2Cpp.dump().classes().build()?

Yes i will get them in a bit, been busy through the christmas season haha

RealIndica commented 2 years ago
// Assembly-CSharp.dll
class BeatStarUI.UICanvas : UnityEngine.MonoBehaviour
{
    static System.Int32 SortOrder_BottomLayer = -2000;
    static System.Int32 SortOrder_HudLayer = -1000;
    static System.Int32 SortOrder_TopLayer = 2000;
    static System.Int32 SortOrder_DialogLayer = 2200;
    static System.Int32 SortOrder_TransitionLayer = 3000;
    SpaceApe.EventBus.EventBus eventBus; // 0x18
    UnityEngine.RectTransform layerParent; // 0x20
    UnityEngine.RectTransform <BottomLayer>k__BackingField; // 0x28
    UnityEngine.RectTransform <HudLayer>k__BackingField; // 0x30
    UnityEngine.RectTransform <DialogLayer>k__BackingField; // 0x38
    UnityEngine.RectTransform <TopLayer>k__BackingField; // 0x40
    UnityEngine.RectTransform <TransitionLayer>k__BackingField; // 0x48
    UnityEngine.GameObject safeRegionObj; // 0x50
    UnityEngine.Canvas <Canvas>k__BackingField; // 0x58
    UnityEngine.UI.CanvasScaler <CanvasScaler>k__BackingField; // 0x60
    com.spaceape.unityui.SAUIScreenConfig <CurrentScreenConfig>k__BackingField; // 0x68
    System.Collections.Generic.List<BeatStarUI.UIScreen> screens; // 0x70
    BeatStarUI.UIDialogs dialogs; // 0x78
    System.Collections.Generic.List<UnityEngine.RectTransform> Layers; // 0x80
    System.Collections.Generic.List<com.spaceape.unityui.SAUISafeRegionConstrain> SafeRegionConstrains; // 0x88
    UnityEngine.ScreenOrientation lastOrientation; // 0x90
    UnityEngine.Rect lastSafeArea; // 0x94
    System.Int32 framesWaitedAfterChange; // 0xa4
    System.Nullable<UnityEngine.Vector2> canvasReferenceResolution; // 0xa8

    UnityEngine.RectTransform get_BottomLayer(); // 0x023ee240
    System.Void set_BottomLayer(UnityEngine.RectTransform value); // 0x023ee248
    UnityEngine.RectTransform get_HudLayer(); // 0x023ee250
    System.Void set_HudLayer(UnityEngine.RectTransform value); // 0x023ee258
    UnityEngine.RectTransform get_DialogLayer(); // 0x023ee260
    System.Void set_DialogLayer(UnityEngine.RectTransform value); // 0x023ee268
    UnityEngine.RectTransform get_TopLayer(); // 0x023ee270
    System.Void set_TopLayer(UnityEngine.RectTransform value); // 0x023ee278
    UnityEngine.RectTransform get_TransitionLayer(); // 0x023ee280
    System.Void set_TransitionLayer(UnityEngine.RectTransform value); // 0x023ee288
    UnityEngine.Canvas get_Canvas(); // 0x023ee290
    System.Void set_Canvas(UnityEngine.Canvas value); // 0x023ee298
    UnityEngine.UI.CanvasScaler get_CanvasScaler(); // 0x023ee2a0
    System.Void set_CanvasScaler(UnityEngine.UI.CanvasScaler value); // 0x023ee2a8
    com.spaceape.unityui.SAUIScreenConfig get_CurrentScreenConfig(); // 0x023ee2b0
    System.Void set_CurrentScreenConfig(com.spaceape.unityui.SAUIScreenConfig value); // 0x023ee2b8
    System.Boolean get_ShowingDialogs(); // 0x023ee2c0
    System.Void Awake(); // 0x023ee338
    System.Void Start(); // 0x023ee49c
    System.Void RefreshScreenConfig(); // 0x023ee4a0
    System.Void CreateLayers(); // 0x023eeac0
    UnityEngine.RectTransform CreateLayer(System.String name, System.Int32 sortOrder, System.Boolean constrainedInSafeRegion); // 0x023eebac
    System.Void HandleOnScreenTransitionBegin(BeatStarUI.UIScreen screen); // 0x023eedb0
    System.Void HandleOnScreenTransitionEnd(BeatStarUI.UIScreen screen); // 0x023eee88
    System.Void AddScreen(BeatStarUI.UIScreen screen, System.Action onTransitionComplete); // 0x023eef60
    System.Void AddDialog(BeatStarUI.UIDialog screen); // 0x023ef0c8 THIS ONE HERE
    System.Void OnDialogEntered(BeatStarUI.UIDialog dialog); // 0x023ef258
    System.Void ClearAllDialogEntries(); // 0x023ef350
    System.Collections.IEnumerator CloseAllDialogs(); // 0x023ef3c0
    System.Void HandleOnScreenDestroy(BeatStarUI.UIScreen screen); // 0x023ef448
    System.Boolean IsScreenShowing();
    System.Collections.IEnumerator CloseAllHudAndDialog(); // 0x023ef4b0
    System.Void SetScreenConfig(com.spaceape.unityui.SAUIScreenConfig screenConfig); // 0x023ee8a4
    System.Void ToggleSafeRegionDisplay(); // 0x023ef554
    System.Boolean HasUIConfigChanged(); // 0x023ef69c
    System.Void Update(); // 0x023ef734
    com.spaceape.unityui.SAUIScreenConfig GetUIConfig(UnityEngine.Canvas canvas); // 0x023ee540
    System.Void .ctor(); // 0x023ef780
}

This should be the only thing I need to attach, as the base class does not contain an AddDialog method either (which i shouldn't anyway, but it was something i checked anyway).

The paramater when I call the method is the right type too, error still shows when I pass the original parameter.

vfsfitvnm commented 2 years ago

@RealIndica Ok, I figured it out. This happens because the actual instance in not a BeatStarUI.UICanvas, but a GameCanvas - this one inherits from BeatStarUI.UICanvas.

The problem here is you are allocating a new Il2Cpp.Object:

 var Instance = new Il2Cpp.Object(this.handle); // btw use const

then, you are calling AddDialog on it, but GameCanvas doesn't have such method (its superclass has).

The following snippet works instead. Why?

Il2Cpp.perform(() => {
    const AssemblyCSharp = Il2Cpp.Domain.assemblies["Assembly-CSharp"].image;
    const UICanvas = AssemblyCSharp.classes["BeatStarUI.UICanvas"];

    UICanvas.methods.AddDialog.implementation = function(screen: Il2Cpp.Object) {
        return this.methods.AddDialog.invoke<void>(screen);
    }
});

Because I artificially cast the instance to the "correct" class: this is already "casted" to a BeatStarUI.UICanvas.

Yes, unfortunately I still didn't implement a nice way to call superclass methods, that's something I'll do at one point

RealIndica commented 2 years ago

I personally didn't realise that "this" returns the current instance of the class in the implementation. Thank you! But yes, it would be nice to have a nicer way to call superclass methods.

RealIndica commented 2 years ago

Sorry for re-opening the issue, but following what you told me it still won't work...? There's no errors which is good, but somehow the method is still being called fully by the target game. How can this be?

All I did, is basically copy your snippet for testing, and then commented out the return so the method does nothing. However, all UI dialogs appear normally.

Is this something to do with the game, or something to do with it being a superclass method?

RealIndica commented 2 years ago

Starting to feel that I might just be at a dead end. All I'm trying to do is create an instance of a UIDialog and show it on the screen. Probably something really stupid I'm missing.

vfsfitvnm commented 2 years ago

@RealIndica well, this is not a frida-il2cpp-bridge issue, as everything works as expected; but I can tell you more saying I don't think there's an issue at all (and if there was, it should be a frida one). My shot is you are looking at the wrong function. More over, I think displaying a custom dialog is not an easy task and involves more stuff than just a function call

RealIndica commented 2 years ago

@vfsfitvnm you are most likely right. I'm very used to modding unity games with C# mono and using unhollower for il2cpp games on PC.

You know, a wrapper for the full Unity API in frida would be awesome; it's time consuming to hook each and every function in the Unity DLLs to make new game objects, add components etc.

Something I could do, but typescript is fairly new to me and the only reason I'm okay with it is because I'm good with C languages.

vfsfitvnm commented 2 years ago

@RealIndica Yeah, a Unity API would be good but it's not something I will do (myself) as it is very boring to implement (track changes across versions, test on every platform...); I also think it should be a separate library / package.

You don't need to hook every function to create new objects, but to modify existing ones.

However, what are you trying to achieve? If you'd like to display custom ui for a mod menu, I'd suggest you to take advantage of frida's rpc methods (send, recv) or whatever the host platform offers. I once did that on android, but using BroadcastReceiver to send/receive commands.

PS: I think we can close this one, please open a post on Discussions tab.

RealIndica commented 2 years ago

I am trying to create an instance of a UIDialog (there's one in the game named "CheatCurrenciesScreen") and simply show it. Just trying to get some more of that in-game currency, as you can't play without it (which is stupid).

In C#, I would do something along the lines of:

var uiCanvas = null; //find ui canvas here, forgot name for it lol
uiCanvas.AddComponent<CheatCurrenciesScreen>();
uiCanvas.GetComponent<CheatCurrenciesScreen>().Show();

Im sure the above code would work in C#, however frida is a different story. Don't even know how to pass a type parameter in frida :(

But yeah, after the next response Ill close this issue.

vfsfitvnm commented 2 years ago

There's

UnityEngine.Component AddComponent(System.Type componentType); // 0x01c7d9ec

You can easily get the underlying System.Type via CheatCurrenciesScreenClass.type.object.

Also, if you had something like this:

UnityEngine.Component AddComponent<T>();

you could follow https://github.com/vfsfitvnm/frida-il2cpp-bridge/wiki/Snippets#generics-handling.