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.
Changes in 12.7.10
- MemoryAccessMonitor is now available on all platforms, and even the Duktape runtime. Kudos to @alvaro_fe for these awesome improvements.
- Android linker detection working properly again. (Regression introduced in 12.7.8.)
- Gadget was improved to support passing a config through its constructor function on i/macOS.
- Gadget’s system loop integration – only implemented on i/macOS – was removed to avoid undefined behavior in some scenarios.
Changes in 12.7.11
- Frida no longer crashes in Android processes without a vDSO. (Regression introduced in 12.7.8.)
- Better error-handling when parsing Mach-O images.
- ARM vs Thumb properly handled when restoring exception on i/macOS. Thanks @alvaro_fe!
- CModule’s header for JSON-GLib is now self-contained, as it should be.
Changes in 12.7.12
- Full-featured iOS lockdown integration and unified devices, so Frida-based tools don’t need to worry as much about jailed vs jailbroken. When interacting with a jailed iOS device, Gadget is now injected automatically and there is no need to repackage the app, it only has to be debuggable.
- Frida is finally able to detect recent iOS devices on Windows.
- Bug in V8 was tracked down and fix backported from upstream. Kudos to @mrmacete for tracking this one down!
Changes in 12.7.13
- Better handling of structs and unions in frida-objc-bridge. Thanks @gebing!
- Our Node.js bindings now also expose type definitions for Crash and CrashParameters.
- Attempts to attach to the system session on jailed iOS throw early and with a clearer error message.
- The iOS Developer Disk Image-related error messages were tweaked for consistency.
Changes in 12.7.14
- Frida now ensures code is readable before accessing it on Android >= 10. This was the last missing piece for full-featured Android 10 support. Being able to instrument system processes means that early instrumentation – i.e. spawn() – works, and starting frida-server doesn’t crash system_server due to the attempt it makes at preloading so the first spawn() will be fast. Kudos to @Alien-AV and @esanfelix for the painful research that made the solution possible to implement in one late Saturday evening. :-)
Changes in 12.7.15
- Node.js bindings also expose the “summary” field of the Crash typing.
Changes in 12.7.16
- The frida-gadget-ios meta-package comes with type definitions so it can be consumed from TypeScript.
- Node.js bindings provide proper typings for Stdio and ChildOrigin.
Changes in 12.7.17
- More robust support for jailed iOS: calling kill() before resume() but after attach() is now working properly.
- Frida now communicates directly with the remote iOS/Android agent when possible. We achieve this by establishing a fresh TCP connection to the frida-server and having its file-descriptor passed to the agent. This means frida-server can remove itself from the data path, improving performance and reliability.
- Clients connecting to frida-server to spawn() a process, but getting disconnected before they get a chance to resume() or kill() said process, will no longer leave such orphans in limbo. We now keep track of spawned processes and kill() them if the client suddenly gets disconnected.
- We no longer crash Zygote on Android 10. Turns out an SELinux rule was missing.
- Our TCP sockets now have TCP_NODELAY set, and we also support communicating with a remote frida-server or frida-gadget using UNIX sockets in addition to TCP.
- NativeFunction now supports variadic functions, to avoid needing to create one instance per unique argument list signature. Thanks @gebing!
Changes in 12.7.18
- Node.js bindings finally link in needed OpenSSL symbols on UNIX, instead of relying on luck where we’d usually end up in processes with another OpenSSL already loaded that’s globally visible and ABI-compatible (!).
Changes in 12.7.19
- We now properly handle apply() on NativeFunction w/o args in the V8 runtime also. Kudos to @taviso for reporting this long-standing bug.
- NativeFunction’s handling of optional args in call() and apply() now behaves like the builtin counterparts.
Changes in 12.7.20
- Frida now supports both cleartext and encrypted iOS lockdown channels, retaining support for “cleartext after TLS handshake”-style channels by appending “?tls=handshake-only” to the channel address. Kudos, @mrmacete!
- The paired lockdown channel itself can be accessed by using “lockdown:” as the channel address. Thanks @mrmacete!
Changes in 12.7.21
- We now support iOS 13 on the checkra1n jailbreak. (Jailed iOS 13 was already supported.)
Changes in 12.7.22
- Our iOS package scripts launch daemon logic is now compatible with checkra1n, so frida-server won’t have to be started/stopped manually.