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

Fatal exception when using ll2Cpp.GC.choose #265

Closed
ExternalAddress4401 opened this issue Mar 22, 2023 · 13 comments
Closed

Fatal exception when using ll2Cpp.GC.choose #265

ExternalAddress4401 opened this issue Mar 22, 2023 · 13 comments
Labels
bug Something isn't working

Comments

@ExternalAddress4401
Copy link

ExternalAddress4401 commented Mar 22, 2023

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 vfsfitvnm added the bug Something isn't working label Mar 22, 2023
@vfsfitvnm
Copy link
Owner

vfsfitvnm commented Mar 22, 2023

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

ExternalAddress4401 commented Mar 22, 2023

Ahh I see.

Specifically it looks like it starts at

Il2Cpp.Api._livenessCalculationFromStatics(state);

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

vfsfitvnm commented Mar 24, 2023

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

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

@ash47
Copy link

ash47 commented Apr 10, 2023

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

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

ash47 commented Apr 12, 2023 via email

@vfsfitvnm
Copy link
Owner

Would you share the script you are using?

@ash47
Copy link

ash47 commented Apr 13, 2023

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

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

ash47 commented Apr 15, 2023

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

Yeah, vfsfitvnm#7025

@ash47
Copy link

ash47 commented Apr 15, 2023 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants