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

Fatal exception when using ll2Cpp.GC.choose #265

Closed ExternalAddress4401 closed 1 year ago

ExternalAddress4401 commented 1 year ago

I suspect this might be some sort of anti frida tampering but I'm not smart enough to make that determination.

The newest version of https://apkcombo.com/beatstar/com.spaceapegames.beatstar/

import "frida-il2cpp-bridge";

Il2Cpp.perform(() => {
    const metalogic = Il2Cpp.Domain.tryAssembly("MetaLogic");
    Il2Cpp.GC.choose(metalogic.class("com.spaceape.flamingo.model.UserBeatmaps"));
})

Causes a crash with the following

FATAL EXCEPTION: UnityMain
Process: com.spaceapegames.beatstar, PID: 17396
java.lang.Error: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Version '2021.3.16f1 (4016570cf34f)', Build type 'Release', Scripting Backend 'il2cpp', CPU 'arm64-v8a'
Revision: '0'
ABI: 'arm64'
Timestamp: 2023-03-21 20:56:32-0700
pid: 17396, tid: 17454, name: UnityMain  >>> com.spaceapegames.beatstar <<<
uid: 10335
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x132
Cause: null pointer dereference
    x0  0000000000000000  x1  0000000000000001  x2  0000000000000000  x3  0000000000000000
    x4  000000750bc43dbe  x5  00000076036dcc17  x6  707974206e6f2027  x7  272065707974206e
    x8  00000000fffffff1  x9  00000000ffffffff  x10 0000000000000040  x11 0000000000000011
    x12 000000000000e236  x13 656d695472657672  x14 ffffffffc4653600  x15 0000063597a830cf
    x16 000000750c49d530  x17 000000784033d820  x18 00000073c8bfa8e0  x19 000000750cd26c30
    x20 00000076837a4e41  x21 0000000000000000  x22 0000000000000000  x23 0000000000000000
    x24 000000750cd28000  x25 000000750cd27610  x26 000000750cd27614  x27 000000750cd27610
    x28 000000750cd27510  x29 000000750cd26e50
    sp  000000750cd26b50  lr  000000750a0c2c34  pc  000000750a0d9d60

backtrace:
      #00 pc 0000000000a22d60  /data/app/~~vbkk_8BKXyExIX0Je4PAxQ==/com.spaceapegames.beatstar-K6zL1JQhOLzcHl7WsU2yrA==/lib/arm64/libil2cpp.so (BuildId: 153513f3740c158bcc1e8225349dafaf7f61bcdd)
      #01 pc 0000000000a0bc30  /data/app/~~vbkk_8BKXyExIX0Je4PAxQ==/com.spaceapegames.beatstar-K6zL1JQhOLzcHl7WsU2yrA==/lib/arm64/libil2cpp.so (BuildId: 153513f3740c158bcc1e8225349dafaf7f61bcdd)
      #02 pc 0000000000a0c07c  /data/app/~~vbkk_8BKXyExIX0Je4PAxQ==/com.spaceapegames.beatstar-K6zL1JQhOLzcHl7WsU2yrA==/lib/arm64/libil2cpp.so (BuildId: 153513f3740c158bcc1e8225349dafaf7f61bcdd)
      #03 pc 00000000009ec6dc  /data/app/~~vbkk_8BKXyExIX0Je4PAxQ==/com.spaceapegames.beatstar-K6zL1JQhOLzcHl7WsU2yrA==/lib/arm64/libil2cpp.so (BuildId: 153513f3740c158bcc1e8225349dafaf7f61bcdd)
      #04 pc 00000000009ec5d0  /data/app/~~vbkk_8BKXyExIX0Je4PAxQ==/com.spaceapegames.beatstar-K6zL1JQhOLzcHl7WsU2yrA==/lib/arm64/libil2cpp.so (BuildId: 153513f3740c158bcc1e8225349dafaf7f61bcdd)
      #05 pc 0000000001b8f020  /data/app/~~vbkk_8BKXyExIX0Je4PAxQ==/com.spaceapegames.beatstar-K6zL1JQhOLzcHl7WsU2yrA==/lib/arm64/libil2cpp.so (BuildId: 153513f3740c158bcc1e8225349dafaf7f61bcdd)
      #06 pc 00000000021e0ce8  /data/app/~~vbkk_8BKXyExIX0Je4PAxQ==/com.spaceapegames.beatstar-K6zL1JQhOLzcHl7WsU2yrA==/lib/arm64/libil2cpp.so (BuildId: 153513f3740c158bcc1e8225349dafaf7f61bcdd)
      #07 pc 0000000000f98b20  /data/app/~~vbkk_8BKXyExIX0Je4PAxQ==/com.spaceapegames.beatstar-K6zL1JQhOLzcHl7WsU2yrA==/lib/arm64/libil2cpp.so (BuildId: 153513f3740c158bcc1e8225349dafaf7f61bcdd)
      #08 pc 0000000000a1dc4c  /data/app/~~vbkk_8BKXyExIX0Je4PAxQ==/com.spaceapegames.beatstar-K6zL1JQhOLzcHl7WsU2yrA==/lib/arm64/libil2cpp.so (BuildId: 153513f3740c158bcc1e8225349dafaf7f61bcdd)
      #09 pc 0000000000a1dac0  /data/app/~~vbkk_8BKXyExIX0Je4PAxQ==/com.spaceapegames.beatstar-K6zL1JQhOLzcHl7WsU2yrA==/lib/arm64/libil2cpp.so (BuildId: 153513f3740c158bcc1e8225349dafaf7f61bcdd)
      #10 pc 00000000002c3ac4  /data/app/~~vbkk_8BKXyExIX0Je4PAxQ==/com.spaceapegames.beatstar-K6zL1JQhOLzcHl7WsU2yrA==/lib/arm64/libunity.so (BuildId: 189b05c84f874a4a14d3154862cd8fb6e53c1426)
      #11 pc 00000000002d2858  /data/app/~~vbkk_8BKXyExIX0Je4PAxQ==/com.spaceapegames.beatstar-K6zL1JQhOLzcHl7WsU2yrA==/lib/arm64/libunity.so (BuildId: 189b05c84f874a4a14d3154862cd8fb6e53c1426)
      #12 pc 00000000002e0224  /data/app/~~vbkk_8BKXyExIX0Je4PAxQ==/com.spaceapegames.beatstar-K6zL1JQhOLzcHl7WsU2yrA==/lib/arm64/libunity.so (BuildId: 189b05c84f874a4a14d3154862cd8fb6e53c1426)

