Frida 12.7 Released ∞release
There’s only one new feature this time, but it’s a big one. We’re going to address the elephant in the room: performance.
This may not sound like a lot, but if a function is called a million times, it’s going to amount to 6 seconds of added overhead. And perhaps the hook only needs to do something really simple, so most of the time is actually spent on entering and leaving the VM.
There’s also the same kind of issue when needing to pass a callback to an API, where the API walks through potentially millions of items and needs to call the callback for each of them. The callback might just look at one byte and collect the few of the items that match a certain criteria.
Naively one could go ahead and use a NativeCallback to implement that callback, but it quickly becomes apparent that this just doesn’t scale.
Or, you might be writing a fuzzer and needing to call a NativeFunction in a tight loop, and the cost of entering/leaving the VM plus libffi just adds up.
Short of writing the whole agent in C, one could go ahead and build a native library, and load it using Module.load(). This works but means it has to be compiled for every single architecture, deployed to the target, etc.
Another solution is to use the X86Writer/Arm64Writer/etc. APIs to generate code at runtime. This is also painful as there’s quite a bit of work required for each architecture to be supported. But up until now this was the only portable option for use in modules such as frida-java-bridge.
But now we finally have something much better. Enter CModule:
It takes the string of C source code and compiles it to machine code, straight to memory. This is implemented using TinyCC, which means that this feature only adds ~100 kB of footprint to Frida.
As you can see, any global functions are automatically exported as NativePointer properties named exactly like in the C source code.
And, it’s fast:
(Measured on an Intel i7 @ 3.1 GHz.)
We can also use this new feature in conjunction with APIs like Interceptor:
We can also combine it with Interceptor.attach():
Yay. Though this last particular example actually writes to stdout of the target process, which is fine for debugging but probably not all that useful.
That is however just a toy example: doing it this way will actually defeat the purpose of writing the hooks in C to improve performance. A real implementation might instead append to a GLib.Array after acquiring a GLib.Mutex, and periodically flush the buffered data by calling back into JS.
For now we don’t have any docs on the built-in C APIs, but you can browse the headers in frida-gum/bindings/gumjs/runtime/cmodule to get an overview. Drop function names into an Internet search engine to look up docs for the non-Frida APIs such as GLib’s.
The intention is to only expose a minimal subset of the standard C library, GLib, JSON-GLib, and Gum APIs; in order to minimize bloat and maximize performance. Things we include should either be impossible to achieve by calling into JS, or prohibitively expensive to achieve that way.
Think of the JS side as the operating system where the functions you plug into it are system calls; and only use CModule for hooking hot functions or implementing high-performance glue code like callbacks passed to performance-sensitive APIs.
One important caveat is that all data is read-only, so writable globals should
be declared extern, allocated using e.g. Memory.alloc(), and passed in as
symbols through the constructor’s second argument. (Like we did with
the last example.)
You might also need to initialize things and clean them up when the CModule gets destroyed – e.g. because the script got unloaded – and we provide a couple of lifetime hooks for such purposes:
Anyway, this post is getting long, but before we wrap up let’s look at how to use CModule with the Stalker APIs:
This shows how you can implement both the transform callback and the callouts in C, but you may also use a hybrid approach where you write the transform callback in JS and only some of the callouts in C.
It is also worth noting that I rewrote ObjC.choose() to use CModule, and it is now roughly 100x faster. When testing it on the login screen of the Twitter app on an iPhone 6S, this went from taking ~5 seconds to now only ~50 ms.
So with that, I hope you’ll enjoy this release. Excited to see what kind of things you will build with the new CModule API. One thing I’m really looking forward to is improving our REPL to support loading a .c file next to the .js, for rapid prototyping purposes.
Changes in 12.7.0
- Brand new CModule API powered by TinyCC. (You just read about it.)
- TinyCC was improved to support Apple’s ABI on macOS/x86.
- Stalker.exclude() is now exposed to JS to be able to mark specific memory ranges as excluded. This is useful to improve performance and reduce noise.
- Concurrent calls to Java.use() are now supported, thanks to a neat contribution by @gebing.
- The hexdump() implementation was improved to clamp the length option to the length of the ArrayBuffer, thanks to another neat contribution by @gebing.
Changes in 12.7.1
- More CModule goodies, including GLib.String, GLib.Timer, and Json.Builder.
- TinyCC was improved to support Apple’s ABI on iOS/arm64.
- ObjC.choose() was rewritten using CModule, and is now ~100x faster.
Changes in 12.7.2
- CModule got some missing ref-counting APIs.
Changes in 12.7.3
- CModule memory ranges are now properly cloaked.
- The V8 garbage collector is now informed about externally allocated CModule memory so it can make better decisions about when to GC.
- Symbols attached to a CModule are now properly kept alive in the V8 runtime also; and the CModule itself is not kept alive indefinitely (or until script unload).
- CModule.dispose() was added for eagerly cleaning up memory.
Changes in 12.7.4
- The frida-inject tool now supports spawn(). Kudos to @hunterli for contributing this neat feature.
- Our V8 runtime no longer deadlocks on i/macOS when thread_suspend() is called with the JS lock still held, like Stalker.follow() indirectly does when asked to follow another thread.
Changes in 12.7.5
- Brand new channels API for establishing TCP connections to a tethered iOS or Android device, as well as talking to lockdown services on a tethered iOS device.
- The timeout logic behind DeviceManager.find_device() and its sibling methods is now working properly.
- Java marshaling of java.lang.Class is now working properly, and instance fields can also be introspected without needing an instance. Kudos to @gebing for contributing these neat fixes!
Changes in 12.7.6
- The Android linker is now properly detected on Android 10.
- Our Android SELinux policy patcher now also handles devices like Samsung S10, thanks to a neat contribution by @cbayet.
- The frida-inject tool now supports -D/–device for working with non-local devices.
- We now have better error-handling to avoid crashing when i/macOS processes terminate unexpectedly during early instrumentation.
- iOS crash reporter integration is way more robust, thanks to some awesome fixes contributed by @mrmacete. One of his fixes also ensures parallel calls to recv().wait() for the same message type don’t end up in an infinite wait.
- Stalking of thread creation is now supported on Linux/arm64. Kudos to @alvaro_fe for this awesome contribution!
- V8 runtime’s WebAssembly support is working again on non-iOS also.
- The Gum.DarwinModule API is now part of the cross-platform Gum API. Useful for parsing Mach-O files on non-Apple systems.
Changes in 12.7.7
- Eternalized agents are now kept around when the last session gets closed, which means they can be reused for as long as the HostSession side, e.g. frida-server, sticks around. This means that additional copies of frida-agent can be avoided in a lot of cases. Kudos to @mrmacete for this awesome improvement.
- Java bridge no longer triggers a use-after-free when a method returns this.
- Our Android SELinux policy patcher no longer prints a warning on older versions of Android. This harmless but confusing regression was introduced by the previous release’ fix for Samsung S10 ROMs.
- Better SELinux-related error messages.
- Rudimentary support for iOS/arm64e.
Changes in 12.7.8
- Android 10 support just landed in our Java bridge thanks to a brilliant contribution by @Alien-AV.
- Better spawn()-handling for Android apps, where the activity parameter can be used in cases where the app doesn’t have a launcher activity. This neat improvement was contributed by @muhzii.
- Android linker seeking logic was made future-proof thanks to an elegant contribution by @timstrazz.
- Massively improved fault-tolerance on iOS: our launchd agent now kills pending processes when unloaded. This means that frida-server dying won’t leave processes stuck in a suspended state. Kudos to @mrmacete for this awesome improvement.
Changes in 12.7.9
- We are back in business on macOS after a last-minute build regression snuck into the previous release.