frida-il2cpp-bridge

A Frida module to dump, trace or hijack any Il2Cpp application at runtime, without needing the global-metadata.dat file.

Usage no npm install needed!

<script type="module">
  import fridaIl2cppBridge from 'https://cdn.skypack.dev/frida-il2cpp-bridge';
</script>

README

frida-il2cpp-bridge

Frida NPM

A Frida module to dump, trace or hijack any Il2Cpp application at runtime, without needing the global-metadata.dat file.

code

Features

  • Dump classes, methods, fields and so on
  • Trace, intercept and replace method calls
  • Mess around with C# runtime
  • Il2Cpp structs and global metadata free
  • (TODO) Emit C scaffold code to improve static analysis

Compatibility

Unity version

It should work for any Unity version in the range 5.3.0 - 2022.1.x.

Platforms

Android, Linux, Windows, iOS, macOS are supported. However, only Android and Linux are tested: expect breakage if you are using another platform.

Changelog

0.7.8

  • Add Il2Cpp::installExceptionListener.
  • Fix #132.

0.7.7

  • Fix #107.

0.7.6

  • Move I2Cpp.Thread::current to Il2Cpp::currentThread.
  • Move I2Cpp.Thread::all to Il2Cpp::attachedThreads.
  • Add Il2Cpp::sheduleOnInitializerThread to run a callback inside the main Il2Cpp thread instead of Frida's one.

0.7.5

  • Fix #66 and #95.

0.7.4

  • Il2Cpp.Method::restoreImplementation was renamed to Il2Cpp.Method::revert.
  • Il2Cpp.Tracer api change:
    Il2Cpp.perform(() => {
      Il2Cpp.trace()
          .classes(Il2Cpp.Image.corlib.class("System.String"))
          .and()
          .attach("detailed");
    });
    

0.7.3

  • Il2Cpp.Thread::id was added.
  • Il2Cpp::perform can now return a value:
    async function foo() {
        const result = await Il2Cpp.perform<string>(() => {
            const SystemBoolean = Il2Cpp.Image.corlib.class("System.Boolean");
            return SystemBoolean.field("TrueLiteral").value.toString();
        });
    
        console.log(`Result from Il2Cpp: ${result}`);
    
        // ...
    }
    

0.7.2

  • Il2Cpp::internalCall, Il2Cpp::applicationDataPath, Il2Cpp::applicationIdentifier, Il2Cpp::applicationVersion, Il2Cpp::unityVersion were added.
  • unity TS module was removed as it was quite useless now that I don't need to interact with Unity native module anymore.
  • Il2Cpp.Dumper was removed as it was just boilerplate code - Il2Cpp::dump gets the exact same job done. Il2Cpp.Dumper::methods is gone - I'll provide a snippet to extract methods from the classic dump.
  • Il2Cpp.Api will not give any hint about the required version when an export isn't found.

0.7.1

  • Support Unity version up to 2022.1.x. Note: Il2Cpp.GC::choose makes the application crash in applications whose Unity version is above 2021.1.
  • Il2Cpp.Class::toString, Il2Cpp.Field::toString and Il2Cpp.Method::toString are now implemented in JavaScript. I know this is a considerable performance loss, but the C code looks much simpler now as less logic is involved, also dumping is actually performed once per application, so it's not a total drama.
  • Il2Cpp.Class::interfaceCount, Il2Cpp.Class::fieldCount and Il2Cpp.Class::methodCount were removed because unnecessary.
  • Faster Unity version detection: the memory isn't scanned anymore, the proper function is invoked instead.

0.7.0

  • Il2Cpp.Domain::assemblies, Il2Cpp.Image::classes, Il2Cpp.Class::methods and so on now return a plain simple array.

  • Il2Cpp.Domain::assembly, Il2Cpp.Image::class, Il2Cpp.Class::method and so on were added to obtain an item with the given name. They are all equivalent to the old accessor way:

    // old
    const mscorlib = Il2Cpp.Domain.assemblies.mscorlib.image;
    const SystemString = mscorlib.classes["System.String"];
    
    // new
    const mscorlib = Il2Cpp.Domain.assembly("mscorlib").image;
    const SystemString = mscorlib.class("System.String");
    

    The new look is more consistent and easier to manage and has a positive noticeable impact on performance (e.g. there's no need to find all classes first). Lastly, but not least importantly, there's no need to cast an object to its base when trying to invoke a base method or accessing a base class!

    However, there are a couple of important changes:

    • Nested classes must be accessed within their declaring class via Il2Cpp.Class::nested:
      // old
      const TransitionTime = mscorlib.classes["System.TimeZoneInfo.TransitionTime"];
      
      // new
      const TransitionTime = mscorlib.class("System.TimeZoneInfo").nested("TransitionTime");
      
    • Generic type parameters must follow IL convention, so <T1, ... TN> becomes `N when calling Il2Cpp.Image::class or Il2Cpp.Image::tryClass:
      // old
      const List = mscorlib.classes["System.Collections.Generic.List<T>"];
      
      // new
      const List = mscorlib.class("System.Collections.Generic.List`1");
      
  • Il2Cpp.Method::overload was added to help picking the correct method with the given parameter type names.

  • Il2Cpp.Object::base was removed because it's not necessary anymore.

  • Il2Cpp.Method::implement does not artificially cast instances to the method declaring class anymore.

  • Il2Cpp.Method::invoke doesn't try to catch C# exceptions anymore: the solutions I adopted (= catch abort was called error) is unreliable and inconsistent.

  • Il2Cpp.Field and Il2Cpp.Method now have type parameters:

    const SystemBoolean = Il2Cpp.Image.corlib.class("System.Boolean");
    
    const TrueLiteral = SystemBoolean.field<Il2Cpp.String>("TrueLiteral");
    TrueLiteral.value = 23; // type error!
    
    const Parse = SystemBoolean.method<boolean>("Parse");
    const result = Parse.invoke(Il2Cpp.String.from("true"));
    

    In Il2Cpp.Method the type parameter was moved out from invoke. Type parameters for method arguments aren't present because they add too much verbosity for little benefit.

Acknowledgements

Thanks to meme and knobse for helping and getting me into this, and to djkaty and nneonneo for providing the Il2Cpp api.

Problems?

Discussions and Wiki are both active. Use them!