I'm not smart enough to determine why this crashes. I've tried going to 0000000000a22d60 in Ghidra but what's there doesn't match what Frida is telling me is at that address and I'm struggling to determine which function this even is.

Is this some sort of anti frida code at work or a library issue?

vfsfitvnm commented 1 year ago

Yeah, this is a bug I "discovered" few months ago, but I never filed an issue for that smh. It's not related to a specific application. See these release notes and the source code!

ExternalAddress4401 commented 1 year ago

Ahh I see.

Specifically it looks like it starts at https://github.com/vfsfitvnm/frida-il2cpp-bridge/blob/1de863fee487bd7c67baa579ae11c2c12698beaa/src/il2cpp/structs/gc.ts#L67

Which throws Error: access violation accessing 0x0

But as I'm a total noob with all this I wouldn't even know where to begin attempting to find that function in something like Ghidra and tracing what might be happening.

I think for my case a temporary "solution" can be this ghetto generic wrapper to hook the constructor of any objects I used Il2Cpp.GC.choose for before and save them to variables until something better is found and can be replaced later.

const objects: Record<string, any> = {};

const collect = (library: string, klass: string) => {
  const assembly = getAssembly(library);
  assembly.class(klass).method(".ctor").implementation = function (...args) {
    this.method(".ctor").invoke(...args);
    if (!objects[klass]) {
      objects[klass] = [];
    }
    objects[klass].push(this);
  };
};

Sure there might be issues with that but I'll get to those later.

vfsfitvnm commented 1 year ago

Beware of two things: 1) Don't use the class name as a key as there might be duplicates; use Il2Cpp.Class::handle::toInt32|toString instead. 2) The objects you are pushing to the array may be garbage collected sooner or later, you can prevent so by using GC handles:

const handles: Record<string, Il2Cpp.GC.Handle[]> = {};

// ...

if (this instanceof Il2Cpp.Object) {
    handles[klass].push(this.ref(true));
}

// ...

for (const handle of handles[klass]) {
    const object = handle.target;
    // ...
    handle.free();
}
vfsfitvnm commented 1 year ago

I don't know if that counts as a workaround or if it's the proper solution, but it looks like it's fixed :smile:

ash47 commented 1 year ago

I don't think this issue should be closed. The thing still crashes.

We can greatly reduce the crashes by stopping garbage collection before using it, and then restarting garbage collection, this causes the game to drop frames / freeze, but its better than a crash -- this approach will still crash on occasion, but it goes from crash every time to crash randomly.

Il2Cpp.GC.stopWorld();

Il2Cpp.GC.choose()

Il2Cpp.GC.startWorld();

vfsfitvnm commented 1 year ago

@ash47 Yeah that's what I did in the commit, however I could not reproduce the crashes. Would you elaborate (i.e. application name, platform, timing etc)?

ash47 commented 1 year ago

Hi, I can give you anything you need, this crash happens in Bloons TD 6, if I select a class such as system.string, it's all good, if I select a class that is based on a monobehaviour then it crashes -- if I log inside of the for loop, it does work, I can see the logs for the instance, it crashes after the loop like a timing issue.

I'm using Windows.

I can buy you a copy of the game if that helps fix the issue.

I can provide the script or anything else, just let me know exactly what would be useful and I can provide it, I can test things too if required.

On Wed, 12 Apr 2023, 4:57 am vfsfitvnm, @.***> wrote:

@ash47 https://github.com/ash47 Yeah that's what I did in the commit, however I could not reproduce the crashes. Would you elaborate (i.e. application name, platform, timing etc)?

— Reply to this email directly, view it on GitHub https://github.com/vfsfitvnm/frida-il2cpp-bridge/issues/265#issuecomment-1503937977, or unsubscribe https://github.com/notifications/unsubscribe-auth/AA4UUDT4UD2VFWBIALDKGBDXAWSTFANCNFSM6AAAAAAWDJJNCY . You are receiving this because you were mentioned.Message ID: @.***>

vfsfitvnm commented 1 year ago

Would you share the script you are using?

ash47 commented 1 year ago

I have a full GUI set up for creating mods for games which lets me hook various modding engines including Frida, and for this mod, I tried out frida-il2cpp-bridge, The mod launcher will load the script, it will pass in all the current options via updateOption RPC call, it would call load but i dont have anything specifically hooked to run when load is run, and then when a user presses "Add Cash" button in the GUI, it runs the runAction which ultiamtely runs the code. I am using the version of frida-il2cpp-bridge that is sitting on NPM right now.

I'll re-write the code below to remove the stuff specific to my Mod Manager.

UI

import "frida-il2cpp-bridge";

let totalToAdd = 0;

let addingIt = false;
async function addIt(amount) {
  totalToAdd += amount;
  if (isNaN(totalToAdd)) {
    totalToAdd = 1000;
  }

  // If nothing to change, stop
  if (totalToAdd === 0) return;

  // only do it once at a time
  if (addingIt) return;
  addingIt = true;

  // cache how much we need to add and set this to zero
  let cacheAmountToAdd = totalToAdd;
  totalToAdd = 0;

  Il2Cpp.perform(() => {
    // Stop the world
    Il2Cpp.GC.stopWorld();

    // do the stuff
    let results = null;
    try {
      const assemblyCSharp = Il2Cpp.Domain.assembly("assembly-csharp").image;
      results = Il2Cpp.GC.choose(
        assemblyCSharp.class("Assets.Scripts.Simulation.Simulation")
      );
    } catch (e) {
      // do nothing
      console.log(e);
    }
    Il2Cpp.GC.startWorld();

    if(results !== null) {
      try {
        results.forEach((instance) => {
          // Get cash and amount to add
          const cashIndex = -1;
          const currentCash = instance.method('GetCash').invoke(cashIndex);

          // Add it
          instance.method('SetCash').invoke(currentCash + cacheAmountToAdd, cashIndex);
        });
      } catch(e) {
        console.log(e);
      }
    }

    // Wait a bit before we are done
    setTimeout(() => {
      addingIt = false;
      addIt(0);
    }, 1000);
  });
}

async function handleAction(actionUuid, actionArgs) {
  const addAmount = parseInt(optionValues['550f2ae8-8069-4411-b1c1-3d14a84a0a81']?.currentValue || '0');
  if (isNaN(addAmount)) {
    addAmount = 1000;
  }

  // get onto a new thread
  setTimeout(() => {
    addIt(addAmount);
  }, 1);
}

const optionValues = {};
rpc.exports = {
  updateOption: async (optionInfo) => {
    if (!optionValues.hasOwnProperty(optionInfo.optionUuid)) optionValues[optionInfo.optionUuid] = {};
    optionValues[optionInfo.optionUuid] = {
      ...optionValues[optionInfo.optionUuid],
      ...optionInfo,
    }
    if (typeof (handleOptionUpdate) === "function") {
      handleOptionUpdate(optionInfo);
    }
  },
  runAction: async (actionUuid, actionArgs) => {
    if (typeof (handleAction) === "function") {
      return await handleAction(actionUuid, actionArgs);
    }

    return null;
  },
  load: async () => {
    if (typeof (handleLoad) === "function") {
      return await handleLoad();
    }

    return null;
  },
  unload: async () => {
    if (typeof (handleUnload) === "function") {
      return await handleUnload();
    }

    return null;
  },
};

I wrote the code the way I did because each time the "add cash" is called, it freezes the game, so, i made it collect all the times it is pressed and only execute it once per second to increase performance. I also played around with the order of starting and stopping the world, starting the world again after we've done the call to Il2Cpp.GC.choose seems to be ok.

---- Here's a rewrite that might be more useful:


import "frida-il2cpp-bridge";

const cashIndex = -1; // hard coded to be for player 1
const amountOfCashToAdd = 1000;

Il2Cpp.perform(() => {
    // Stop the world
    Il2Cpp.GC.stopWorld();

    const assemblyCSharp = Il2Cpp.Domain.assembly("assembly-csharp").image;

    Il2Cpp.GC.choose(
        assemblyCSharp.class("Assets.Scripts.Simulation.Simulation")
    ).forEach((instance) => {
        // Get the current cash
        const currentCash = instance.method('GetCash').invoke(cashIndex);

        // Add the current cash and the amount we want to add
        instance.method('SetCash').invoke(currentCash + amountOfCashToAdd, cashIndex);
    });

    Il2Cpp.GC.startWorld();
});

The thing is, this works fine, aside from the freezing, it's just that once every so often it will crash, it becomes much more obvious when using a GUI because it's really easy to keep clicking "Add Cash"


Please let me know if you need any other details.

vfsfitvnm commented 1 year ago

The freezing probably happens because you are invoking GetCash/SetCash from the Frida threa, you might want to call it from the main thread. And maybe, you need to "restart the world" before invoking such methods.

Il2Cpp.perform(() => {
    const AssemblyCSharp = Il2Cpp.Domain.assembly("Assembly-CSharp").image;
    const AssetsScriptsSimulationSimulation = AssemblyCSharp.class("Assets.Scripts.Simulation.Simulation");

    Il2Cpp.GC.stopWorld();
    const instances = Il2Cpp.GC.choose(AssetsScriptsSimulationSimulation);
    Il2Cpp.GC.startWorld();

    Il2Cpp.attachedThreads[0].schedule(() => {
        instances.forEach(instance => {
            const currentCash = instance.method<number>("GetCash").invoke(cashIndex);
            instance.method("SetCash").invoke(currentCash + amountOfCashToAdd, cashIndex);
        });
    });
});
ash47 commented 1 year ago

I had a bit of a play around, I'm on Windows, it seems like Il2Cpp.attachedThreads[0] doesn't work in Bloons TD6, it results in Error: access violation accessing 0x7d44

I did some quick debugging via:

        console.log('1');
        console.log('2', Il2Cpp);
        console.log('3', Il2Cpp.attachedThreads);
        console.log('4', Il2Cpp.attachedThreads[0]);

I get numbers 1, 2, and 3, there is no 4, just the Error: access violation accessing 0x7d44

Is there something such as Discord I can message you directly on?

vfsfitvnm commented 1 year ago

Yeah, vfsfitvnm#7025

ash47 commented 1 year ago

Thanks, I added you as a friend, I can't message you unless you accept.

On Sat, Apr 15, 2023 at 9:04 PM vfsfitvnm @.***> wrote:

Yeah, vfsfitvnm#7025

— Reply to this email directly, view it on GitHub https://github.com/vfsfitvnm/frida-il2cpp-bridge/issues/265#issuecomment-1509729824, or unsubscribe https://github.com/notifications/unsubscribe-auth/AA4UUDQ5HEK2HP7YFX6II4TXBJ6D7ANCNFSM6AAAAAAWDJJNCY . You are receiving this because you were mentioned.Message ID: @.***>