Frida 16.2.5 Released

A quick bug-fix release with three improvements:

  • ci: Fix the frida-node prebuild loop for macOS, so we generate prebuilds for all targets, not just the first one.
  • node: Avoid relying on package-lock.json, to support fallback build when prebuild is missing.
  • android: Set DexFile to read-only in Java.registerClass(). As of Android 14, apps with targetSdk >= 34 are not allowed to have writable permissions on dynamically loaded Dex files. Thanks @pandasauce!


Frida 16.2.4 Released

Just a quick bug-fix release to fix a regression in the DebugSymbol API on Windows, where we failed to bundle the DbgHelp and SymSrv libraries. Kudos to 0xDC00 for reporting this so quickly.


Frida 16.2.2 Released

It’s release o’clock, and this one comes jam-packed with improvements! 🎉

Build System

After years of being dissatisfied with our build system situation, I have finally worked up the courage to restructure it — in a big way.

User Experience

One of the things that bothered me was that our build system felt very eccentric and complex. You would invoke make and see a menu of things you could build. But that menu would be different depending on the OS you’re running this on. Changing build options meant either editing or overriding them on the command-line. And if you were on Windows you’d have to use Visual Studio (MSVS).

The MSVS build system is now gone, and you can build Frida the same way you’d build many other OSS projects:

$ ./configure
$ make

The configure step can be skipped if you don’t need to pass any options, such as –prefix. This script is really just a thin wrapper around Meson’s setup command, and you can pass options straight to Meson by adding -- followed by the options. You can also run make install to install things, which supports the DESTDIR environment variable to change where the output goes.

Cross-compilation is easy too; for example if you want to build for iOS/arm64, invoke configure with –host=ios-arm64. If you have a toolchain for an embedded system, pass –host=$triplet, which will look for $triplet-gcc, $triplet-g++, etc. on your PATH. You can also add CFLAGS, LDFLAGS, etc. to the environment to pass in extra flags, just like you’d expect from other build systems.

What’s cool is that our different repos can now be built standalone. You can clone the frida-gum repo and build it, just like you can do with frida-python, frida-tools, etc. The frida repo is no longer as important, it’s only kept around to be able to version a collection of repos, and host the CI used for rolling new releases.

Each repo contains any needed subprojects/*.wrap files that tell Meson where it can find the dependencies. This means if you grab frida-core and try to build it without already having frida-gum on your PKG_CONFIG_PATH, it will grab and build that for you automatically. What’s also neat is that our Python and Node.js bindings now make use of this to build from source if a .whl or prebuild can’t be found or downloaded.

So now that our different components work well as subprojects, this also means anyone can integrate Frida components into their own projects just as easily. All you need to do is add a .wrap-file to subprojects/ in your project’s top source dir, and call dependency() from your Meson then looks on your system first, and if it fails to find the dependency it will clone the git repo in subprojects/ and build it as part of your project. It is also possible to tell Meson to force such a fallback without looking on your system.

Here’s what such a .wrap-file could look like for Gum:

url =
revision = 16.2.4
depth = 1

dependency_names = frida-gum-1.0, frida-gum-heap-1.0, frida-gum-prof-1.0, frida-gumjs-1.0, frida-gumjs-inspector-1.0

And for frida-core:

url =
revision = 16.2.4
depth = 1

dependency_names = frida-core-1.0

Our thin Meson wrapper is also easy to get started with, and you can read more about it here.

A bit of history

The reason I chose to maintain a separate build system for Windows was that I previously worked with quite a few experienced Windows developers, and noticed they’d be a lot more excited about an OSS project if they could work on it using their favorite IDE. It was important to them that they could look at a crash in the debugger, jump to a frame belonging to an OSS library, and be able to add some temporary logging code. And then hit the “Run” hotkey to have the IDE incrementally compile and relink everything, for a short and sweet feedback loop.

At the time, Frida’s non-Windows build system was autotools. We later moved to Meson. Although Meson has a backend for MSVS, meaning we could have it generate MSVS project files for us, there was one issue blocking us from doing that. Unlike MSVS, where you can mix projects targeting different machines in the same “solution” (workspace), Meson only supports compiling for one machine at a time, in addition to the build machine. Due to how Frida supports injecting code into e.g. both 32- and 64-bit targets on Windows, we would introduce a usability regression if we were to remove our MSVS build system. So because of this I hesitated to drop the MSVS build system.

We also had the same challenge on non-Windows. I ended up solving it by writing a Makefile that invokes Meson for each architecture, to glue it all together. While this did work, I never got it to the point where incremental builds would also work reliably. However, late March this year I finally settled on a way to solve this locally in frida-core – the only component where we need this kind of multi-arch madness. By recursively invoking Meson from a custom_target() we can build the needed components for the other architectures.

This took a lot of work to get working right, but once there it made things so much more pleasant to work with in the rest of the stack. The frida repo’s Makefiles and shell-scripts could be dropped and replaced with Meson. The CI became simpler and more uniform. Anyone can just clone the frida-core repo and run make, and the resulting binaries would support cross-arch. (Unless explicitly disabled through –disable-compat / -Dfrida-core:compat=disabled.)

Now you might be wondering why we’re still running make, if we’re now only using Meson. I ended up writing a thin wrapper around Meson that automates the downloading of prebuilt dependencies, choosing sensible linker flags for smaller binaries, etc. The new configure and Makefile files in each repo take care of invoking releng/ and releng/, which constitute the thin wrapper around Meson. The releng directory is a submodule, shared by Frida’s repos. The other thing done before invoking the wrapper, is to also make sure the releng submodule has been initialized and updated.


Before this release we were maintaining seven different build systems:

  1. Meson (main components, on non-Windows)
  2. Visual Studio (main components, on Windows)
  3. GNU Make (main components meta build system, on non-Windows)
  4. setuptools (Python bindings)
  5. GYP (Node.js bindings)
  6. Xcode (Swift bindings)
  7. QMake (QML bindings)

As of this release, I am excited to announce that we are down to just one build system: Meson.

The main pain point with the previous situation was having to deal with both 1) and 2), since they were involved in all of the main components. This meant that something as simple as adding a source file would require knowledge of two different build systems. Not only that, but contributors on Linux for example, would typically have a hard time testing their Visual Studio build system changes.

Another aspect was that developers would be less inclined to refactor their code if something as simple as adding a new file involves dealing with two build systems. The long-term consequence of this was that it fostered bad habits. “Hmm, I should split this out into a separate file, but uhh… no, that’s too painful, I’m gonna add that code here for now.”

As for the remaining build systems, only used in bindings, it may not seem that bad to maintain those – they’re typically familiar to the folks touching that code anyway. The challenge there was the lack of pkg-config integration in all but one of them (QMake). This meant that include paths, library paths, and libraries to link with would end up being duplicated in multiple places. This is particularly gnarly when linking with a static library that uses other libraries internally, which is how our language bindings typically consume frida-core.

New Features

Quite a few new things to be excited about in this release:

  • gumjs: Add Process.runOnThread(), to make it easy to run arbitrary code on a specific thread. Must be used with care to avoid deadlocks/reentrancy issues.
  • gumjs: Add limit option to Thread.backtrace(). Thanks @davinci-tech!
  • gumjs: Expand CModule glib.h with alternatives to deprecated APIs.
  • stalker: Add StalkerIterator.put_chaining_return(). Thanks @s1341!
  • stalker: Add run_on_thread() and run_on_thread_sync().
  • interceptor: Add support for shadow stacks on x86. Thanks @yjugl!
  • cpu-features: Add CET_SS flag and detection logic. Thanks @yjugl!
  • x86-writer: Add cpu_features field. Thanks @yjugl!
  • spinlock: Add try_acquire(). Thanks @mrmacete!
  • cloak: Add with_lock_held() and is_locked(). Thanks @mrmacete!
  • interceptor: Add with_lock_held() and is_locked(). Thanks @mrmacete!
  • darwin: Hint at macOS boot arguments when helper crashes.
  • java: Support instantiating classes without constructors. Thanks @AeonLucid!
  • java: Add support for arrays of array. Thanks @histausse!
  • python: Make the source distribution buildable fully from source, instead of only supporting building using a devkit.
  • python: Drop the MSVS build system.
  • node: Add Meson build system, drop prebuild and node-gyp bits.
  • node: Support building fully from source when a prebuilt can’t be found.
  • clr: Add Meson build system, drop the MSVS build system.
  • qml: Add Meson build system, drop the qmake build system.
  • qml: Add support for Qt 6. Thanks @zaxo7!
  • qml: Drop support for Qt 5.


Last but not least, we’re also bringing you a long list of quality improvements:

  • gumjs: Preserve thread’s system error over NativeCallback invocations. Thanks @HexKitchen!
  • gumjs: Always expose thread’s system error to NativeCallback.
  • gumjs: Plug leak of Stalker instance.
  • stalker: Fix block events disrupting exclusive access on arm64. Thanks @saicao!
  • memory: Fix patch_code() protection flipping on RWX systems.
  • swift-api-resolver: Fix handling of indirect type entries.
  • swift: Work around Module.load() issue on iOS Simulator. Thanks @zydeco!
  • base: Fix racy crash in custom GSource implementations.
  • base: Fix the p2p AgentSession registration logic.
  • buffer: Fix the read_string() size logic. Thanks @hsorbo!
  • linux: Fix early instrumentation on certain 32-bit systems.
  • linux: Fix inject_library_blob() on modern Android.
  • linux: Fix unreliable exec transition logic.
  • linux: Fix unreliable injection when libc is absent.
  • agent: Fix hang in child-gating fork() scenarios. Reproducible in situations where pidfd_getfd() is available but not permitted, such as inside Docker containers.
  • windows: Use RW/RX permissions for injection. This makes Frida injection compatible with more software. In particular, Mozilla Firefox rejects thread startup if the start address is RWX. Thanks @yjugl!
  • darwin: Massage libunwind around Frida hooks, to avoid breaking code making use of exceptions, such as through @try/@catch in Objective-C. Thanks @mrmacete!
  • darwin: Take Interceptor and Cloak locks in ThreadSuspendMonitor, to extend its scope to prevent deadlock scenarios where threads holding the Cloak or Interceptor lock get suspended. Thanks @mrmacete!
  • darwin: Fix racy teardown of InjectInstance dispatch source.
  • darwin: Fix racy teardown of SpawnInstance dispatch source.
  • android: Fix child-gating of execl() and friends.
  • compiler: Bump @types/frida-gum. Thanks @s1341!
  • modulate: Gracefully handle missing symbols. Thanks @hsorbo!
  • python: Move _frida package inside the frida package.


And that pretty much sums it up. Enjoy!

Frida 16.2.1 Released

Just a quick bug-fix release to deal with an iOS packaging bug affecting users on palera1n (rootless) and Dopamine. Kudos to @miticollo for reporting this so quickly.


Frida 16.2.0 Released

Lots of exciting new things this time around. Let’s dive right in.

Thread Names

The Process.enumerateThreads() API now also exposes thread names when available:

$ frida -U -F
[Pixel 6 ]-> Process.enumerateThreads()[1]
    "id": 9579,
    "name": "Signal Catcher",
    "state": "waiting",
    "context": {}
[Pixel 6 ]->

Kudos to @Hexploitable for the awesome pull-request that got this ball rolling 🙌

Memory Protection Queries

Sometimes it’s essential to quickly determine the current protection of a page in memory. Thanks to @mrmacete, now you can:

$ frida -p 0
[Local::SystemSession ]-> Memory.queryProtection(Module.getExportByName(null, 'open'))
[Local::SystemSession ]->

QuickJS 2024-01-13

This release also contains the latest QuickJS, released last month. This means there’s almost complete ES2023 support, and even some features from the upcoming ES2024 specification. Plus, quite a few bug-fixes as well. It’s also worth mentioning that top-level await is now supported, making it easier to write portable scripts that work on both of our JavaScript runtimes.


Frida keeps track of its own memory ranges, threads, etc., to keep you from seeing yourself during process introspection. Introspection APIs such as Process.enumerateThreads() ensure that Frida’s own resources are hidden, and things appear as if you were not inside the process being instrumented.

For those of you using Stalker, or other things that expose you to raw memory locations, you might see code not belonging to any loaded module, and wonder where it came from. For example, if you use Stalker.follow() to follow execution into or out of a hooked function, it’s going to execute some trampoline code generated by Interceptor.

Agents using Gum’s C APIs could already query whether a given memory address, thread, etc., is owned by Frida, but this hadn’t yet been exposed to our JavaScript bindings. But now, thanks to another awesome contribution by @mrmacete, this is now supported. For example:

$ frida -p 0
[Local::SystemSession ]-> open = Module.getExportByName(null, 'open')
[Local::SystemSession ]-> Interceptor.attach(open, () => {})
[Local::SystemSession ]-> Instruction.parse(open).toString()
"jmp 0x7f928940c408"
[Local::SystemSession ]-> Cloak.hasRangeContaining(ptr('0x7f928940c408'))
[Local::SystemSession ]-> pointInsideOpen = open.add(16)
[Local::SystemSession ]-> Instruction.parse(pointInsideOpen).toString()
"push rbx"
[Local::SystemSession ]-> Cloak.hasRangeContaining(pointInsideOpen)
[Local::SystemSession ]->

Fast Interceptor

One little known and fairly recent feature, now also available from JavaScript, is Interceptor.replaceFast(). What’s different about it is that the target is modified to vector directly to your replacement, which means there is less overhead compared to Interceptor.replace(). This also means that you need to use the returned pointer if you want to call the original implementation. It is also not possible to combine it with Interceptor.attach() for the same target. However, if you’re dealing with a hot target it’s definitely something you want to have in your toolbox, especially when combined with CModule.

ELF Exports Regression

We discovered way late that Frida 16.1.0 broke Module#enumerateExports() on some ELF binaries, and this is now finally fixed. It turned out to be a bug in the bounds-checking that I added when making Gum.ElfModule a cross-platform API, when it got support for offline use-cases like that of our Barebone backend. (Where we use it to dynamically inject Rust code into OS kernels and bare-metal targets.)

ELF Import Slots

Those of you using Frida on Apple platforms may have noticed that Module#enumerateImports() provides you with import objects that always have a slot property. One way you can use this is to write a new pointer to that address, letting you rebind imports on a per-module basis. Super-handy if Interceptor is unable to hook a function, or for scenarios where you want to avoid inline hooks.

As of this release, we now provide the slot property on ELF-based platforms too, so you can also use this feature on Linux, Android, FreeBSD, and QNX. Yay!

Android Stability

Avid users on recent versions of Android may have experienced “soft-loops”, where Frida randomly crashes Zygote and causes a big chunk of user-space to restart. This turned out to be caused by the runtime preparing itself to fork() by polling /proc/self/stat while waiting for the thread count to drop to one, i.e. waiting for the process to become single-threaded.

This has now been solved by subtracting the threads owned by Frida, which we determine by internally using the Cloak API, touched upon earlier in this post. We also make use of the new ELF Import Slots feature, so we can hook read() only for callers in Inserting an inline hook in’s read() is not a great idea in this case, as the process we’re in might use it for a blocking read that blocks indefinitely. This becomes a challenge when wanting to unload, and getting stuck waiting for the chance to roll back our inline hook.

Anyway, quite an interesting bug. Shout-out to @enovella_ for reporting and helping track this one down 🙌

While on the topic of Android, this release also improves Java hooking on Android 14, where a new ART quick entrypoint is being used in case of –enable-optimizations. Kudos to @cr4zyserb for this neat contribution.

Better iOS 16 support

If you ever got hit by Service cannot load in requested session when trying to install Frida on iOS, this is now finally fixed. Kudos to @as0ler for the assist on fixing this.

Jailbroken tvOS

Another exciting development is that we now support jailbroken tvOS, and you can grab a .deb straight from our repo at Special thanks to @tmm1 for the pull-requests that made this happen.


This release also has a lot more to offer. The remaining changes are:

  • symbolutil-libdwarf: Fix handling of DW_AT_ranges in DWARF 5. This means the DebugSymbol API works properly on binaries built by newer toolchains.
  • elf-module: Fix relocation address when online.
  • interceptor: Check module prefix when claiming grafts on arm64, for compatibility with Xcode’s new libLogRedirect.dylib interposing on iOS 17. Thanks @mrmacete!
  • interceptor: Cloak thunks on arm64. Thanks @mrmacete!
  • windows: Ensure that the MSVC source-charset is set to UTF-8, so embedded JavaScript can be parsed correctly at runtime. Thanks @Qfrost911!
  • freebsd: Improve allocate-near strategy.
  • gumjs: Plug leak during QJS load() w/ ESM.
  • objc: Ensure block structure is writable in implementation setter. Thanks @mrmacete!
  • compiler: Bump @types/frida-gum to 18.6.0.


Frida 16.1.11 Released

Lots of goodies this time around:

  • stalker: Improve stability on multiple fronts. Kudos to @as0ler, @hsorbo, and @mrmacete for the fun and productive mob programming sessions that resulted in these wonderful improvements:
    • stalker: Copy BLR for excluded calls on arm64, instead of replacing them with functionally-equivalent ones, so that any pointer authentication context is used as expected. Thanks @mrmacete!
    • stalker: Abort when allocate_near() fails on arm64, instead of crashing due to the subsequent NULL pointer dereference.
    • gumjs: Fix crash in Stalker.flush() on a stopped sink. This happens if Stalker.garbageCollect() was just called.
    • gumjs: Fix use-after-free in Stalker QuickJS callback logic. We need to keep the callback values alive in case Stalker.garbageCollect() happens in the middle and releases them.
  • darwin: Improve symbolicator cache invalidator logic. Thanks @mrmacete!
  • swift-api-resolver: Handle signed pointers.
  • linux: Improve spawn() to handle partial link maps.
  • linux: Improve injector to handle XOM pages.
  • linux: Improve injector RTLD API detection.
  • linux: Fix injector ELF SYMTAB name parsing.
  • node: Link against the inspector library on UNIX, to fix RTLD panic when Script#enableDebugger() is called. Thanks @pandasauce!
  • ci: Publish FreeBSD prebuilds for Node.js 20 and Electron 27.

Frida 16.1.10 Released

Some neat little bug-fixes, just in time for Christmas:

  • server: Add missing entitlements for iOS 16, required to remap binary files in memory. Thanks @as0ler!
  • android: Fix Java hooking of interpreter-run methods on Android 14.
  • Fix argv[0] shown in CLI tools such as frida-server and frida-inject. Thanks @bet4it!

Frida 16.1.9 Released

Quite a few goodies in this release:

  • interceptor: Pause cloaked threads too. This prevents random SIGBUS crashes on our own threads while using Interceptor to hook functions residing on the same page as any of the ones potentially used internally. Thanks @mrmacete!
  • darwin: Move to our POSIX Exceptor backend. Mach exception handling APIs have become increasingly restrictive in recent Apple OS versions.
  • darwin: Resolve import trampolines on arm64, allowing us to hook targets such as sigaction().
  • linux: Improve spawn() to handle r_brk being hit again.
  • linker: Improve spawn() to consider on-disk ELF for RTLD symbols. This means we might find r_debug on additional Android systems, for example.
  • linux: Fix spawn() when DT_INIT_ARRAY contains sentinel values.
  • linux: Improve spawn() to use DT_PREINIT_ARRAY if present.
  • android: Handle symlinks in RTLD fallback logic.

Frida 16.1.8 Released

Three exciting changes this time around:

  • process: Add get_main_module(), exposed to JavaScript as Process.mainModule. Useful when needing to know which module represents the main executable of the process. In the past this was typically accomplished by enumerating the loaded modules and assuming that the first one in the list is the one. This is no longer the case on the latest Apple OSes, so we now provide an efficient and portable solution with this new API. Thanks @mrmacete!
  • compiler: Bump @types/frida-gum to 18.5.0, now with typings for recent API additions.
  • barebone: Fix compatibility with latest Corellium.

Frida 16.1.7 Released

Some neat refinements this time around:

  • stalker: Allow transformer to skip calls on x86. Thanks @s1341!
  • objc: Tolerate mangled Swift class names. Thanks @hsorbo!
  • cpu-features: Improve the AVX2 detection. Thanks for the assist, @smx-smx!
  • windows: Add support for MinGW. Thanks for the assist, @smx-smx!
  • openssl: Upgrade to openssl-3.0.12+quic@b81f0ae.

Frida 16.1.6 Released

Just a quick bug-fix release to roll back two of the Interceptor/Relocator arm64 changes that went into the previous release. Turns out that these need some more refinement before they can land, so we will roll them back for now.


  • Revert “relocator: Improve scratch register strategy on arm64”.
  • Revert “interceptor: Relocate tiny targets on arm64”.
  • stalker: Allow transformer to skip calls on arm64.

Frida 16.1.5 Released

Since our last release, @hsorbo and I had a lot of fun pair-programming on a wide range of exciting tech. Let’s dive right in.


We’ve introduced a brand new ApiResolver for Swift, which you can use like this:

const r = new ApiResolver('swift');
.forEach(({ name, address }) => {
  console.log('Found:', name, 'at:', address);

There’s also a new and exciting frida-tools release, 12.3.0, which upgrades frida-trace with Swift tracing support, using the new ApiResolver:

$ frida-trace Xcode -y '*CoreDevice!*RemoteDevice*'


Our Module API now also provides enumerateSections() and enumerateDependencies(). And for when you want to scan loaded modules for specific section names, our existing module ApiResolver now lets you do this with ease:

const r = new ApiResolver('module');
.forEach(({ name, address }) => {
  console.log('Found:', name, 'at:', address);


There’s also a bunch of other exciting changes, so definitely check out the changelog below.



  • swift-api-resolver: Add a brand new Swift API Resolver.
  • module-api-resolver: Support resolving sections.
  • api-resolver: Add optional size field to matches.
  • module: Add enumerate_sections().
  • module: Add enumerate_dependencies().
  • device: Add unpair(). Only implemented for iOS-devices for now.
  • compiler: Bump frida-compile to 16.4.1, and @types/frida-gum to 18.4.5.
  • gdb: Handle empty response packets.
  • gdb: Handle error reply to feature document request.
  • darwin-mapper: Initialize TLV descriptors on load. Thanks @fabianfreyer!
  • darwin-module: Add Thread Local Variable APIs. Thanks @fabianfreyer!
  • darwin-module: Optimize exports enumeration slightly.
  • elf-module: Improve the section ID generation.
  • x86-writer: Add reg-reg {fs,gs}-based MOV insns. Thanks @fabianfreyer!
  • arm64-writer: Add MRS instruction. Thanks @fabianfreyer!
  • arm64-writer: Add UBFM, LSL, and LSR instructions. Thanks @fabianfreyer!
  • relocator: Improve scratch register strategy on arm64.
  • interceptor: Branch to trampoline using computed scratch register.
  • interceptor: Relocate tiny targets on arm64.
  • linux: Handle disabled process_vm_{read,write}v(). Thanks @Pyraun!
  • server: Use sysroot for temporary files on rootless iOS. Thanks @fabianfreyer!
  • gumjs: Fix crash in File and Database when Interceptor is absent. Thanks @mrmacete!
  • gumjs: Fix NativePointer from number for 32-bit BE (#752). Thanks @forky2!
  • gumjs: Bump frida-swift-bridge to 2.0.7.
  • ci: Publish prebuilds for Node.js 20 & 21, and Electron 27.
  • ci: Do not publish Swift bindings for now. There’s a long-standing heisenbug that causes the x86_64 slice to randomly end up corrupted, in turn resulting in CI release jobs failing. As I’m not too keen on sinking time into this anytime soon, considering how easy it is to build these bindings locally using a downloaded core devkit, simply dropping the release asset seems like the best solution.

Frida 16.1.4 Released

Some exciting improvements this time around:

  • ios: Fix spawn() on iOS 17. Thanks @hsorbo!
  • ios: Add support for rootless systems. Thanks for the pair-programming, @hsorbo!
  • android: Fix dynamic linker compatibility regression. Thanks for the pair-programming, @hsorbo! Kudo to @getorix for reporting.
  • gumjs: Add Worker API, so heavy processing can be moved to a background thread, allowing hooks to be handled in a timely manner. Only implemented in the QuickJS runtime for now. Kudos to @mrmacete for tracking down and fixing last-minute bugs in the implementation.
  • linux: Improve error-handling when trying to attach to processes that are near death.

Frida 16.1.3 Released

Time for a new release, just in time for the weekend:

  • server: Add missing entitlement for iOS >= 17. Kudos to @alexhude for helping get to the bottom of this one.
  • stalker: Improve exclusive store handling on arm and arm64. Instead of potentially expanding the current block to include instructions beyond where the block would naturally end, we move to a safer approach: Once we encounter an exclusive store, we look back at the previously generated blocks to see if we can find one with an exclusive load. If we do, we mark this range of blocks as using exclusive access. We also invalidate the blocks, so that problematic instrumentation can be omitted upon recompilation. To also allow custom transformers to adapt their generated code, we introduce StalkerIterator.get_memory_access(). Thanks for the fun and productive pair-programming, @hsorbo!
  • gumjs: Add StalkerIterator.memoryAccess, allowing a custom transformer to determine what kind of instrumentation is safe to add without disturbing an exclusive store operation. Set to either ‘open’, when “noisy” instrumentation such as callouts are safe, or ‘exclusive’, when such instrumentation is risky and may lead to infinite loops. (Due to an exclusive store failing, and every subsequent retry also failing.)
  • gumjs: Fix CModule bindings for Stalker on arm.
  • stalker: Fix crash on invalidation in added slabs.
  • arm64-writer: Add put_eor_reg_reg_reg(). Thanks for the fun and productive pair-programming, @hsorbo!

Frida 16.1.2 Released

Time for a new release to refine a few things:

  • darwin: Fix Stalker.follow() regression where ongoing system calls would get brutally interrupted, typically resulting in the target crashing. Thanks for the pair-programming, @hsorbo!
  • gumjs: Implement the WeakRef API for QuickJS.
  • compiler: Bump @types/frida-gum to 18.4.0.

Frida 16.1.1 Released

Only a few changes this time around:

  • compiler: Bump frida-compile to 16.3.0, now with TypeScript 5.1.5 and other improvements. Among them is a fix by @hsorbo that changes the default moduleResolution to Node16.
  • stalker: Add Iterator.get_capstone(), so transformers can use Capstone APIs that require the Capstone handle.
  • node: Fix RPC message array check. Thanks @ZachQin!

Frida 16.1.0 Released

For quite some years I’ve been dreaming of taking Frida beyond user-space software, to also support instrumenting OS kernels as well as barebone systems. Perhaps even microcontrollers…


Earlier this year my family’s cat door broke down. After some back and forth with the retailer, double-checking the installation and such, it would work for a little while before it eventually started malfunctioning.

This was obviously not much fun for our cats:


It’s also no surprise that they’d end up making a lot of noise, in turn making it hard to get a good night’s sleep when having to get up to let them back in manually.

I ended up buying a second cat door and, lo and behold, no more issues. The old one ended up collecting dust for a while. Something I kept thinking of was whether I could debug it, and perhaps even extend the software to do more useful things.

Feeling the urge to open it up to poke at the electronics inside of it, I eventually gave in:


That looked like an STM32F030C6T6, which is an ARM Cortex M0-based MCU. My first thought was whether I could dump the flash memory to do some static analysis.

After quickly skimming MCU docs, and a little bit of multimeter probing, I figured out the JP12 pads:

PAD 1/2     PAD 7/8

This made it easy to pull BOOT0 high, so the MCU boots into its internal bootloader instead of user code.

By hooking up a USB to 3.3V TTL device to the USART1 pads, I could dump the flash:

$ ./stm32flash -r firmware.bin /dev/ttyUSB0
stm32flash 0.7

Interface serial_posix: 57600 8E1
Version      : 0x31
Option 1     : 0x00
Option 2     : 0x00
Device ID    : 0x0444 (STM32F03xx4/6)
- RAM        : Up to 4KiB  (2048b reserved by bootloader)
- Flash      : Up to 32KiB (size first sector: 4x1024)
- Option bytes  : 16b
- System memory : 3KiB
Memory read
Read address 0x08008000 (100.00%) Done.

And perform some static analysis: cat-door-firmware

Seeing as two of the other pads are connected to SWDIO and SWCLK, used for Serial Wire Debug (SWD), the natural next step was to hook up a Raspberry Pi Debug Probe to those. After getting that set up, I fired up OpenOCD:

$ openocd -f interface/cmsis-dap.cfg -f target/stm32f0x.cfg
Open On-Chip Debugger 0.11.0-g8e3c38f7-dirty (2023-05-05-14:25)
Licensed under GNU GPL v2
For bug reports, read
Info : auto-selecting first available session transport "swd". To override use 'transport select <transport>'.
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : Using CMSIS-DAPv2 interface with VID:PID=0x2e8a:0x000c, serial=E6614103E78B482F
Info : CMSIS-DAP: SWD  Supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 0 SWDIO/TMS = 0 TDI = 0 TDO = 0 nTRST = 0 nRESET = 0
Info : CMSIS-DAP: Interface ready
Info : clock speed 1000 kHz
Info : SWD DPIDR 0x0bb11477
Info : stm32f0x.cpu: hardware has 4 breakpoints, 2 watchpoints
Info : starting gdb server for stm32f0x.cpu on 3333
Info : Listening on port 3333 for gdb connections

The idea I had been thinking about for a long time was to add a new Frida backend where the only process you can attach to is PID 0. Any scripts loaded there would actually be run locally, and implement the familiar JavaScript API. Any API that accesses memory, such as when dereferencing an int * by doing e.g. ptr(‘0x80000’).readInt(), would end up querying the target, through SWD in the above case.

I initially started sketching this where the backend would talk to the OpenOCD daemon through its telnet interface. But I quickly realized that it would be better to talk to its GDB-compatible remote stub. In this way, Frida will be able to instrument any target with an available remote stub. Whether that’s OpenOCD, Corellium (iOS kernel instrumentation!), QEMU, etc.

As for Interceptor, my thinking was that basic functionality would be implemented using breakpoints. But, only if the user supplies JavaScript callbacks. If function pointers are supplied instead, we could perform inline hooking so the target can run without any traps/ping-pongs with the host. This means it could even be used for observing and modifying hot code inside an OS kernel or MCU firmware.

After some initial sketching, I was able to run the following script:

Interceptor.breakpointKind = 'hard';

const THUMB_BIT = 1;

const initRest = ptr('0x0800306a').or(THUMB_BIT);
Interceptor.attach(initRest, {
  onEnter(args) {
    console.log('>>> init_rest()',
        JSON.stringify(this.context, null, 2));
  onLeave(retval) {
    console.log(`<<< init_rest() retval=${retval}`);

Using the Frida REPL:

$ frida -D barebone -p 0 -l demo.js
    / _  |   Frida 16.1.0 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at
   . . . .
   . . . .   Connected to GDB Remote Stub (id=barebone)

[Remote::SystemSession ]-> $gdb.continue()
[Remote::SystemSession ]-> >>> init_rest() {
  "r7": "0xffffffff",
  "pc": "0x800306a",
  "r8": "0xffffffff",
  "xPSR": "0x41000000",
  "r9": "0xffffffff",
  "sp": "0x20000578",
  "r0": "0x0",
  "r10": "0xffffffff",
  "lr": "0x8003069",
  "r1": "0x40021008",
  "r11": "0xffffffff",
  "r2": "0xffffffff",
  "r12": "0xffffffff",
  "r3": "0xffffffff",
  "r4": "0xffffffff",
  "r5": "0xffffffff",
  "r6": "0xffffffff"
<<< init_rest() retval=0x1

A few things to note here:

  • We set Interceptor.breakpointKind to hard, as our target’s code resides in flash, which means software-breakpoints won’t work. This would not have been necessary if I was using a J-Link or similar SWD interface, which transparently reflashes as software-breakpoints are added.
  • The backend does not yet automatically resume, so we are doing that manually through $gdb.continue(), part of this internal API, which exposes most of GDB.Client to JavaScript. This is meant to become an internal implementation detail, but will be needed while this new backend matures – it should not yet be considered stable API.
  • We set the least significant bit to indicate to Interceptor that the target function uses Thumb instruction encoding. This part may already be familiar to you if you have previously used Frida’s conventional backends on 32-bit ARM.
  • The new Barebone backend connects to the GDB-compatible remote stub at by default. This matches what OpenOCD typically defaults to, but can be overridden by setting the FRIDA_BAREBONE_ADDRESS environment variable.

OS kernels

While my fun little cat door side-quest is a great test case for the tiny part of the spectrum, there’s also a lot of potential in supporting larger systems.

One of the cooler use-cases there is definitely Corellium, as it means we can instrument the iOS kernel. Using a Tamarin Cable it should even be possible to get this working on a checkm8-exploitable physical device.

Before we touch on that though, let’s see if we can get things going with QEMU and a live Linux kernel.


First, we’ll fire up a VM we can play with:

$ pip install arm_now
$ arm_now start aarch64 --add-qemu-options='-gdb tcp::9000'
Welcome to arm_now
buildroot login:

Next, we’ll use the Frida REPL to look around:

$ frida -D barebone -p 0
    / _  |   Frida 16.1.0 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at
   . . . .
   . . . .   Connected to GDB Remote Stub (id=barebone)

[Remote::SystemSession ]-> Process.arch
[Remote::SystemSession ]-> Process.enumerateRanges('r-x')
        "base": "0xffffff8008080000",
        "protection": "r-x",
        "size": 4259840
[Remote::SystemSession ]-> $gdb.state
[Remote::SystemSession ]-> $gdb.exception
    "breakpoint": null,
    "signum": 2,
    "thread": {}
[Remote::SystemSession ]-> $gdb.exception.thread.readRegisters()
    "cpsr": 1610613189,
    "pc": "0xffffff8008096648",
    "sp": "0xffffff80085f3f10",
    "x0": "0x0",
    "x1": "0xffffff80085e6b78",
    "x10": "0x880",
    "x11": "0xffffffc00e877180",
    "x12": "0x0",
    "x13": "0xffffffc00ffe1f30",
    "x14": "0x0",
    "x15": "0xfffffff8",
    "x16": "0xffffffbeff000000",
    "x17": "0x0",
    "x18": "0xffffffc00ffe17e0",
    "x19": "0xffffff80085e0000",
    "x2": "0x40079f5000",
    "x20": "0xffffff80085f892c",
    "x21": "0xffffff80085f88a0",
    "x22": "0xffffff80085ffe80",
    "x23": "0xffffff80085ffe80",
    "x24": "0xffffff80085d5028",
    "x25": "0x0",
    "x26": "0x0",
    "x27": "0x0",
    "x28": "0x405a0018",
    "x29": "0xffffff80085f3f10",
    "x3": "0x30c",
    "x30": "0xffffff800808492c",
    "x4": "0x0",
    "x5": "0x40079f5000",
    "x6": "0x1",
    "x7": "0x1c0",
    "x8": "0x2",
    "x9": "0xffffff80085f3e80"
[Remote::SystemSession ]->

You might wonder how we’ve implemented Process.enumerateRanges(). This part is for now only implemented on arm64, and it is accomplished by parsing the page tables. (And if we’re talking to Corellium’s remote stub we use a vendor-specific monitor command to save ourselves a lot of network roundtrips.)

So now that we’re peeking into a running kernel, one of the things we might want to do is find internal functions and data structures. This is where the memory- scanning API comes handy:

for (const r of Process.enumerateRanges('r-x')) {
  console.log(JSON.stringify(r, null, 2));
  const matches = Memory.scanSync(r.base, r.size,
      '7b2000f0 fa03082a 992480d2 : 1f00009f ffffffff 1f00e0ff');
  console.log('Matches:', JSON.stringify(matches, null, 2));

Here we’re looking for the Linux kernel’s arm64 syscall handler, matching on its first three instructions. We use the masking feature to mask out the immediates of the ADRP and MOV instructions (first and third instruction).

Let’s take it for a spin:

$ frida -D barebone -p 0 -l scan.js
    / _  |   Frida 16.1.0 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at
   . . . .
   . . . .   Connected to GDB Remote Stub (id=barebone)
  "base": "0xffffff8008080000",
  "size": 4259840,
  "protection": "r-x"
Matches: [
    "address": "0xffffff8008082f00",
    "size": 12
[Remote::SystemSession ]->

So now we’ve dynamically detected the kernel’s internal syscall handler! 🚀

Re-implementing the memory scanning feature was one of the highlights for me personally, as @hsorbo and I had a lot of fun pair-programming on it. The implementation is conceptually very similar to what we’re doing in our Fruity backend for jailed iOS, and our new Linux injector: instead of transferring data to the host, and searching that, we can get away with transferring only the search algorithm, to run that on the target.

The memory scanner implementation is written in Rust, and helped prepare the groundwork for a new cool feature I’m going to cover a bit later in this post.

So, now that we know where the Linux kernel’s syscall handler is, we can use Interceptor to install an instruction-level hook:

const el0Svc = ptr('0xffffff8008082f00');
Interceptor.attach(el0Svc, function (args) {
  const { context } = this;
  const scno = context.x8.toUInt32();
  console.log(`syscall! scno=${scno}`);

And try that out on our running VM:

$ frida -D barebone -p 0 -l kernhook.js
    / _  |   Frida 16.1.0 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at
   . . . .
   . . . .   Connected to GDB Remote Stub (id=barebone)

[Remote::SystemSession ]-> $gdb.continue()
[Remote::SystemSession ]-> syscall! scno=63
syscall! scno=64
syscall! scno=73
syscall! scno=63
syscall! scno=64
syscall! scno=73
syscall! scno=63
syscall! scno=64
syscall! scno=56
syscall! scno=62
syscall! scno=64
syscall! scno=57
syscall! scno=29
syscall! scno=134

And that’s it – we are monitoring system calls across the entire system! 💥


One of the first things you’ll probably notice if you try the previous example is that we slow down the system quite a bit. This is because Interceptor uses breakpoints when a JavaScript function is specified as the callback.

Not to worry though. If we write our callback in machine code and pass a NativePointer instead, Interceptor will pick a different strategy: it will modify the target’s machine code to redirect execution to a trampoline, which in turn calls the function at the address that we specify.

So that’s great. We only need to get our machine code into memory. Some of you may be familiar with our CModule API. We don’t yet implement that one in this new Barebone backend (and we will, eventually!), but we have something even better. Enter RustModule:

const kernBase = ptr('0xffffff8008080000');
const procPidStatus = kernBase.add(0x15e600);

const m = new RustModule(`
pub unsafe extern "C" fn hook(ic: &mut gum::InvocationContext) -> () {
    let regs = &mut ic.cpu_context;
    println!("proc_pid_status() was called with x0={:#x} x1={:#x}",

Interceptor.attach(procPidStatus, m.hook);

The RustModule implementation uses a local Rust toolchain, assumed to be on your PATH, to compile the code you provide it into a no_std self-contained ELF. It relocates this ELF and writes it into the target’s memory. As part of this it will also parse the MMU’s page tables and insert new entries there so the uploaded code becomes part of the virtual address space, where the pages are read/write/execute.

In this example we’re hooking proc_pid_status() in our live Linux kernel.

Note that File.readAllText() can be used to avoid having to inline Rust code inside your JavaScript. Here we’re using inline code for the sake of brevity.

Now, with our Rust-powered agent, let’s take it for a spin:

$ frida -D barebone -p 0 -l kernhook2.js
    / _  |   Frida 16.1.0 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at
   . . . .
   . . . .   Connected to GDB Remote Stub (id=barebone)

Error: to enable this feature, set FRIDA_BAREBONE_HEAP_BASE to the physical base address to use, e.g. 0x48000000
    at <eval> (/home/oleavr/src/demo/kernhook2.js:13)
    at evaluate (native)
    at <anonymous> (/frida/repl-2.js:1)

[Remote::SystemSession ]->

Oops! That didn’t quite work. There is still one piece missing in our new backend: we don’t yet have any “kernel bridges” in place that automatically fingerprint the internals of known kernels in order to find a suitable internal memory allocator we can use. This will also be needed to implement APIs such as Process.enumerateModules(), which will allow listing the loaded kernel modules/kexts. We could also locate the kernel’s process list and implement enumerate_processes(), so frida-ps works. And those are only a couple of examples… How about injecting frida-gadget into a user-space process? That would be super-useful for an embedded system where we want to avoid modifying the flash. Anyway, I digress 😊

So, on MCUs and unknown kernels you will have to tell Frida where, in physical memory, we may clobber if you want to use intrusive features such as RustModule, Interceptor in its inline hooking mode, Memory.alloc(), etc.

With that in mind, let’s retry our example, but this time we’ll set the FRIDA_BAREBONE_HEAP_BASE environment variable:

$ export FRIDA_BAREBONE_HEAP_BASE=0x48000000
$ frida -D barebone -p 0 -l kernhook2.js
    / _  |   Frida 16.1.0 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at
   . . . .
   . . . .   Connected to GDB Remote Stub (id=barebone)

[Remote::SystemSession ]-> m
    "hook": "0xffffff80080103e0"
[Remote::SystemSession ]-> $gdb.continue()

Yay! 🎉 So now, in the terminal where we have QEMU running, let’s try accessing /proc/$pid/status three times, so the hooked function gets called:

# head -3 /proc/self/status
Name:	head
Umask:	0022
State:	R (running)
# head -3 /proc/self/status
Name:	head
Umask:	0022
State:	R (running)
# head -3 /proc/self/status
Name:	head
Umask:	0022
State:	R (running)

Over in our REPL, we should see our hook() getting hit three times:

proc_pid_status() was called with x0=0xffffffc00d4bca00 x1=0xffffff8008608758
proc_pid_status() was called with x0=0xffffffc00d4bc780 x1=0xffffff8008608758
proc_pid_status() was called with x0=0xffffffc00d4bc780 x1=0xffffff8008608758

It works! 🥳

There’s one important thing to note though: In our example we’re using println!(), and this actually causes the target to hit a breakpoint, so the host can read out the message passed to it, and bubble that up just like a console.log() from JavaScript. This means you should only use this feature for temporary debugging purposes, and throttle how often it’s called if on a hot code-path.

The next thing you might want to do is pass external symbols into your RustModule. For example if you want to call internal kernel functions from your Rust code. This is accomplished by declaring them like this:

extern "C" {
    fn frobnicate(data: *const u8, len: usize);

Then when constructing the RustModule, pass it in through the second argument:

const m = new RustModule(source, {
  frobnicate: ptr('0xffffff8008084320'),

For those of you familiar with our CModule API, this part is exactly the same. You can also use NativeCallback to implement portions host-side, in JavaScript, but this needs to be handled with care to avoid performance bottlenecks. Going in the opposite direction there is also NativeFunction, which you can use to call into your Rust code from JavaScript.

Last but not least, you may also want to import existing Rust crates from This is also supported:

const m = new RustModule(source, {}, {
  dependencies: [
    'cstr_core = { version = "0.2.6", default-features = false }',


What’s so exciting is that all of the Linux bits above all “just work” on Corellium as well. All you need to do is point FRIDA_BAREBONE_ADDRESS at the endpoint shown in Corellium’s UI under “Advanced Options” -> “gdb”.

Shout-out to the awesome folks at Corellium for their support while doing this. They even implemented new protocol features to improve interoperability 🔥


This new backend should be considered alpha quality for now, but I think it’s already capable of so many useful things that it would be a shame to keep it sitting on a branch.

You may notice that the JS APIs implemented only cover a subset, and not all features are available on non-arm64 targets yet. But all of this will improve as the backend matures. (Pull-requests are super-welcome!)

And as a fun aside, here’s Frida attached to the BeOS kernel:



There’s also a bunch of other exciting changes, so definitely check out the changelog below.



  • Add Barebone backend. (Covered extensively above.)
  • objc: Handle modifiers. This makes type parsing more reliable, especially when dealing with ivars which often times have the “atomic” modifier: exceptions were thrown because the modifiers ended up being treated as unknown types. Thanks @mrmacete!
  • android: Fix support for Android 14. Thanks @gsingh93! Also thanks @jayluxferro for contributing a follow-up fix for a mistake I made while merging @gsingh93’s PR.
  • gum-graft: Add support for chained imports. Thanks @mrmacete!
  • gumjs: Add NativePointer#readVolatile(), to provide a safe way to read from memory that may become unmapped or have its memory protection changed midway through. Thanks @hsorbo!
  • darwin: Improve tvOS support to also cover frida-server. Thanks @tmm1!
  • darwin: Plug memory leak in fallback kill() logic. Thanks @tmm1!
  • fruity: Handle failure to fetch dyld symbols.
  • compiler: Bump frida-compile to 16.2.2. Dependencies’ source maps are now also bundled – thanks @vfsfitvnm!
  • compiler: Bump @types/frida-gum to 18.3.2, now with improved typings for hexdump().
  • gdb: Add GDB.Client, by factoring out the guts of Fruity’s LLDB.Client, with many protocol enhancements and interoperability fixes added on top.
  • elf-module: Improve API and make it cross-platform. Support loading from a blob, expose relocations, and improve overall robustness.
  • capstone: Fix crash on x86 when building with MSVC.

Frida 16.0.19 Released

Some exciting stability improvements this time around:

  • darwin: Make spawn() aware of the prewarm feature used on iOS >= 15. What happens is that the most frequently used apps are being spawned ahead of time by dasd and kept suspended until the user attempts to launch them, in order to save some startup time. This was in the way of Frida’s spawn mechanism because:
    • apps launched this way won’t be going through launchd when expected, causing a “timeout was reached” error, or more complex race conditions
    • Frida needs to spawn a fresh process to ensure early instrumentation

    We solve these problems by:

    • ignoring prewarm spawns from the launchd agent (though they’re still emitted as spawn if spawn gating is enabled)
    • killing existing prewarmed apps before attempting to spawn them Thanks @mrmacete!
  • glib: Ensure {Input,Output}Stream only g_poll() if pollable. This is essential on Apple OSes and BSDs, where GLib uses kqueue to implement GMainContext, as it may otherwise end up waiting forever when the file-descriptor represents an ordinary file. Thanks @hsorbo!
  • android: Fix x86 devkits by improving libffi to avoid problematic relocations.
  • gadget: Fix deadlock during Windows process termination. Kudos to @Palacee-hun for reporting!

Frida 16.0.18 Released

Just a quick bug-fix release reviving support for Android x86/x86_64 systems with ARM emulation. This is still a blind spot in our CI, and I forgot all about it while working on the new Linux injector. Kudos to @stopmosk for promptly reporting and helping triage this regression.

Frida 16.0.17 Released

Time for a bug-fix release with only one change: turns out the ARMv8 BTI interop introduced in 16.0.14 is problematic on Apple A12+ SoCs when running in arm64e mode, i.e. with pointer authentication enabled.

Kudos to @miticollo for reporting and helping triage the cause, and @mrmacete for digging further into it, brainstorming potential fixes, and implementing the fix. You guys rock! ❤️

Frida 16.0.16 Released

Seeing as TypeScript 5.0 was released last month, and frida.Compiler was still at 4.9, we figured it’s time to upgrade it. So with this release we’re now shipping 5.0.4. The upgrade also revealed a couple of bugs in our V8-based runtime, and that our embedded frida-gum typings were slightly outdated.



  • gumjs: Fix task deadlock during V8 Isolate teardown.
  • gumjs: Fix undefined behavior during V8 snapshot creation.
  • compiler: Bump frida-compile.
  • compiler: Use the proper @types/frida-gum.

Frida 16.0.15 Released

Here’s a release fixing two stability issues affecting users on Apple platforms:

  • interceptor: Keep thread ports alive between suspend and resume on Darwin. We were borrowing the Mach thread port right names released by enumerate_threads(), assuming that another reference existed to keep each of them alive. When this wasn’t the case we would at best pass an invalid Mach port right name to thread_resume(), and worst-case we would use a totally unrelated port. Regardless, we would leave such threads suspended forever. Thanks @mrmacete!
  • agent: Dispose ThreadSuspendMonitor in its own transaction. Since Interceptor relies on ThreadSuspendMonitor passthrough, disposing of it in its own transaction and after all the other components which use Interceptor at destruction time makes sure no Frida thread will attempt executing code on non-executable pages at teardown time. Thanks @mrmacete!

Frida 16.0.14 Released

More exciting bug-fixes:

  • linux: Fix ProcMapsEntry major/minor number parsing. Thanks @chouex!
  • interceptor: Fix ARMv8 BTI interoperability. Thanks @zjw88282740!
  • arm64-writer: Add put_ret_reg(). Thanks @zjw88282740!
  • compiler: Fix filesystem access on some Linux systems, e.g. Ubuntu 20.04. Kudos to @pancake for reporting and helping track this one down!

Frida 16.0.13 Released

Only a few bug-fixes this time around:

  • android: Fix dynamic linker probing on older systems.
  • android: Limit graceful injection to arm and arm64.
  • linux: Fix syscall wait logic on x86 and x86_64.

Frida 16.0.12 Released

Lots of goodies this time around. One of them is our brand new Linux injector. This was a lot of fun, especially as it involved lots of pair-programming with @hsorbo.

We’re quite excited about this one. Frida now supports injecting into Linux containers, such as Flatpak apps. Not just that, it can finally inject code into processes without a dynamic linker present.

Another neat improvement is if you’re running Frida on Linux >= 3.17, you may notice that it no longer writes out any temporary files. This is accomplished through memfd and a reworked injector signaling mechanism.

Our new Linux injector has a fully asynchronous design, without any dangerous blocking operations that can result in deadlocks. It is also the first injector to support providing a control channel to the injected payload.

Down the road the plan is to implement this in our other backends as well, and make it part of our cross-platform Injector API. The thinking there is to make it easier to implement custom payloads.

There are also many other goodies in this release, so definitely check out the changelog below.



  • linux: Re-engineer injector. Thanks for the productive pair-programming, @hsorbo!
  • linux: Fix the libc-based module enumeration.
  • linux: Fix query_libc_name() on musl.
  • linux: Fix module handle resolver logic on musl.
  • linux: Fix Module.ensure_initialized() on musl.
  • darwin: Make ThreadSuspendMonitor passthrough if called by Frida, to avoid deadlock scenarios. Thanks @mrmacete!
  • darwin: During spawn(), always use PTYs for piped stdio, and enable close-on-exec.
  • windows: Use existing DbgHelp instance if present. Thanks @fesily!
  • windows: Implement Module.enumerate_symbols(). Thanks @fesily!
  • process: Skip cloaked modules in enumerate_modules().
  • cloak: Add has_range_containing().
  • elf-module: Replace has_interp() with get_interpreter().
  • gumjs: Fix runtime serialization unsigned encoding.
  • objc: Add symbol property on method as a means for the method object to fully describe itself in a human readable way. Thanks @mrmacete!
  • java: Use symbols for unique property names. Thanks @yotamN!
  • java: Add toString() method to overloads. Thanks @oriori1703!
  • java: Add toString() method to types and field. Thanks @yotamN!
  • java: Fix toString() for overloaded methods. Thanks @yotamN!
  • server: Support loading as a shared library. Thanks @Yannayli!

Frida 16.0.11 Released

Only a few bug-fixes this time around:

  • darwin: Fix deadlock while pausing threads on non-RWX systems. Thanks @mrmacete!
  • darwin: Fix mapping zero-sized segments. Thanks @comex!
  • linux: Improve Gum to support programs without a runtime linker.
  • linux: Fix error-propagation from Gum.Linux APIs.

Frida 16.0.10 Released

This time we’re bringing you additional iOS 15 improvements, an even better frida.Compiler, brand new support for musl libc, and more. Do check out the changelog below for more details.



  • ios: Fix spawn() on lower versions of iOS 15. Thanks @as0ler and @mrmacete!
  • ios: Ensure launchd prioritizes our daemon. Kudos to @getorix for investigating and suggesting this fix.
  • compiler: Bump frida-compile, frida-fs, and Gum typings. This means that frida.Compiler now also works properly on linux-arm64 and linux-x86.
  • linux: Add support for musl libc, including CI that publishes release assets for x86_64 and arm64.
  • gumjs: Add console.debug() and Thanks @oriori1703!
  • gumjs: Fix build when node_modules/@types exists above us.
  • gumjs: Ignore tcclib.h symbols when generating runtime. Thanks @milahu!
  • build: Improve support for shared builds. Thanks @milahu!

Frida 16.0.9 Released

The main theme of this release is improved support for jailbroken iOS. We now support iOS 16.3, including app listing and launching. Also included is improved support for iOS 15, and some regression fixes for older iOS versions.

There are also some other goodies in this release, so definitely check out the changelog below.



  • darwin: Fix app listing and launching on iOS >= 16. Thanks for the productive pair-programming, @hsorbo!
  • darwin: Fix crash during early instrumentation of modern dylds.
  • darwin: Fix breakpoint conflict in early instrumentation, a regression introduced in 16.0.8 as part of improving spawn() performance. Thanks @mrmacete!
  • package-server-ios: Force xz compression.
  • darwin-grafter: Create multiple segments if needed. Thanks @mrmacete!
  • interceptor: Flip page protection on Darwin grafted import activation. Thanks @mrmacete!
  • stalker: Fix handling of ARM LDMIA without writeback.
  • darwin: Add query_protection(). Thanks @mrmacete!
  • darwin: Avoid kernel task port probing in hardened processes. Thanks @as0ler!
  • darwin: Fix Process.modify_thread() reliability.
  • darwin: Fix query_hardened() on iOS w/ tweaks enabled. Thanks @as0ler!
  • darwin: Make Process.modify_thread() less disruptive.
  • darwin: Optimize Process.modify_thread().
  • darwin: Add modify_thread().
  • arm-writer: Add put_ldmia_reg_mask_wb().
  • gumjs: Fix runtime serialization not handling unicode. Thanks @milahu!
  • python: Add async variant to RPC calls. Thanks @yotamN!
  • python: Add specific signals type hinting for core. Thanks @yotamN!

Frida 16.0.8 Released

This time we’ve focused on polishing up our macOS and iOS support. For those of you using spawn() and spawn-gating for early instrumentation, things are now in much better shape.

i/macOS spawn() performance

The most exciting change in this release is all about performance. Programs that would previously take a while to start when launched using Frida should now be a lot quicker to start. This long-standing bottleneck was so bad that an app with a lot of libraries could fail to launch due to Frida slowing down its startup too much.

i/macOS and SIGPIPE

Next up we have a fix for a long-standing reliability issue. Turns out our file-descriptors used for IPC did not have SO_NOSIGPIPE set, so we could sometimes end up in a situation where either Frida or the target process terminated abruptly, and the other side would end up getting zapped by SIGPIPE while trying to write().

Sandboxed environments, part two

The previous release introduced some bold new changes to support injecting into hardened targets. Since then @hsorbo and me dug back into our recent GLib kqueue() patch and fixed some rough edges. We also fixed a regression where attaching to hardened processes through usbmuxd would fail with “connection closed”.


On the Linux and Android side of things, some of you may have noticed that thread enumeration could fail randomly, especially inside busy processes. That issue has now finally been dealt with.

Also, thanks to @drosseau we also have an error-handling improvement that should avoid some confusion when things fail in 32-/64-bit cross-arch builds.


That is all this time around. Enjoy!

Frida 16.0.7 Released

It’s been a busy week. Let’s dive in.

Sandboxed environments

This week @hsorbo and me spent some days trying to get Frida working better in sandboxed environments. Our goal was to be able to get Frida into Apple’s SpringBoard process on iOS. But to make things a little interesting, we figured we’d start with imagent, the daemon that handles the iMessage protocol. It has been hardened quite a bit in recent OS versions, and Frida was no longer able to attach to it.

So we first started out with this daemon on macOS, just to make things easier to debug. After finding the daemon’s sandbox profile at /System/Library/Sandbox/Profiles/, it was hard to miss the syscall policy. It disallows all syscalls by default, and carefully enables some groups of syscalls, plus some specific ones that it also needs.

We then discovered that Frida’s use of the pipe() syscall was the first hurdle. This code is not actually in Frida itself, but in GLib, the excellent library that Frida uses for data structures, cross-platform threading primitives, event loop, etc. It uses pipe() to implement a primitive needed for its event loop. More precisely, it uses this primitive to wake up the event loop’s thread in case it is blocking in a poll()-style syscall.

Anyway, we noticed that kqueue() is part of the groups of syscalls explicitly allowed. Given that Apple’s kqueue() supports polling file-descriptors and Mach ports at the same time, among other things, it’s likely to be needed in a lot of places, and thus allowed by a broad range of sandbox profiles. It is also a great fit for us, since EVFILT_USER means there is a way to wake up the event loop’s thread. Not just that, but it doesn’t cost us a single file-descriptor.

After lots of coffee and fun pair-programming, we arrived at a patch that switches GLib’s event loop over to kqueue() on OSes that support it. This got us to the next hurdle: Frida is using socket APIs for file-descriptor passing, part of the child-gating feature used for instrumenting children of the current process. However, since hardened system services aren’t likely to be allowed to do things like fork() and execve(), it is fine to simply degrade this part of our functionality. That was tackled next, and boom… Frida is finally able to attach to imagent. 🎉 Yay!

Next up we moved over to iOS and took it for a spin there. Much to our surprise, Frida could attach to SpringBoard right out of the gate. Later we tried notifyd and profiled, and could attach to those too. Even on latest iOS 16. But, there’s still work to do, as Frida cannot yet attach to imagent and WebContent on iOS. This is exciting progress, though.

Injection on iOS >= 15

While doing all of this we also tracked down a crash on iOS where frida-server would get killed due to EXC_GUARD during injection on iOS >= 15. That has now also been fixed, just in time for the release!

DebugSymbol API

Another exciting piece of news is that @mrmacete improved our DebugSymbol API to consistently provide the full path instead of only the filename. This was a long-standing inconsistency across our different platform backends. While at it he also exposed the column, so you also get that in addition to the line number.

Interceptor.replace(), but fast

Last but not least it’s worth mentioning an exciting new improvement in Interceptor. For those of you using it from C, there’s now replace_fast() to complement replace(). This new fast variant emits an inline hook that vectors directly to your replacement. You can still call the original if you want to, but it has to be called through the function pointer that Interceptor gives you as an optional out-parameter. It also cannot be combined with attach() for the same target. It is a lot faster though, so definitely good to be aware of when needing to hook functions in hot code-paths.


That’s all this time. Enjoy!


  • darwin: Disable advanced features in hardened processes.
  • darwin: Port GLib’s MainContext to use kqueue() instead of poll().
  • darwin: Fix EXC_GUARD during injection on iOS >= 15.
  • package-server-ios: Port launchd plist to iOS 16.
  • gadget: Do not cloak main thread while loading.
  • debug-symbol: Ensure path is absolute and add column field. Thanks @mrmacete!
  • darwin: Add query_hardened().
  • interceptor: Add replace_fast().
  • interceptor: Reduce memory usage per target.

Frida 16.0.6 Released

Turns out a serious stability regression made it into Frida 16.0.3, where our out-of-process dynamic linker for Apple OSes could end up crashing the target process. This is especially disastrous when the target process is launchd, as it results in a kernel panic. Thanks to the amazing work of @mrmacete, this embarrassing regression is now fixed. 🎉 Enjoy!

Frida 16.0.5 Released

Quick bug-fix release this time around: the Android system_server instrumentation adjustments should now work reliably on all systems.

Frida 16.0.4 Released

Here’s a brand new release just in time for the weekend! 🎉 A few critical stability fixes this time around.



  • gumjs: Fix use-after-free in the V8 JobState logic. Kudos to @pancake for reporting and helping track this one down!
  • android: Fix racy Zygote and system_server instrumentation. Thanks for the fun and productive pair-programming, @hsorbo!
  • submodules: Add frida-go. Thanks @lateralusd!

Frida 16.0.3 Released

Some cool new things this time. Let’s dive right in.

tvOS and watchOS

One of the exciting contributions this time around came from @tmm1, who opened a whole bunch of pull-requests adding support for tvOS. Yay! As part of landing these I took the opportunity to add support for watchOS as well.

This also turned out to be a great time to simplify the build system, getting rid of complexity introduced to support non-Meson build systems such as autotools. So as part of this clean-up we now have separate binaries for Simulator targets such as the iOS Simulator, tvOS Simulator, etc. Previously we only supported the x86_64 iOS Simulator, and now arm64 is covered as well.

macOS 13 and iOS 16

Earlier this week, @hsorbo and I did some fun and productive pair-programming where we tackled the dynamic linker changes in Apple’s latest OSes. Those of you using Frida on i/macOS may have noticed that spawn() stopped working on macOS 13 and iOS 16.

This was a fun one. It turns out that the dyld binary on the filesystem now looks in the dyld_shared_cache for a dyld with the same UUID as itself, and if found vectors execution there instead. Explaining why this broke Frida’s spawn() functionality needs a little context though, so bear with me.

Part of what Frida does when you call attach() is to inject its agent, if it hasn’t already done this. Before performing the injection however, we check if the process is sufficiently initialized, i.e. whether libSystem has been initialized.

When this isn’t the case, such as after spawn(), where the target is suspended at dyld’s entrypoint, Frida basically advances the main thread’s execution until it reaches a point where libSystem is ready. This is typically accomplished using a hardware breakpoint.

So because the new dyld now chains to another copy of itself, inside the dyld_shared_cache, Frida was placing a breakpoint in the version mapped in from the filesystem, instead of the one in the cache. Obviously that never got hit, so we would end up timing out while waiting for this to happen.

The fix was reasonably straight-forward though, so we managed to squeeze this one into the release in the last minute.

Compiler improvements

The frida.Compiler just got a whole lot better, and now supports additional configuration through tsconfig.json, as well as using local frida-gum typings.

V8 debugger

The V8 debugger integration was knocked out by the move to having one V8 Isolate per script, which was a delicate refactoring needed for V8 snapshot support. This is now back in working order.

Dependency upgrades

One of the heavier lifts this time around was clearly dependency upgrades, where most of our dependencies have now been upgraded: from Capstone supporting ARMv9.2 to latest GLib using PCRE2, etc.

The move to PCRE2 means our Memory.scan() regex support just got upgraded, since GLib was previously using PCRE1. We don’t yet enable PCRE2’s JIT on any platforms though, but this would be an easy thing to improve down the road.

Cross-post: frida-tools 12.0.2

We also have a brand new release of frida-tools, which thanks to @tmm1 has a new and exciting feature. The frida-ls-devices tool now displays higher fidelity device names, with OS name and version displayed in a fourth column:


To upgrade:

$ pip3 install -U frida frida-tools


There are also some other goodies in this release, so definitely check out the changelog below.



  • darwin: Add support for watchOS and tvOS. Thanks @tmm1!
  • darwin: Fix early instrumentation on macOS 13 and iOS 16. (Thanks for the pair-programming on this one, @hsorbo!)
  • interceptor: Suspend threads while mutating pages on W^X systems such as iOS. This improves stability when instrumenting busy processes.
  • system-session: Re-enable Exceptor for now.
  • compiler: Allow configuring target, lib, and strict.
  • compiler: Fix support for local frida-gum typings.
  • compiler: Use latest @types/frida-gum from git.
  • ci: Drop prebuilds for Node.js 10.
  • ci: Publish prebuilds for Node.js 19.
  • ci: Publish prebuilds for Electron 21 instead of 20.
  • unw-backtracer: Improve accuracy on 32-bit ARM.
  • thread: Add suspend() and resume() to the Gum C API.
  • darwin: Improve handling of chained fixups.
  • darwin: Fix Objective-C symbol synthesis on arm64e.
  • linux: Detect noxsave on Linux.
  • linux: Improve injector to handle faux .so mappings. Thanks @lx866!
  • module-map: Support lookups with ptrauth bits set.
  • gumjs: Add NativeFunction traps: ‘none’ option. Thanks @mrmacete!
  • gumjs: Prevent File and SQLite APIs from triggering Interceptor. Thanks @mrmacete!
  • gumjs: Hold Isolate lock while performing V8 jobs.
  • gumjs: Fix deadlock on V8 script teardown.
  • gumjs: Fix V8 debugger not seeing loaded scripts.
  • gumjs: Fix CModule external toolchain support on Darwin/arm64e.
  • gumjs: Fall back if V8 MAP_JIT fails on Darwin.
  • gumjs: Do not freeze V8 flags after init, to avoid issues with hardened processes on Darwin.
  • socket: Upgrade to libsoup 3.x for HTTP/2 support.
  • devkit: Add .gir to the frida-core kit. Thanks @lateralusd!
  • devkit: Update examples to the current Device API.
  • python: Support UNIX socket addresses everywhere.
  • node: Fix Node.js v19 compatibility.
  • deps: Upgrade most of the dependencies to the latest and greatest.
  • build: Refactor to get rid of non-Meson cruft.

Frida 16.0.2 Released

It’s Friday! Here’s a brand new release with lots of improvements:

  • macOS: Fix support for attaching to arm64e system apps and services on macOS >= 12.
  • i/macOS: Upgrade support for chained fixups.
  • iOS: Fix system session on arm64e devices running iOS < 14.
  • system-session: Disable Exceptor and monitors.
    • Exceptor interferes with the signal handling of the hosting process, which is risky and prone to conflict.
    • Monitors aren’t really useful for the system session.
  • interceptor: Fix trampolines used in grafted mode on iOS/arm64. Thanks @mrmacete!
  • compiler: Fix stability issues on multiple platforms.
  • compiler: Fix the @frida/path shim.
  • compiler: Speed things up by also making use of snapshot on non-x86/64 Linux.
  • devkit: Fix regressions in the Windows, macOS, and Linux devkits.
  • v8: Fix heap corruptions caused by our libc-shim failing to implement malloc_usable_size() APIs, which V8 relies on.
  • v8: Fix support for scripts using different snapshots, which was broken due to V8 previously being configured to share read-only space across isolates. That option conflicts with multiple snapshots.
  • v8: Improve teardown.
  • v8: Fix v8::External API contract violations.
  • v8: Upgrade to 10.9.42.
  • zlib: Upgrade to 1.2.13.
  • gum: Fix subproject build for ELF-based OSes.
  • python: Fix tags of published Linux wheels.
    • Add legacy platform tags so older versions of pip recognize them.
    • Pretend some of the wheels require glibc >= 2.17, as lower versions aren’t recognized on certain architectures.

Frida 16.0.1 Released

Two small but significant bugfixes this time around:

  • python: Fix computed sdist version metadata.
  • python: Assume non-universal devkit build on macOS.

Frida 16.0.0 Released

Hope some of you are enjoying frida.Compiler! In case you have no idea what that is, check out the 15.2.0 release notes.


Back in 15.2.0 there was something that bothered me about frida.Compiler: it would take a few seconds just to compile a tiny “Hello World”, even on my i9-12900K Linux workstation:

$ time frida-compile explore.ts -o _agent.js

real	0m1.491s
user	0m3.016s
sys	0m0.115s

After a lot of profiling and insane amounts of yak shaving, I finally arrived at this:

$ time frida-compile explore.ts -o _agent.js

real	0m0.325s
user	0m0.244s
sys	0m0.109s

That’s quite a difference! This means on-the-fly compilation use-cases such as frida -l explore.ts are now a lot smoother. More importantly though, it means Frida-based tools can load user scripts this way without making their users suffer through seconds of startup delay.


You might be wondering how we made our compiler so quick to start. If you take a peek under the hood, you’ll see that it uses the TypeScript compiler. This is quite a bit of code to parse and run at startup. Also, loading and processing the .d.ts files that define all of the types involved is actually even more expensive.

The first optimization that we implemented back in 15.2 was to simply use our V8 runtime if it’s available. That alone gave us a nice speed boost. However, after a bit of profiling it was clear that V8 realized that it’s dealing with a heavy workload once we start processing the .d.ts files, and that resulted in it spending a big chunk of time just optimizing the TypeScript compiler’s code.

This reminded me of a really cool V8 feature that I’d noticed a long time ago: custom startup snapshots. Basically if we could warm up the TypeScript compiler ahead of time and also pre-create all of the .d.ts source files when building Frida, we could snapshot the VM’s state at that point and embed the resulting startup snapshot. Then at runtime we can boot from the snapshot and hit the ground running.

As part of implementing this, I extended GumJS so a snapshot can be passed to create_script(), together with the source code of the agent. There is also snapshot_script(), used to create the snapshot in the first place.

For example:

import frida

session = frida.attach(0)

snapshot = session.snapshot_script("const example = { magic: 42 };",
print("Snapshot created! Size:", len(snapshot))

This snapshot could then be saved to a file and later loaded like this:

script = session.create_script("console.log(JSON.stringify(example));",

Note that snapshots need to be created on the same OS/architecture/V8 version as they’re later going to be loaded on.

V8 10.x

Another exciting bit of news is that we’ve upgraded V8 to 10.x, which means we get to enjoy the latest VM refinements and JavaScript language features. Considering that our last upgrade was more than two years ago, it’s definitely a solid upgrade this time around.

The curse of multiple build systems, part two

As you may recall from the 15.1.15 release notes, we were closer than ever to reaching the milestone where all of Frida can be built with a single build system. The only component left at that point was V8, which we used to build using Google’s GN build system. I’m happy to report that we have finally reached that milestone. We now have a brand new Meson build system for V8. Yay!


There’s also a bunch of other exciting changes, so definitely check out the changelog below.



  • compiler: Use snapshot to reduce startup time.
  • compiler: Bump frida-compile and other dependencies.
  • Add support for JavaScript VM snapshots. This is only implemented by the V8 backend, as QuickJS does not currently support this.
  • Move debugger API from Session to Script. This is necessary since V8’s debugger works on a per-Isolate basis, and we now need one Isolate per Script in order to support snapshots.
  • server+portal: Fix daemon parent ready fail exit. Thanks @pachoo!
  • resource-compiler: Add support for compression. We make use of this for frida.Compiler’s heap snapshot.
  • ipc: Bump UNIX socket buffer sizes for improved throughput.
  • meson: Promote frida-payload to public API. This allows implementing custom payloads for use-cases where frida-agent and frida-gadget aren’t suitable.
  • windows: Move to Visual Studio 2022.
  • windows: Move toolchain/SDK logic to use granular SDKs.
  • windows: Do not rely on .py file association.
  • darwin: Fix compatibility with macOS 13 and iOS >= 15.6.1.
  • darwin: Use Apple’s libffi-trampolines.dylib if present, so we can support iOS 15 and beyond. Thanks for the fun pair-programming sessions, @hsorbo!
  • fruity: Fix handling of USBMUXD_SOCKET_ADDRESS. Thanks @0x3c3e!
  • fruity: Drop support for USBMUXD_SERVER_* envvars. Thanks @as0ler!
  • droidy: Improve handling of ADB envvars. Thanks @0x3c3e!
  • java: (android) Fix ClassLinker offset detection on Android 11 & 12 (#264). Thanks @sh4dowb!
  • java: (android) Fix early instrumentation on Android 13.
  • java: Handle methods and fields prefixed with $. Thanks @eybisi!
  • android: Move to NDK r25.
  • arm64: Optimize memory copy implementation.
  • stalker: Ensure EventSink gets stopped on teardown.
  • stalker: Fix ARM stack clobbering when branch involves a shift.
  • stalker: Handle ARM PC load involving shifted register.
  • stalker: Notify ARM observer when backpatches are applied.
  • stalker: Apply ARM backpatches when notified.
  • stalker: Add ARM support for switch block callback.
  • arm-reader: Expose disassemble_instruction_at().
  • thumb-reader: Expose disassemble_instruction_at().
  • memory: Realign API with current V8 semantics.
  • gumjs: Move V8 backend to one Isolate per script.
  • gumjs: Support passing V8 flags using an env var: FRIDA_V8_EXTRA_FLAGS.
  • gumjs: Use V8 write protection on Darwin/arm*.
  • gumjs: Add support for dynamically defined scripts.
  • prof: Support old system headers on Linux/MIPS.
  • devkit: Improve examples’ compilation docs on UNIX.
  • ci: Migrate remainder of the legacy CI to GitHub Actions.
  • quickjs: Fix use-after-free on error during module evaluation.
  • v8: Upgrade to latest V8 10.x.
  • v8: Add Meson build system.
  • usrsctp: Lower Windows requirement to XP, like the rest of our components.
  • xz: Avoid ANSI-era Windows API.
  • libc-shim: Support old system headers on Linux/MIPS.
  • glib: Add Linux libc fallbacks for MIPS.
  • Add option to be able to disable emulated agents on Android, to allow building smaller binaries. Thanks @muhzii!
  • python: Drop Python 2 support, modernize code, add docstrings, typings, add CI with modern tooling, and many other goodies. Thanks @yotamN!
  • python: Build Python wheels instead of eggs. Thanks @oriori1703!
  • python: Fix Device.get_bus(). The previous implementation called _Device.get_bus(), which doesn’t exist. Thanks @oriori1703!
  • python: Move to the stable Python C API.
  • python: Add support for building from source, using a frida-core devkit.
  • python: Add support for the new snapshot APIs.
  • node: Add support for the new snapshot APIs.
  • node: Fix Electron v20 compatibility.

Frida 15.2.2 Released

Two more improvements, just in time for the weekend:

  • darwin: Always host system session locally. In this way we avoid needing to write our frida-helper to a temporary file and spawn it just to use the system session (PID 0).
  • darwin: Rework frida-helper IPC to avoid Mach ports. This means we avoid crashing on recent versions of macOS. Kudos to co-author @hsorbo for the productive pair-programming on this one!

Frida 15.2.1 Released

Two small but significant bugfixes this time around:

  • compiler: Ignore irrelevant changes during watch().
  • darwin: Improve accuracy of memory range file info. By using PROC_PIDREGIONPATHINFO2 when available, so the query is constrained to vnode-backed mappings. Kudos to @i0n1c for discovering and tracking down this long-standing issue.

Frida 15.2.0 Released

Super-excited about this one. What I’ve been wanting to do for years is to streamline Frida’s JavaScript developer experience. As a developer I may start out with a really simple agent, but as it grows I start to feel the pain.

Early on I may want to split up the agent into multiple files. I may also want to use some off-the-shelf packages from npm, such as frida-remote-stream. Later I’d want code completion, inline docs, type checking, etc., so I move the agent to TypeScript and fire up VS Code.

Since we’ve been piggybacking on the amazing frontend web tooling that’s already out there, we already have all the pieces of the puzzle. We can use a bundler such as Rollup to combine our source files into a single .js, we can use @frida/rollup-plugin-node-polyfills for interop with packages from npm, and we can plug in @rollup/plugin-typescript for TypeScript support.

That is quite a bit of plumbing to set up over and over though, so I eventually created frida-compile as a simple tool that does the plumbing for you, with configuration defaults optimized for what makes sense in a Frida context. Still though, this does require some boilerplate such as package.json, tsconfig.json, and so forth.

To solve that, I published frida-agent-example, a repo that can be cloned and used as a starting point. That is still a bit of friction, so later frida-tools got a new CLI tool called frida-create. Anyway, even with all of that, we’re still asking the user to install Node.js and deal with npm, and potentially also feel confused by the .json files just sitting there.

Then it struck me. What if we could use frida-compile to compile frida-compile into a self-contained .js that we can run on Frida’s system session? The system session is a somewhat obscure feature where you can load scripts inside of the process hosting frida-core. For example if you’re using our Python bindings, that process would be the Python interpreter.

Once we are able to run that frida-compile agent inside of GumJS, we can communicate with it and turn that into an API. This API can then be exposed by language bindings, and frida-tools can consume it to give the user a frida-compile CLI tool that doesn’t require Node.js/npm to be installed. Tools such as our REPL can seamlessly use this API too if the user asks it to load a script with a .ts extension.

And all of that is precisely what we have done! 🥳


Here’s how easy it is to use it from Python:

import frida

compiler = frida.Compiler()
bundle ="agent.ts")

The bundle variable is a string that can be passed to create_script(), or written to a file.

Running that example we might see something like:

Traceback (most recent call last):
  File "/home/oleavr/src/", line 4, in <module>
    bundle ="agent.ts")
  File "/home/oleavr/.local/lib/python3.10/site-packages/frida/", line 76, in wrapper
    return f(*args, **kwargs)
  File "/home/oleavr/.local/lib/python3.10/site-packages/frida/", line 1150, in build
    return, **kwargs)
frida.NotSupportedError: compilation failed

That makes us wonder why it failed, so let’s add a handler for the diagnostics signal:

import frida

def on_diagnostics(diag):
    print("on_diagnostics:", diag)

compiler = frida.Compiler()
compiler.on("diagnostics", on_diagnostics)
bundle ="agent.ts")

And suddenly it’s all making sense:

on_diagnostics: [{'category': 'error', 'code': 6053,
    'text': "File '/home/oleavr/src/agent.ts' not "
            "found.\n  The file is in the program "
            "because:\n    Root file specified for"
             " compilation"}]

We forgot to actually create the file! Ok, let’s create agent.ts:

console.log("Hello from Frida:", Frida.version);

And let’s also write that script to a file:

import frida

def on_diagnostics(diag):
    print("on_diagnostics:", diag)

compiler = frida.Compiler()
compiler.on("diagnostics", on_diagnostics)
bundle ="agent.ts")
with open("_agent.js", "w", newline="\n") as f:

If we now run it, we should have an _agent.js ready to go:

$ cat _agent.js
175 /
39 /explore.js
console.log(`Hello ${Frida.version}!`);

This weird-looking format is how GumJS’ allows us to opt into the new ECMAScript Module (ESM) format where code is confined to the module it belongs to instead of being evaluated in the global scope. What this also means is we can load multiple modules that import/export values. The .map files are optional and can be omitted, but if left in they allow GumJS to map the generated JavaScript line numbers back to TypeScript in stack traces.

Anyway, let’s take _agent.js for a spin:

$ frida -p 0 -l _agent.js
    / _  |   Frida 15.2.0 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at
   . . . .
   . . . .   Connected to Local System (id=local)
Hello 15.2.0!
[Local::SystemSession ]->

It works! Now let’s try refactoring it to split the code into two files:


import { log } from "./log.js";

log("Hello from Frida:", Frida.version);


export function log(...args: any[]) {

If we now run our example compiler script again, it should produce a slightly more interesting-looking _agent.js:

204 /
72 /agent.js
199 /
58 /log.js
import { log } from "./log.js";
log("Hello from Frida:", Frida.version);{"version":3,"file":"log.js","sourceRoot":"/home/oleavr/src/","sources":["log.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,GAAG,CAAC,GAAG,IAAW;IAC9B,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;AACzB,CAAC"}export function log(...args) {

Loading that into the REPL should yield the exact same result as before.


Let’s turn our toy compiler into a tool that loads the compiled script, and recompiles whenever a source file changes on disk:

import frida
import sys

session = frida.attach(0)
script = None

def on_output(bundle):
    global script
    if script is not None:
        print("Unloading old bundle...")
        script = None
    print("Loading bundle...")
    script = session.create_script(bundle)
    script.on("message", on_message)

def on_diagnostics(diag):
    print("on_diagnostics:", diag)

def on_message(message, data):
    print("on_message:", message)

compiler = frida.Compiler()
compiler.on("output", on_output)
compiler.on("diagnostics", on_diagnostics)"agent.ts")

And off we go:

$ python3
Loading bundle...
Hello from Frida: 15.2.0

If we leave that running and then edit the source code on disk we should see some new output:

Unloading old bundle...
Loading bundle...
Hello from Frida version: 15.2.0



We can also use frida-tools’ new frida-compile CLI tool:

$ frida-compile agent.ts -o _agent.js

It also supports watch mode:

$ frida-compile agent.ts -o _agent.js -w


Our REPL is also powered by the new frida.Compiler:

$ frida -p 0 -l agent.ts
    / _  |   Frida 15.2.0 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at
   . . . .
   . . . .   Connected to Local System (id=local)
Compiled agent.ts (1428 ms)
Hello from Frida version: 15.2.0
[Local::SystemSession ]->


Shoutout to @hsorbo for the fun and productive pair-programming sessions where we were working on frida.Compiler together! 🙌


There are also quite a few other goodies in this release, so definitely check out the changelog below.



  • core: Add Compiler API. Only exposed by Python bindings for now, but available from C/Vala.
  • interceptor: Improve replace() to support returning original. Thanks @aviramha!
  • gumjs: Fix typing for pc in the writer options.
  • gumjs: Fix V8 ESM crash with circular dependencies.
  • gumjs: Handle ESM bundles with multiple aliases per module.
  • gumjs: Tighten up the Checksum data argument parsing.
  • android: Fix null pointer deref in crash delivery. Thanks @muhzii!
  • fruity: Use env variables to find usbmuxd. Thanks @0x3c3e!
  • ios: Make Substrate detection logic a bit more resilient. Thanks @lemon4ex!
  • meson: Only try to use V8 if available. Thanks @muhzii!
  • windows: Add support for building without V8.
  • devkit: Fix library dependency hints on Windows. Thanks @nblog!

Frida 15.1.28 Released

A couple of exciting new things in this release.

File API

Those of you using our JavaScript File API may have noticed that it supports writing to the given file, but there was no way to read from it. This is now supported.

For example, to read each line of a text-file as a string:

const f = new File('/etc/passwd', 'r');
let line;
while ((line = f.readLine()) !== '') {
  console.log(`Read line: ${line.trimEnd()}`);

(Note that this assumes that the text-file is UTF-8-encoded. Other encodings are not currently supported.)

You can also read a certain number of bytes at some offset:

const f = new File('/var/run/utmp', 'rb');;
const data = f.readBytes(3);
const str = f.readText(3);

The argument may also be omitted to read the rest of the file. But if you’re just looking to read a text file in one go, there’s an easier way:

const text = File.readAllText('/etc/passwd');

Reading a binary file is just as easy:

const bytes = File.readAllBytes('/var/run/utmp');

(Where bytes is an ArrayBuffer.)

Sometimes you may also want to dump a string into a text file:

File.writeAllText('/tmp/secret.txt', 'so secret');

Or perhaps dump an ArrayBuffer:

const data = args[0].readByteArray(256);
File.writeAllBytes('/tmp/mystery.bin', data);

Going back to the example earlier, seek() also supports relative offsets:, File.SEEK_CUR);, File.SEEK_END);

Retrieving the current file offset is just as easy:

const offset = f.tell();

Checksum API

The other JavaScript API addition this time around is for when you want to compute checksums. While this could be implemented in JavaScript entirely in “userland”, we do get it fairly cheap since Frida depends on GLib, and it already provides a Checksum API out of the box. All we needed to do was expose it.

Putting it all together, this means we can read a file and compute its SHA-256:

const utmp = File.readAllBytes('/var/run/utmp');
const str = Checksum.compute('sha256', utmp);

Or, for more control:

const checksum = new Checksum('sha256');
console.log('Result:', checksum.getString());
console.log(hexdump(checksum.getDigest(), { ansi: true }));

(You can learn more about this API from our TypeScript bindings.)


There are also a few other goodies in this release, so definitely check out the changelog below.



  • gumjs: Extend the File API.
  • gumjs: Add Checksum API.
  • gumjs: Fix handling of relative ESM imports from root.
  • glib: (win32) Add Input/OutputStream support for plain files.
  • build: Make XP SDK optional on Windows.
  • node: Target Electron 19.0.0 instead of 19.0.0-alpha.1.
  • node: Define openssl_fips to work around node-gyp issue.

Frida 15.1.27 Released

It appears I should have had some more coffee this morning, so here’s another release to actually fix this broken fix back in 15.1.25:

  • java: (android) Prevent ART from compiling replaced methods

Turns out the kAccCompileDontBother constant changed in Android 8.1. It also didn’t exist before 7.0. Oops! This release fixes it, for real this time 😊

Frida 15.1.26 Released

Only one change this time around, related to this fix in the previous release:

  • java: (android) Prevent ART from compiling replaced methods

Well, turns out the kAccCompileDontBother constant was incorrect. This release fixes it. (Spoiler from the future: it didn’t.)

Frida 15.1.25 Released

Quite a few exciting bits in this release. Let’s dive right in.

FPU/vector register access

Some great news for those of you using Frida on 32- and 64-bit ARM. Up until now, we have only exposed the CPU’s integer registers, but as of this release, FPU/vector registers are also available! 🎉

For 32-bit ARM this means q0 through q15, d0 through d31, and s0 through s31. As for 64-bit ARM they’re q0 through q31, d0 through d31, and s0 through s31. If you’re accessing these from JavaScript, the vector properties are represented using ArrayBuffer, whereas for the others we’re using the number type.


Our existing Java.backtrace() API now provides a couple of new properties in the returned frames, which now also expose methodFlags and origin.


I finally plugged a memory leak in our RPC server-side code. This was introduced by me in 15.1.10 when implementing an optimization in the Vala compiler’s code generation for DBus reply handling. Shoutout to @rev1si0n for reporting and helping track down this regression!


There are also some other goodies in this release, so definitely check out the changelog below.



  • vala: Plug leak in server-side GDBus reply handling. This affected all server-side implementations in Frida.
  • glib: Disable support for “charset.alias”. This means we no longer try to open this file, which could cause sandbox violations on some systems, such as iOS.
  • java: (android) Add methodFlags to Java.backtrace() frames.
  • java: (android) Add origin to Java.backtrace() frames.
  • java: (android) Prevent ART from compiling replaced methods. Kudos to @s1341 for figuring this one out!
  • cpu-context: Add ARM FPU/vector registers and NZCV.
  • cpu-features: Add VFPD32 flag and detection logic.
  • stalker: Fix VFP D32 detection in the arm backend.
  • gumjs: Add vector regs to arm_reg bindings.
  • x86-writer: Add put_fx{save,rstor}_reg_ptr().
  • arm-writer: Add load/store variants without offset.
  • arm-writer: Add put_v{push,pop}_range().
  • arm-writer: Remove noop check from put_ands_reg_reg_imm().
  • arm-writer: Rename *_registers() to *_regs().
  • arm-writer: Support vector push/pop with Q regs.
  • thumb-writer: Add put_v{push,pop}_range().
  • thumb-writer: Support vector push/pop with Q regs.
  • arm64-writer: Add load/store variants without offset.
  • arm64-writer: Add put_mov_{reg_nzcv,nzcv_reg}().
  • libc-shim: Support old system headers on Linux/ARM.
  • node: Bump dependencies.
  • node: Publish prebuilds for Electron 19 instead of 18.

Frida 15.1.24 Released

Only one change this time, but it’s an important one for those of you using Frida on Android: Our Java method hooking implementation was crashing in some cases, where we would pick a scratch register that conflicted with the generated code. This is now fixed.


Frida 15.1.23 Released

The main theme of this release is OS support, where we’ve fixed some rough edges on Android 12, and introduced preliminary support for Android 13. While working on frida-java-bridge I also found myself needing some of the JVMTI code in the JVM-specific backend. Those bits are now shared, and the JVM-specific code is in better shape, with brand new support for JDK 17.

We have also improved stability in several places, and made it easier to use the CodeWriter APIs safely. Portability has also been improved, where our QuickJS-based JavaScript runtime finally works when cross-compiled for big-endian from a little-endian build machine, and vice versa.

To learn more, be sure to check out the changelog below.



  • linux: Handle spurious signals during ptrace().
  • android: Add missing SELinux rule for system_server on Android 12+.
  • android: Fix Android 13 detection on real devices.
  • android: Handle new linker internals in Android 13.
  • java: Improve the Java.enumerateMethods() error message. Thanks @jpstotz!
  • java: (android) Handle inlined GetOatQuickMethodHeader().
  • java: (android) Improve support for non-Google Android 12+ ROMs.
  • java: (android) Fix Java.choose() on Android >= 12.
  • java: (android) Add support for Android 13.
  • java: (android) Fix threadReg clobber in the x64 recompilation logic.
  • java: (android) Explain why Java.deoptimizeBootImage() is unavailable.
  • java: (android) Expose JVMTI through api.jvmti.
  • java: (android) Improve error messages about OS features.
  • java: (jvm) Add basic support for JDK 17.
  • java: (jvm) Add fallback for thread_from_jni_environment().
  • java: (jvm) Fix UAF in withJvmThread() prologue/epilogue logic.
  • java: (jvm) Improve InstanceKlass offset detection.
  • code-writer: Add flush_on_destroy option.
  • gumjs: Disable the CodeWriter flush_on_destroy option. In this way, the writers are safer to use as they won’t be writing to memory once they’re garbage-collected. At that point the target memory may no longer be writable, or might be owned by other code.
  • gumjs: Embed byteswapped QuickJS bytecode when needed. This means GumJS can be cross-compiled across endians.
  • gumjs: Fix double free in the Instruction copy logic.
  • gumjs: Fix Relocator instruction accessors.
  • gumjs: Flush CodeWriter on reset() and dispose().
  • gumjs: Improve NativePointer#strip() to support ARM TBI.
  • gumjs: Make Instruction wrapper safer in zero-copy mode.
  • gumjs: Plug Relocator leak in the QuickJS runtime.
  • quickjs: Fix support for byteswapped output. Also upgrade QuickJS to latest upstream version with Unicode 14 updates.

Frida 15.1.22 Released

Turns out the major surgery that Gum went through in 15.1.15 introduced some error-handling regressions. Errors thrown vs. actually expected by the Vala code in frida-core did not match, which resulted in the process crashing instead of a friendly error bubbling up to e.g. the Python bindings. That is now finally taken care of. I wish we had noticed it sooner, though — we’re clearly lacking test coverage in this area.

Beside the error-handling fixes, we’re also including a build system fix for incremental build issues. Kudos to Londek for this nice contribution.


Frida 15.1.20 Released

It was discovered that 15.1.10 broke inline hooking in frida-java-bridge on Android/x86_64. This release fixes it.

This time we’re also moving to shipping Node.js prebuilds for v18 instead of v17. (Sigh, should port frida-node to Node-API so we can stop this madness!)


Frida 15.1.19 Released

Turns out 15.1.18 had a release automation bug that resulted in stale Python binding artifacts getting uploaded.

To make this release a little happier, I also threw in a Stalker improvement for x86/64, where the clone3 syscall is now handled as well. This was caught by Stalker’s test-suite on some systems.


Frida 15.1.18 Released

Lots of improvements all over the place this time. Many stability improvements.

I have continued improving our CI and build system infrastructure. QNX and MIPS support are back from the dead, after long-standing regressions. These went unnoticed due to lack of CI coverage. We now have CI in place to ensure that won’t happen again.

Do check out the changelog below for a full overview of what’s new.



  • gadget: Fix deadlock when Gadget is blocking the dynamic linker with its internal lock(s) held while waiting for resume(). In that case we are processing the JS MainContext while blocking, with network I/O handled by the Gadget thread. Because Stalker may interact with the dynamic linker during its class_init(), we must ensure that it happens from the JS thread and not the network I/O thread.
  • exit-monitor: Fix deadlock when a fork() goes unnoticed.
  • darwin: Skip pseudo-signing when running on Corellium.
  • darwin: Fix compatibility with older iOS SDKs.
  • darwin-backtracer: Fix loop variable wrap-around.
  • darwin-mapper: Fix footprint budgeting w/ chained fixups.
  • linux: Fix the Process.modify_thread() wait logic.
  • linux: Fully resolve libc on modern glibc systems.
  • linux: Remove the Linjector cleanup delay.
  • ios: Add option for building without jailbreak support code, for compatibility with e.g. TestFlight.
  • ios: Eliminate dependency on external Mach VM header.
  • android: Fix unw_getcontext() on Android/x86, which resulted in stack corruption during e.g. Thread.backtrace().
  • freebsd: Add BSDmakefile for convenience.
  • freebsd: Fix gum_clear_cache() reliability.
  • freebsd: Improve the program path query API.
  • freebsd: Try a bit harder when MAP_32BIT fails.
  • mips: Fix long-standing regressions that went unnoticed due to lack of CI.
  • qnx: Fix long-standing regressions that went unnoticed due to lack of CI. Also improve the QNX backend while at it.
  • posix: Use posix_madvise() if madvise() is unavailable.
  • stalker: Detect thread exit implementation on glibc, Android, and FreeBSD.
  • stalker: Fix Linux clone() support in the x86 backend.
  • stalker: Fix backpatching of JECXZ/JRCXZ x86 instructions.
  • stalker: Fix two pc vs code mixups in the x86 backend.
  • stalker: Optimize Linux syscall logic in the x86 backend.
  • stalker: Move unwind support behind an API.
  • stalker: Simplify and fix the x86 unwind Interceptor logic.
  • stalker: Implement unwind hooking in the arm64 backend. Thanks @s1341!
  • stalker: Add backpatch query support.
  • stalker: Add support for recompiling blocks.
  • process: Add resolve_module_pointer().
  • elf-module: Improve the DT_SYMTAB entry count detection.
  • elf-module: Skip symbols with a dangling name reference.
  • symbolutil-libdwarf: Add support for DWARF v5.
  • libdwarf: Backport upstream fix for handling of DW_FORM_line_strp.
  • dbghelp-backtracer: Improve reliability on 32-bit x86.
  • arm-relocator: Simplify PC-relative LDR handling.
  • arm-writer: Add call_reg_with_arguments*().
  • arm-writer: Handle put_call*() w/ more than four args.
  • thumb-writer: Handle put_call*() stack alignment.
  • arm64-writer: Optimize LDR reg, #0.
  • x86-relocator: Add input_pc to support offline use.
  • x86-relocator: Fix PC vs output mixup in RIP-relative fast-path.
  • x86-relocator: Improve the RIP offset fixup logic.
  • bounds-checker: Fall back to the matching heap API.
  • gumjs: Fix CModule memory allocation logic.
  • gumjs: Fix CModule runtime with internal TinyCC.
  • gumjs: Lower build-time Python requirement to 3.7.
  • heap-api: Improve libc detection on non-Windows OSes.
  • heap-api: Include static MSVC CRT APIs in the list.
  • gum: Improve Meson build system to support MSVC.
  • gum: Improve Gum vapis to expose more APIs.
  • core: Fix build failure with some locales by switching off localization when invoking tools from
  • python: Fix stability issue caused by incorrect refcounting in get_max_argument_count().
  • python: Fix get_index_url_from_pip(). Thanks @X5tar!
  • python: Fix index URL retrieval on Python < 3.6. Thanks @serfend!
  • node: Publish prebuilds for Electron 18 instead of 16.
  • ci: Add Linux/MIPS, FreeBSD/x86_64, FreeBSD/arm64, and QNX/armeabi. For Gum, also add Windows/x86_64, macOS/x86_64, Linux/x86, Linux/x86_64, iOS/arm64, Android/x86, Android/arm, and Android/arm64.

Frida 15.1.17 Released

One notable improvement in this release is that Java.backtrace() got a major overhaul. It is now lazy and >10x faster. I have also refined its API, which is now considered stable.

While working on frida-java-bridge, I optimized how Env objects are handled, so we can recycle an existing instance if we already have one for the current thread.

The remaining goodies are covered by the changelog below, so definitely check it out.



  • Fix devkits for i/macOS, Android, and QNX, where parts of libiconv were missing.
  • Improve devkit packaging on LLVM toolchains.
  • Improve diet mode support in GLib.
  • cmodule: Expose g_strndup().
  • cmodule: Expose Gum’s Thread Local Storage API.
  • java: Rework Java.backtrace() to be lazy and >10x faster.
    • Move thread transition and stack walking logic to CModule.
    • Return a Backtrace object instead of an array with the frames.
    • Provide a cheap id property that can be used for deduplication.
    • Lazily compute the frames when accessing the frames property.
  • java: Avoid expensive Env creation when possible.
  • python: Improve index URL handling. Thanks @GalaxySnail!

Frida 15.1.16 Released

This time we’re bringing you two bugfixes and one new feature, just in time for the weekend.

Gum used to depend on GIO, but that dependency was removed in the previous release. The unfortunate result of that change was that agent and gadget no longer tore down GIO, as they were relying on Gum’s teardown code doing that. What this meant was that we were leaving threads behind, and that is never a good thing. So that’s the first bugfix.

Also in the previous release, over in our Python bindings, went through some heavy changes. We improved the .egg download logic, but managed to break the local .egg logic. That’s the second bugfix.

Onto the new feature. For those of you using Gum’s JavaScript bindings, GumJS, we now support console.count() and console.countReset(). These are implemented by browsers and Node.js, and make it easy to count the number of times a given label has been seen. Kudos to @yotamN for this nice contribution.


Frida 15.1.15 Released

Quite a few exciting bits in this release. Let’s dive right in.


Our ambition is to support all platforms that our users care about. In this release I wanted to plant the first seed in expanding that to BSDs. So now I’m thrilled to announce that Frida finally also supports FreeBSD! 🎉

For now we only support x86_64 and arm64, but expanding to the remaining architectures is straight-forward in case anybody is interested in helping out.

The porting effort resulted in several architectural refinements and improved robustness for ELF-based OSes in general. It also gave me some ideas on how to improve our Linux injector to support injecting into containers, which is something I’d like to do down the road.

Stalker Performance

Back in 15.1.10, Stalker got a massive performance boost on x86/64. In this release those same ideas have been applied to our arm64 backend. This includes improved locality, better inline caches, etc. I’m told we were able to beat or match QEMU in FuzzBench back then, and now we should be in good shape on arm64 as well. We also managed to improve stability while at it. Exciting!

GObject Introspection

Back in 14.1, @meme wired up build system support for GObject Introspection. This means we have a machine-readable description of all of our APIs, which lets us piggyback on existing language binding infrastructure, and even get auto-generated reference docs for free.

This release adds a lot of annotations and doc-strings to Gum, and we are now closer than ever to having auto-generated reference docs. Still some work left to do before it makes sense to publish the generated documentation, but it’s not far off. If anyone is interested in pitching in, check out Gum’s CI and have a look at the warnings output by GObject Introspection.

Meson subproject support

One thing I really like about the Meson build system is its support for subprojects. Gum now supports being used as a subproject. Some of you may already be consuming Gum through its devkit binaries, and now you have a brand new option that is even easier.

The main advantage over using a devkit is that everything is built from source, so it’s easy to experiment with the code.

Let’s say we have a file hello.c that contains:

#define _GNU_SOURCE
#include <dlfcn.h>
#include <fcntl.h>
#include <gum.h>
#include <stdio.h>
#include <unistd.h>

static int (* open_impl) (const char * path, int oflag, ...);
static int replacement_open (const char * path, int oflag, ...);

main (int argc,
      char * argv[])
  gum_init ();

  GumInterceptor * interceptor = gum_interceptor_obtain ();

  gum_interceptor_begin_transaction (interceptor);

  open_impl = dlsym (RTLD_DEFAULT, "open");
  gum_interceptor_replace (interceptor, open_impl, replacement_open,
      NULL, NULL);

  gum_interceptor_end_transaction (interceptor);

  close (open_impl ("/etc/hosts", O_RDONLY));
  close (open_impl ("/etc/fstab", O_RDONLY));

  return 0;

static int
replacement_open (const char * path,
                  int oflag,
  printf ("!!! open(\"%s\", 0x%x)\n", path, oflag);

  return open_impl (path, oflag);

To build it we can create a next to it with the following:

project('hello', 'c')
gum = dependency('frida-gum-1.0')
executable('hello', 'hello.c', dependencies: [gum])

And create subprojects/frida-gum.wrap containing:

url =
revision = main
depth = 1

dependency_names = frida-gum-1.0, frida-gum-heap-1.0, frida-gum-prof-1.0, frida-gumjs-1.0

In case you don’t have Meson and Ninja already installed, run pip install meson ninja.

Then, to build and run:

$ meson setup build
$ meson compile -C build
$ ./build/hello
!!! open("/etc/hosts", 0x0)
!!! open("/etc/fstab", 0x0)


We put a lot of effort into making sure that Frida can scale from desktops all the way down to embedded systems. In this release I spent some time profiling our binary footprint, and based on this I ended up making a slew of tweaks and build options to reduce our footprint.

I was curious how small I could make a Gum Hello World program that only uses Interceptor. The end result was measured on 32-bit ARM w/ Thumb instructions, where Gum and its dependencies are statically linked, and the only external dependency is the system’s libc.

The result was as small as 55K (!), and that made me really excited. What I did was to introduce new build options in Gum, GLib, and Capstone. For Gum we now support a “diet” mode where we don’t make use of GObject and only offer a plain C API. This means it won’t support GObject Introspection and fancy language bindings. It also means we don’t offer the full Gum API, but that is something that can be expanded on in the future.

Similarly for GLib there is also a new “diet” mode, and boils down to disabling its slice allocator, debugging features, and a few other minor tweaks like that.

As for Capstone, I ended up introducing a new “profile” option that can be set to “tiny”. The result of doing so is that Capstone only understands enough of the instruction set to determine each instruction’s length, and provide some details on position-dependent instructions. The idea is to only support what our Relocator implementations need, as those do most of the heavy lifting behind Interceptor and Stalker.

While I wouldn’t recommend using these build options unless you really need a footprint that small, it’s good to be aware of what’s possible. We also offer other, less extreme options. Read more in our footprint docs.

The curse of multiple build systems

Something that has been bothering me for as long as Frida has existed, is that building Frida involves dealing with multiple build systems. While we do of course try to hide that complexity behind scripts/makefiles, we are inevitably going to have unhappy users who find themselves trying to figure out why Frida is not building for them.

Some are also interested in cross-compiling Frida for a slightly different libc, toolchain, or what have you. They may even be looking to add support for an OS we don’t yet support. Chances are that we’re going to demotivate them the moment they realize they need to deal with four different build-systems: Meson, autotools, custom Perl scripts (OpenSSL), and GN (V8).

As we are happy users of Meson, my goal is to “Mesonify all the things!”. With this release, we have now finally reached the point where virtually all of our required dependencies are built using Meson. The only exception is V8, but we will hopefully also build that with Meson someday. (Spoiler from the future: Frida 16 will get us there!)


There’s also a bunch of other exciting changes, so definitely check out the changelog below.



  • Add support for FreeBSD.
  • Add support for ARMv4, for instrumenting old embedded systems.
  • Add build options for binary footprint reductions (see
  • Tweak Capstone and GLib to enable much smaller binary footprints.
  • droidy: Add AXML decoder. Thanks @meme!
  • windows: Fix dbghelp backtracer support for libffi-frames.
  • linux: Fix compatibility with new glibc versions.
  • linux: Only use one frida-helper when assets are installed.
  • qnx: Remove tempfiles when injector is closed.
  • core: Plug leak when agent teardown is cancelled.
  • gumjs: Fix handling of RPC invocations returning null.
  • interceptor: Use a “jumbo”-JMP on x86 when needed, when impossible to allocate memory reachable from a “JMP ".
  • interceptor: Generate variable size x86 NOP padding.
  • stalker: Improve performance of the arm64 backend, by applying ideas recently used to optimize the x86/64 backend – e.g. improved locality, better inline caches, etc.
  • stalker: Fix handling of zero-sized freeze/thaw().
  • stalker: Rework x86 PLT exclusion code to avoid reentrancy-issues during stalking.
  • stalker: Don’t require a C++ runtime to be present.
  • arm-writer: Add put_bl_reg(), put_call_reg().
  • arm-writer: Fix register clobbering in put_call_address*().
  • arm64-reader: Expose disassemble_instruction_at().
  • arm64-writer: Fix handling of mixed-width literals.
  • arm64-writer: Fix TBZ/TBNZ encoding.
  • arm64-writer: Fix put_and_reg_reg_imm() on 32-bit systems.
  • arm64-writer: Add put_{ldr,str}_reg_reg_offset_mode().
  • elf-module: Expose more details, add support for offline mode, add Vala bindings.
  • exceptor: Add reset() for exception handler recovery.
  • gum: Add lots of GObject Introspection annotations and API docs.
  • gum: Add support for using as a Meson subproject.
  • gum: Port Meson build system to Windows.
  • gum: Eliminate the GIO dependency.
  • gum: Add diet mode, allowing a “Hello World” C program that uses Interceptor to be as small as 55K on 32-bit ARM w/ Thumb instructions.
  • gum: Add CI. Thanks @meme!
  • Improve build system with many portability improvements.
  • Build elfutils, libiconv, libdwarf, libunwind, openssl, xz with Meson.
  • Upgrade dependencies: capstone, elfutils, libdwarf, libunwind.
  • Release packages for Fedora 35 instead of 34.
  • python: Use PEP 503 instead of PyPI xmlrpc. Thanks @GalaxySnail!
  • python: Fix support for Python >= 3.10 on Windows.

Frida 15.1 Released

Introducing the brand new Swift bridge! Now that Swift has been ABI-stable since version 5, this long-awaited bridge allows Frida to play nicely with binaries written in Swift. Whether you consider Swift a static or a dynamic language, one thing is for sure, it just got a lot more dynamic with this Frida release.


Probably the first thing a reverser does when they start reversing a binary is getting to know the different data structures that the binary defines. So it made most sense to start by building the Swift equivalent of the ObjC.classes and ObjC.protocols APIs. But since Swift has other first-class types, i.e. structs and enums, and since the Swift runtime doesn’t offer reflection primitives, at least not in the sense that Objective-C does, it meant we had to dig a little deeper.

Luckily for us, the Swift compiler emits metadata for each type defined by the binary. This metadata is bundled in a TargetTypeContextDescriptor C++ struct, defined in include/swift/ABI/Metadata.h at the time of writing. This data structure includes the type name, its fields, its methods (if applicable,) and other useful data depending on the type at hand. These data structures are pointed to by relative pointers (defined in include/swift/Basic/RelativePointer.h.) In Mach-O binaries, these are stored in the __swift5_types section.

So to dump types, Frida basically iterates over these data structures and parses them along the way, very similar to what dsdump does, except that you don’t have to build the Swift compiler in order to tinker with it.

Frida also has the advantage of being able to probe into internal Apple dylibs written in Swift, and that’s because we don’t need to parse the dyld_shared_cache thanks to the private getsectiondata API, which gives us section offsets hassle-free.

Once we have the metadata, we’re able to easily create JavaScript wrappers for object instances and values of different types.


To be on par with the Objective-C bridge, the Swift bridge has to support calling Swift functions, which also proved to be not as straight forward.

Swift defines its own calling convention, swiftcall, which, to put it succinctly, tries to be as efficient as possible. That means, not wasting load and store instructions on structs that are smaller than 4 registers-worth of size. That is, to pass those kinds of structs directly in registers. And since that could quickly over-book our precious 8 argument registers (on AARCH64 x0-x7), it doesn’t use the first register for the self argument. It also defines an error register where callees can store errors which they throw.

What we just described above is termed “physical lowering” in the Swift compiler docs, and it’s implemented by the back-end, LLVM.

The process that precedes physical lowering is termed “semantic lowering,” which is the compiler front-end figuring out who “owns” a value and whether it’s direct or indirect. Some structs, even though they might be smaller than 4 registers, have to be passed indirectly, because, for example, they are generic and thus their exact memory layout is not known at compile time, or because they include a weak reference that has to be in-memory at all times.

We had to implement both semantic and physical lowering in order to be able to call Swift functions. Physical lowering is implemented using JIT-compiled adapter functions (thanks to the Arm64Writer API) that does the necessary SystemV-swiftcall translation. Semantic lowering is implemented by utilizing the type’s metadata to figure out whether we should pass a value directly or not.

The compiler docs are a great resource to learn more about the calling convention.


Because Swift passes structs directly in registers, there isn’t a 1-to-1 mapping between registers and actual arguments.

And now that we have JavaScript wrappers for types, and are able to call Swift functions from the JS runtime, a good next step would be extending Interceptor to support Swift functions.

For functions that are not stripped, we use a simple regex to parse argument types and names, same for return values. After parsing them we retrieve the type metadata, figure out the type’s layout, then simply construct JS wrappers for each argument, which we pass the Swift argument value, however many registers it occupies.


Note that the bridge is still very early in development, and so:

  • Currently supports Darwin arm64(e) only.
  • Performance is not yet in tip-top shape, some edge case might not be handled properly and some bugs are to be expected.
  • There’s a chance the API might change in breaking ways in the short-to-medium term.
  • PRs and issues are very welcome!

Refer to the documentation for an up-to-date resource on the current API.


Changes in 15.1.0

  • Implement the Swift bridge, which allows Frida to:
    • Explore Swift modules along with types implemented in them, i.e. classes, structs, enums and protocols.
    • Create JavaScript wrappers for object instances and values.
    • Invoke functions that use the swiftcall calling convention from the JavaScript runtime.
    • Intercept Swift functions and automatically parse their arguments and return values.
  • Fix i/macOS regression where changes related to iOS 15 support ended up breaking support for attaching to Apple system daemons.
  • gadget: Add interaction.parameters in connect mode. These parameters are then “reflected” into app’s info under parameters.config. Thanks @mrmacete!

Changes in 15.1.1

  • gumjs: Fix Swift lifetime logic in the V8 runtime.

Changes in 15.1.2

  • control-service: Fix signal wiring, so signals such as Device.output get emitted correctly by a remote frida-server. Thanks @mrmacete!
  • gadget: Fix the “runtime” option, which was forgotten during the refactorings leading up to Frida 15.
  • relocator: Optimize handling of x86 RIP relative code, by simply adjusting the offset where possible.
  • gumjs: Add ESM support, so tools like frida-compile can output better code.
  • gumjs: Throw away source code after parsing it.
  • gumjs: Plug leak when compiling to QuickJS bytecode.
  • java: Expose JNIEnv->GetDirectBufferAddress. Thanks @pandasauce!

Changes in 15.1.3

  • objc: Fix the Proxy respondsToSelector implementation. Thanks @hot3eed!
  • gumjs: Fix V8 runtime crash when module is missing.
  • gumjs: Emit V8 exceptions thrown by ESM entrypoints.

Changes in 15.1.4

  • gumjs: Fix double-free related to weak ref callbacks in the QuickJS runtime. Thanks @mrmacete!
  • gumjs: Ignore Interceptor context in unrelated NativeCallback invokes. In this way invalid Interceptor contexts from higher up the call stack will be safely ignored in favor of the minimal but correct callback context. Thanks @mrmacete!
  • gumjs: Fix ESM module name normalization logic.
  • gumjs: Add Process getters for cwd, home, and tmp dirs.
  • swift: Improve metadata caching performance by ~3x. Thanks @hot3eed!
  • node: Publish Electron prebuilds for v15 instead of v13.

Changes in 15.1.5

  • gumjs: Fix crash when QuickJS stringifies large numbers. Thanks @vfsfitvnm!
  • gumjs: Expose GError and GIConv to CModule. Thanks @0xDC00!
  • droidy: Support ADB server host/port environment variables. Thanks @amirpeles90!
  • swift: Improve load behavior on non-Darwin.

Changes in 15.1.6

  • swift: Fix crash during load on non-Darwin. Thanks @hot3eed!

Changes in 15.1.7

  • swift: Fix CoreSymbolication integration on older OS versions. Thanks @hot3eed!
  • python: Add download logic for Android/ARM. Our CI doesn’t yet build and upload such a binary, though.

Changes in 15.1.8

  • darwin: Add support for working in the SRD environment. Thanks @Nessphoro!
  • darwin: Add support for building with newer iOS SDKs.

Changes in 15.1.9

  • x86-relocator: Fix patching of RIP relative instructions. This was a regression introduced in 15.1.2, resulting in Stalker becoming unreliable.
  • portal-service: Always remove ClusterNode sessions whenever a session ID is unset from PortalService. This avoids both NULL dereferences and leaks. Thanks @mrmacete!
  • frida-portal: Fix typo in –help output.

Changes in 15.1.10

  • p2p: Gracefully handle unsupported ICE candidates.
  • p2p: Disable ICE-TCP for now.
  • p2p: Rework PeerSocket to fix synchronization issues.
  • socket: Fix WebService teardown.
  • vala: Optimize server-side GDBus reply handling. This means our RPC and network protocols become less chatty/more performant.
  • x86-writer: Add UD2 instructions after/in indirect branches.
  • x86-writer: Emit larger NOPs when possible.
  • stalker: Improve performance of the x86/64 backend.
  • objc-api-resolver: Guard against disposed ObjC classes. Thanks @mrmacete!
  • gumjs: Fix V8 Interceptor.{replace,revert}() regression. Thanks @3vilWind!
  • gumjs: Expand Instruction API w/ regsAccessed and operand.access. Thanks @3vilWind!

Changes in 15.1.11

  • x86-writer: Add put_sahf() and put_lahf().
  • x86-relocator: Fix handling of out-of-range Jcc branch targets. Thanks @0xDC00!
  • stalker: Optimize target address retrieval.
  • stalker: Avoid expensive XCHG instructions.
  • stalker: Optimize IC prolog to use SAHF/LAHF.
  • memory: Improve scan() to support regex patterns. Thanks @hot3eed!
  • kernel: Get base from all_image_info where supported. Thanks @mrmacete!
  • windows: Improve dbghelp backtracer reliability. Thanks @HonicRoku!

Changes in 15.1.12

  • agent: Fix hang on unload w/ NO_REPLY_EXPECTED calls, where we would wait for a reply to be sent. Due to the server-side DBus code being generated by the Vala compiler, and it previously (in Frida <= 15.1.9) ignoring NO_REPLY_EXPECTED, this bug went unnoticed.
  • android: Move to NDK r24 Beta 1.

Changes in 15.1.13

  • linux: Improve module resolution on glibc systems.
  • fruity: Fix spawn on dyld v4 case (jailed iOS 15.x). Thanks @mrmacete!
  • objc-api-resolver: Protect against objc_disposeClassPair() via mutex. Thanks @mrmacete!
  • gumjs: Update Kernel.scan*() to match Memory.scan*(). Thanks @hot3eed!
  • stalker: Fix emitted branch op-code on x86.
  • stalker: Fix handling of Thumb IT AL.
  • stalker: Handle excluded Linux calls through PLT on x86.
  • stalker: Fix Linux exception handling on x86.
  • java: Add Java.backtrace(), without any API stability guarantees for now.

Changes in 15.1.14

  • backtracer: Improve fuzzy backtracers to also include the immediate caller, and avoid walking past stack end when known.
  • windows: Implement Thread.try_get_ranges().
  • linux: Implement Thread.try_get_ranges().
  • ios: Check in with launchd on SRD systems.
  • stalker: Fix accidental clobbery in call depth code on x86.
  • node: Publish Node.js prebuilds for v17, too.

Frida 15.0 Released

So much has changed. Let’s kick things off with the big new feature that guided most of the other changes in this release:


Part I: Conception

Earlier this year @insitusec and I were brainstorming ways we could simplify distributed instrumentation use-cases. Essentially ship a Frida Gadget that’s “hollow”, where application-specific instrumentation is provided by a backend.

One way one could implement this is by using the Socket.connect() JavaScript API, and then define an application-specific signaling protocol over which the code is loaded, before handing it off to the JavaScript runtime.

But this way of doing things does quickly end up with quite a bit of boring glue code, and existing tools such as frida-trace won’t actually be usable in such a setup.

That’s when @insitusec suggested that perhaps Frida’s Gadget could offer an inverse counterpart to its Listen interaction. So instead of it being a server that exposes a frida-server compatible interface, it could be configured to act as a client that connects to a Portal.

Such a Portal then aggregates all of the connected gadgets, and also exposes a frida-server compatible interface where all of them appear as processes. To the outside it appears as if they’re processes on the same machine as where the Portal is running: they all have unique process IDs if you use enumerate_processes() or frida-ps, and one can attach() to them seamlessly.

In this way, existing Frida tools work exactly the same way – and by enabling spawn-gating on the Portal, any Gadget connecting could be instructed to wait for somebody to resume() it after applying the desired instrumentation. This is the same way spawn-gating works in other situations.

Part II: Implementation

Implementing this was a lot of fun, and it wasn’t long until the first PoC was up and running. It took some time before all the details were clear, though, but this eventually crystallized into the following:

The Portal should expose two different interfaces:

  1. The cluster interface that Gadgets can connect to, allowing them to join the cluster.
  2. Optionally also a control interface that controllers can talk to. E.g. frida-trace -H -n Twitter -i open

To a user this would be pretty simple: just grab the frida-portal binary from our releases, and run it on some machine that the Gadget is able to reach. Then point tools at that – as if it was a regular frida-server.

That is however only one part of the story – how it would be used for simple use-cases. The frida-portal CLI program is actually nothing more than a thin CLI wrapper around the underlying PortalService. This CLI program is just a bit north of 200 lines of code, of which very little is actual logic.

One can also use our frida-core language bindings, for e.g. Python or Node.js, to instantiate the PortalService. This allows configuring it to not provide any control interface, and instead access its device property. This is a standard Frida Device object, on which one can enumerate_processes(), attach(), etc. Or one can do both at the same time.

Using the API also offers other features, but we will get back to those.


Given how useful it might be to run a frida-portal on the public Internet, it was also clear that we should support TLS. As we already had glib-networking among our dependencies for other features, this made it really cheap to add, footprint-wise.

And implementation-wise it’s a tiny bit of logic on the client side, and similarly straight-forward for the server side of the story.

For the CLI tools it’s only a matter of passing --certificate=/path/to/pem. If it’s a server it expects a PEM-encoded file with a public + private key, where it will accept any certificate from incoming clients. For a client it’s also expecting a PEM-encoded file, but only with the public key of a trusted CA, which the server’s certificate must match or be derived from.

At the API level it boils down to this:

import frida

manager = frida.get_device_manager()
device = manager.add_remote_device("",
session = device.attach("Twitter")

Part IV: Authentication

The next fairly obvious feature that goes hand in hand with running a frida-portal on the public Internet, is authentication. In this case our server CLI programs now support --token=secret, and so do our CLI tools.

At the API level it’s also pretty simple:

import frida

manager = frida.get_device_manager()
device = manager.add_remote_device("",
session = device.attach("Twitter")

But this gets a lot more interesting if you instantiate the PortalService through the API, as it makes it easy to plug in your own custom authentication backend:

import frida

def authenticate(token):
    # Where `token` might be an OAuth access token
    # that is used to grab user details from e.g.
    # GitHub, Twitter, etc.
    user = 

    # Attach some application-specific state to the connection.
    return {

cluster_params = frida.EndpointParameters(authentication=('token', "wow-such-secret"))
control_params = frida.EndpointParameters(authentication=('callback', authenticate))
service = frida.PortalService(cluster_params, control_params)

The EndpointParameters constructor also supports other options such as address, port, certificate, etc.

Part V: Offline Mode

That leads us to our next challenge, which is how to deal with transient connectivity issues. I did make sure to implement automatic reconnect logic in PortalClient, which is what Gadget uses to connect to the PortalService.

But even if the Gadget reconnects to the Portal, what should happen to loaded scripts in the meantime? And what if the controller gets disconnected from the Portal?

We now have a solution that handles both situations. But it’s opt-in, so the old behavior is still the default.

Here’s how it’s done:

session = device.attach("Twitter",

Now, once some connectivity glitch occurs, scripts will stay loaded on the remote end, but any messages emitted will get queued. In the example above, the client has 30 seconds to reconnect before scripts get unloaded and data is lost.

The controller would then subscribe to the Session.detached signal to be able to handle this situation:

def on_detached(reason, crash):
    if reason == 'connection-terminated':
        # Oops. Better call session.resume()

session.on('detached', on_detached)

Once session.resume() succeeds, any buffered messages will be delivered and life is good again.

The above example does gloss over a few details such as our current Python bindings’ finicky threading constraints, but have a look at the full example here. (This will become a lot simpler once we port our Python bindings off our synchronous APIs and onto async/await.)

Part VI: Latency and Bottlenecks

Alright, so next up we’ve got a Portal running in a data center in the US, but the Gadget is at my friend’s place in Spain, and I’m trying to control it from Norway using frida-trace. It would be a shame if the script messages coming from Spain would have to cross the Atlantic twice, not just because of the latency, but also the AWS bill I’ll have to pay next month. Because I’m dumping memory right now, and that’s quite a bit of traffic right there.

This one’s a bit harder, but thanks to libnice, a lightweight and mature ICE implementation built on GLib, we can go ahead and use that. Given that GLib is already part of our stack – as it’s our standard library for C programming (and our Vala code compiles to C code that depends on GLib) – it’s a perfect fit. And this is very good news footprint-wise.

As a user it’s only a matter of passing --p2p along with a STUN server:

$ frida-trace \
    -H \
    --p2p \ \
    -n Twitter \
    -i open

(TURN relays are also supported.)

The API side of the story looks like this:


That’s all there is to it!

Part VII: Are Only Gadgets Invited To The Party?

You may have noticed that our Gadget has been a recurring theme so far. I’m not very excited about adding features that only apply to one mode, such as only Injected mode but not Embedded mode. So this was something that came to mind quite early on, that Portals had to be a universally available feature.

So say my buddy is reversing a target on his iPhone from his living room in Italy, and I’d like to join in on the fun, he can go ahead and run:

$ frida-join -U ReversingTarget cert.pem secret

Now I can jump in with the Frida REPL:

$ frida \
    -H \
    --certificate=cert.pem \
    --token=secret \
    --p2p \ \
    -n ReversingTarget

And if my buddy would like to use the API to join the Portal, he can:

session = frida.get_usb_device().attach("ReversingTarget")
membership = session.join_portal("",

Part VIII: The Web

Something I’ve been wanting to build since before Frida was born, is an online collaborative reversing app. Back in the very beginning of Frida, I built a desktop GUI that had integrated chat, console, etc. My not-so-ample spare-time was a challenge, however, so I eventually got rid of the GUI code and decided to focus on the API instead.

Now we’re in 2021, and single-page apps (SPAs) can be a really appealing option in many cases. I’ve also noticed that there’s been quite a few SPAs built on top of Frida, and that’s super-exciting! But what I’ve noticed when toying with SPAs on my own, is that it’s quite tedious to have to write the middleware.

Well, with Frida 15 I had to make some protocol changes to accomodate the features that I’ve covered so far, so it also seemed like the right time to really break the protocol and go ahead with a major-bump. This is something I’ve been trying to avoid for a long time, as I know how painful they are to everyone, myself included.

So now browsers can finally join in on the fun, without any middleware needed:

async function start() {
  const ws = wrapEventStream(new WebSocket(`ws://${}/ws`));
  const bus = dbus.peerBus(ws, {
    authMethods: [],

  const hostSessionObj = await bus.getProxyObject('re.frida.HostSession15',
  const hostSession = hostSessionObj.getInterface('re.frida.HostSession15');

  const processes: HostProcessInfo[] = await hostSession.enumerateProcesses({});
  console.log('Got processes:', processes);

  const target = processes.find(([, name]) => name === 'hello2');
  if (target === undefined) {
    throw new Error('Target process not found');
  const [pid] = target;
  console.log('Got PID:', pid);

  const sessionId = await hostSession.attach(pid, {
    'persist-timeout': new Variant('u', 30)

(Full example can be found in examples/web_client.)

This means that Frida’s network protocol is now WebSocket-based, so browsers can finally talk directly to a running Portal/frida-server, without any middleware or gateways in between.

I didn’t want this to be a half-baked story though, so I made sure that the peer-to-peer implementation is built on WebRTC data channels – this way even browsers can communicate with minimal latency and help keep the AWS bill low.

Part IX: Assets

Once we’ve built a web app to go with our Portal, which is speaking WebSocket natively, and thus also HTTP, we can also make it super-easy to serve that SPA from the same server:

$ ./frida-portal --asset-root=/path/to/web/app

This is also easy at the API level:

control_params = frida.EndpointParameters(asset_root="/path/to/web/app")
service = frida.PortalService(cluster_params, control_params)

Part X: Collaboration

A natural next step once we have a controller, say a web app, is that we might want collaboration features where multiple running instances of that SPA are able to communicate with each other.

Given that we already have a TCP connection between the controller and the PortalService, it’s practically free to also let the developer use that channel. For many use-cases, needing an additional signaling channel brings a lot of complexity that could be avoided.

This is where the new Bus API comes into play:

import frida

def on_message(message, data):
    # TODO: Handle incoming message.

manager = frida.get_device_manager()
device = manager.add_remote_device("")
bus = device.bus

bus.on('message', on_message)
    'type': 'rename',
    'address': "0x1234",
    'name': "EncryptPacket"
    'type': 'chat',
    'text': "Hey, check out EncryptPacket everybody"

Here we’re first attaching a message handler so we can receive messages from the Portal.

Then we’re calling attach() so that the Portal knows we’re interested in communicating with it. (We wouldn’t want it sending messages to controllers that don’t make use of the Bus, such as frida-trace.)

Finally, we post() two different message types. It is up to the PortalService to decide what to do with them.

So this means that the remote PortalService needs to be instantiated through the API, as incoming messages need to be handled – the Portal won’t forward them to other controllers on its own.

Worry not, though, this is easy:

import frida
import sys

def on_message(connection_id, message, data):
    # TODO: Handle incoming message.

cluster_params = frida.EndpointParameters()
control_params = frida.EndpointParameters()
service = frida.PortalService(cluster_params, control_params)
service.on('message', on_message)

In on_message() it should look at the message and decide what to do.

It might choose to reply to the controller that sent it the message:, {
    'type': 'rename-rejected',
    'reason': "Not authorized"

Another useful thing to do is sending a welcome message whenever somebody calls attach() on their Bus object:

def on_subscribe(connection_id):, {
        'type': 'welcome',
        'users': [user.nick for user in connected_users]

service.on('subscribe', on_subscribe)

Depending on your application, you might also need a way to broadcast a message to all controllers who are attached to their Bus:

    'type': 'announce',
    'text': "Important Service Announcement"

You can also narrowcast() a message to a subset of controllers:

service.narrowcast("#reversing", {
    'type': 'chat',
    'sender': user.nick,
    'text': "Hello everyone"

This means any controller connection tagged with #reversing will receive that message. Tagging is done like this:

service.tag(connection_id, "#reversing")

Such tags could then be added based on actions, like a controller sending a “join” message to join a channel. They could also be applied based on authentication, so that only connections belonging to a certain GitHub organization receive that message – just as an example.

Lastly, on the cluster side, it is also possible to specify an Access Control List (ACL) when joining the Portal. The ACL is an array of strings that specify tags which will grant controllers access to discover and interact with the given process. This means that service.tag() needs to be used for each controller that should be granted access to a certain node/group of nodes.

That is pretty much all there is to it. For a more comprehensive example, check out examples/ and examples/, which implement an IRC-esque chat service.

System Parameters

Back in May I had a chat with @Hexploitable, who was working on a tool where he needed to pick a Device object based on whether it’s running iOS vs Android. This is a feature that’s been requested in the past, and it felt like it might be time to finally address it.

While one could do device.attach(0) and load a script in the system session, in order to run code inside Frida itself (e.g. in a remote frida-server), it is somewhat tedious. It also doesn’t work if the Device represents a jailed/non-rooted device, where code execution is a lot more constrained.

So after brainstorming this a bit, @Hexploitable started working on implementing it, and quickly got to the point where he had the first draft working. This was later refined by me, and got merged shortly after the Portals feature had finally landed.

The API is simple, and easy to extend in the future:

$ python3 -c 'import frida; import json; \
    print(json.dumps(frida.query_system_parameters()))' \
    | jq

macOS Device

And if I attach a jailed iOS device, I can also query it:

$ python3 -c 'import frida; import json; \
    device = frida.get_usb_device(); \
    print(json.dumps(device.query_system_parameters()))' \
    | jq

iOS Device

An important detail to note here is access: 'jailed'. This is how you can determine whether you’re accessing the device through our support for jailed iOS/Android systems, i.e. limited to debuggable apps, or actually talking to a remote frida-server – which is what access: 'full' means.

Things are not quite as juicy for Android yet (PRs welcome, btw!), but there are still plenty of useful details:

Android Device

We’re also able to identify the specific Linux distro if it’s LSB-compliant:

Ubuntu Device

And last but not least, Windows:

Windows Device

Application and Process Parameters

Another cool idea that started taking shape after some impromptu chats, was when @pancake told me it would be useful to know the particular version of an installed iOS app.

As I had just broken the protocol in so many ways working on the Portals feature, it also seemed like a great time to break it some more, and avoid another painful major bump down the road.

Fast forward a bit, and here’s how it turned out: Our Application and Process objects no longer have any small_icon or large_icon properties, but they now have a parameters dict.

By default, with enumerate_applications(), things look familiar:


But by changing that to enumerate_applications(scope='metadata'), things get a lot more interesting:


Here we can see the iOS Twitter app’s version and build number, where its app bundle is on the filesystem, the containers that it owns, that it is currently the frontmost app, how long ago it was started, etc.

We can also crank that up to enumerate_applications(scope='full') and get icons as well:


The debuggable: true parameter is very useful if query_system_parameters() reported access: 'jailed', as that means your application may want to filter the list of apps to only show the ones it is able to spawn() and/or attach() to, or perhaps show debuggable apps more prominently to provide a better UX.

It’s probably also worth mentioning that get_frontmost_application() now supports passing a scope as well.

Those of you familiar with the old API may have noticed that icons may now be delivered in compressed form, as PNGs. Previously this was always uncompressed RGBA data, and the iOS side would do the PNG decoding and downscaling to two fixed resolutions (16x16 and 32x32).

All of this meant that we would waste a lot of CPU time, memory and bandwidth to include icons, even if all of that data would end up in a CLI tool that doesn’t make use of it. So now with Frida 15 you might notice that application and process listing is a lot faster. And even if you do request icons, it should also be faster than before as we don’t do any decompression and downscaling.

That was application listing. All of the above is also true for process listing, and this is what enumerate_applications(scope='full') might look like now:


Here it is also clear that the Twitter app is currently frontmost, that its parent PID is launchd (PID 1), the user it is running as, when it was started, etc.

You might be wondering why applications is an array though, and the answer is probably best illustrated by an example from Android:


The “” process actually hosts six different “applications”!

And once again, last but not least, I didn’t forget about Windows:


So that’s the “scope” option. There’s also another one, meant for UIs. The idea is that a UI might want to grab the list of applications/processes quickly, and may not actually need metadata/icons until the user interacts with a particular entry, or scrolls a subset of entries into view. So we now provide an option to support such use-cases.

Say we only want to grab the metadata for two specific apps, we can now do:

ids = [
apps = device.enumerate_applications(identifiers=ids,


We also support the same feature for process listing, where it looks like this:

processes = device.enumerate_processes(pids=[1337, 1338],

Portals and Application/Process Parameters

Now that we have covered application parameters and portals, there’s an important detail that’s worth mentioning: Given that it doesn’t make much sense to implement query_system_parameters() in the case of a PortalService, as it’s surfacing processes from any number of (potentially remote) systems, we can use application/process parameters to fill this void.

This means that any Application and Process coming from a PortalService will, if scope is set to metadata or full, provide one parameter named system, which contains the system parameters for that particular application/process. This way an application can still know ahead of time if it’s interested in a particular process.

Jailed iOS and Android Improvements

I had a lot of fun implementing the Application and Process parameters feature, and tried to see how narrow I could make the gap between jailed (non-rooted) and jailbroken (rooted). For example on Android, we didn’t even fetch app labels in the non-rooted code-path. This was because we were relying on running shell commands over ADB, and I couldn’t find a way to grab labels in that case.

The shell command route is very fragile, as most tools output details in a format that’s meant to be consumed by a human, not a machine. And obviously such output is likely to change as Android evolves.

Because of this we now have a tiny prebuilt .dex that we copy over and run, and grabbing metadata is only a matter of making RPC calls to that helper process. This means we are able to provide all the same details for non-rooted as we provide in the rooted case, where we have a frida-server running on the Android side.

Another thing worth mentioning is that we no longer consider Android’s launcher a frontmost app, which means this is now consistent with our behavior on iOS, where SpringBoard is never considered the frontmost app.

As part of these major changes I also added code to fetch icons on Android as well, both non-rooted and rooted, so that this feature is no longer just limited to iOS, macOS, and Windows.

We didn’t provide icons for jailed iOS though, but that feature gap is now also closed. There is however still one difference between jailed and jailbroken iOS: the ppid and user parameters are not available in the jailed case, as this is not exposed by any lockdown/DTX API that I’m currently aware of. But other than that, things are in pretty good shape.

Massively improved backtraces on i/macOS

Thanks to a very exciting pull-request by @hot3eed, we now have an i/macOS symbolication fallback that uses the Objective-C runtime. In this way, instead of showing module!0x1234, we may be able to resolve that to an Objective-C method. Yay!

We also got another awesome contribution by @mrmacete, where NativeCallback now always exposes a context, so you can do Thread.backtrace(this.context) and expect it to work in all cases.

This was previously only possible when NativeCallback was used as an Interceptor replacement. So if you were using ObjC.implement() to swizzle an Objective-C API, you couldn’t actually capture a backtrace from that NativeCallback. So this is a super-exciting improvement!

Unextracted Native Libraries on Android

For those of you using Frida on Android, you may have encountered apps where native libraries don’t reside on the filesystem, but are loaded directly from the app’s .apk. Thanks to a great contribution by @P-Sc, we now support this transparently – no changes needed in your existing instrumentation code.

Upgraded OS Support

We now also support the latest betas of macOS Monterey, iOS 15, and Android 12. Special thanks to @alexhude at Corellium for helping debug and test things on iOS 15, and @pengzhangdev who contributed a fix for frida-java-bridge to support Android 12.

Networked iOS Devices

Another feature that’s been requested a few times is support for networked iOS devices. This is great if you don’t want to destroy your iPhone/iPad’s battery by leaving it plugged in all day. What’s great about this feature is that it “just works” – you should see them if you run frida-ls-devices.

Only two pitfalls worth mentioning: You may now have two different Device objects with the same ID, in case a networked iOS device is reachable through the network while also being plugged in.


$ frida-ls-devices
Id                         Type    Name
-------------------------  ------  -------------------------------------
local                      local   Local System
00008027-xxxxxxxxxxxxxxxx  usb     iPad
socket                     remote  Local Socket
00008027-xxxxxxxxxxxxxxxx  remote  iOS Device [fe80::146f:75af:d79:630c]

So if you’re using -U or frida.get_usb_device() things will work just like before, where you’ll be using your device through USB. But if you want to use the networked device, then resolving it by ID means the USB entry will take precedence, as it’s typically ahead of the networked device in the list of devices.

This means you would also need to check its type. Our CLI tools don’t yet provide a switch to do this, but this would be a welcome pull-request if anyone’s interested!

The second pitfall is that frida-server only listens on the loopback interface by default, meaning we won’t be able to connect to it over the network. So if you’re using our iOS .deb either manually or through Cydia, you will have to edit /Library/LaunchDaemons/re.frida.server.plist to add the --listen switch, and then use launchctl to restart it.

This may also be a situation where you want to make use of the new TLS and authentication features mentioned earlier, depending on how much you trust your network environment.


There’s also a bunch of other exciting changes, so definitely check out the changelog below.


Changes in 15.0.0

  • Introduce PortalService API and daemon, a network service that orchestrates a cluster of remote processes instrumented by Frida. Implements both a frida-server compatible control interface, as well as a cluster interface that agents and gadgets in target processes can talk to. Connected controllers can enumerate processes as if they were local to the system where the portal is running, and are able to attach() and also enable spawn-gating to apply early instrumentation.
  • Add Session.join_portal(), making it easy to share control of a process with a remote PortalService, joining its cluster together with other nodes.
  • Add “connect” interaction to frida-gadget, so it can join a PortalService cluster as well.
  • Add PortalClient, used to implement Session.join_portal() and frida-gadget’s “connect” interaction. Connects to the PortalService and joins its cluster. Implements automatic reconnect in case of transient failures. Also supports specifying an ACL, which is a list of tags that the PortalService must require connected controllers to possess at least one of. It’s up to the application to implement tagging of controllers based on e.g. authentication.
  • Add Device.bus API to allow clients connected to a PortalService to exchange application-specific messages with it. Requires instantiating the service using the API in order to wire up message handlers and protocol logic.
  • Add Session persistence support, enabled by specifying a non-zero “persist_timeout” option when attach()ing to a process. When the server subsequently detects that the client owning the session got disconnected, it will allow scripts to stay loaded until the timeout (in seconds) is reached. Any script and debugger messages emitted in the meantime are queued, and may later be delivered if the client returns before the timeout is reached.
  • Add TLS support, enabled by specifying a certificate. On the server end this is a PEM with a public and private key, where the server will accept any certificate from the client’s side. However for the client this is a PEM with the public key of a trusted CA, which the server’s certificate must match or be derived from.
  • Add authentication support, enabled by specifying a token. The daemons allow specifying a static token through a CLI option, and the APIs allow plugging in a custom authentication backend – which means the token can be interpreted as desired.
  • Move network protocols to WebSocket.
  • Add protocol-level keepalives.
  • Implement WebRTC Data Channel compatible peer-to-peer support, enabled by calling setup_peer_connection() on Session. This allows a direct connection to be established between the client and the remote process, which is useful when talking to it through e.g. a Portal.
  • Optimize protocol by skipping the DBus authentication handshake and telling GDBus not to fetch properties, saving an additional roundtrip.
  • Drop deprecated protocol bits, such as Session.enable_jit().
  • Add Device.query_system_parameters(). Thanks @Hexploitable!
  • Improve the application and process query APIs. (Covered extensively above.)
  • Switch Crash parameter names to kebab-case.
  • Add Session.is_detached(), useful in multi-threaded scenarios where the “detached” signal may already have been emitted by the time we manage to connect to it.
  • Fix Stalker handling of SYSCALL instructions on Linux/x86.
  • Fix the iPad device name on macOS.
  • Massively improve backtraces/symbolication on i/macOS in cases where symbols are missing, but the address to be symbolicated belongs to an Objective-C method. Thanks @hot3eed!
  • Improve backtracer accuracy and flexibility, now exposing a minimal context to NativeCallback in cases where it’s not used as an Interceptor replacement. One such example is ObjC.implement(), used for swizzling APIs. Thanks @mrmacete!
  • Improve Interceptor reliability on macOS/arm64, by switching to the mapping strategy that we use on iOS.
  • Add support for network-connected iOS devices.
  • Avoid leaving behind a file when sandbox check fails on iOS.
  • Fix Stalker on newer iOS hardware jailbroken with checkra1n. This was solved by disabling RWX support on iOS for now: Even if the jailbreak appears to make it available, whether it actually works boils down to which mitigations the hardware supports. Shout-out to @stacksmashing for reporting and helping get to the bottom of this one!
  • Remove bashisms from iOS maintainer scripts, for improved jailbreak compatibility. Thanks @nyuszika7h!
  • Improve Interceptor frame layout on arm64, so backtracing code is able to walk the stack past our generated code. This also makes it easier to use a debugger together with Interceptor.
  • Fix ObjC ApiResolver deadlock, which occurred when free() was resolved lazily from a dyld image callback, at which point it is a bad idea to call dlsym().
  • Add support for unextracted native libraries on Android. Thanks @P-Sc!
  • Fix Android get_frontmost_application() name truncation.
  • Don’t consider the Android launcher a frontmost app.
  • Use the Android app’s label as the process name if the process represents is its main UI process. This is finally consistent with what we do on iOS.
  • Improve jailed Android injector: Now using a long-lived shell session to speed things up. Also ensure filenames dropped into /data/local/tmp are unique, and clean up temporary files.
  • Drop Firefox OS support.
  • Rename the weak ref API to avoid clashing with ES2021. So instead of WeakRef.bind() and WeakRef.unbind(), these are now Script.bindWeak() and Script.unbindWeak().
  • Massively improve memory allocation performance on Windows, by lowering the dlmalloc spin-lock sleep duration to zero – which means it will only yield the remainder of its time slice. The default of 50 ms is prone to introduce significant delay as soon as threads start competing for the lock.
  • Lazily create the ScriptScheduler thread pool. This means anyone using GumJS can avoid background threads until they’re really needed. Very useful if you’re writing a tool or agent that needs to handle fork(), without having to implement logic to stop and later restart threads. (Something frida-agent does, but which isn’t necessarily needed for simple use-cases.)
  • Java: Add support for Android 12 beta. Thanks @pengzhangdev!
  • Java: Fix Java.array() for unloaded array types: If the type of the array was not used in the application it means that it wasn’t loaded into memory, and Java.array() previously failed because of this. Thanks @yotamN!
  • Java: Add toString() to primitive arrays, so that instead of the generic “[Object Object]” string it will generate a string where the array values are separated by commas. Thanks @yotamN!
  • CModule: Add missing TinyCC builtins for 32-bit x86.
  • python: Add Script.is_destroyed property.
  • python: Add Session.is_detached property.
  • python: Add Device.is_lost property.
  • python: Throw when attempting an RPC call on a destroyed script.
  • node: Fix compatibility with newer versions of Node.js, where any given Buffer’s backing store needs to be unique. We solve this by simply making a copy in cases where we cannot guarantee that the same buffer can only be observed once.
  • node: Fix use-after-free in signal transform callbacks.
  • node: Fix use-after-free in signal connection callbacks.
  • node: Move to C++17, for compatibility with newer Node.js headers.
  • node: Add Script.isDestroyed property.
  • node: Add Device.isLost property.

Changes in 15.0.1

  • Ensure DarwinGrafter’s merging of binds doesn’t make gaps in __LINKEDIT. Not doing so triggers a bug in codesign for which the resulting signed binary turns out corrupted. Thanks @mrmacete!
  • node: Fix compilation error when building with MSVC.

Changes in 15.0.2

  • Fix handling of large messages, for both client-server and p2p. Also bump the WebSocket payload size limit to 256 KiB - same as is typically negotiated for data channels in p2p mode.
  • Move WebSocket I/O to the DBus thread, to avoid unnecessary thread hopping.
  • Plug leaks when using p2p.

Changes in 15.0.3

  • Implement a new frida-pipe strategy for i/macOS. Turns out that our previous strategy of directly setting up a Mach port in the target process becomes problematic with guarded Mach ports. So instead we register a Mach service that’s globally visible, and have the target process contact it to fetch its end of the socketpair. We also check with the sandbox up front, and issue an extension token if needed. However, if we’re unable to register the Mach service with launchd, we fall back to our previous strategy.
  • Skip iOS platformized detection on iOS >= 15. It interacts badly with guarded Mach ports.
  • Port early instrumentation to macOS 12 and iOS 15.
  • Gracefully handle agent failing to start. Instead of crashing the target process, simply log the error and unload.
  • Linux: Use an abstract name for frida-pipe when supported.
  • Unlink UNIX socket when frida-pipe is done with it.
  • Fix iOS policyd Mach message lifetime logic.

Changes in 15.0.4

  • Fix the i/macOS injector’s data size logic. It’s been hard-coded since the beginning, and the recent entrypoint data size adjustment broke it on systems with 4K pages.
  • Fix i/macOS early instrumentation regression on dyld < 4.

Changes in 15.0.5

  • Fix the __CFInitialize() code-path on dyld >= 4.
  • Fix i/macOS sandbox extension logic during spawn().
  • Port jailed iOS injector to iOS 15.
  • Implement Android USAP interop.
  • Improve DarwinGrafter lazy binds merge logic. Thanks @mrmacete!

Changes in 15.0.6

  • Fix Windows build regression.

Changes in 15.0.7

  • Fix early instrumentation on Android 12.
  • node: Fix the ApplicationParameters typings. Thanks for reporting, @pancake!

Changes in 15.0.8

  • iOS: Add support for unc0ver v6.1.2.
  • iOS: Update Substrate interop logic to support 0.9.7113.

Changes in 15.0.9

  • iOS: Revive support for older OS versions. (Verified on iOS 10.3.)
  • iOS: Fix support for older Apple usbmuxd versions.
  • Android: Handle devices where jailed is unsupported.
  • Fix DarwinGrafter alignment issue, aligning to 16 bytes also when lazy binds follow regular binds. Thanks @mrmacete!

Changes in 15.0.10

  • Fix regression where Device.open_channel(“lockdown:”) ended up closing the stream right away.
  • Fix crash when talking to older versions of ADB.

Changes in 15.0.11

  • Rewrite macOS spawn gating to use DTrace. This means we’ve now dropped support for our kernel extension, and we don’t need to worry about future OSes no longer supporting extensions. It also means we finally support spawn gating on Apple Silicon as well.
  • Work around i/macOS arm64 single-step delay during early instrumentation.

Changes in 15.0.12

  • Fix macOS spawn gating task port lifetime issue. Should not try to be clever and keep task ports around – bad things happen if a task port is used after an exec transition.
  • Remove the i/macOS task port caching logic. It is dangerous in exec transitions, adds some complexity, and doesn’t help all that much performance-wise, anyway.
  • Make the macOS spawn gating DTrace predicate environment variable optional.
  • Improve the macOS spawn gating error message.

Changes in 15.0.13

  • Improve clients to specify Host header and use TLS SNI.
  • Revert i/macOS single-step delay workarounds.
  • iOS: Fix usbmux port number encoding in the connect request.
  • Python, Node.js: Fix ownership in Device.spawn() aux options logic.

Changes in 15.0.14

  • Port iOS crash reporter integration to iOS 14.7.1.
  • Configure internal agents to be less intrusive on i/macOS.
  • Improve i/macOS error-handling during process preparation.
  • Revive i/macOS single-step delay workarounds, which turned out to be needed after all.
  • Handle reentrancy when about to yield JS lock, which may happen if a replaced function gets called by Interceptor during end_transaction().
  • Fix conditional Interceptor unignore logic in GumJS. This was preventing Interceptor from ignoring internal calls, and resulted in noise and reduced performance.
  • Enhance the i/macOS DebugSymbol fallback name to also include the unslid address. This makes it easy to plop it into a static analysis tool.
  • Fix Stalker code slab refill logic.
  • Add Stalker stats interface.

Changes in 15.0.15

  • Rework i/macOS Exceptor to support latest iOS 14. This would previously result in a deadlock whenever a native exception occurred.

Changes in 15.0.16

  • Fix i/macOS single-step handling on ARM during early instrumentation, which would result in attach() failing randomly for a freshly spawn()ed process. Thanks @mrmacete!
  • Add Stalker backpatch prefetching support.
  • Make Stalker inline cache size configurable on x86.
  • Optimize Stalker x86 return handling.
  • ObjC: Allow proxies to implement methods. Thanks @hot3eed!

Changes in 15.0.17

  • gadget: Support packaging as framework on i/macOS. Load config from “Resources/config.json”, and resolve relative paths relative to the same directory. As an added bonus, we now also support specifying relative paths for “certificate” and “asset_root”.
  • web-service: Implement basic directory listing with NGINX-style output. This is useful with e.g. “frida-server –asset-root=/”.
  • stalker: Fix backpatch args for 32-bit x86.

Changes in 15.0.18

  • Plug long-standing memory leaks, both in our internal heap’s realloc behavior being misconfigured and causing leaks, and in how we register JavaScript classes with QuickJS. Kudos to @mrmacete for discovering and helping track down these long-standing bugs!

Changes in 15.0.19

  • gadget: Fix framework resource lookup logic on iOS.

Frida 14.2 Released

So much to talk about. Let’s kick things off with a big new feature:


Frida has supported Android for quite a while, but one particular feature has kept getting requested (mostly) by users who thought they were looking at a bug. The conversation usually started something like: “I’m using Frida inside the hardware-accelerated Android emulator X, and when I attach to this process Y, Process.enumerateModules() is missing JNI library Z. But I can see it in Process.enumerateRanges() and /proc/$pid/maps. How come?”

As you may have guessed, we’re talking about Android’s NativeBridge, typically used on Intel-powered Android devices to enable them to run apps that only support ARM – i.e. apps with one or more JNI components only built for ARM.

In a Frida context, however, we’re usually talking about a VirtualBox-based emulator that runs an Android system built for x86. This system then ships with NativeBridge support powered by libhoudini, a proprietary ARM translator.

There’s quite a few of these emulators, e.g. BlueStacks, LDPlayer, NoxPlayer, etc. While the ones mentioned are optimized for running games, there’s now also Google’s official Android 11 AVDs which ship with NativeBridge support out of the gate.

Through the years I’ve been thinking about how we could support such scenarios in Frida, but thinking about it always made my head hurt a little. It did feel like something we should support at some point, though, I just had a hard time figuring out what the API would look like.

Then along came 2020 and Apple announced their transition to ARM, and suddenly Rosetta became relevant once again. “Alright”, I thought, “now we have two platforms where it would be useful to support processes containing an emulated realm that’s running legacy code.”

And yeah there’s also Windows, but we don’t yet support Windows on ARM. We totally should though, so if somebody’s interested in taking a stab at this then please do get in touch.

Anyway, I’m exited to announce that our Android binaries for x86 and x86_64 now support such processes out of the box. You may already be familiar with the following frida-core API, where the Python flavor looks like this:

session = device.attach(target)

(Or frida.attach() if your code only deals with the local system.)

If target has an emulated realm, you can now do:

session = device.attach(target, realm='emulated')

The default is realm='native', and you can actually use both realms at the same time. And when using our CLI tools, pass --realm=emulated to act on the emulated realm.

One important caveat when using this on Android is that you will need to apply your Java-level instrumentation in the native realm.

Lastly it’s worth noting that this new feature is only supported on Android for now, but it shouldn’t be hard to support Rosetta on macOS down the road. Definitely get in touch if you want to help out with this.

Taking Android Java Hooking Inline

The way Frida’s Java bridge replaces Java methods on Android has up until now been accomplished by mutating the in-memory method metadata so that the target method becomes native – if it wasn’t already. This allows us to install a NativeCallback that matches the given method’s JNI signature.

This has presented some challenges, as the ART runtime does have other internal state that depends on the given method’s personality. We have devised a few hacks to dance around some of these issues, but some particularly gnarly edge-cases remained unsolved. One such example is JIT profiling data maintained by the ART VM.

An idea I had been thinking about for a while was to stop mutating the method metadata, and instead perform inline hooking of the AOT-generated machine code – for non-native methods that is. That still leaves methods run on the VM’s interpreter, but the assumption was that we could deal with those by hooking VM internals.

I took a stab at an early prototype to explore this approach further. It seemed like it could work, but there were still many challenges to work through. After some brainstorming with @muhzii, he kept working on evolving this rough PoC further in his spare time. Then one day I almost fell off my chair out of pure excitement when I saw the amazing pull-request he had just opened.

Thanks to Muhammed’s amazing work, you can now all enjoy a much improved Java instrumentation experience on Android. This means improved stability and also that direct calls won’t bypass your replacement method. Yay!


For those of you using Java.deoptimizeEverything() on Android to ensure that your hooks aren’t skipped due to optimizations, there’s now a more granular alternative. Thanks to @alkalinesec’s neat contribution to our Java bridge, you can now use Java.deoptimizeBootImage(). It ensures only code in the boot image OAT files gets deoptimized. This is a serious performance gain in some situations where the app code itself is slow when deoptimized, and it is not necessary to deoptimize it in order for hooks to be hit reliably.


Another really exciting update here. The next hero in our story is @mephi42, who started porting Frida to S390x. Our CModule implementation relies on TinyCC behind the scenes, and it doesn’t yet support this architecture. The system might have a C compiler though, so @mephi42 proposed that we add support for using GCC on systems where TinyCC cannot help us out.

I really liked this idea. Not only from the perspective of architecture support, but also because of the potential for much faster code – TinyCC optimizes for small compiler footprint and fast compilation, not fast code.

So needless to say I got more and more excited with each pull-request towards GCC support. Once the last one landed it inspired me to add support for using Apple’s clang on i/macOS.

In the end we arrived at this:

const cm = new CModule(`…`, {}, { toolchain: 'external' });

Where toolchain is either any, internal, or external. The default is any, which means we will use TinyCC if it supports your Process.arch, and fall back to external otherwise.

The story doesn’t end here, though. While implementing support for i/macOS, it wasn’t really clear to me how we could fuse in symbols provided by the JavaScript side. (The second argument to CModule’s constructor.)

The GCC implementation uses a linker script, which is a really elegant solution that Apple’s linker doesn’t support. But then it hit me: we already have our own dynamic linker that we use for our injector.

Once I had wired that up, it seemed really obvious that we could also trivially support skipping Clang entirely, and allow the user to pass in a precompiled shared library.

The thinking there was that it would enable cross-compilation, but also make it possible to implement a CModule in languages such as Swift and Rust: basically anything that can interop with C.

So this means we now also support the following:

const cm = new CModule(blob);

Where blob is an ArrayBuffer containing the shared library to construct it from. For now this part is only implemented on i/macOS, but the goal is to support this on all platforms. (Contributions welcome!)

Also, as of frida-tools 9.2, the REPL’s -C switch also supports this, making it easy to use an external toolchain without missing out on live-reload – which makes for a much shorter feedback loop during development.

Taking that one step further, the CModule API now also provides a the property CModule.builtins, which scaffolding tools can use to obtain the built-in headers and preprocessor defines.

And on that note we now have such a tool in frida-tools:

$ mkdir pewpew
$ cd pewpew
$ frida-create cmodule
Created ./
Created ./pewpew.c
Created ./.gitignore
Created ./include/glib.h
Created ./include/gum/gumstalker.h
Created ./include/gum/gumprocess.h
Created ./include/gum/gummetalarray.h
Created ./include/gum/guminterceptor.h
Created ./include/gum/gumspinlock.h
Created ./include/gum/gummetalhash.h
Created ./include/gum/gummemory.h
Created ./include/gum/gumdefs.h
Created ./include/gum/gummodulemap.h
Created ./include/json-glib/json-glib.h
Created ./include/gum/arch-x86/gumx86writer.h
Created ./include/capstone.h
Created ./include/x86.h
Created ./include/platform.h

Run `meson build && ninja -C build` to build, then:
- Inject CModule using the REPL: frida Calculator -C ./build/pewpew.dylib
- Edit *.c, and build incrementally through `ninja -C build`
- REPL will live-reload whenever ./build/pewpew.dylib changes on disk

$ meson build && ninja -C build
[2/2] Linking target pewpew.dylib
$ frida Calculator -C ./build/pewpew.dylib

And yes, it live-reloads! Taken to the extreme you could use a file-watcher tool and make it run ninja -C build whenever pewpew.c changes – then just save and instantly see the instrumentation go live in the target process.

It’s worth noting that you can also use the above when using the internal CModule toolchain, as having the headers available on disk is handy for editor features such as code completion.


There’s also a bunch of other exciting changes, so definitely check out the changelog below.


Changes in 14.2.0

  • Brand new realms API for instrumenting emulated realms inside native processes. Only implemented on Android for now.
  • Add Java.deoptimizeBootImage(). Thanks @alkalinesec!
  • Add –disable-preload/-P to frida-server. Useful in case of OS compatibility issues where Frida crashes certain OS processes when attaching to them.
  • Fix libc detection on older versions of Android.
  • Fix crash when resolving export of the vDSO on Android. Thanks @ant9000!
  • Restore support for libhoudini on Android.
  • Fix ARM cache flushing on Android 11’s translator.
  • Fix linker offsets for Android 5.x. Thanks @muhzii!
  • Start refactoring CModule’s internals to prepare for multiple backends. Thanks @mephi42!
  • Fix CModule aggregate initializations on ARM.
  • Fix ModuleApiResolver fast-path emitting bad matches.

Changes in 14.2.1

  • Fix CModule constructor error-path in the V8 runtime.
  • Use V8 runtime for the “system_server” agent on Android.

Changes in 14.2.2

  • Fix Darwin.Mapper arm64e handling of pages without fixups. This went unnoticed out of pure “luck”, until our binaries eventually mutated sufficiently to expose this bug.

Changes in 14.2.3

  • Upgrade to using inline hooking for the ART runtime. Thanks @muhzii!
  • Fix direct transport regression on i/macOS, introduced by GLib upgrade where GLib.Socket gained GLib.Credentials support on Apple OSes. A typical symptom of this regression is that frida-server gets killed by Jetsam.
  • Fix libffi support for stdcall, thiscall, and fastcall on 32-bit Windows.
  • Extend Memory.alloc() to support allocating near a given address. Thanks @muhzii!
  • Fix relocation of RIP-relative indirect branches on x86_64. Thanks @dkw72n!
  • Improve the JVM C++ allocator API probing logic by consulting debug symbols before giving up. Thanks @Happyholic1203!
  • Upgrade SELinux libraries to support bleeding edge Android systems.
  • Add gum-linux-x86_64-gir target for GIR generation. Thanks @meme!

Changes in 14.2.4

  • Fix Android performance regression when ART’s interpreter is used, such as when using deoptimizeEverything() or deoptimizeBootImage(), which results in our JS callbacks becoming extremely hot. Move the hot callbacks to CModule to speed things up.
  • Fix V8 debugger support in Node.js bindings on Linux.
  • Fix crash on ELF init error in the libdwarf backend.

Changes in 14.2.5

  • Fix regression on older Android systems, introduced in 14.2.4.

Changes in 14.2.6

  • Fix compatibility with legacy NativeBridge v3 and newer, where a namespace needs to be specified.

Changes in 14.2.7

  • Fix frida-java-bridge crash on systems where printf() renders %p without “0x” prefix.
  • Fix jni_ids_indirection_ offset parsing on ARM64. Thanks @muhzii!

Changes in 14.2.8

  • Fix GLib SO_NOSIGPIPE regression on i/macOS. This would typically result in frida-server dying due to SIGPIPE. Thanks @mrmacete!
  • Refactor CModule internals and lay foundations for GCC backend. Thanks @mephi42!
  • Add EventSink.make_from_callback() for Stalker C API consumers that only care about events, and don’t need lifecycle hooks or code transformations.
  • Emit Stalker BLOCK event at the start of the block, as this is what’s the most intuitive, as one would expect at least as many BLOCK events as COMPILE events. This behavior is also the most suitable for measuring coverage.
  • Add Stalker prefetch support, useful for optimizing “AFL fork server”-like use-cases.

Changes in 14.2.9

  • Handle permanent entries in Darwin CodeSegment backend. Starting from iOS 14.3 on A12+ devices, mach_vm_remap() can return KERN_NO_SPACE when the target VM map entries are marked as “permanent”. Thanks @mrmacete!
  • Wire up GCC support in CModule. Thanks @mephi42!
  • Add CModule backend for Clang on Apple OSes.
  • Add support for linking in a prebuilt CModule. (Only on i/macOS for now.)
  • Finalize the CModule toolchain selection API.
  • Add CModule.builtins property for tooling support.
  • Generate frida-core GIR by default. Thanks @meme!
  • Fix regressions on Linux/MIPS.

Changes in 14.2.10

  • Improve frida-inject to support bidirectional stdio.
  • Add support for Termux in frida-python: pip install frida-tools now works.

Changes in 14.2.11

  • Improve frida-inject to support raw terminal mode.
  • Add internal policy daemon for Darwin.
  • Improve Gum.Darwin.Mapper to support strict kernels.

Changes in 14.2.12

  • Fix ART method hooking reliability after GC. Thanks @muhzii!

Changes in 14.2.13

  • Fix Instruction operands parsing on x86, ensuring the immediate value is always represented by an Int64 and never a number. Thanks @muhzii!
  • Fix frida-inject when process is not attached to a terminal. Thanks @muhzii!
  • Expose Base64 and Checksum GLib primitives to CModule. Thanks @mrmacete!

Changes in 14.2.14

  • Fix Gadget crash on i/macOS when loaded early.
  • Make frida-inject stdin communication optional. Thanks @muhzii!
  • Support iOS app spawn on unc0ver 6.x. Thanks @mrmacete!
  • Work around single-step delay when spawning iOS apps, to avoid failing randomly. Thanks @mrmacete!
  • Fix read() signature mismatch in the libc shim, which would result in a compilation error on newer Apple toolchains. Thanks @Manouchehri!
  • Fix Android enumerate_applications() name truncation on newer versions of Android. Kudos to @pancake for reporting and helping figure this one out!
  • Fix hang when target is unable to load frida-agent.
  • Fix support for attaching to Windows services.
  • Clean up stale Windows services before registering new ones.
  • Add build option to support using installed assets instead of embedding them.
  • Update iOS packaging to use installed assets.
  • Add basic support for jailed Android. Thanks @enovella_ for all the fun and productive pair-programming on this one!
  • Extend Arm64Writer API to support more immediates.
  • Improve Stalker to support temporarily misaligned stack on arm64.
  • Fix crash in Stalker follow() without a sink.
  • Implement Stalker invalidation support. This allows updating the instrumentation without throwing away all of the translated code. Thanks for the assist on this one, @p1onk!
  • Add Gum.DarwinModule.enumerate_function_starts().
  • Add Gum.DarwinGrafter for AOT grafting to be able to prepare binaries so they can be instrumented when runtime code modification isn’t possible. Thanks for the assist, @mrmacete!
  • Add Memory.allocate_near().
  • Improve Stalker performance and robustness on all supported architectures:
    • Improve call probes to probe at the target instead of at the call site, and take advantage of the new invalidation infrastructure.
    • Handle adding/removing call probes from within call probes.
    • Refactor callout-handling so user data can be destroyed on invalidate. This also eliminates the callout lock.
    • In case of self-modifying code, recompile instead of allocating a new block.
    • Use separate slabs for code and data, to avoid cases where we only use part of the slab because we run out of metadata storage.
    • Inline the first code/data slab in ExecCtx, so we use a lot less memory when following threads that either don’t touch much code, or none at all in case they don’t wake up.
    • Don’t bother storing original code when trust_threshold is 0.
    • Simplify Stalker block metadata to reduce the per-block memory consumption.
  • Fix result of Java.enumerateMethods() on ART. This was a bug where static initializer methods were being included in the enumerated set as ‘$init’ when they should be skipped altogether. Thanks @muhzii!
  • Fix the Android/ART near memory allocation code path used during Java method hooking. Thanks @muhzii!
  • Fix handling of generic Java array types. This allows array objects obtained from the runtime to be reused later when marshalling array types, which is necessary to preserve type information particularly in cases where the type is dynamic. Thanks @muhzii!
  • Port Android/ART StackVisitor to x86, x64, and ARM32. Thanks @P-Sc!
  • Fix ARM cache flushing on Android. Turns out cacheflush() expects a range on Linux/ARM. This 32-bit Android/ARM regression was introduced in 14.2.0.
  • Add a few missing TinyCC builtins for 32-bit ARM. Kudos to @giantpune for reporting and helping figure this one out!
  • Fix wrap-around in Stalker block recycling logic.
  • Fix V8 NativePointer construction from large numbers.
  • Fix Stalker local thread actions on Windows.
  • Fix the CModule temporary directory cleanup logic.
  • Remove forgotten InspectorServer debug code.
  • Fix the V8 debugger integration. Kudos to @taviso for reporting!

Changes in 14.2.15

  • Fix compatibility with latest unc0ver iOS jailbreak. Thanks @mrmacete!
  • Add support for Anbox. Thanks @asabil!
  • Add Java.deoptimizeMethod(). Thanks @liuyufei!
  • Handle replacing ART methods that may be devirtualized. Thanks @liuyufei!

Changes in 14.2.16

  • Add many missing TinyCC builtins for 32-bit ARM. Kudos to @giantpune for reporting and helping figure this one out!
  • Fix Android ART trampoline alignment when using ADRP on arm64, previously resulting in Error: invalid argument exception being thrown when attempting to replace some methods. Kudos to @pandasauce for reporting and helping figure this one out!

Changes in 14.2.17

  • Enumerate Darwin imports from chained fixups, to support the latest arm64e binaries. Thanks @mrmacete!
  • Fix chained fixups handling in the jailed iOS injector. Thanks @mrmacete!
  • qml: Compile with no_keywords for GLib compatibility. Thanks @suy!

Changes in 14.2.18

  • Fix i/macOS injector on recent XNU versions, where mach_port_extract_right() fails with KERN_INVALID_CAPABILITY when trying to steal the target process’ POSIX thread port send right. This resulted in the injector assuming that we’d uninjected, subsequently deallocating memory still in use.
  • Fix ___error symbol name in the jailed iOS injector. Thanks @mrmacete!
  • Fix enumeration of modules with spaces on path in the Linux backend. Thanks @suy!
  • Fix Stalker handling of direct branch addresses on x64.
  • python: Add RPC exports listing functionality. Thanks @NewbieGoose!

Frida 14.1 Released

Lots of goodies this time! 🎉 Let’s dive in.


We’ve just upgraded all of our dependencies to the latest and greatest. Part of this work included refurbishing the build system bits used for building them.

With these improvements we will finally support building past versions of Frida fully from source, which has been a long-standing issue that’s caused a lot of frustration.

It is now also a whole lot easier to tweak our dependencies, e.g. while debugging an issue. Say you’re troubleshooting why Thread.backtrace() isn’t working well on Android, you might want to play around with libunwind’s internals. It is now really easy to build one specific dependency:

$ make -f FRIDA_HOST=android-arm64 libunwind

Or if you’re building it for the local system:

$ make -f libunwind

But you might already have built Frida, and want to switch out libunwind in the prebuilt SDK that it is using. To do that you can now do:

$ make -f symlinks-libunwind

You can then keep making changes to “deps/libunwind”, and perform an incremental compilation by re-running:

$ make -f libunwind


We now support iOS 14.2. It was kinda already working, but our crash reporter integration would deadlock Apple’s crash reporter, and this isn’t great for system stability overall.

GumJS support for size_t and ssize_t

Thanks to @mame82 we finally support “size_t” and “ssize_t” in APIs such as NativeFunction. This means that your cross-platform agents no longer need to maintain mappings to the native types that these correspond to. Yay!

System GLib support

Gum can finally be built with the upstream version of GLib, and we now support generating GObject introspection definitions. This paves the way for future language bindings that are fully auto-generated.

Kudos to @meme for these awesome improvements!

Windows inprocess injection

Our Windows backend finally supports inprocess injection. By this I mean that in the most common cases where the target process’ architecture is the same – and no elevation is needed – we can now avoid writing out “frida-helper-{32,64}.exe” to a temporary directory and launching it before we’re able to attach() to a given target. As an added bonus this also reduces our startup time.

The motivation behind this improvement was to fix a long-standing issue where some endpoint security products would prevent our injector from working, as our logic was prone to trigger false positives in such software. We will still obviously run into such issues when we do need to spawn our helpers, but there’s now a good chance that the most common use-cases will actually work.

Stalker ARM improvements

For those of you using Stalker on 32-bit ARM, it should now be working a whole lot better than ever before. A whole slew of fixes landed in this release.

Bytecode and frida-tools

One of the realizations since 14.0 was released is that QuickJS’ bytecode format is a lot more volatile than expected. Because of this I would caution against using “frida-compile -b” unless your application is designed to only be used with one exact version of Frida.

As I wasn’t aware of this pitfall when cutting the previous release of frida-tools, I opted to precompile the frida-trace agent to bytecode. Upon realizing my mistake while working on releasing 14.1, I reverted this mistake and released a new version of frida-tools.

So make sure you also grab its latest release while upgrading:

$ pip3 install -U frida-tools


There’s also a bunch of other exciting changes, so definitely check out the changelog below.


Changes in 14.1.0

  • All dependencies upgraded to the latest and greatest.
  • Heavily refurbished build system for dependencies. Going forward we will finally support building past versions of Frida fully from source.
  • Port iOS crash reporter integration to iOS 14.2.
  • Fix error propagation when talking to iOS devices.
  • Add support for “size_t” and “ssize_t” in GumJS. Thanks @mame82!
  • Support linking against system GLib and libffi. Thanks @meme!
  • Support GObject Introspection. Thanks @meme!
  • Improve Windows backend to support inprocess injection. This means we can dodge common AV heuristics and speed things up in the most common cases where the target process’ architecture is the same, and no elevation is needed.
  • Fix Stalker ARM handling of “ldr pc, [sp], #4”.
  • Fix Stalker ARM clobbering of flags in IT blocks.
  • Fix Stalker ARM handling of CMN/CMP/TST in IT blocks.
  • Fix suppression flags for ThumbWriter instruction.
  • Fix Stalker ARM exclusion logic reliability.
  • Fix ARM Stalker follow() of thread in Thumb mode.
  • Fix ARM Stalker SVC handling in Thumb mode.
  • Fix ThumbRelocator handling of unaligned ADR.
  • Move Stalker ARM to runtime VFP feature detection.
  • Refuse to Interceptor.attach() without any callbacks.
  • Improve GumJS error message formatting.
  • Fix starvation in the V8 debugger integration.
  • Keep NativeCallback alive during calls on V8 also.

Changes in 14.1.1

  • Fix CModule regression where Capstone went missing.
  • Add missing CModule builtins for 32-bit ARM.
  • Fix Thread.backtrace() on Android/ARM64.

Changes in 14.1.2

  • Fix Thread.backtrace() on Android/ARM.

Changes in 14.1.3

  • Fix ObjC.choose(). The TinyCC upgrade in 14.1.0 exposed an existing bug.
  • Re-enable V8 by default. Turns out we have use-cases where it is a much better fit than QuickJS, and its debugger features were also being sorely missed.
  • Add –ignore-crashes/-C to frida-server for disabling the native crash reporter integration. For use-cases where the integration is undesired, or when running on bleeding edge OS versions that we don’t yet fully support. (Crash reporter integration is only available on iOS and Android for now.)
  • Enhance devkits to ensure Capstone APIs are exposed on all platforms.
  • Improve devkit examples.

Frida 14.0 Released

Here’s a major new release that took weeks of intense coding, with way too many cups of coffee. But before we dive into it, we need to have a quick look down memory lane.

For years now our V8-based runtime has served us well. But eventually we needed to support constrained systems where V8 isn’t a great fit, so we introduced a second runtime.

This has worked out nicely, but there were some trade-offs we were left with:

  • Language feature support being wildly different between the two runtimes. We tried to alleviate some of this by making the minimalistic runtime be the default, as it’s available everywhere and is the lowest common denominator in terms of features.
  • Needing to sacrifice performance when using a tool such as frida-compile to compile modern JavaScript to old JavaScript that runs on both runtimes.
  • Non-trivial agents with lots of code and data floating around make it clear not just how fast V8 is – no surprise there – but that it’s really good at packing objects to avoid wasting precious RAM. And to widen the gap between the two runtimes even more, V8 can run the modern JavaScript as-is and doesn’t need to run a bloated version that contains compatibility shims to fill in the missing runtime bits such as Map and Set.
  • Example code and documentation tends to look arcane to avoid confusing users who might try to run modern code on the default runtime.
  • Garbage collector implementation differences may hide the user’s bugs in one runtime that instantly blow up in the other where resources are released way more eagerly. One such example is failing to keep a NativeCallback alive while external code is still using it.
  • Terrible UX: All of the above is a very frustrating and confusing story to tell our users.
  • New features and refinements need to be implemented twice. This is a real pain for me as a maintainer for obvious reasons.

Fast-forward to 2019 and QuickJS caught my eye. I was really busy with other things at the time, though, so by the time I looked closer at it I noticed it supports ES2020, and also performs impressively well for an interpreter.

But as I started thinking about bringing up a new runtime from scratch, and seeing as the other two are roughly ~25 KLOC each, it just felt overwhelming.

I kept coming back to the QuickJS website though, devouring the technical details, and even started reading deeper into the public API at some point.

Then I noticed that it didn’t support cooperative multi-threaded use, where multiple threads execute JavaScript in lockstep. This made the mountain of work ahead feel even more daunting, but then I remembered that I’d already contributed support for this in Duktape, and it wasn’t that hard.

Eventually I mustered up the courage. Picked a super-simple test from GumJS’ extensive test-suite as my first challenge, and went ahead and copy-pasted the ScriptBackend and Script implementations from the youngest of the existing two runtimes. First renaming things, then stubbing out all of the modules (Interceptor, Stalker, etc.), just wanting to get a near-empty “shell” to compile and run.

At this point I was hooked and couldn’t stop. Lots of coffee was consumed, and before I knew it I’d gotten the core bits and the first module implemented. Then another, and then one more.

After working quite a bit with the QuickJS API, and jumping around its internals to make sure I understood the reference counting rules etc., it suddenly seemed really clear what was needed to implement the cooperative multi-threading API that would be needed to make this a real runtime and not just a toy.

What we need to be able to do is suspend JS execution while calling out to a NativeFunction. This is because the called function may block waiting for a lock which another thread might already be holding, but that other thread may have just called a hooked function and is waiting to enter the JS runtime. So if we didn’t let go of the JS lock before calling the NativeFunction, we’d now be in a deadlock.

Another use-case is calling Thread.sleep() or some other blocking API where we’d cause starvation if we did that while holding the JS lock.

Anyway, the QuickJS multi-threading API turned out to be straight-forward, so from there I kept on going, until it was all finally done! 🎉

At this point I was really curious about the performance of this brand new runtime, starting with the question of what it costs to enter and leave it.

Went ahead and took it for a spin on an iPhone 6, running the GumJS test that uses Interceptor to hook a nearly empty C function, supplying an empty JS callback, and then measures the wall-clock time spent on each call as it keeps calling the C function over and over.

The idea is to simulate what would happen if the user hooks a function that’s called frequently, to get an idea of the base overhead.

Here’s what I got:

# QuickJS
<min: 1.0 us, max: 7.0 us, median: 2.0 us> ok 1 /GumJS/Script/Interceptor/Performance/interceptor_on_enter_performance#QJS
<min: 2.0 us, max: 54.0 us, median: 2.0 us> ok 2 /GumJS/Script/Interceptor/Performance/interceptor_on_leave_performance#QJS
<min: 3.0 us, max: 18.0 us, median: 3.0 us> ok 3 /GumJS/Script/Interceptor/Performance/interceptor_on_enter_and_leave_performance#QJS
# Duktape
<min: 2.0 us, max: 8.0 us, median: 3.0 us> ok 4 /GumJS/Script/Interceptor/Performance/interceptor_on_enter_performance#DUK
<min: 2.0 us, max: 6.0 us, median: 3.0 us> ok 5 /GumJS/Script/Interceptor/Performance/interceptor_on_leave_performance#DUK
<min: 4.0 us, max: 89.0 us, median: 4.0 us> ok 6 /GumJS/Script/Interceptor/Performance/interceptor_on_enter_and_leave_performance#DUK
# V8
<min: 13.0 us, max: 119.0 us, median: 14.0 us> ok 7 /GumJS/Script/Interceptor/Performance/interceptor_on_enter_performance#V8
<min: 15.0 us, max: 127.0 us, median: 16.0 us> ok 8 /GumJS/Script/Interceptor/Performance/interceptor_on_leave_performance#V8
<min: 26.0 us, max: 198.0 us, median: 28.0 us> ok 9 /GumJS/Script/Interceptor/Performance/interceptor_on_enter_and_leave_performance#V8

Wow, so that was looking promising! How about baseline memory usage, i.e. how much memory is consumed by one instance of the runtime itself?

QJS Memory Baseline

That’s quite an improvement – only one fifth of the previous runtime!

The next thing I was curious about was the approximate initial size of Frida’s internal heap when using our REPL. That includes all of the memory used by frida-agent, the JS runtime, and the REPL agent that was loaded:


Yay, 1 MB freed up for other purposes!

So with that, I hope you’re as excited as I am about this new release. We’ve replaced our previous default runtime with this brand new one built on QuickJS.

And as an experiment I have also decided to build our official binaries without our V8 runtime. This means that the binaries are way smaller than they’ve ever been before.

I do realize that some of you may have use-cases where the V8 runtime is essential, so my hope is that you will take the new QuickJS runtime for a spin and let me know how it works for you. If it’s an absolute disaster for your particular use-case then don’t worry, just let me know and we will figure something out.

If you’d like to build Frida yourself with the V8 runtime enabled, it’s only a matter of tweaking this line. But please do let me know if you can’t live without it, so we can decide on whether we need to keep supporting this runtime down the road.

The only other change in this major release applies to i/macOS, where we’re finally following Apple’s move to drop support for 32-bit programs. We’ll keep the codepaths around for now though, but our official binaries have a lot less fat, and the top-level build system is also a bit slimmer. E.g. make core-macos-thin is now just make core-macos.

That’s all in Frida itself, but there’s more. We’ve also released frida-tools 9.0, freshly upgraded to make use of modern JavaScript features everywhere. That includes frida-trace, where the generated boilerplate hooks have become a lot more readable after some syntax upgrades. Last but not least we have also released frida-compile 10.0, where the Babel dependencies are gone and so are the corresponding command-line switches; it’s faster and so much simpler.

So with that, I hope you’ll enjoy this new release!

Changes in 14.0.0

  • Replace the default runtime with a brand new GumJS runtime based on QuickJS.
  • Disable V8 by default.
  • Retain callback object in Interceptor.attach() on V8.
  • Drop “enumerate” trap from the global access API.

Changes in 14.0.1

  • QJS: Fix nested global access requests.
  • qml: Update to the new frida-core API.

Changes in 14.0.2

  • QJS: Keep NativeCallback alive during calls.
  • QJS: Speed up the NativeCallback construction logic.
  • QJS: Disable stack limitation for now.
  • iOS: Port the iOS crash reporter integration to iOS 14.
  • iOS: Remove packaging logic for 32-bit.
  • Android: Use the default runtime for the “system_server” agent.
  • Modernize internal JavaScript agents.

Changes in 14.0.3

  • Disable V8 on Windows also.
  • iOS: Improve the packaging script.

Changes in 14.0.4

  • iOS: Fix arm64e regression caused by toolchain upgrade.

Changes in 14.0.5

  • QJS: Fix Interceptor error-handling.

Changes in 14.0.6

  • ObjC: Fix lifetime of replaced methods so they’re not tied to the class wrapper, and also kept alive in chaining use-cases. Kudos to @Hexploitable and @mrmacete for the assist!
  • Fix Exceptor sigaction() registration failure when act == oact. Thanks @hluwa!
  • Improve Linux libc detection.
  • Fix intermittent hang when enumerating and modifying threads on Linux.
  • Fix inconsistent PC vs CPSR Thumb bit handling.
  • Fix build regressions on Linux/armhf and Linux/arm64.
  • Publish binaries for Raspberry Pi 32- and 64-bit.

Changes in 14.0.7

  • Avoid deadlocking in scenarios where we crash while executing JS code, e.g. when calling out to a NativeFunction w/ exceptions: 'propagate', or in case of a bug in GumJS. Thanks @mrmacete!
  • Fix CModule on macOS/arm64.
  • Publish Python and Node.js binaries for Raspberry Pi 32-bit.
  • Publish binaries for Fedora 33 instead of Fedora 32.
  • Publish binaries for Ubuntu 20.10.

Changes in 14.0.8

  • Improve jailed iOS upload reliability by adding some bi-directional communication into the upload connection. This is to prevent DoS protections from kicking in during gadget upload in complex remote configurations. Thanks @mrmacete!

Frida 12.11 Released

In anticipation of Apple releasing macOS 11, it’s time for Frida 12.11! This release brings full compatibility with macOS 11 Beta 3. Not only that, we now also support macOS on Apple silicon. Yay!

It’s worth noting that we didn’t stop at arm64, we also support arm64e. This ABI is still a moving target, so if you have a Developer Transition Kit (DTK) and want to take this for a spin you will have to disable SIP, and then add a boot argument:

$ sudo nvram boot-args="-arm64e_preview_abi"

Considering this awesome convergence of platforms, there’s actually a chance that we may already support jailbroken iOS 14. We will know once a public jailbreak becomes available. At least it shouldn’t require much work to support.

So for those of you exploring your DTK, you can grab our CLI tools and Python bindings the usual way:

$ pip3 install frida-tools

As a sidenote we just released CryptoShark 0.2.0 and would highly recommend checking it out. Only caveat is that we only provide binaries for macOS/x86_64 for now, so if you want to try this on macOS/arm64 you will be able to run it thanks to Rosetta, but attaching to processes on the “Local System” device won’t work.

The workaround is simple though – just grab a frida-server binary from our releases and fire it up, then point CryptoShark at the “Local Socket” device. You can also use local SSH port forwarding if you’d like to run CryptoShark on one system and attach to processes on another:

$ ssh -L 27042: dtk

There’s also a lot of other exciting changes in this release, so definitely check out the changelog below.


Changes in 12.11.0

  • Add support for macOS 11 and Apple silicon.
  • Daemonize helper process on Darwin. Thanks @mrmacete!
  • Daemonize helper process on Linux.
  • Fix unreliable iOS device handling when using usbmuxd. Thanks @mrmacete!
  • Fix infinite wait when i/macOS frida-helper dies early.
  • Add Android spawn() “uid” option to specify user ID. Thanks @sowdust!
  • Add support for the latest checkra1n jailbreak. Thanks for the assist, @Hexploitable!
  • Improve Stalker ARM stability.
  • Plug leak in Interceptor arm64 backend error path.
  • Fix interception near memcpy() on systems w/o RWX pages.
  • Fix encoding of CpuContext pointers on Darwin/arm64e.
  • Always strip backtrace items on Darwin/arm64.
  • Fix Linux architecture detection on big-endian systems.
  • Fix Capstone endianness configuration on ARM BE8.

Changes in 12.11.1

  • Handle i/macOS targets using different ptrauth keys.
  • Fix Linux CPU type detection on big-endian systems.
  • Fix early instrumentation on Linux/ARM-BE8.
  • Fix injection into Linux processes blocked on SIGTTIN or SIGTTOU.

Changes in 12.11.2

  • Fix Stalker thread_exit probing on macOS 11/x86_64.
  • Fix slow exports resolution on macOS 11/x86_64.
  • Fix CModule support for Capstone headers on ARM.
  • Add ArmWriter to the CModule runtime for ARM.
  • qml: Add support for specifying the script runtime to use.

Changes in 12.11.3

  • Fix prototype of ModuleMap.values() in the V8 runtime.
  • qml: Sync DetachReason enum with the current Frida API.
  • qml: Fix Device lifetime logic.

Changes in 12.11.4

  • Fix injector on macOS 11 beta 3. Drop support for older betas.
  • Drop helper hack made redundant by macOS 11 beta 3.
  • Fix handling of i/macOS introspection modules.

Changes in 12.11.5

  • Fix i/macOS early instrumentation of processes using dyld’s modern code path on macOS 11 and iOS 14.
  • Make JVM method interception safer by installing new methods using VMThread::execute(), which blocks all Java threads and makes it safer to do interception of hot methods. Thanks @0xraaz!
  • Add support for SUB instruction to ARM Relocator. This means improved reliability when using Interceptor and Stalker on 32-bit ARM.
  • qml: Fix build with GCC by adding missing include.

Changes in 12.11.6

  • Port iOS jailed injector to the new arm64e ABI. This means iOS 14 beta 3 is now fully supported in jailed mode, even on A12+ devices.

Changes in 12.11.7

  • Improve libc detection on Linux and QNX. Thanks @demantz!
  • Fix checking of symbol sizes in libdwarf backend. This means more reliable debug symbol resolution on Linux.
  • Fix brittle Android activity start logic. Thanks @muhzii!
  • Improve Android Java hooking reliability by clearing the kAccFastInterpreterToInterpreterInvoke flag. Thanks @deroko!
  • Guard against using Java wrappers after $dispose(), to make such dangerous bugs easier to detect.
  • Improve the frida-qml build system and add support for standalone use.

Changes in 12.11.8

  • Add support for macOS 11 beta 4 on Apple silicon.

Changes in 12.11.9

  • Add support for jailed iOS w/ Xcode 12 developer disk images.

Changes in 12.11.10

  • node: Plug leak in IOStream’s WriteOperation. Thanks @mrmacete!
  • qml: Add support for listing applications.
  • qml: Expose a “count” property on each model.
  • Fix ARM relocation of “add sb, pc, r4”.
  • Fix ARM relocation of “add ip, pc, #4, #12”.
  • Fix ARM writer support for LDMIA when Rn is in reglist.

Changes in 12.11.11

  • Add support for opaque JNI IDs on Android R, to support debuggable apps. Thanks @muhzii!
  • qml: Add support for spawning processes.
  • qml: Add missing libraries when linking with devkit on Linux.
  • qml: Fix static linking on Linux.
  • qml: Optimize startup to not wait for enumerate_devices().

Changes in 12.11.12

  • Initialize CoreFoundation during early instrumentation on i/macOS. Thanks @mrmacete!
  • Support a NULL EventSink in Stalker. Thanks @meme!
  • node: Provide Electron prebuilds for v10 and v11. Next release will drop prebuilds for v9.
  • qml: Add post(QJsonArray).

Changes in 12.11.13

  • Fix ART internals probing on Android 11/arm64. Thanks @enovella_!
  • Build GumJS runtime for V8 without compression for now, as we need to improve frida-compile to use the latest version of terser.

Changes in 12.11.14

  • Build GumJS runtime for V8 with compression now that frida-compile has been upgraded to the latest version of terser.

Changes in 12.11.15

  • Add support for iOS 14.x secure DTX. Thanks @mrmacete!
  • Fix Java.deoptimizeEverything() on Android 11. Thanks @Gh0u1L5!

Changes in 12.11.16

  • Fix arm64e support in Arm64Relocator.can_relocate(). Thanks @mrmacete!
  • Add “onEvent” option to Stalker.follow(). This allows synchronous processing of events in native code – typically implemented using CModule. Useful when wanting to implement custom filtering and/or queuing logic to improve performance, or sacrifice performance in exchange for reliable event delivery.
  • Expose Stalker’s live CpuContext to EventSink. This can be accessed through the “onEvent” callback, and through the Gum C API.
  • Add Spinlock to the CModule runtime.

Changes in 12.11.17

  • Kill via LLDB on jailed iOS, to avoid killing via ProcessControl when possible. Turns out our previous behavior left debugserver in a bad state for which killed apps sometimes would appear as already running, failing early instrumentation on subsequent spawn() attempts. Thanks @mrmacete!
  • Fix Java bridge initialization on older Android API levels by letting the instrumentation field detection fail gracefully. We don’t need it on older API levels anyway.
  • Reduce Duktape memory usage a little per script. There is no need to intern the script source code string.

Changes in 12.11.18

  • Skip app extensions when detecting frontmost app on jailed iOS. Sometimes an app extension was returned as the first matched process, subsequently throwing “Unable to resolve bundle path to bundle ID”. Thanks @mrmacete!
  • Improve Android ART instrumentation offset detection for x86/x86_64. Thanks @Gh0u1L5!
  • Fix JDWP initialization failure on Android 7.1-8.1. Thanks @Gh0u1L5!
  • Fix nearest symbol logic in the libdwarf backend.
  • Plug a leak in the Duktape-based runtime’s argument parsing logic, where any collected memory range arrays would leak in case an error occurs parsing one of the following arguments.

Frida 12.10 Released

This time we have some exciting news for Java developers and reversers: frida-java-bridge now supports the HotSpot JVM. This means our Java runtime bridge is no longer exclusively an Android feature. Huge thanks to Razvan Sima for this amazing addition.

The timing couldn’t have been any better either, as we recently also added Java.enumerateMethods(query), a brand new API for efficiently locating methods matching a given query. We made sure to also implement this for the HotSpot JVM.

The query is specified as "class!method", with globs permitted. It may also be suffixed with / and one or more modifiers:

  • i: Case-insensitive matching.
  • s: Include method signatures, so e.g. "putInt" becomes "putInt(java.lang.String, int): void". Handy to match on argument and return types, such as "*!*: boolean/s" to match all methods that return a boolean.
  • u: User-defined classes only, ignoring system classes.

For instance:

Java.perform(() => {
  const groups = Java.enumerateMethods('*youtube*!on*')
  console.log(JSON.stringify(groups, null, 2));

Which might output something like:

    "loader": "<instance: java.lang.ClassLoader, $className: dalvik.system.PathClassLoader>",
    "classes": [
        "name": "",
        "methods": [
        "name": "",
        "methods": [
        "name": "",
        "methods": [

We’ve also enhanced frida-trace to support Java method tracing:

$ frida-trace \
    -U \
    -f \
    --runtime=v8 \
    -j '*!*certificate*/isu'
X509Util.addTestRootCertificate: Auto-generated handler at "/Users/oleavr/__handlers__/"
X509Util.clearTestRootCertificates: Auto-generated handler at "/Users/oleavr/__handlers__/"
X509Util.createCertificateFromBytes: Auto-generated handler at "/Users/oleavr/__handlers__/"
X509Util.isKnownRoot: Auto-generated handler at "/Users/oleavr/__handlers__/"
X509Util.verifyKeyUsage: Auto-generated handler at "/Users/oleavr/__handlers__/"
X509Util.verifyServerCertificates: Auto-generated handler at "/Users/oleavr/__handlers__/"
ResourceLoader$CppProxy.native_enableDevCertificate: Auto-generated handler at "/Users/oleavr/__handlers__/"
ResourceLoader$CppProxy.enableDevCertificate: Auto-generated handler at "/Users/oleavr/__handlers__/"
AndroidCertVerifyResult.getCertificateChainEncoded: Auto-generated handler at "/Users/oleavr/__handlers__/"
bjbm.a: Auto-generated handler at "/Users/oleavr/__handlers__/bjbm/a.js"
bjbn.a: Auto-generated handler at "/Users/oleavr/__handlers__/bjbn/a.js"
AndroidNetworkLibrary.addTestRootCertificate: Auto-generated handler at "/Users/oleavr/__handlers__/"
AndroidNetworkLibrary.clearTestRootCertificates: Auto-generated handler at "/Users/oleavr/__handlers__/"
AndroidNetworkLibrary.verifyServerCertificates: Auto-generated handler at "/Users/oleavr/__handlers__/"
vxr.checkClientTrusted: Auto-generated handler at "/Users/oleavr/__handlers__/vxr/checkClientTrusted.js"
vxr.checkServerTrusted: Auto-generated handler at "/Users/oleavr/__handlers__/vxr/checkServerTrusted.js"
vxr.getAcceptedIssuers: Auto-generated handler at "/Users/oleavr/__handlers__/vxr/getAcceptedIssuers.js"
ResourceLoader.enableDevCertificate: Auto-generated handler at "/Users/oleavr/__handlers__/"
Started tracing 18 functions. Press Ctrl+C to stop.
           /* TID 0x339d */
   955 ms  AndroidNetworkLibrary.verifyServerCertificates([[48,-126,9,…],[48,-126,4,…]], "RSA", "")
   972 ms  AndroidCertVerifyResult.getCertificateChainEncoded()
  1043 ms  AndroidNetworkLibrary.verifyServerCertificates([[48,-126,4,…],[48,-126,4,…]], "RSA", "")
  1059 ms  AndroidCertVerifyResult.getCertificateChainEncoded()
           /* TID 0x33a0 */
  1643 ms  AndroidNetworkLibrary.verifyServerCertificates([[48,-126,5,…],[48,-126,4,…]], "RSA", "")
           /* TID 0x339d */
  1651 ms  AndroidNetworkLibrary.verifyServerCertificates([[48,-126,9,…],[48,-126,4,…]], "RSA", "")
           /* TID 0x33a1 */
  1665 ms  AndroidNetworkLibrary.verifyServerCertificates([[48,-126,15,…],[48,-126,4,…]], "RSA", "")
           /* TID 0x33a0 */
  1674 ms  AndroidCertVerifyResult.getCertificateChainEncoded()
           /* TID 0x339d */
  1674 ms  AndroidCertVerifyResult.getCertificateChainEncoded()
           /* TID 0x3417 */
  1674 ms  AndroidNetworkLibrary.verifyServerCertificates([[48,-126,15,…],[48,-126,4,…]], "RSA", "")
           /* TID 0x33a1 */
  1684 ms  AndroidCertVerifyResult.getCertificateChainEncoded()
           /* TID 0x3417 */
  1688 ms  AndroidCertVerifyResult.getCertificateChainEncoded()
  2513 ms  AndroidNetworkLibrary.verifyServerCertificates([[48,-126,9,…],[48,-126,4,…]], "RSA", "")
  2527 ms  AndroidCertVerifyResult.getCertificateChainEncoded()
  2722 ms  AndroidNetworkLibrary.verifyServerCertificates([[48,-126,9,…],[48,-126,4,…]], "RSA", "")
           /* TID 0x33a1 */
  2741 ms  AndroidNetworkLibrary.verifyServerCertificates([[48,-126,9,…],[48,-126,4,…]], "RSA", "")
           /* TID 0x339d */
  2758 ms  AndroidNetworkLibrary.verifyServerCertificates([[48,-126,9,…],[48,-126,4,…]], "RSA", "")
           /* TID 0x33a1 */
  2771 ms  AndroidCertVerifyResult.getCertificateChainEncoded()
           /* TID 0x3417 */
  2772 ms  AndroidCertVerifyResult.getCertificateChainEncoded()
           /* TID 0x339d */
  2777 ms  AndroidCertVerifyResult.getCertificateChainEncoded()
  2892 ms  AndroidNetworkLibrary.verifyServerCertificates([[48,-126,6,…],[48,-126,4,…]], "RSA", "")
           /* TID 0x3417 */
  2908 ms  AndroidNetworkLibrary.verifyServerCertificates([[48,-126,6,…],[48,-126,4,…]], "RSA", "")
           /* TID 0x33a1 */
  2926 ms  AndroidNetworkLibrary.verifyServerCertificates([[48,-126,6,…],[48,-126,4,…]], "RSA", "")
           /* TID 0x3417 */
  2935 ms  AndroidCertVerifyResult.getCertificateChainEncoded()
           /* TID 0x339d */
  2937 ms  AndroidCertVerifyResult.getCertificateChainEncoded()
           /* TID 0x33a1 */
  2942 ms  AndroidCertVerifyResult.getCertificateChainEncoded()

This was just released as part of frida-tools 8.0 – which you may grab through e.g.: pip3 install -U frida-tools

We’ve also been working hard on quality improvements across the board. One good example is Stalker for 32-bit ARM, which now works a lot better on Android. It is also a lot faster, in part because of a bug resulting in Thumb blocks being recompiled over and over. We have also implemented one of the adaptive optimizations that the other Stalker backends make use of, and this alone typically amounts to a ~5x performance improvement.

So that should cover the highlights – but if you’re curious about the details I’d highly recommend reading the changelog below.


Changes in 12.10.0

  • Java: Add support for HotSpot JVM. Uses JVMTI to enumerate classes and choose objects. Method interception works if the JVM library has symbols (default with JDK on macOS). Tested on macOS with java 8, 11, 13, 14. Thanks @0xraaz!
  • Java: Fix non-return from _getUsedClass(), where calling Java.use() twice without using Java.perform() would result in _getUsedClass() getting stuck in an infinite sleep loop. Thanks @0xraaz!
  • Java: Fix $alloc(), which got broken by the refactoring a while back.
  • ObjC: Add Block.declare() to be able to work with blocks without signature metadata.
  • ObjC: Fix ObjC pointer handling regression introduced in 12.9.8.

Changes in 12.10.1

  • Java: Allow ClassFactory.get(null), for convenience when using enumerateMethods().
  • Java: Restore the JVM method adjustment logic, which got accidentally dropped from the pull-request. Thanks @0xraaz!

Changes in 12.10.2

  • Fix handling of long symbol names on i/macOS. Thanks @mrmacete!
  • Java: Fix JVM interception issues for static/final methods. Thanks @0xraaz!
  • Fix Stalker ARM handling of Thumb-2 “mov pc, <reg>”.
  • Fix Stalker ARM handling of volatile VFP registers.

Changes in 12.10.3

  • Fix device removal wiring in the Fruity backend. Thanks @mrmacete!
  • Avoid clobbering R9 in ArmWriter.put_branch_address().
  • Add ThumbWriter.can_branch_directly_between().
  • Add ThumbWriter.put_branch_address().
  • Improve ThumbRelocator to handle ADR.
  • Fix Stalker ARM block corruption.
  • Fix Stalker ARM block recycling logic for Thumb blocks.
  • Add missing Stalker ARM continuation logic, to support long basic blocks.
  • Implement Stalker ARM backpatching logic to improve performance, typically 5x.

Changes in 12.10.4

  • Fix encoding of in the V8 runtime. Thanks @mrmacete!

Frida 12.9 Released

Our previous big release was all about Stalker. For those of you not yet familiar with it, it’s basically a code tracing engine – allowing threads to be followed, capturing every function, every block, even every instruction which is executed. Beyond just tracing code, it also allows you to add and remove instructions anywhere. It even uses advanced JIT tricks to make all of this really fast.

That may still sound a little abstract, so let’s have a look at a couple of examples. One way to use it is when you want to determine “what other functions does this function call”. Or, perhaps you’d like to use Apple’s speech synthesizer to announce the RAX register’s value at every RET instruction in code belonging to the app? Here is how that can be done. This was one of the demos at my r2con presentation back in 2017.

Up until now Stalker has only been available on Intel architectures and ARM64. So I’m really excited to announce that Stalker is now also available on ARM32! Yay! 🎉 I’m hopeful that this sorely missed Stalker backend will motivate a lot of you to start building really cool things on top of Stalker. I feel like it has a ton of potential beyond “just” code tracing. And combined with CModule it’s become really easy to balance rapid prototyping and dynamic behavior with performance.

There’s so much to talk about in this release. One of the other major changes is that we have upgraded all of our dependencies. Most interesting among them is probably V8, which we have upgraded to 8.4. This means you can use all of the latest JavaScript language features such as optional chaining and nullish coalescing operator without having to frida-compile your agent. That, and performance improvements, another area where V8 just keeps on getting better and better.

We’ve also just added support for Android 11 Developer Preview 4, and iOS/arm64e apps are now fully supported even on jailed iOS. Things have improved so much across all of our supported platforms. One thing in particular that I’d like to highlight is that we have finally eliminated a long-standing resource leak affecting our Duktape-based JS runtime – a bug that’s been around for as long as we’ve been using Duktape as our default JS runtime.

Anyway, there’s really no easy way to dig into all of the areas where things have improved, so definitely check out the changelog below.


Changes in 12.9.0

  • Stalker now also available on ARM32. 🎉
  • Stalker JS integrations no longer clobber errno / LastError.
  • Stalker.follow() now reliable on x86 and ARM64, including when the target thread is in a syscall on Windows.
  • Stalker is finally reliable on WoW64. Thanks @zuypt!
  • All dependencies have been updated to the latest and greatest. Most exciting is V8 8.4, supporting the latest JavaScript language features.
  • Long-standing Duktape memory leak finally discovered and fixed. Thanks to @disazoz for the bug-report that lead to this breakthrough.
  • Socket.connect() no longer leaks the file-descriptor (and associated memory) on error. (Fixed by the GLib dependency upgrade.) Thanks for reporting, @1215clf!
  •*() no longer leaks in the V8 runtime.
  • UNIX build system moved to Meson 0.54.
  • Windows build system moved to VS2019.
  • Node.js prebuilds provided for v14, in addition to v10 and v12.
  • Electron prebuilds provided for v8 and v9.
  • Fedora packages for F32.
  • Ubuntu packages for Ubuntu 20.04.
  • Python bindings no longer using any deprecated APIs.
  • Support for leanback-only Android apps. Thanks @5murfette!
  • iOS jailed spawn() w/o closure supported on arm64e. Thanks @mrmacete!
  • iOS usbmux pairing record plist parsing now also handles binary plists, fixing a long-standing issue where Frida would reject a tethered iOS USB device. Thanks @pachoo!
  • ObjC.choose() also supported on arm64e. Thanks @mrmacete!
  • ObjC.protocols enumeration finally working properly, and not just the first time. Thanks for reporting, @CodeColorist!
  • Initial support for Android 11 Developer Preview. Thanks @abdawoud!
  • MUSL libc compatibility.
  • Support for older versions of glibc, so our binaries can run on a wide variety of desktop Linux systems.
  • Libc shim also covers memalign() and supports newer GNU toolchains.
  • Exceptor’s POSIX backend is now detecting Thumb correctly on ARM32, which would previously result in random crashes.
  • Exceptor no longer clobbers “rflags” (x86_64) and “cpsr” (ARM64) on i/macOS, and provides write access to the native context.
  • Four essential i/macOS 64-bit syscalls added to the libc shim: read(), write(), mmap(), munmap(). Thanks @mrmacete!
  • iOS binaries now signed with the “skip-library-validation” entitlement for convenience. Thanks @elvanderb!
  • The frida-core Vala API bindings are no longer missing the frida.Error type.
  • Our scripts now allow messages to be post()ed to them while they are in the LOADING state. This is useful when a script needs to make a synchronous request during load(). Thanks @Gbps!
  • Gadget finally supports early instrumentation with the V8 runtime on 64-bit ELF targets, where constructor functions would previously be run in the wrong order. Thanks @tacesrever!
  • Support for ADB channels beyond just TCP in Device.open_channel(). Thanks @aemmitt-ns!
  • Many new instructions supported in the ArmWriter and ThumbWriter APIs.
  • Massive improvements to our ARM32 relocator implementations.
  • Linux module enumeration working when invoked through loader.
  • Linux symbol resolution improvements.
  • Better argument list handling in the V8 runtime, treating undefined the same as in the Duktape runtime. Thanks @mrmacete!
  • CModule Stalker API is back in working order.
  • CModule runtime now exposes Thread.{get,set}_system_error().
  • CModule is now a stub on Linux/MIPS, instead of failing to compile due to TinyCC not yet supporting MIPS.
  • Capstone configured to support ARMv8 A32 encodings.

Changes in 12.9.1

  • The Python bindings’ does the right thing for Python 3.x on macOS.

Changes in 12.9.2

  • Fruity (iOS USB) backend no longer emits a warning on stdio.

Changes in 12.9.3

  • Android 11 Developer Preview 4 is now supported. Thanks for the assist, @enovella_!
  • Linux file monitoring is back in great shape.
  • ArmRelocator properly relocates ADD instructions involving the PC register.
  • ThumbRelocator properly handles IT blocks containing an unconditional branch. This means Interceptor is able to hook even more tricky cases. Thanks @bet4it!
  • Stalker ARM32 also supports clone syscalls in Thumb mode.
  • Stalker ARM32 now suppresses events around exclusive ops like the ARM64 backend.
  • Stalker ARM32 trust threshold support.
  • Improved error-handling in ObjC and Java bridges, to avoid crashing the process on unsupported OSes.

Changes in 12.9.4

  • ObjC.available no longer pretends that the Objective-C runtime is available when it indeed is not. The error-handling refactoring in 12.9.3 broke this, and the regression went unnoticed due to this being a blind spot in our test coverage.
  • Electron v9 is out, so we now only provide prebuilds for v9.

Changes in 12.9.5

  • iOS early instrumentation – i.e. spawn() – supported on latest unc0ver.
  • iOS crash reporter integration ported to iOS 13.5.
  • SystemFunction now implements call() and apply() in the Duktape runtime, and not only in the V8 runtime.
  • Java bridge finally handles strings with embedded nuls, fixing a long-standing issue that’s been around for as long as the Java bridge has existed. Thanks @tacesrever!

Changes in 12.9.6

  • No changes except for proper Windows binaries this time. The Windows CI worker did not actually build anything last time around, and released stale binaries.

Changes in 12.9.7

  • iOS early instrumentation more reliable on the unc0ver jailbreak: we now load substrate-inserter.dylib as part of our early instrumentation. This means it gets a chance to bootstrap the process, and lets you hook system APIs hooked by the bootstrapper without worrying about the bootstrapper getting confused when it encounters your hooks. Thanks @mrmacete!

Changes in 12.9.8

  • ApiResolver implementations now support case-insensitive matching by appending “/i” to the query string. Thanks @Hexploitable!
  • The module ApiResolver no longer leaks the MatchInfo instance.
  • CModule runtime gained GLib.PatternSpec and GLib UTF-8 case helpers.
  • DebugSymbol.load() introduced to be able to explicitly load debug symbols. For now this is only implemented on Windows, where we now also support “module!symbol” notation for improved performance and precision. Thanks @ohjeongwook!
  • Java bridge got a brand new API: Java.enumerateMethods(query) This enables efficiently locating methods matching a given query.
  • ObjC.choose() no longer crashes due to a lifetime issue only reproducible in our V8 runtime.

Frida 12.8 Released

Get ready for an exciting new release. This time around we’re giving some long overdue love to our Stalker engine. It’s been around for roughly ten years, but it wasn’t until Frida 10.5 in late 2017 that we started unleashing its massive potential.

Up until now we were able to Stalker.follow() existing threads and not only observe them, but also mutate their instruction streams any way we’d like. It could also be combined with Interceptor to instrument the current thread between strategic points. This allowed us to build tools such as AirSpy.

But, what if we want to Stalker.follow() a NativeFunction call? This may seem really simple, but reentrancy makes this really hard. It’s easy to end up following execution inside e.g. our private heap, and end up needing to allocate memory for the instrumentation itself… all kinds of fun scenarios that are mind-boggling to reason about.

The way we dealt with this was to teach Stalker to exclude certain memory ranges, so that if it sees a call going to such a location it will simply emit a call instruction there instead of following execution. So what we did was to automatically exclude frida-agent’s own memory range, and that way we didn’t have to deal with any of the reentrancy madness.

We also took care to special-case attempts to Stalker.follow() the current thread, so that we queued that work until we’re about to leave our runtime and transition back into user code (or our main loop, in the case of the JS thread).

That still left the big unanswered question of how to use Stalker in conjunction with NativeFunction. We can now finally put that behind us:

const open = new NativeFunction(
    Module.getExportByName(null, 'open'),
    'int', ['pointer', 'int'],
    { traps: 'all' }

  events: {
    call: true
  onReceive(e) {

const fd = open(Memory.allocUtf8String('/foo/bar'), 0);
console.log('open() =>', fd);

By setting the traps: 'all' option on the NativeFunction, it will re-activate Stalker when called from a thread where Stalker is temporarily paused because it’s calling out to an excluded range – which is the case here because all of frida-agent’s code is marked as excluded.

We can also achieve the same goal for Objective-C methods:

  events: {
    call: true
  onReceive(e) {

const NSAutoreleasePool = ObjC.classes.NSAutoreleasePool;
const NSFileManager = ObjC.classes.NSFileManager;

const fileExistsAtPath = NSFileManager['- fileExistsAtPath:']
    .clone({ traps: 'all' });

const pool = NSAutoreleasePool.alloc().init();
try {
  const manager = NSFileManager.defaultManager();
  const result =, '/foo/bar');
  console.log('fileExistsAtPath() =>', result);
} finally {

And also for Java methods on Android:

  events: {
    call: true
  onReceive(e) {

Java.perform(() => {
  const JFile = Java.use('');
  const exists = JFile.exists.clone({ traps: 'all' });

  const file = JFile.$new('/foo/bar');
  const result =;
  console.log('exists() =>', result);

Yay. That said, these examples are barely scratching the surface of what’s possible using Stalker. One of the really cool use-cases is in-process fuzzing, which frida-fuzz is a great example of. There’s also a bunch of other use-cases, such as reversing, measuring code coverage, fault injection for testing purposes, hooking inline syscalls, etc.

So that’s the main story of this release. Would like to thank @andreafioraldi for the great bug-reports and help testing these tricky changes.

Wrapping up

One cool new feature worth mentioning is the new ArrayBuffer.wrap() API, which allows you to conveniently and efficiently access memory regions as if they were JavaScript arrays:

const header = Memory.alloc(16);

const bytes = new Uint8Array(ArrayBuffer.wrap(header, 16));
bytes[0] = 1;
bytes[0] += 2;
bytes[1] = 2;

console.log(hexdump(header, { length: 16, ansi: true }));
console.log('First byte is:', bytes[0]);

This means you can hand over direct memory access to JavaScript APIs without needing to copy memory in/out of the runtime. The only drawback is that bad pointers won’t result in a JS exception, and will crash the process.

We now also allow you to access the backing store of any ArrayBuffer, through the new unwrap() method on ArrayBuffer. An example use-case for this is when using an existing module such as frida-fs where you get an ArrayBuffer that you then want to pass to native code.

Kudos to @DaveManouchehri for contributing the first draft of the ArrayBuffer.wrap() API, and also big thanks to @CodeColorist for suggesting and helping shape the unwrap() feature.

Changes in 12.8.0

  • Stalker reactivation working properly.
  • Stalker thread lifetime properly handled. Will also no longer crash when following a thread to its death on i/macOS.
  • Safer garbage collection logic in Stalker.
  • Making mistakes in the Stalker transform callback which ends up throwing a JS exception now results in Stalker.unfollow(), so the error doesn’t get swallowed by the process crashing.
  • Robust support for Stalker transform calling unfollow().
  • Stalker support for older x86 CPUs without AVX2 support.
  • Support for disabling automatic Stalker queue drain.
  • NativeFunction is better at handling exceptions through the brand new Interceptor unwinding API.
  • Java and ObjC APIs to specify NativeFunction options for methods through clone(options), and blocks through the second argument to ObjC.Block().
  • ObjC class and protocol caching logic finally works. Thanks @gebing!
  • The prebuilt Python 3 extension for Windows finally supports all Python 3 versions >= 3.4 on Windows, just like on the other platforms.
  • ArrayBuffer wrap() and unwrap().
  • DebugSymbol API has better error-handling on Linux/Android.
  • Java integration no longer crashes in recompileExceptionClearForArm64() in system processes on Android 10.
  • GumJS devkit on i/macOS supports V8 once again.

Changes in 12.8.1

  • The CModule Stalker integration is back in business.

Changes in 12.8.2

  • Thumb IT blocks are finally relocated correctly. This means we are able to hook a lot more functions on 32-bit ARM targets, e.g. Android. Thanks @bigboysun!

Changes in 12.8.3

  • Java.ClassFactory.get() introduced to be able to work with multiple class loaders without worrying about colliding class names. This means that assigning to the loader property is now considered deprecated. We still keep it around for backwards compatibility, but using it alongside the new API is not supported.
  • Java.enumerateLoadedClasses() also provides class handles and not just names.
  • The JNI GetByteArrayRegion() function is now part of the Env wrapper. Thanks @iddoeldor!

Changes in 12.8.4

  • Internal hooks no longer result in crashes on Linux/ELF targets when PLT/GOT entries haven’t been warmed up.

Changes in 12.8.5

  • Python bindings finally provide properly encoded error messages on Python 2.x.

Changes in 12.8.6

  • Android linker detection is finally working again in sandboxed processes. This was a regression introduced in 12.7.8. Kudos to @DaveManouchehri for reporting and helping track this one down!

Changes in 12.8.7

  • Our Node.js IOStream bindings received two critical stability improvements. Turns out the cancellation logic had a race-condition that resulted in the cancellable not always being used. There was also a bug in the teardown logic that could result in a stream being closed before all I/O operations had completed. Kudos to @mrmacete for these awesome fixes!

Changes in 12.8.8

  • Gadget no longer deadlocks on Android/Linux during early instrumentation use-cases where Gadget’s entrypoint gets called with dynamic linker lock(s) held. Due to Exceptor now using dlsym() to avoid running into PLT/GOT issues during early instrumentation, we need to ensure that Exceptor gets initialized from the entrypoint thread, and not the Gadget thread.

Changes in 12.8.9

  • Stalker’s JavaScript integration is no longer performing a use-after-free in EventSink::stop(), i.e. after Stalker.unfollow().

Changes in 12.8.10

  • Gadget is once again able to run on iOS without a debugger present. This was a regression introduced in 12.8.8. Kudos to @ddzobov for reporting!

Changes in 12.8.11

  • The i/macOS Exceptor’s API hooks no longer perform an OOB write when a user of the Mach exception handling APIs only requests a subset of the handlers. Such a user would typically be a crash reporter or analytics framework.
  • Electron prebuilds are now provided for v8 (stable) and v9 (beta). We no longer provide prebuilds for v7.

Changes in 12.8.12

  • Massively overhauled Android Java integration, now using Proxy objects and CModule to lazily resolve things. And no more eval-usage to dynamically generate method and field wrappers – i.e. less memory required per wrapper generated. All of these changes reduce memory usage and allow Java.use() to complete much faster.
  • Android Java integration provides uncensored access to methods and fields on Android versions that attempt to hide private APIs, i.e. Android >= 9.
  • Way faster Android device enumeration. No longer running any adb shell commands to determine the device name when the locally running ADB daemon is new enough (i.e. ADB >= sometime during 2017).
  • We’ve finally eliminated a long-standing memory leak on Linux-based OSes, affecting restricted processes such as zygote and system_server on newer versions of Android. This was a bug in our logic that garbage-collects thread-local data shortly after a given thread has exited. The mechanism that determines that the thread has indeed finished exiting would fail and never consider the thread gone. This would result in more and more garbage accumulating, with a longer and longer collection of garbage to iterate over. So not only would we be spending increasingly more time on futile GC attempts, we would also be eating CPU retrying a GC every 50 ms.
  • The Python bindings allow obtaining a file-descriptor from a Cancellable in order to integrate it into event loops and other poll()-style use-cases. Worth noting that frida-tools 7.0.1 is out with a major improvement built on this: The CLI tools no longer delay for up to 500 ms before exiting. So short-lived programs like frida-ls-devices and frida-ps now feel very snappy.
  • Duktape source-map handling now also works with scripts loaded by the REPL – where the inline source-map isn’t the last line of the script due to the REPL appending its own code. This means stack-traces always contain meaningful filenames and line numbers.
  • Duktape: the baked in JavaScript runtime – i.e. GumJS’ glue code, ObjC, and Java – is now Babelified with the loose option enabled, to reduce bloat and improve performance. No modern JavaScript data structures leak out through the APIs, so there’s no need to have Babel be spec-compliant.
  • V8: the baked in JavaScript runtime is compressed, for a smaller footprint and faster code. This was previously only done to the Duktape one.
  • Better Linux process name heuristics in enumerate_processes().

Changes in 12.8.13

  • Java.performNow() is back in working order.
  • Python bindings’ now looks for a local .egg before attempting to download one, and expects the download to complete within two minutes. Kudos to @XieEDeHeiShou for these nice improvements!

Changes in 12.8.14

  • The iOS Simulator is now properly supported, both in Gadget form and attaching to a running Simulator process from macOS. Kudos to @insitusec for helping fix these issues!
  • Gadget now also looks for its .config in the directory above on iOS, but only if its parent directory is named “Frameworks”. Kudos to @insitusec for the suggestion!

Changes in 12.8.15

  • Brand new feature-complete support for iOS/arm64e, including new NativePointer methods: sign(), strip(), blend().
  • Latest iOS Unc0ver jailbreak is now supported. Kudos to @mrmacete for the pull-request, and @Pwn20wnd for the assistance! ❤️
  • Improved support for the Chimera jailbreak, making sure its pspawn_payload-stg2.dylib is initialized. Thanks @mrmacete!
  • The i/macOS injector no longer fails when an agent entrypoint returns instantly.
  • Better error message about needing Gadget for jailed iOS.
  • Improved error-handling in the Windows injector to avoid crashing the target process when our DLL injection fails. Kudos @dasraf9!
  • Support for injection into live newborn targets on i/macOS, and also no longer treating suspended processes as needing to be prepared for injection, regardless of whether they actually need it.
  • Improved iOS fault tolerance, handling frontmost iOS app name query failing.
  • Improved Android fault tolerance, handling zygote and system_server processes dying without having to restart frida-server.
  • Now able to launch frida-server during boot on Android 10, as LD_LIBRARY_PATH no longer interferes with the spawning of frida-helper-32. Kudos to @enovella for helping track this one down!
  • No more infinite loop when failing to handle SIGABRT on UNIXy platforms.
  • We now support nested signals in Exceptor’s POSIX backend. Kudos @bannsec!
  • Proper handling of invalid Windows ANSI strings. Thanks, @clouds56!
  • Java.perform() working properly again on Android < 5.
  • Improved varargs-handling in NativeFunction, now promoting varargs smaller than int. Thanks for reporting, @0x410c!

Changes in 12.8.16

  • Large CModule instances now working on iOS systems with 16K pages. Kudos to @mrmacete for discovering and fixing this long-standing issue!
  • Stalker also works in arm64 processes on iOS/arm64e. Kudos to @AeonLucid for reporting and helping track this one down!

Changes in 12.8.17

  • Support for injection into live newborn targets on i/macOS turned out to cause regressions, so we’ve reverted it for now. Specifically, notifyd on iOS 12.4 is a case where libSystemInitialized is not getting set. Need to dig deeper to figure out why, so decided to walk back this logic for now.

Changes in 12.8.18

  • New and improved Java.scheduleOnMainThread() to allow calling APIs such as getApplicationContext(). Kudos to @giantpune for reporting!
  • Ability to hook CriticalNative methods on newer versions of Android. Kudos to @abdawoud for reporting!

Changes in 12.8.19

  • Scripts are now properly cleaned up if destroyed without first being loaded. This ensures that ultimately when the script core is disposed, it in turn disposes its reference to the Exceptor. Failing to do so causes indefinite hang when attaching after detaching in presence of unloaded scripts, due to the Exceptor thread remaining in the target process. Kudos to @mrmacete for discovering and fixing this long-standing issue!

Changes in 12.8.20

  • The remove_remote_device() API is back in working order. This was an unfortunate regression introduced in 12.7.17. Thanks for reporting, @CodeColorist!

Frida 12.7 Released

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.

While Frida’s instrumentation core, Gum, is written in C and can be used from C, most use-cases are better off using its JavaScript bindings.

There are however situations where performance becomes an issue. Even when using our V8-based runtime, which means your JavaScript will be profiled while it’s running and optimized based on where the hotspots are… (Which by the way is amazing – V8 is truly an impressive feat of engineering!)

…there is a small price to pay for entering and leaving the JavaScript VM. On an iPhone 5S this might amount to something like six microseconds if you use Interceptor.attach() and only specify onEnter, and leave it empty.

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:

CModule Hello World

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:

CModule Speed

(Measured on an Intel i7 @ 3.1 GHz.)

We can also use this new feature in conjunction with APIs like Interceptor:

const m = new CModule(`
#include <gum/guminterceptor.h>

#define EPERM 1

open (const char * path,
      int oflag,
  GumInvocationContext * ic;

  ic = gum_interceptor_get_current_invocation ();
  ic->system_error = EPERM;

  return -1;

const openImpl = Module.getExportByName(null, 'open');


(Note that this and the following examples use modern JavaScript features like template literals, so they either need to be run on our V8 runtime, or compiled using frida-compile.)

We can also combine it with Interceptor.attach():

const openImpl = Module.getExportByName(null, 'open');

Interceptor.attach(openImpl, new CModule(`
  #include <gum/guminterceptor.h>
  #include <stdio.h>

  onEnter (GumInvocationContext * ic)
    const char * path;

    path = gum_invocation_context_get_nth_argument (ic, 0);

    printf ("open() path=\\"%s\\"\\n", path);

  onLeave (GumInvocationContext * ic)
    int fd;

    fd = (int) gum_invocation_context_get_return_value (ic);

    printf ("=> fd=%d\\n", fd);

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.

We can however fix that by calling back into JavaScript. Let’s see what that might look like:

const openImpl = Module.getExportByName(null, 'open');

Interceptor.attach(openImpl, new CModule(`
  #include <gum/guminterceptor.h>

  extern void onMessage (const gchar * message);

  static void log (const gchar * format, ...);

  onEnter (GumInvocationContext * ic)
    const char * path;

    path = gum_invocation_context_get_nth_argument (ic, 0);

    log ("open() path=\\"%s\\"", path);

  onLeave (GumInvocationContext * ic)
    int fd;

    fd = (int) gum_invocation_context_get_return_value (ic);

    log ("=> fd=%d", fd);

  static void
  log (const gchar * format,
    gchar * message;
    va_list args;

    va_start (args, format);
    message = g_strdup_vprintf (format, args);
    va_end (args);

    onMessage (message);

    g_free (message);
`, {
  onMessage: new NativeCallback(messagePtr => {
    const message = messagePtr.readUtf8String();
    console.log('onMessage:', message);
  }, 'void', ['pointer'])

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.

And just like JavaScript functions can be called from C, we can also share data between the two realms:

const calls = Memory.alloc(4);

const openImpl = Module.getExportByName(null, 'open');

Interceptor.attach(openImpl, new CModule(`
  #include <gum/guminterceptor.h>

  extern volatile gint calls;

  onEnter (GumInvocationContext * ic)
    g_atomic_int_add (&calls, 1);
`, { calls }));

setInterval(() => {
  console.log('Calls so far:', calls.readInt());
}, 1000);

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.

Also bear in mind that the machine code generated by TinyCC is not as efficient as that of Clang or GCC, so computationally expensive algorithms might actually be faster to implement in JavaScript. (When using our V8-based runtime.) But for hooks and glue code this difference isn’t significant, and you can always generate machine code using e.g. Arm64Writer and plug into your CModule if you need to optimize an inner loop.

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 calls in 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:

const cm = new CModule(`
#include <stdio.h>

init (void)
  printf ("init\\n");

finalize (void)
  printf ("finalize\\n");

cm.dispose(); // or wait until it gets GCed or script unloaded

Anyway, this post is getting long, but before we wrap up let’s look at how to use CModule with the Stalker APIs:

const cm = new CModule(`
#include <gum/gumstalker.h>

static void on_ret (GumCpuContext * cpu_context,
    gpointer user_data);

transform (GumStalkerIterator * iterator,
           GumStalkerOutput * output,
           gpointer user_data)
  cs_insn * insn;

  while (gum_stalker_iterator_next (iterator, &insn))
    if (insn->id == X86_INS_RET)
      gum_x86_writer_put_nop (output->writer.x86);
      gum_stalker_iterator_put_callout (iterator,
          on_ret, NULL, NULL);

    gum_stalker_iterator_keep (iterator);

static void
on_ret (GumCpuContext * cpu_context,
        gpointer user_data)
  printf ("on_ret!\n");

const mainThread = Process.enumerateThreads()[0];

Stalker.follow(, {
  transform: cm.transform,
  data: ptr(1337)

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.

Changes in 12.7.23

  • Enhanced support for the checkra1n jailbreak: Stalker is now way faster as it makes use of RWX pages.
  • Stability improvements when using Stalker on iOS on jailbreaks without RWX support. Thanks @mrmacete!
  • CModule is now compatible with additional iOS jailbreak flavors. Kudos, @mrmacete!
  • CModule runtime support for working with ModuleMap objects.
  • Support for ARMBE8 thanks to awesome contributions by Jon Wilson.
  • Frida now makes parent file-descriptors available to child processes when spawn()ing on i/macOS. This is consistent with the current behavior on Linux. Thanks @wizche!

Changes in 12.7.24

  • Node.js prebuilds also provided for Node.js v13.

Changes in 12.7.25

  • Log handler APIs got an overhaul in the Python and Node.js bindings, as part of a critical fix: the Node.js setter’s type wasn’t the same as the getter, as it also allowed null. This inconsistency resulted in recent TypeScript compiler versions choking on it. Thanks @mrmacete!
  • No more timestamp truncations in the V8 platform integration. Thanks for reporting, @DaveManouchehri!
  • Our Module.enumerateSymbols() API provides a “size” property when available, i.e. only on Linux/Android for now. Thanks @DaveManouchehri!
  • Java.use(name, { cache: ‘skip’ }) can now be used to bypass caching. Useful when dealing with multiple class-loaders and colliding class names. Thanks @ChaosData and @H4oK3!

Changes in 12.7.26

  • Stalker now supports temporary reactivation, to allow stalking code from inside an excluded memory range.
  • NativeFunction got a brand new option traps: 'all' to allow calls to be stalked even when Frida’s own memory range is marked as excluded.
  • Thread enumeration on Linux is finally working when using Yama. Thanks Jon Wilson!

Frida 12.6 Released

After a flurry of fixes across all platforms over the last few weeks, I figured it was time to do another minor bump to call attention to this release.

One particular fix is worth mentioning specifically. There was a long- standing bug in our Android Java integration, where exception delivery would intermittently result in the process crashing with GetOatQuickMethodHeader() typically in the stack-trace. Shout-out to Jake Van Dyke and Giovanni Rocca for helping track this one down. This bug has been around for as long as ART has been supported, so this fix is worth celebrating. 🎉

Our V8 runtime is also a lot more stable, child-gating works better than ever before, Android device compatibility is much improved, etc.

So bottom line is that this is the most stable version of Frida ever released – and now is the time to make sure you’re running Frida 12.6.


Changes in 12.6.1

  • The exception delivery fix in the Android Java integration introduced a performance bottleneck when running the VM in interpreter mode, e.g. through Java.deoptimizeEverything(). For example when running the Dropbox app from start to login screen, this would take ~94 seconds on a Pixel 3 running Android 9, and now takes ~6 seconds.

Changes in 12.6.2

  • Android Java integration now supports more arm64 systems thanks to a fix contributed by Giovanni Rocca.
  • Android Java integration once again supports being used by more than one script at a time.

Changes in 12.6.3

  • Java.choose() is now working again on Android >= 8.1, thanks to a fix contributed by Eugene Kolo.
  • Android Java integration unhooking is now working again. This also means hooks are properly reverted on script unload.
  • Frida can now talk to old versions of Frida, from before the addition of per-script runtime selection.

Changes in 12.6.4

  • Build system is back in business on all platforms.

Changes in 12.6.5

  • Linux thread enumeration is now working properly on x86-64.
  • Stalker finally handles restartable Linux syscalls.

Changes in 12.6.6

  • Android Java integration is back in fully working condition on 32-bit ARM.

Changes in 12.6.7

  • Latest Chimera iOS jailbreaks are now supported; confirmed working on 1.0.8.
  • Linux injector handles target processes where the libc’s name is ambiguous, which is often an issue on Android.

Changes in 12.6.8

  • ObjC.Object now provides $moduleName, useful for determining which module owns a given class. Kudos to David Weinstein for contributing this neat feature!

Changes in 12.6.9

  • Latest unc0ver jailbreak is now fully supported.
  • Early instrumentation logic has been improved to fully support iOS 12. Kudos to Francesco Tamagni for helping get these tricky changes across the finish-line.
  • Enumeration of memory ranges is now more reliable on iOS 12, as memory ranges belonging to threads are now correctly cloaked.
  • Cloaked memory ranges are now compacted, making lookups faster.
  • Child gating is able to hold children for longer than 25 seconds. Previously these would get resumed automatically after that accidental timeout. This also affects early instrumentation on Android, as it is built on top of child gating.
  • Swift bindings support RPC and have been moved to Swift 5.0, thanks to John Coates’ awesome contribution.

Changes in 12.6.10

  • Enumeration of memory ranges is now reliable on all platforms. There was a long-standing bug when a removal ends up splitting an existing range.

Changes in 12.6.11

  • Enumeration of applications includes icons on iOS >= 12, thanks to a nice fix by CodeColorist.
  • Gadget was taught how to detect the executable and package name of Android apps, thanks to a neat contribution by gebing.
  • Cloaking of memory ranges got a critical fix affecting Windows users.

Changes in 12.6.12

  • The frida-inject tool now supports passing parameters to the script through -P/–parameters. Kudos to Eugene Kolo for contributing this neat feature.
  • Child-gating no longer deadlocks when script unload blocks. Kudos to Ioannis Gasparis for helping track this one down.
  • Child-gating is more reliable as Frida now allocates file-descriptors in a higher range to avoid them getting closed by applications calling dup2() during a fork()+exec(). This would typically happen on Android when an app called Runtime.exec(). Kudos to Ioannis Gasparis for helping track this one down.
  • The painful Android NDK upgrade to r20 landed, thanks to a slew of awesome contributions by Muhammed Ziad.
  • Error-handling was improved to avoid crashing in scenarios where we fail to initialize due to lack of permissions. Kudos to pancake for reporting.
  • The native lockdown integration for iOS that had been sitting in a branch for a very long time was finally merged. It is unfinished and considered unstable API, but had to be merged due to a major refactoring that’s in progress.
  • Stalker now allows unfollow() from the transform callback, instead of crashing the process like it used to. Kudos to Giovanni Rocca for helping fix this.
  • Gadget’s Android package name detection logic was improved to handle one edge-case not previously accounted for. Kudos to xiaobaiyey for reporting and suggesting a fix.
  • The Java.registerClass() API was improved to support specifying the super class, along with a slew of fixes for both that API and handling of arrays with generics. Big thanks to gebing for these awesome improvements.

Changes in 12.6.13

  • Constructor/destructor functions in Agent and Gadget are now finally correctly ordered, and our libc shim’s memory allocator hacks could be dropped. Those fragile hacks broke in a new and colorful way with 32-bit ARM processes on Android due to a subtle change in the toolchain’s libgcc.
  • Our libc shim now also handles __cxa_atexit() and atexit(), where the former is crucial to avoid leaks.

Changes in 12.6.14

  • The Java.registerClass() API was improved to support user-defined constructors and fields, thanks to the awesome improvements contributed by gebing.
  • Temporary files are now cleaned up on all platforms.
  • Ctrl+C is handled by frida-server on Windows to support graceful shutdown without leaving temporary files behind.

Changes in 12.6.15

  • NativePointerValue, i.e. support for passing an object with a handle property in addition to NativePointer, is once again working everywhere.

Changes in 12.6.16

  • The frida-gadget-ios meta-package is now dependency-free.

Changes in 12.6.17

  • Crash reporter integration is now compatible with iOS 12.4.
  • Java integration was improved to eagerly release global handles when a replaced method is called, to avoid running out of handles. Kudos to gebing for this nice improvement.
  • Java.retain() was added to allow storing this for later use outside a replaced Java method.
  • Support for older versions of Android should now be slightly better.
  • Frida no longer crashes on i/macOS if the target process dies while injecting a library into it.
  • Support for MIPS64 thanks to awesome contributions by Jon Wilson.
  • Memory.patchCode() no longer crashes on android-arm64, thanks to a fix by gebing.
  • Interception of already intercepted Thumb function is now working properly.
  • Frida no longer crashes when logging early in the process lifetime on iOS.
  • Simple ModuleApiResolver queries are now blazing fast.
  • Duktape runtime no longer includes the problematic Reflect builtin.
  • Interceptor C API was refactored to improve naming.

Changes in 12.6.18

  • Gadget binaries are once again stripped on all platforms.

Changes in 12.6.19

  • All APIs are now cancellable. Python and Node.js language bindings support passing a Cancellable object. Remaining language bindings work like before, and contributions are most welcome.

Changes in 12.6.20

  • DeviceManager.find_device() no longer crashes on startup.

Changes in 12.6.21

  • Future.wait_async() no longer crashes when cancelled last-minute.

Changes in 12.6.22

  • State management was improved to allow eternalize() and post() in disposed state as well.
  • The dispose() RPC export is now passed a parameter specifying the reason, to be able to differentiate between unload, exit, and exec.
  • Scripts may now be dispose()d again if an exec transition fails.
  • The frida-inject CLI tool was improved to exit on detach.
  • RpcClient no longer crashes when post_rpc_message() fails.
  • Fruity and Droidy backends are now disabled on iOS and Android, to reduce footprint size on platforms where these backends are not applicable.

Changes in 12.6.23

  • Frida no longer crashes when HostSession gets lost mid-request.

Frida 12.5 Released

This is one packed release. So many things to talk about.


The main story this time is V8. But first, a little background.

Frida uses the Duktape JavaScript runtime by default to provide scriptable access to its instrumentation core, Gum. We were originally only using V8, but as V8 used to depend on operating support for RWX pages – i.e. executable pages of memory that are also writable – we ended up adding a secondary JS runtime based on Duktape. Due to this OS constraint with V8, and the fact that Duktape lacks support for the latest JS syntax and features, I decided to make Duktape our default runtime so that scripts written for one platform wouldn’t need any syntactic changes to work on another. Duktape was basically the lowest common denominator.

Fast forward to today, and V8 no longer depends on OS support for RWX pages. It’s actually moving towards flipping between RW- and R-X by default. By doing so it means it can be used on modern iOS jailbreaks, and also on jailed iOS if the process is marked as being debugged, so it is able to run unsigned code. But there’s more; V8 can now even run JIT-less, which means Frida can use V8 on every single platform, and users no longer have to frida-compile their agents to use the latest JavaScript syntax and features. This last point only applies to trivial agents, though, as being able to split a non-trivial agent into multiple source files is still desirable. Plus, frida-compile makes it easy to use TypeScript, which is highly recommended for any non-trivial Frida agent.

So with all of that in mind, it was clearly time to upgrade our V8 to the latest and greatest. As of this release we are running 7.6.48, and we also have a much deeper integration with V8 than ever before. Both C++ memory allocations and page-level allocations are now managed by Frida, so we are able to hide these memory ranges from APIs like Process.enumerateRanges(), and also avoid poisoning the application’s own heap with allocations belonging to Frida. These details may not sound all that significant, but are actually crucial for implementing memory-dumping tools on top of Frida. Not only that, however, we also interfere less with the process that is being observed. That means there’s a smaller risk of it exhibiting a different behavior than when it runs without instrumentation.

Runtime Selection

You may remember the session.enable_jit() API. It has finally been deprecated, as you can now specify the desired runtime during script creation. E.g. using our Python bindings:

script = session.create_script(source, runtime='duk')

And using our Node.js bindings:

const script = await session.createScript(source, {
  runtime: 'v8'


Another significant change in this release is that Stalker no longer depends on RWX pages on arm64, thanks to John Coates’ awesome contribution. This means Stalker is finally much more accessible on iOS.

For those of you using Stalker on 64-bit Windows and stalking 32-bit processes, it finally handles the WOW64 transitions on newer versions of Windows. This brain-twisting improvement was contributed by Florian Märkl.


There are times when you might want to load your own shared library, perhaps containing hooks written in C/C++. On most platforms you could achieve this by using NativeFunction to call dlopen() (POSIX) or LoadLibrary() (Windows). It’s a very different story on newer versions of Android, however, as their dlopen()-implementation looks at the caller and makes decisions based on it. One such decision is whether the app is trying to access a private system API, which would make it hard for them to later remove or break that API. So as of Android 8 the implementation will return NULL when this is the case. This was a challenge that Frida solved for its own injector’s needs, but users wanting to load their own library were basically on their own.

As of Frida 12.5, there’s a brand new JavaScript API that takes care of all the platform-specific quirks for you:

const hooks = Module.load('/path/to/');
Interceptor.replace(Module.getExportByName('', 'read'),


We fixed so many Android-specific bugs in this release. For example, Module.getExportByName() on an app-bundled library no longer results in the library being loaded a second time at a different base address. This bug alone is reason enough to make sure you’ve got all your devices upgraded and running the latest release.


The iOS Chimera jailbreak is also supported thanks to Francesco Tamagni’s awesome contributions.

All the rest

There’s also lots of other improvements across platforms.

In chronological order:

  • Child gating also works on older versions of Windows. Thanks Fernando Urbano!
  • Executables are smaller on UNIX OSes as they no longer export any dynamic symbols.
  • Frida’s agent and gadget no longer crash on 32-bit Linux when loaded at a high address, i.e. where MSB is set.
  • Only one of the two frida-helper-{32,64} binaries for Linux/Android is needed, and none of them for builds without cross-arch support. This means smaller footprint and improved performance.
  • Linux/ARM64 is finally supported, with binaries uploaded as part of the release process.
  • We now provide a hint about Magisk Hide when our early instrumentation fails to instrument Zygote on Android.

Changes in 12.5.1

  • Script runtime can be specified per script, deprecating enable_jit().

Changes in 12.5.2

  • Gadget no longer crashes at script load time when using V8 on Linux and Android. Huge thanks to Leon Jacobs for reporting and helping track this one down.

Changes in 12.5.3

  • Android linker integration supports a lot more devices.
  • Android Java integration no longer crashes with “Invalid instruction” on some arm64 devices. Kudos to Jake Van Dyke for reporting and helping track this one down.
  • LineageOS 15.1 is supported after adding a missing SELinux rule.

Changes in 12.5.4

  • Hooks are no longer able to interfere with our V8 page allocator integration.
  • Android stability improved greatly after plugging a hole in our libc shim. Big thanks to Giovanni Rocca for reporting and helping track this one down!

Changes in 12.5.5

  • Apple USB devices are properly detected on Windows. Thanks @xiofee!

Changes in 12.5.6

  • Android Java integration now has a workaround for a bug in ART’s exception delivery logic, where one particular code-path assumes that there is at least one Java stack frame present on the current thread. That is however not the case on a pure native thread, like Frida’s JS thread. Simplest reproducer is Java.deoptimizeEverything() followed by Java.use() of a non-existent class name. Kudos to Jake Van Dyke for reporting and helping track this one down.
  • Android Java integration no longer crashes the process when calling Java.deoptimizeEverything() in a process unable to TCP listen().
  • Android Java integration supports JNI checked mode like it used to.
  • Node.js 12 supported in addition to 8 and 10, with prebuilds for all supported platforms.
  • Node.js bindings’ enableDebugger() method no longer requires specifying which port to listen on.

Changes in 12.5.7

  • Android teardown no longer crashes on systems where we are unable to spawn logcat for crash reporting purposes.
  • Better SuperSU integration teardown logic on Android.
  • Android Java integration now properly supports JNI checked mode, which massively improves Android ROM compatibility. Kudos to @muhzii for reporting and assisting with testing the changes.
  • V8 backend teardown no longer suffers from a use-after-free, and also no longer crashes when a WeakRef is bound late.

Changes in 12.5.8

  • Linux child-gating now handles children changing architecture, e.g. a 32-bit app doing fork+exec to run a 64-bit executable. Big thanks to @gebing for the fix.
  • Child gating no longer deadlocks in case of a fork+exec where a child process is not followed. Kudos to @gebing for the fix.
  • Module export lookups no longer fail on Android apps’ own modules.

Changes in 12.5.9

  • Our libc shim now includes memcpy(), making it safe to hook. Thanks to Giovanni Rocca for debugging and contributing the fix.
  • Interceptor.flush() now also works even when a thread has temporarily released the JS lock, e.g. while calling a NativeFunction.
  • Android Java integration no longer crashes intermittently during ART exception delivery, e.g. when hooking ClassLoader.loadClass(). Kudos to Jake Van Dyke and Giovanni Rocca for helping track this one down. This bug has been around for as long as ART has been supported, so this fix is worth celebrating. 🎉
  • Android Java integration no longer crashes processes where the JDWP transport cannot be started.

Frida 12.2 Released

Let’s talk about iOS kernel introspection. It’s been a while since Frida got basic support for introspection of the iOS kernel, but in the last months we kept improving on that. Today’s release includes significant additions to our Kernel API to work with recent 64-bit kernels.

Kernel base

You can get the kernel’s base address by reading the Kernel.base property. Having the base allows for example to calculate the slid virtual address of any symbol you already know from static analysis of the kernel cache.

The memory search API has been ported to the Kernel, so you can use Kernel.scan() (or Kernel.scanSync()) in the same way you use Memory.scan() (or Memory.scanSync()) in userland. This is a powerful primitive which, combined with the recent bit mask feature, allows you to create your own symbol finding code by searching for arm64 patterns.

KEXTs and memory ranges

With Kernel.enumerateModules() (or Kernel.enumerateModulesSync()) it’s now possible to get the names and the offsets of all the KEXTs.

Kernel.enumerateModuleRanges() (or Kernel.enumerateModuleRangesSync()) is the way to enumerate all the memory ranges defined by the Mach-O sections belonging to a module (by name) filtering by protection. The result is similar to what you can get in userland when calling Module.enumerateRanges() but it also includes the section names.

Final notes

All Kernel APIs don’t rely on NativePointer because its size depends on the user-space which doesn’t necessarily match the kernel space one. Instead all addresses are represented as UInt64 objects.

All of this, plus the existing JavaScript interfaces for reading, writing, and allocating kernel memory can provide a powerful starting point to build your own kernel analysis or vulnerability research tools.

Note that this is to be considered experimental and messing with the kernel in random ways can wildly damage your devices, so be careful, and happy hacking!


Problem: Kernel.available is false

The Kernel API is available if both of these conditions are met:

  • Your device is jailbroken
  • Frida is able to get a send right to the kernel task, either by traditional task_for_pid (0) or by accessing the host special port 4 (which is what modern jailbreaks are doing)

The recommended way to accomplish the latter is to attach to the system session, i.e. PID 0, and load your scripts there.

Problem: can’t do much with my 32-bit kernel

Yes, that could improve in the future but 32-bit iOS is quite far down on the list of priorities nowadays, but you’re very welcome to contribute and send PRs.

Problem: I was trying to do X and the kernel panicked

Don’t worry that’s normal. You can go to the /private/var/mobile/Library/Logs/CrashReporter directory on your device, or navigate to Settings -> Privacy -> Analytics -> Analytics Data, find your panic log and figure out what you (or Frida) did wrong. Remember: the Kernel is always right!

Problem: I unrecoverably damaged my device using Frida Kernel APIs

Sorry to hear, if the damage is at the hardware level and you can dedicate enough time and money you can probably repair it yourself by following tutorials at

Frida 12.1 Released

Massive changes under the hood this time. All of our dependencies have been upgraded to the latest and greatest. Let’s have a look at the highlights.

V8 7.0

Frida’s V8 dependency was previously at 6.2.2, and has now been upgraded to 7.0.242. The move to such a new version means that the V8 debugger API is gone and has been replaced with the new Inspector API, which the latest Node.js is also using. One thing that’s pretty awesome about it is that it’s natively supported by Google Chrome’s Inspector.

To start using it, just tell Frida to use V8, by calling session.enable_jit(), and then session.enable_debugger().

Or when using the CLI tools:

$ frida-trace --enable-jit --debug -f /bin/cat -i read

Then in Google Chrome, right-click and choose “Inspect”, and click on the green Node.js icon in the upper left corner of the Inspector. That’s it, you are now debugging your Frida scripts. That means a nifty console with auto-completion, pause/continue, stepping, breakpoints, profiling, and heap snapshots. What makes it really convenient is that the server listens on your host machine, so you can call enable_debugger() on a session representing a process on your USB-tethered Android device, and it all works the same.

Here’s what it looks like:

Console Profiler Heap Snapshot

Note however that V8 is currently not included in our prebuilt iOS binaries, but it should be possible to get it running again on iOS now that it’s able to run without RWX pages. We do however plan on bridging Duktape’s binary debugger protocol behind the scenes so debugging “just works” with Duktape also, though probably with a slightly reduced feature-set.

It is often quite useful to know the PID of the process that your agent is executing inside, especially in preloaded mode. So instead of requiring you to use NativeFunction to call e.g. getpid(), this is now a lot simpler as you can use the brand new property.

Anything else?

No other features, but some really nice bug-fixes. Thanks to mrmacete we are now able to attach to* processes on iOS 11.x. And thanks to viniciusmarangoni this release also packs a couple of goodies for frida-java, fixing one Android 8.0 regression and adding the ability to properly instrument system_server.


Frida 12.0 Released

As some of you may have picked up on, there may be a book on Frida in the works. In my day to day at NowSecure I spend a good chunk of time as a user of Frida’s APIs, and consequently I’m often reminded of past design decisions that I’ve since come to regret. Even though I did address most of them over the years, some were so painful to address that I kept them on the backburner. Fast forward to today, and the thought of publishing a book with all these in mind got me thinking that it was time to bite the bullet.

This is why I’m stoked to announce Frida 12. We have finally reached a point in Frida’s evolution where our foundations can be considered sufficiently stable for a book to be written.

Let’s have a look at the changes.

CLI tools

One thing that caused a bit of confusion in the past was the fact that our Python bindings also came with some CLI tools. Frida is a toolkit for building tools, and even though we provide a few sample tools it should be up to you if you want to have them installed.

Up until now this meant anyone building a tool using our Python bindings would end up depending on colorama, prompt-toolkit, and pygments, because our CLI tools happen to depend on those.

Well, that changes now. If you do:

$ pip install frida

You will now only get our Python bindings. Nothing more. And this package has zero dependencies.

The CLI tools might still be useful to you, though, so to install those do:

$ pip install frida-tools

Convenience APIs in bindings

Something that seemed like a great idea at the time was having our language bindings provide some convenience APIs on the Session object. The thinking was that simple use-cases that only need to enumerate loaded modules and perhaps a few memory ranges, to then read or write memory, wouldn’t have to load their own agent. So both our Python and our Node.js bindings did this behind the scenes for you.

Back then it was somewhat tedious to communicate with an agent as the rpc API didn’t exist, but even so, it was a bad design decision. The JS APIs are numerous and not all can be exposed without introducing new layers of complexity. Another aspect is that every language binding would have to duplicate such convenience APIs, or we would have to add core APIs that bindings could expose. Both are terrible options, and cause confusion by blurring the lines, ultimately confusing people new to Frida. Granted, it did make things easier for some very simple use-cases, like memory dumping tools, but for everybody else it just added bloat and confusion.

These APIs are now finally gone from our Python and Node.js bindings. The other bindings are unaffected as they didn’t implement any such convenience APIs.

Node.js bindings

It’s been a few years since our Node.js bindings were written, and since then Node.js has evolved a lot. It now supports ES6 classes, async / await, arrow functions, Proxy objects, etc.

Just the Proxy support alone means we can simplify rpc use-cases like:

const api = await script.getExports();
const result = await api.add(2, 5);

to just:

const result = await script.exports.add(2, 5);

Some of you may also prefer writing your application in TypeScript, which is an awesome productivity boost compared to old-fashioned JavaScript. Not only do you get type checking, but if you’re using an editor like VS Code you also get type-aware refactoring and amazing code completion.

However, for type checking and editor features to really shine, it is crucial to have type definitions for your project’s dependencies. This is rarely ever an issue these days, except for those of you using Frida’s Node.js bindings. Up until now we didn’t provide any type definitions. This has finally been resolved. Rather than augmenting our bindings with type definitions, I decided to rewrite them in TypeScript instead. This means we also take advantage of modern language features like ES6 classes and async / await.

We could have stopped there, but those of you using our Node.js bindings from TypeScript would still find this a bit frustrating:'message', (message, data) => {

Here, the compiler knows nothing about which events exist on the Script object, and what the callback’s signature is supposed to be for this particular event. We’ve finally fixed this. The API now looks like this:

script.message.connect((message, data) => {

Voilà. Your editor can even tell you which events are supported and give you proper type checking for the code in your callback. Sweet!


Something that’s caused some confusion in the past is the observation that accessing this.context.pc from onEnter or onLeave would give you the return address, and not the address of the instruction that you put the hook on. This has finally been fixed. Also, this.context.sp now points at the return address on x86, instead of the first argument. The same goes for Stalker when using call probes.

As part of this refactoring breaking our backtracer implementations, I also improved our default backtracer on Windows.


You might have wondered why frida.get_usb_device() would give you a Device whose type was ‘tether’. It is now finally ‘usb’ as you’d expect. So our language bindings are finally consistent with our core API.

Changes in 12.0.1

  • core: fix argument access on 32-bit x86
  • core: update Stalker to the new CpuContext semantics
  • python: publish the correct README to PyPI
  • python: fix the Windows build system

Changes in 12.0.2

  • core: upgrade to Capstone’s next branch
  • core: fix the DbgHelp backtracer on Windows and update to latest DbgHelp
  • python: fix long description
  • java: fix hooking of java.lang.Class.getMethod() – thanks 0x3430D!

Changes in 12.0.3

  • core: fix the iOS build system broken by Capstone upgrade

Changes in 12.0.4

  • core: fix i/macOS libc++ initialization on early instrumentation – thanks mrmacete!
  • core: add support for memory search with mask – thanks mrmacete!
  • core: fix InvocationContext.get_return_address()
  • node: fix crash on RPC object return from an async function

Changes in 12.0.5

  • core: fix arm64 crashes caused by Capstone upgrade, where test-and-branch decoding logic failed to decode negative offsets – kudos to mrmacete for discovering and fixing this one!
  • core: fix MIPS regressions and one crasher – thanks r0ck3tAKATrashPanda!

Changes in 12.0.6

  • python: improve spawn() to support unicode aux options on Python 2.x
  • java: fix Java.registerClass() when cache dir is missing
  • java: make the temporary file naming configurable

Changes in 12.0.7

  • core: fix early instrumentation on iOS 11.3.1 through 11.4.1 – thanks mrmacete!

Changes in 12.0.8

  • core: fix launching of Android apps with custom process name – thanks giantpune!
  • java: fix ClassLinker field offset detection on Android 8.0


Frida 11.0 Released

It’s time to overhaul the spawn() API and fix some rough edges in the spawn- and child-gating APIs.


Say you’re using Frida’s Python bindings, you’d currently do:

pid = device.spawn(["/bin/cat", "/etc/passwd"])

Or to spawn an iOS app:

pid = device.spawn([""])

Well, that’s pretty much all you could do with that API really… except one thing that wasn’t exposed by the Python and Node.js bindings. We’ll get to that in a bit. Before we go there, let’s take a look at the underlying API in frida-core, which these bindings expose to different languages:

namespace Frida {
	public class Device : GLib.Object {
		public async uint spawn (string path,
			string[] argv, string[] envp)
			throws Frida.Error;
		public uint spawn_sync (string path,
			string[] argv, string[] envp)
			throws Frida.Error;

That’s Vala code by the way, which is the language that frida-core is written in. It’s a C#-like language that compiles to C, and it’s pretty awesome. But I digress. The first method, spawn() is asynchronous, allowing the calling thread to do other things while the call is in progress, whereas spawn_sync() blocks until the operation completes.

Those two methods compile down to the following three C functions:

void frida_device_spawn (FridaDevice * self,
    const gchar * path,
    gchar ** argv, int argv_length,
    gchar ** envp, int envp_length,
    GAsyncReadyCallback callback, gpointer user_data);
guint frida_device_spawn_finish (FridaDevice * self,
    GAsyncResult * result, GError ** error);
guint frida_device_spawn_sync (FridaDevice * self,
    const gchar * path,
    gchar ** argv, int argv_length,
    gchar ** envp, int envp_length,
    GError ** error);

The first two constitute spawn(), where you’d call the first giving it a callback, and once that callback gets called you’d call the second one, spawn_finish(), giving it the GAsyncResult your callback was given. The return value is the PID, or, in case it failed, the error out-argument explains what went wrong. This is the GIO async pattern in case you’re curious.

As for the third, spawn_sync(), this is what Frida’s Python bindings use. Our Node.js bindings actually use the first two, as those bindings are fully asynchronous. Someday it would be nice to also migrate our Python bindings to be fully async, by integrating with the async/await support introduced in Python 3.5.

Anyway, returning to the examples above, I mentioned there was something not exposed. If you look closely at the frida-core API you’ll notice that there’s the envp string-array. Peeking under the hood of the bindings, you’d realize we did indeed not expose this, and we actually did this:

  envp = g_get_environ ();
  envp_length = g_strv_length (envp);

So that means we passed along whatever the Python process’ environment happened to be. That’s definitely not good if the actual spawning happened on another system entirely, like on a connected iOS or Android device. What made this slightly less problematic was the fact that envp was ignored when spawning iOS and Android apps, and only used when spawning regular programs.

Another issue with this old API is that the declaration, string[] envp, means it isn’t nullable, which it would have been if the declaration had been: string[]? envp. That means there is no way to distinguish between wanting to spawn without any environment, which intuitively would mean “use defaults”, and an empty environment.

As I was about to fix this aspect of the API, I realized that it was time to also fix a few other long-standing issues with it, like being able to:

  • provide just a few extra environment variables on top of the defaults
  • set the working directory
  • customize stdio redirection
  • pass along platform-specific options

Up until this point we have always redirected stdio to our own pipes, and streamed any output through the output signal on Device. There was also Device.input() for writing to stdin. Those APIs are still the same, the only difference is that we no longer do such redirection by default. Most of you were probably not too bothered with this, though, as we didn’t implement such redirection for iOS and Android apps. Starting with this release we do however finally implement it for iOS apps.

By now you’re probably wondering what the new API looks like. Let’s have a look:

namespace Frida {
	public class Device : GLib.Object {
		public async uint spawn (string program,
			Frida.SpawnOptions? options = null)
			throws Frida.Error;
		public uint spawn_sync (string program,
			Frida.SpawnOptions? options = null)
			throws Frida.Error;
	public class SpawnOptions : GLib.Object {
		public string[]? argv { get; set; }
		public string[]? envp { get; set; }
		public string[]? env { get; set; }
		public string? cwd { get; set; }
		public Frida.Stdio stdio { get; set; }
		public GLib.VariantDict aux { get; }

		public SpawnOptions ();

So going back to the Python examples at the beginning, those still work without any changes. But, instead of:


You can now also do:


As the first argument is the program to spawn. You can still pass an argv here and that will be used to set the argv option, meaning that argv[0] will be used for the program argument. You can also do this:

device.spawn("/bin/busybox", argv=["/bin/cat", "/etc/passwd"])

And if you’d like to replace the entire environment instead of using defaults:

device.spawn("/bin/ls", envp={ "CLICOLOR": "1" })

Though in most cases you probably only want to add/override a few environment variables, which is now also possible:

device.spawn("/bin/ls", env={ "CLICOLOR": "1" })

You might also want to use a different working directory:

device.spawn("/bin/ls", cwd="/etc")

Or perhaps you’d like to redirect stdio:

device.spawn("/bin/ls", stdio="pipe")

The stdio default value is inherit, as mentioned earlier.

We have now covered all of the SpawnOptions, except the last of them: aux. This is a dictionary for platform-specific options. Setting such options is pretty simple with the Python bindings: any keyword-argument not recognized will end up in that dictionary.

For example, to launch Safari and tell it to open a specific URL:

device.spawn("", url="")

Or perhaps you’d like to spawn an i/macOS program with ASLR disabled:

device.spawn("/bin/ls", aslr="disable")

Another example is spawning an Android app with a specific activity:

spawn("", activity=".SecuritySettings")

And that’s actually all of the aux options we currently support – and what’s great is that we can add new ones without needing to update our bindings.

But before we move on, let’s take a quick look at what this new API would look like using our Node.js bindings:

const pid = await device.spawn('/bin/sh', {
  argv: ['/bin/sh', '-c', 'ls /'],
  env: {
    'BADGER': 'badger-badger-badger',
    'SNAKE': true,
    'MUSHROOM': 42,
  cwd: '/usr',
  stdio: 'pipe',
  aslr: 'auto'

So as you can see, the second argument is an object with options, and those not recognized end up in the aux dictionary.

Remaining changes in 11.0.0

Let’s just summarize the remaining changes, starting with the Device class:

  • enumerate_pending_spawns() is now enumerate_pending_spawn() to be grammatically correct.
  • The spawned signal has been renamed to spawn-added, and there is now also spawn-removed.
  • The delivered signal has been renamed to child-added, and there is now also child-removed.

The final change is that the Child class’ path, argv, and envp properties are now all nullable. This is to be able to discern e.g. “no envp provided” from “empty envp provided”.

Changes in 11.0.1

  • core: fix stack alignment of agent thread for 32-bit ARM processes
  • core: fix fragile SELinux rule patching

Changes in 11.0.2

  • core: plug Mach ports leaks in spawn()-logic on i/macOS (long-standing issue)

Changes in 11.0.3

  • core: fix crash on iPhone 8 and X caused by bad tls_base calculation on arm64

Changes in 11.0.4

  • python: update metadata and bump requirements

Changes in 11.0.5

  • core: fix compatibility issue with Electra’s Tweak Injector
  • core: fix process name truncation in enumerate_processes() on iOS
  • java: Java.registerClass() now supports overloads
  • packaging: every new release now comes with packages for Fedora and Ubuntu

Changes in 11.0.6

  • python: fix dependency spec so REPL works again when installed from PyPI

Changes in 11.0.7

  • core: better child gating API coverage on Windows
  • python: 2.7 binaries for Linux are no longer broken when installed from PyPI

Changes in 11.0.8

  • core: fix race resulting in crash on system session setup on some platforms
  • python: fix deadlock on interpreter shutdown

Changes in 11.0.9

  • java: fix issue preventing Java.registerClass() during early instrumentation

Changes in 11.0.10

  • core: fix crash when enumerating imports/exports of ELF modules with an empty DT_GNU_HASH

Changes in 11.0.11

  • java: fix type compatibility checking, which typically resulted in the wrong overload getting called, in turn causing the VM to abort()

Changes in 11.0.12

  • core: fix compatibility issues with the latest macOS Mojave and iOS 12 betas
  • core: improve the iOS Kernel API available in the system session, i.e. PID 0
  • frida-trace: fix crash when tracer scripts send() arbitrary data

Changes in 11.0.13

  • core: fix hang on teardown of script that was never load()ed


So that’s about it. If you didn’t read about the Frida 10.8 release that happened last week, make sure you go read about it here.


Frida 10.8 Released

Get ready for a major upgrade. This time we have solved our three longest standing limitations – all in one release.

Limitation #1: fork()

As a quick refresher, this old-school UNIX API clones the entire process and returns the child’s process ID to the parent, and zero to the child. The child gets its own copy of the parent’s address space, usually at a very small cost due to copy-on-write.

Dealing with this one gets tricky once multiple threads are involved. Only the thread calling fork() survives in the child process, so if any of the other threads happen to be holding locks, those locks will still be held in the child, and nobody is going to release them.

Essentially this means that any application doing both forking and multi-threading will have to be really carefully designed. Even though most applications that fork are single-threaded, Frida effectively makes them multi-threaded by injecting its agent into them. Another aspect is file-descriptors, which are shared, so those also have to be carefully managed.

I’m super-excited to announce that we are finally able to detect that a fork() is about to happen, temporarily stop our threads, pause our communications channel, and start things back up afterwards. You are then able to apply the desired instrumentation to the child, if any, before letting it continue running.

Limitation #2: execve(), posix_spawn(), CreateProcess(), and friends

Or in plain English: programs that start other programs, either by replacing themselves entirely, e.g. execve(), or by spawning a child process, e.g. posix_spawn() without POSIX_SPAWN_SETEXEC.

Just like after a fork() happened you are now able to apply instrumentation and control when the child process starts running its first instructions.

Limitation #3: dealing with sudden process termination

An aspect that’s caused a lot of confusion in the past is how one might hand off some data to Frida’s send() API, and if the process is about to terminate, said data might not actually make it to the other side.

Up until now the prescribed solution was always to hook exit(), abort() etc. so you can do a send() plus recv().wait() ping-pong to flush any data still in transit. In retrospect this wasn’t such a great idea, as it makes it hard to do this correctly across multiple platforms. We now have a way better solution.

So with that, let’s talk about the new APIs and features.

Child gating

This is how we address the first two. The Session object, the one providing create_script(), now also has enable_child_gating() and disable_child_gating(). By default Frida will behave just like before, and you will have to opt-in to this new behavior by calling enable_child_gating().

From that point on, any child process is going to end up suspended, and you will be responsible for calling resume() with its PID. The Device object now also provides a signal named delivered which you should attach a callback to in order to be notified of any new children that appear. That is the point where you should be applying the desired instrumentation, if any, before calling resume(). The Device object also has a new method named enumerate_pending_children() which can be used to get a full list of pending children. Processes will remain suspended and part of that list until they’re resumed by you, or eventually killed.

So that’s the theory. Let’s have a look at a practical example, using Frida’s Python bindings:

import frida
from frida.application import Reactor
import threading

class Application(object):
    def __init__(self):
        self._stop_requested = threading.Event()
        self._reactor = Reactor(run_until_return=lambda _:

        self._device = frida.get_local_device()
        self._sessions = set()

        self._device.on("delivered", lambda child:
                lambda: self._on_delivered(child)))

    def run(self):
        self._reactor.schedule(lambda: self._start())

    def _start(self):
        argv = ["/bin/sh", "-c", "cat /etc/hosts"]
        print("✔ spawn(argv={})".format(argv))
        pid = self._device.spawn(argv)

    def _stop_if_idle(self):
        if len(self._sessions) == 0:

    def _instrument(self, pid):
        print("✔ attach(pid={})".format(pid))
        session = self._device.attach(pid)
        session.on("detached", lambda reason:
                self._on_detached(pid, session, reason)))
        print("✔ enable_child_gating()")
        print("✔ create_script()")
        script = session.create_script("""
Interceptor.attach(Module.getExportByName(null, 'open'), {
  onEnter(args) {
      type: 'open',
      path: Memory.readUtf8String(args[0])
        script.on("message", lambda message, data:
                lambda: self._on_message(pid, message)))
        print("✔ load()")
        print("✔ resume(pid={})".format(pid))

    def _on_delivered(self, child):
        print("⚡ delivered: {}".format(child))

    def _on_detached(self, pid, session, reason):
        print("⚡ detached: pid={}, reason='{}'"
            .format(pid, reason))
        self._reactor.schedule(self._stop_if_idle, delay=0.5)

    def _on_message(self, pid, message):
        print("⚡ message: pid={}, payload={}"
            .format(pid, message["payload"]))

app = Application()

And action:

$ python3
✔ spawn(argv=['/bin/sh', '-c', 'cat /etc/hosts'])
✔ attach(pid=42401)
✔ enable_child_gating()
✔ create_script()
✔ load()
✔ resume(pid=42401)
⚡ message: pid=42401,
↪payload={'type': 'open', 'path': '/dev/tty'}
⚡ detached: pid=42401, reason='process-replaced'
⚡ delivered: Child(pid=42401, parent_pid=42401,
↪path="/bin/cat", argv=['cat', '/etc/hosts'],
↪envp=['SHELL=/bin/bash', 'TERM=xterm-256color', …],
✔ attach(pid=42401)
✔ enable_child_gating()
✔ create_script()
✔ load()
✔ resume(pid=42401)
⚡ message: pid=42401,
↪payload={'type': 'open', 'path': '/etc/hosts'}
⚡ detached: pid=42401, reason='process-terminated'


As for the third limitation, namely dealing with sudden process termination, Frida will now intercept the most common process termination APIs and take care of flushing any pending data for you.

However, for advanced agents that optimize throughput by buffering data and only doing send() periodically, there is now a way to run your own code when the process terminates, or the script is unloaded. All you need to do is to define an RPC export named dispose. E.g.:

rpc.exports = {
  dispose() {

In closing

Building on the brand new fork()-handling in Frida, there is also a fully reworked Android app launching implementation. The frida-loader-{32,64}.so helper agents are now gone, and our behind-the-scenes Zygote instrumentation is now leveraging the brand new child gating to do all of the heavy lifting. This means you can also instrument Zygote for your own needs. Just remember to enable_child_gating() and resume() any children that you don’t care about.

So that’s pretty much it for this release. Enjoy!

Frida 10.7 Released

iOS users rejoice: Frida is now compatible with the latest Electra jailbreak on iOS 11! At the time of this writing that means 1.0.4, but now that Electra seems to have stabilized it should be safe to assume that we’ll also be compatible with future updates. Just make sure you’re not running anything older than 1.0.4.

In other news, our Android support has improved significantly over the last releases, so if you’re even slightly behind: go grab the latest 10.7.x right away.


Frida 10.6 Released

It’s time for some big updates to Frida’s Gadget.

This component is really useful when dealing with jailed iOS and Android devices, but is now also able to cover a lot of other scenarios.

Its environment variables are now gone, and have been replaced by an optional configuration file. Because some apps may look for loaded libraries with “Frida” in their name as part of their “anti-debug” defenses, we now allow you to rename Gadget’s binary however you like. Along with this it now also supports three different interaction types.

It can listen on a TCP port, like before, and it can also load scripts from the filesystem and run fully autonomously. The latter part used to be really limited but is now really flexible, as you can even tell it to load scripts from a directory, where each script may have filters. This is pretty useful for system-wide tampering, and should allow for even more interesting use-cases.

So without further ado, I would urge you all to check out the brand new docs available here.


Frida 10.5 Released

The midnight oil has been burning and countless cups of coffee have been consumed here at NowSecure, and boy do we have news for you this time.

Continuing in the spirit of last release’ low-level bag of goodies, we’ll be moving one level up the stack this time. We are going to introduce a brand new way to use new CodeWriter APIs, enabling you to weave in your own instructions into the machine code executed by any thread of your choosing. We’re talking lazy dynamic recompilation on a per-thread basis, with precise control of the compilation process.

But first a little background. Most people using Frida are probably using the Interceptor API to perform inline hooking, and/or doing method swizzling or replacement through the ObjC and Java APIs. The idea is typically to modify some interesting API that you expect to be called, and be able to divert execution to your own code in order to observe, augment, or fully replace application behavior.

One drawback to such approaches is that code or data is modified, and such changes can be trivially detected. This is fine though, as being invisible to the hosting process’ own code is always going to be a cat and mouse game when doing in-process instrumentation.

These techniques are however quite limited when trying to answer the question of “behind this private API, which other APIs actually get called for a given input?”. Or, when doing reversing and fuzzing, you might want to know where execution diverges between two known inputs to a given function. Another example is measuring code coverage. You could use Interceptor’s support for instruction-level probes, first using a static analysis tool to find all the basic blocks and then using Frida to put single-shot probes all over the place.

Enter Stalker. It’s not a new API, but it’s been fairly limited in what it allowed you to do. Think of it as a per-thread code-tracer, where the thread’s original machine code is dynamically recompiled to new memory locations in order to weave in instrumentation between the original instructions.

It does this recompilation lazily, one basic-block at a time. Considering that a lot of self-modifying code exists, it is careful about caching compiled blocks in case the original code changes after the fact.

Stalker also goes to great lengths to recompile the code such that side-effects are identical. E.g. if the original instruction is a CALL it will make sure that the address of the original next instruction is what’s pushed on the stack, and not the address of the next recompiled instruction.

Anyway, Stalker has historically been like a pet project inside of a pet project. A lot of fun, but other parts of Frida received most of my attention over the years. There have been some awesome exceptions though. Me and @karltk did some fun pair-programming sessions many years ago when we sat down and decided to get Stalker working well on hostile code. At some later point I put together CryptoShark in order get people excited about its potential. Some time went by and suddenly Stalker received a critical bug-fix contributed by Eloi Vanderbeken. Early this year, Antonio Ken Iannillo jumped on board and ported it to arm64. Then, very recently, Erik Smit showed up and fixed a critical bug where we would produce invalid code for REP-prefixed JCC instructions. Yay!

Stalker’s API has so far been really limited. You can tell it to follow a thread, including the thread you’re in, which is useful in combination with inline hooking, i.e. Interceptor. The only two things you could do was:

  1. Tell it which events you’re interested in, e.g. call: true, which will produce one event per CALL instruction. This means Stalker will add some logging code before each such instruction, and that would log where the CALL happened, its target, and its stack depth. The other event types are very similar.
  2. Add your own call probes for specific targets, giving you a synchronous callback into JavaScript when a CALL is made to a specific target.

I’m super-excited to announce that we’ve just introduced a third thing you can do with this API, and this one is a game changer. You can now customize the recompilation process, and it’s really easy:

const appModule = Process.enumerateModulesSync()[0];
const appStart = appModule.base;
const appEnd = appStart.add(appModule.size);

Process.enumerateThreadsSync().forEach(thread => {
  console.log('Stalking ' +;

  Stalker.follow(, {
    transform(iterator) {
      const instruction =;

      const startAddress = instruction.address;
      const isAppCode = >= 0 &&
 === -1;

      do {
        if (isAppCode && instruction.mnemonic === 'ret') {
          iterator.putCmpRegI32('eax', 60);
          iterator.putJccShortLabel('jb', 'nope', 'no-hint');

          iterator.putCmpRegI32('eax', 90);
          iterator.putJccShortLabel('ja', 'nope', 'no-hint');



      } while ((instruction = !== null);

function onMatch (context) {
  console.log('Match! pc=' + context.pc +
      ' rax=' + context.rax.toInt32());

The transform callback gets called synchronously whenever a new basic block is about to be compiled. It gives you an iterator that you then use to drive the recompilation-process forward, one instruction at a time. The returned Instruction tells you what you need to know about the instruction that’s about to be recompiled. You then call keep() to allow Stalker to recompile it as it normally would. This means you can omit this call if you want to skip some instructions, e.g. because you’ve replaced them with your own code. The iterator also allows you to insert your own instructions, as it exposes the full CodeWriter API of the current architecture, e.g. X86Writer.

The example above determines where the application’s own code is in memory, and adds a few extra instructions before every RET instruction in any code belonging to the application itself. This code checks if eax contains a value between 60 and 90, and if it does, calls out to JavaScript to let it implement arbitrarily complex logic. This callback can read and modify registers as it pleases. What’s nice about this approach is that you can insert code into hot code-paths and selectively call into JavaScript, making it easy to do really fast checks in machine code but offload more complex tasks to a higher level language. You can also Memory.alloc() and have the generated code write directly there, without entering into JavaScript at all.

So that’s the big new thing in 10.5. Special thanks to @asabil who helped shape this new API.

In closing, the only other big change is that the Instruction API now exposes a lot more details of the underlying Capstone instruction. Stalker also uses a lot less memory on both x86 and arm64, and is also more reliable. Lastly, Process.setExceptionHandler() is now a documented API, along with our SQLite API.


Frida 10.4 Released

Frida provides quite a few building blocks that make it easy to do portable instrumentation across many OSes and architectures. One area that’s been lacking has been in non-portable use-cases. While we did provide some primitives like Memory.alloc(Process.pageSize) and Memory.patchCode(), making it possible to allocate and modify in-memory code, there wasn’t anything to help you actually generate code. Or copy code from one memory location to another.

Considering that Frida needs to generate and transform quite a bit of machine code for its own needs, e.g. to implement Interceptor and Stalker, it should come as no surprise that we already have C APIs to do these things across six different instruction set flavors. Initially these APIs were so barebones that I didn’t see much value in exposing them to JavaScript, but after many years of interesting internal use-cases they’ve evolved to the point where the essential bits are now covered pretty well.

So with 10.4 we are finally exposing all of these APIs to JavaScript. It’s also worth mentioning that these new bindings are auto-generated, so future additions will be effortless.

Let’s take a look at an example on x86:

const getLivesLeft = Module.getExportByName('',
const maxPatchSize = 64; // Do not write out of bounds, may be
                         // a temporary buffer!
Memory.patchCode(getLivesLeft, maxPatchSize, code => {
  const cw = new X86Writer(code, { pc: getLivesLeft });
  cw.putMovRegU32('eax', 9999);

Which means we replaced the beginning of our target function with simply:

mov eax, 9999

I.e. assuming the return type is int, we just replaced the function body with return 9999;.

As a side-note you could also use Memory.protect() to change the page protection and then go ahead and write code all over the place, but Memory.patchCode() is very handy because it also

  • ensures CPU caches are flushed;
  • takes care of code-signing corner-cases on iOS.

So that was a simple example. Let’s try something a bit crazier:

const multiply = new NativeCallback(function (a, b) {
  return a * b;
}, 'int', ['int', 'int']);

const impl = Memory.alloc(Process.pageSize);

Memory.patchCode(impl, 64, code => {
  const cw = new X86Writer(code, { pc: impl });

  cw.putMovRegU32('eax', 42);

  const stackAlignOffset = Process.pointerSize;
  cw.putSubRegImm('xsp', stackAlignOffset);

  cw.putCallAddressWithArguments(multiply, ['eax', 7]);

  cw.putAddRegImm('xsp', stackAlignOffset);


  cw.putMovRegU32('eax', 43);



const f = new NativeFunction(impl, 'int', []);

Though that’s quite a few hoops just to multiply 42 by 7, the idea is to illustrate how calling functions, even back into JavaScript, and jumping to labels, is actually quite easy.

Finally, let’s look at how to copy instructions from one memory location to another. Doing this correctly is typically a lot more complicated than a straight memcpy(), as some instructions are position-dependent and need to be adjusted based on their new locations in memory. Let’s look at how we can solve this with Frida’s new relocator APIs:

const impl = Memory.alloc(Process.pageSize);

Memory.patchCode(impl, Process.pageSize, code => {
  const cw = new X86Writer(code, { pc: impl });

  const libcPuts = Module.getExportByName(null, 'puts');
  const rl = new X86Relocator(libcPuts, cw);

  while (rl.readOne() !== 0) {
    console.log('Relocating: ' + rl.input.toString());


const puts = new NativeFunction(impl, 'int', ['pointer']);

We just made our own replica of puts() in just a few lines of code. Neat!

Note that you can also insert your own instructions, and use skipOne() to selectively skip instructions in case you want to do custom instrumentation. (This is how Stalker works.)

Anyway, that’s the gist of it. You can find the brand new API references at:

Also note that Process.arch is convenient for determining which writer/relocator to use. On that note you may wonder why there’s just a single implementation for 32- and 64-bit x86. The reason is that these instruction sets are so close that it made sense to have a unified implementation. This also makes it easier to write somewhat portable code, as some meta register-names are available. E.g. xax resolves to eax vs rax depending on the kind of process you are in.


Frida 10.0 Released

This time we’re kicking it up a notch. We’re bringing you stability improvements and state of the art JavaScript support.

Let’s talk about the stability improvements first. We fixed a heap corruption affecting all Linux users. This one was particularly hard to track down, but rr saved the day. The other issue was a crash on unload in the Duktape runtime, affecting all OSes.

Dependencies were also upgraded, so as of Frida 10.0.0 you can now enjoy V8 6.0.124, released just days ago. We also upgraded Duktape to the latest 2.1.x. The Duktape upgrade resulted in slight changes to the bytecode semantics, which meant we had to break our API slightly. Instead of specifying a script’s name at load time, it is now specified when compiling it to bytecode, as this metadata is now included in the bytecode. This makes a lot more sense, so it was a welcome change.

Beside V8 and Duktape we’re also using the latest GLib, Vala compiler, etc. These upgrades also included JSON-GLib, which recently ditched autotools in favor of Meson. This is excellent news, as we’re also planning on moving to Meson down the road, so we’ve now done the necessary groundwork for making this happen.

So that’s about it. This upgrade should not require any changes to existing code – unless of course you are among the few using the bytecode API.


Frida 9.0 Released

Some big changes this time. We now use our Duktape-based JavaScript runtime by default on all platforms, iOS app launching no longer piggybacks on Cydia Substrate, and we are bringing some massive performance improvements. That, and some bugfixes.

Let’s talk about Duktape first. Frida’s first JS runtime was based on V8, and I’m really happy about that choice. It is however quite obvious that there are use-cases where it is a bad fit.

Some systems, e.g. iOS, don’t allow RWX memory1, and V8 won’t run without that. Another example is resource-constrained embedded systems where there just isn’t enough memory. And, as reported by users from time to time, some processes decide to configure their threads to have tiny stacks. V8 is however quite stack-hungry, so if you hook a function called by any of those threads, it won’t necessarily be able to enter V8, and your hooks appear to be ignored2.

Another aspect is that V8 is way more expensive than Duktape for the native ⇔ JS transitions, so if your Frida agent is all about API hooks, and your hooks are really small, you might actually be better off with Duktape. Garbage collection is also more predictable with Duktape, which is good for hooking time-sensitive code.

That said, if your agent is heavy on JavaScript, V8 will be way faster. It also comes with native ES6 support, although this isn’t too big a deal since non-trivial agents should be using frida-compile, which compiles your code to ES5.

So the V8 runtime is not going away, and it will remain a first-class citizen. The only thing that’s changing is that we pick Duktape by default, so that you are guaranteed to get the same runtime on all platforms, with a high probability that it’s going to work.

However, if your use-case is JS-heavy, all you have to do is call Session#enable_jit() before the first script is created, and V8 will be used. For our CLI tools you may pass –enable-jit to get the same effect.

That was Duktape. What’s the story about app launching and Substrate, then? Well, up until now our iOS app launching was piggybacking on Substrate. This was a pragmatic solution in order to avoid going into interoperability scenarios where Frida and Substrate would both hook posix_spawn() in launchd and xpcproxy, and step on each other.

It was however on my long-term TODO to fix this, as it added a lot of complexity in other areas. E.g. an out-of-band callback mechanism so our Substrate plugin could talk back to us at load time, having to manage temporary files, etc. In addition to that, it meant we were depending on a closed source third-party component, even though it was a soft-dependency only needed for iOS app launching. But still, it was the only part of Frida that indirectly required permanent modifications to the running system, and we really want to avoid that.

Let’s have a look at how the new app launching works. Imagine that you ran this on your host machine that’s got a jailbroken iOS device connected to it over USB:

$ frida-trace -U -f com.atebits.Tweetie2 -i open

We’re telling it to launch Twitter’s iOS app and trace functions named open. As a side-note, if you’re curious about the details, frida-trace is written in Python and is less than 900 lines of code, so it might be a good way to learn more about building your own tools on top of Frida. Or perhaps you’d like to improve frida-trace? Even better!

The first part that it does is that it gets hold of the first USB device and launches the Twitter app there. This boils down to:

import frida

device = frida.get_usb_device()
pid = device.spawn(["com.atebits.Tweetie2"])

What now happens behind the scenes is this:

  1. We inject our launchd.js agent into launchd (if not done already).
  2. Call the agent’s RPC-exported prepareForLaunch() giving it the identifier of the app we’re about to launch.
  3. Call SBSLaunchApplicationWithIdentifierAndLaunchOptions() so SpringBoard launches the app.
  4. Our launchd.js agent then intercept launchd’s __posix_spawn() and adds POSIX_SPAWN_START_SUSPENDED, and signals back the identifier and PID. This is the /usr/libexec/xpcproxy helper that will perform an exec()-style transition to become the app.
  5. We then inject our xpcproxy.js agent into this so it can hook __posix_spawn() and add POSIX_SPAWN_START_SUSPENDED just like our launchd agent did. This one will however also have POSIX_SPAWN_SETEXEC, so that means it will replace itself with the app to be launched.
  6. We resume() the xpcproxy process and wait for the exec to happen and the process to be suspended.

At this point we let the device.spawn() return with the PID of the app that was just launched. The app’s process has been created, and the main thread is suspended at dyld’s entrypoint. frida-trace will then want to attach to it so it can load its agent that hooks open. So it goes ahead and does something similar to this:

session = device.attach(pid)
script = session.create_script("""
Interceptor.attach(Module.getExportByName(null, 'open'), {
  onEnter() {

Now that it has applied the instrumentation, it will ask Frida to resume the process so the main thread can call main() and have some fun:


Note that I did skip over a few details here, as the attach() operation is actually a bit more complicated due to how uninitialized the process is, but you can read more about that here.

Finally, let’s talk about footprint and performance. First, let’s examine how much disk space is required when Frida is installed on an iOS device and is in a fully operational state:

That’s the 64-bit version, which is only 1.87 MB xz-compressed. The 32-bit version is obviously even smaller. Quite a few optimizations at play here:

  • We used to write the frida-helper binary out to a temporary file and spawn it. The meat of the frida-helper program is now statically linked into frida-server, and its entitlements have been boosted along with it. This binary is only necessary when Frida is used as a plugin in an unknown process, i.e. where we cannot make any guarantees about entitlements and code-signing. In the frida-server case, however, it is able to guarantee that all such constraints are met.
  • The library that we inject into processes to be instrumented, frida-agent.dylib, is no longer written out to a temporary file. We use our own out-of-process dynamic linker to map it from frida-server’s memory and directly into the address space of the target process. These mappings are made copy-on-write, so that means it is as memory-efficient as the old dlopen() approach was.
  • V8 was disabled for the iOS binaries as it’s only really usable on old jailbreaks where the kernel is patched to allow RWX pages. (If V8 is important to your use-case, you can build it like this: make server-ios FRIDA_DIET=no)
  • The iOS package has been split into two, “Frida” for 64-bit devices, and “Frida for 32-bit devices” for old devices.
  • Getting rid of the Substrate dependency for iOS app launching also meant we got rid of FridaLoader.dylib. This is however a very minor improvement.

Alright, so that’s disk footprint. How about memory usage?

Nice. How about performance? Let’s have a look:

Note that these measurements include the time spent communicating from the macOS host to the iOS device over USB.


1 Except if the process has an entitlement, although that’s limited to just one region.

2: It is technically possible to work around this by having a per-thread side-stack that we switch to before calling into V8. We did actually have this partially implemented in the past. Might be something we should revive in the longer term.

Frida 8.1 Released

It’s time for a release, and this time we have some big new things for those of you building Frida-based tools, plus a few additional goodies. Let’s start with the first part.

There’s no doubt that Frida’s JavaScript API is fairly low-level and only meant to provide low-level building blocks that don’t pertain to just one specific use-case. If your use-case involves grabbing screenshots on iOS, for example, this is not functionality one would expect to find in Frida itself.

You may then wonder how different tools with common features are supposed to share agent code with each other, and luckily the answer is not “copy paste”. We have a growing ecosystem of Frida-specific libraries, like frida-screenshot, frida-uikit, frida-trace, etc.

Perhaps some of you would be interested in APIs for instrumenting backend software written in Java, .NET, Python, Ruby, or Perl, or perhaps you would want to trace crypto APIs across different OSes and libraries, or some other cool idea. I would then highly recommend that you publish your module to npm, perhaps naming your module frida-$name to make it easy to discover.

Now you might be asking “but Frida does not support require(), how can I even split my agent code into multiple files in the first place?”. I’m glad you asked! This is where a handy little CLI tool called frida-compile enters the picture.

You give it a .js file as input and it will take care of bundling up any other files it depends on into just one file. But unlike a homegrown concatenation solution using cat, the final result also gets an embedded source map, which means filenames and line numbers in stack traces are meaningful. Modules are also separated into separate closures so variables are contained and never collide. You can also use the latest JavaScript syntax, like arrow functions, destructuring, and generator functions, as it compiles the code down to ES5 syntax for you. This means that your code also runs on our Duktape-based runtime, which you are forced to use if you use Frida on a jailed iOS device, or on a jailbroken iOS device running iOS >= 9.

In order to give you a short feedback loop while developing, frida-compile also provides a watch mode through -w, so you get instant incremental builds as you develop your agent.

Anyway, enough theory. Let’s look at how we can use an off-the-shelf web application framework from npm, and inject that into any process.

First, make sure you have the latest version of Node.js installed. Next, create an empty directory and paste this into a file named “package.json”:

  "name": "hello-frida",
  "version": "1.0.0",
  "scripts": {
    "prepublish": "npm run build",
    "build": "frida-compile agent -o _agent.js",
    "watch": "frida-compile agent -o _agent.js -w"
  "devDependencies": {
    "express": "^4.14.0",
    "frida-compile": "^2.0.6"

Then in agent.js, paste the following code:

const express = require('express');

const app = express();

  .get('/ranges', (req, res) => {
      protection: '---',
      coalesce: true
  .get('/modules', (req, res) => {
  .get('/modules/:name', (req, res) => {
    try {
    } catch (e) {
  .get('/modules/:name/exports', (req, res) => {
  .get('/modules/:name/imports', (req, res) => {
  .get('/objc/classes', (req, res) => {
    if (ObjC.available) {
    } else {
      res.status(404).send('Objective-C runtime not available in this process');
  .get('/threads', (req, res) => {


Install frida-compile and build your agent in one step:

$ npm install

Then load the generated _agent.js into a running process:

$ frida Spotify -l _agent.js

You can now hit it with HTTP requests:

$ curl
$ curl
$ curl
$ curl
$ curl
$ curl
$ curl

Sweet. We just built a process inspection REST API with 7 different endpoints in fewer than 50 lines of code. What’s pretty cool about this is that we used an off-the-shelf web application framework written for Node.js. You can actually use any existing modules that rely on Node.js’ built-in net and http modules. Like an FTP server, IRC client, or NSQ client.

So up until this release you could use Frida-specific modules like those mentioned earlier. You could also use thousands of other modules from npm, as most of them don’t do any I/O. Now with this release you also get access to all net and http based modules, which opens up Frida for even more cool use-cases.

In case you are curious how this was implemented, I added Socket.listen() and Socket.connect() to Frida. These are minimal wrappers on top of GIO’s SocketListener and SocketClient, which are already part of Frida’s technology stack and used by Frida for its own needs. So that means our footprint stays the same with no dependencies added. Because frida-compile uses browserify behind the scenes, all we had to do was plug in our own builtins for net and http. I simply ported the original net and http modules from Node.js itself.

This release also brings some other goodies. One long-standing limitation with NativeFunction is that calling a system API that requires you to read errno (UNIX) or call GetLastError() (Windows) would be tricky to deal with. The challenge is that Frida’s own code might clobber the current thread’s error state between your NativeFunction call and when you try to read out the error state.

Enter SystemFunction. It is exactly like NativeFunction, except that the call returns an object wrapping the returned value and the error state right afterwards. Here’s an example:

const open = new SystemFunction(
    Module.getExportByName(null, 'open'),
    ['pointer', 'int']);
const O_RDONLY = 0;

const path = Memory.allocUtf8String('/inexistent');
const result = open(path, O_RDONLY);
console.log(JSON.stringify(result, null, 2));
 * Which on Darwin typically results in the following output:
 * {
 *   "value": -1,
 *   "errno": 2
 * }
 * Where 2 is ENOENT.

This release also lets you read and modify this system error value from your NativeCallback passed to Interceptor.replace(), which might come handy if you are replacing system APIs. Note that you could already do this with Interceptor.attach(), but that’s not an option in cases where you don’t want the original function to get called.

Another big change worth mentioning is that our V8 runtime has been heavily refactored. The code is now easier to understand and it is way less work to add new features. Not just that, but our argument parsing is also handled by a single code-path. This means that all of our APIs are much more resilient to bad or missing arguments, so you get a JavaScript exception instead of having some APIs do fewer checks and happily crash the target process in case you forgot an argument.

Anyway, those are the highlights. Here’s a full summary of the changes:


  • core: add Socket.listen() and Socket.connect()
  • core: add setImmediate() and clearImmediate()
  • core: improve set{Timeout,Interval}() to support passing arguments
  • core: fix performance-related bug in Interceptor’s dirty state logic


  • core: add Script.nextTick()


  • core: teach Socket.listen() and Socket.connect() about UNIX sockets
  • core: fix handling of this.errno / this.lastError replacement functions
  • core: add SystemFunction API to get errno / lastError on return
  • core: fix crash on close() during I/O with the Stream APIs
  • core: fix and consolidate argument handling in the V8 runtime


  • core: temporarily disable Mapper on macOS in order to confirm whether this was the root cause of reported stability issues
  • core: add .call() and .apply() to NativeFunction
  • objc: fix parsing of opaque struct types


  • core: fix crash in the V8 runtime caused by invalid use of v8::Eternal
  • frida-repl: add batch mode support through -e and -q


  • node: generate prebuilds for 6.0 (LTS) and 7.0 only


  • node: generate prebuilds for 4.0 and 5.0 in addition to 6.0 and 7.0


  • objc: fix infinite recursion when proxying some proxies
  • objc: add support for proxying non-NSObject instances
  • python: fix removal of signal callbacks that are member functions


  • core: implement hooking of single-instruction ARM functions
  • core: plug leak in the handling of unhookable functions on some architectures
  • core: fix setImmediate() callback processing behavior
  • core: plug leak in setTimeout()
  • core: fix race condition in the handling of setTimeout(0) and setImmediate() in the Duktape runtime
  • core: fix crash when processing tick callbacks in the Duktape runtime
  • core: fix lifetime issue in the Duktape runtime
  • core: fix the reported module sizes on Linux
  • core: fix crash when launching apps on newer versions of Android
  • core: fix handling of attempts to launch Android apps not installed
  • core: improve compatibility with different versions and flavors of Android by detecting Dalvik and ART field offsets dynamically
  • core: fix unload issue on newer versions of Android, which resulted in only the first attach() succeeding and subsequent attempts all timing out
  • core: move ObjC and Java into their own modules published to npm, and use frida-compile to keep baking them into Frida’s built-in JS runtime
  • java: improve ART compatibility by detecting ArtMethod field offsets dynamically
  • node: update dependencies
  • node: fix unhandled Promise rejection issues


  • core: fix use-after-free caused by race condition on script unload


  • core: make ApiResolver and DebugSymbol APIs preemptible to avoid deadlocks


  • core: use a Mach exception handler on macOS and iOS, allowing us to reliably catch exceptions in apps that already have a Mach exception handler of their own
  • core: fix leak in InvocationContext copy-on-write logic in the Duktape runtime, used when storing data on this across onEnter and onLeave


  • core: fix Interceptor argument replacement issue in the V8 runtime, resulting in the argument only being replaced the first time


Frida 8.0 Released

It is time to level up to the next major version.

First off is the long-standing issue where multiple Frida clients attached to the same process were forced to coordinate so none of them would call detach() while one of the others was still using the session.

This was probably not a big deal for most users of Frida. However, we also had the same issue if one running frida-server was shared by multiple clients. You might have frida-trace running in one terminal while using the REPL in another, both attached to the same process, and you wouldn’t then expect one of them calling detach() to result in the other one getting kicked out.

Some of you may have tried this and observed that it works as expected, but this was due to some crazy logic in frida-server that would keep track of how many clients were interested in the same process, so it could ignore a detach() call if other clients were still subscribed to the same session. It also had some logic to clean up a certain client’s resources, e.g. scripts, if it suddenly disconnected.

Starting with 8.0 we have moved the session awareness into the agent, and kept the client-facing API the same, but with one little detail changed. Each call to attach() will now get its own Session, and the injected agent is aware of it. This means you can call detach() at any time, and only the scripts created in your session will be destroyed. Also, if your session is the last one alive, Frida will unload its agent from the target process.

That was the big change of this release, but we didn’t stop there.

One important feature of Frida’s scripts is that you can exchange messages with them. A script may call send(message[, data]) to send a JSON-serializable message, and optionally a binary blob of data next to it. The latter is so you don’t have to spend CPU-cycles turning your binary data into text that you include in the message.

It is also possible to communicate in the other direction, where the script would call recv(callback) to get a callback when you post_message() to it from your application. This allowed you to post a JSON-serializable message to your script, but there was no support for sending a binary blob of data next to it.

To address this shortcoming we renamed post_message() to post(), and gave it an optional second argument allowing you to send a binary blob of data next to it.

We also improved the C API by migrating from plain C arrays to GBytes, which means we are able to minimize how many times we copy the data as it flows through our APIs.

So in closing, let’s summarize the changes:


  • core: add support for multiple parallel sessions
  • core: rename Script’s post_message() to post() and add support for passing out-of-band binary data to the script
  • core: replace C arrays with GBytes to improve performance
  • core: fix heap corruption caused by use-after-free in libgee
  • core: fix multiple crashes
  • core: fix exports enumeration crash on macOS Sierra
  • core: add basic support for running on Valgrind
  • core: bump the macOS requirement to 10.9 so we can rely on libc++
  • node: update to the new 8.x API
  • python: update to the new 8.x API
  • swift: update to the new 8.x API
  • swift: upgrade to Swift 3
  • qml: update to the new 8.x API
  • clr: update to the new 8.x API
  • clr: plug leaks


  • node: fix Script#post()


  • core: fix deadlock when calling recv().wait() from our JS thread


  • core: reduce Interceptor base overhead by up to 65%
  • core: minimize Interceptor GC churn in our V8 runtime, using the same recycling and copy-on-write tricks as our Duktape runtime
  • core: speed up gum_process_get_current_thread_id() on macOS and iOS


Frida 7.3 Released

It’s finally release o’clock, and this time around the focus has been on improving quality. As it’s been a while since the last time we upgraded our third-party dependencies, and I found myself tracking down a memory-leak in GLib that had already been fixed upstream, I figured it was time to upgrade our dependencies. So with this release I’m happy to announce that we’re now packing the latest V8, GLib, Vala compiler, etc. Great care was also taken to eliminate resource leaks, so you can attach to long-running processes without worrying about memory allocations or OS handles piling up.

So in closing, let’s summarize the changes:


  • core: upgrade to the latest V8, GLib, Vala, Android NDK, etc.
  • core: plug resource leaks
  • core: fix thread enumeration on Linux/x86-32
  • core: (arm64) improve function hooking by adding support for relocating LDRPC with an FP/SIMD destination register


  • core: build Android binaries with PIE like we used to


  • core: add Script.setGlobalAccessHandler() for handling attempts to access undeclared global variables, which is useful for building REPLs


  • objc: convert Number to NSNumber when an object is expected
  • objc: add support for auto-converting to an array of objects, useful when calling e.g. +[NSArray arrayWithObjects:count:]


  • core: improve the unstable accessor API
  • core: fix the Duktape globals accessor logic so it’s only applied to reads


  • core: improve hexdump() to support any NativePointer-conforming object
  • objc: fix handling of the L type


  • core: fix regression in devkit header auto-generation logic


Frida 7.2 Released

Some of you may be aware that Frida has two JavaScript runtimes, one based on V8, and another one based on Duktape. We also used to have a runtime based on JavaScriptCore, but it got retired when our Duktape runtime proved better in all of the situations where V8 wasn’t a good fit, e.g. on tiny embedded systems and systems where RWX pages are forbidden.

Anyway, what is pretty neat is that Duktape has an API for compiling to bytecode, allowing you to cache the compiled code and save precious startup time when it’s time to instrument a new process. Starting with this release we now have brand new API for compiling your JavaScript to bytecode, and of course instantiating a script from it. This API is not yet supported in our V8 runtime, but we should be able to implement it there after our next V8 upgrade, by using the WebAssembly infrastructure that started appearing in the latest releases.

So without further ado, let’s take this new API for a spin with the Duktape runtime by forcing Frida to favor Duktape through Session.disable_jit().

From Node.js:

const co = require('co');
const frida = require('frida');

co(function* () {
  const systemSession = yield frida.attach(0);
  yield systemSession.disableJit();
  const bytecode = yield systemSession.compileScript(`
    rpc.exports = {
      listThreads() {
        return Process.enumerateThreadsSync();

  const session = yield frida.attach('Twitter');
  yield session.disableJit();
  const script = yield session.createScriptFromBytes(bytecode);
  yield script.load();

  const api = yield script.getExports();
  console.log('api.listThreads() =>', yield api.listThreads());

  yield script.unload();
.catch(err => {

And from Python:

import frida

system_session = frida.attach(0)
bytecode = system_session.compile_script("""
rpc.exports = {
  listThreads() {
    return Process.enumerateThreadsSync();

session = frida.attach("Twitter")
script = session.create_script_from_bytes(bytecode)

api = script.exports
print("api.list_threads() =>", api.list_threads())

Note that the same caveats as specified in the Duktape documentation apply here, so make sure the code you’re trying to load is well-formed and generated by the same version of Duktape. It may get upgraded when you upgrade to a future version of Frida, but will at least be architecture-neutral; i.e. you can compile to bytecode on a 64-bit x86 desktop and load it just fine in a 32-bit iOS app on ARM.

So that’s bytecode compilation through the API, but you probably want to use the frida-compile CLI tool for this instead:

$ npm install frida-compile
$ ./node_modules/.bin/frida-compile agent.js -o agent.bin -b

While developing you can also use it in watch mode by adding -w, which makes it watch the inputs and perform fast incremental builds whenever one of them changes.

Whether you’re using bytecode (-b) or not, frida-compile is highly recommended as it also comes with a number of other benefits, letting you:

  • Split your script into multiple .js files by using require().
  • Leverage thousands of existing modules from npm, including some that are Frida-specific. For example: frida-trace, frida-uikit, frida-screenshot, etc.
  • Use ES6 syntax and have your code compiled to ES5 so it’s compatible with the Duktape runtime.

So in closing, let’s summarize the changes:


  • core: add support for compiling and loading to/from bytecode
  • core: include error name and stack trace in RPC error replies
  • node: add support for the new bytecode APIs
  • node: augment RPC errors with name and stack when available
  • node: port examples to ES6
  • python: add support for the new bytecode APIs
  • python: update to the revised RPC protocol


  • objc: add support for resolving methods on minimal Objective-C proxies


  • objc: fix handling of methods returning structs and floating point values


  • objc: expose the raw handle of Objective-C methods


  • core: fix deadlock that was easily reproducible on iOS 9
  • java: improve Java.perform() robustness and handling of non-app processes


  • objc: fix handling of methods returning a struct in registers on x86-64


  • core: port Gum to MIPS
  • core: avoid swallowing exception when a Proxy object misbehaves
  • objc: add support for accessing Objective-C instance variables


  • core: port .so injector to MIPS
  • core: enhance MIPS fuzzy backtracer with more branch-and-link instructions
  • core: fix UnixInputStream and UnixOutputStream pollable behavior on TTYs, fixing hang on script unload
  • core: remove “0x” prefix from hexdump() offsets


  • objc: fix parsing of type hints
  • objc: add support for including type hints
  • objc: make ObjC.Block’s types field public
  • objc: add support for properly declaring void *
  • core: (MIPS) fix stack offset when getting/setting stack arguments


  • core: fix bug preventing registers from being written in the V8 runtime


  • core: add support for attaching to iOS Simulator processes
  • core: fix Android class-resolving regression introduced in 7.2.4


  • core: always kill iOS apps through SpringBoard


  • objc: unregister Objective-C classes on unload and GC


  • core: fix application kill logic on iOS 9


  • core: make the Duktape runtime preemptible like the V8 runtime
  • core: fix a few locking bugs in the V8 runtime


  • core: implement the Kernel API in the Duktape runtime also
  • core: remove the dangerous Kernel.enumerateThreads() API


  • core: improve robustness when quickly reattaching to the same process
  • core: fix deadlock when pending calls exist at detach time
  • core: fix hooking regression on 32-bit ARM
  • core: fix dlsym() deadlock in frida-gadget on Linux
  • core: fix Windows build regression
  • core: fix iOS 7 regression


  • core: fix session teardown regression


  • core: fix long-standing stability issue on iOS 9, where the injected bootstrap code was not pseudo-signed and caused processes to eventually lose their CS_VALID status
  • core: speed up app launching on iOS by eliminating unnecessary disk I/O
  • core: fix temporary directory clean-up on iOS


  • core: fix preemption-related lifetime-issue in the Duktape runtime


  • core: rework the V8 runtime to support fully asynchronous unloading
  • core: rework the Duktape runtime to support fully asynchronous unloading
  • core: make the Duktape runtime fully reentrant
  • core: add and Script.unpin() for extending a script’s lifetime in critical moments, e.g. for callbacks expected from external APIs out of one’s control
  • core: fix a timer-related leak in both the V8 and the Duktape runtime
  • objc: keep script alive until callback scheduled by ObjC.schedule() has been executed
  • objc: add a dealloc event to the ObjC proxy API


  • core: fix hang on detach()


  • core: fix hang on script unload
  • core: fix hang on abrupt connection loss during detach()


  • core: fix two low-probability crashes during script unload


  • core: fix use-after-free in the Duktape runtime
  • core: fix use-after-free bugs in ModuleApiResolver
  • core: improve unload-behavior when an exception handler is set


  • core: fix app launching on iOS 9.3.3
  • frida-server: fix “hang” on detach when another client is attached to the same process


Frida 7.1 Released

If you’ve ever used Frida to spawn programs making use of stdio you might have been frustrated by how the spawned process’ stdio state was rather undefined and left you with very little control. Starting with this release we have started addressing this, and programs are now always spawned with stdin, stdout and stderr redirected, and you can even input your own data to stdin and get the data being written to stdout and stderr. Frida’s CLI tools get this for free as this is wired up in the ConsoleApplication base-class. If you’re not using ConsoleApplication or you’re using a different language-binding, simply connect to the output signal of the Device object and your handler will get three arguments each time this signal is emitted: pid, fd, and data, in that order. Hit the input() method on the same class to write to stdin. That’s all there is to it.

Now that we have normalized the stdio behavior across platforms, we will later be able to add API to disable stdio redirection.

Beside this and lots of bug-fixes we have also massively improved the support for spawning plain programs on Darwin, on both Mac and iOS, where spawn() is now lightning fast on both and no longer messes up the code-signing status on iOS.

For those of you doing advanced instrumentation of Mac and iOS apps there’s now also brand new API for dynamically creating your own Objective-C protocols at runtime. We already supported creating new classes and proxy objects, and with this new API you can do even more.

So in closing, here’s a summary of the changes:


  • core: add Device.input() API for writing to stdin of spawned processes
  • core: add Device.output signal for propagating output from spawned processes
  • core: implement the new spawn() stdio behavior in the Windows, Darwin, and Linux backends
  • core: downgrade to Capstone 3.x for now due to non-trivial regressions in 4.x
  • node: add support for the new stdio API
  • node: add missing return to error-path
  • python: add support for the new stdio API


  • core: fix intermittent crash in spawn()


  • core: rework the spawn() implementation on Darwin, now much faster and reliable
  • core: add support for enumerating and looking up dynamic symbols on Darwin
  • core: fix page size computation in the Darwin Mach-O parser


  • core: revert temporary hack


  • python: fix ConsoleApplication crash on EOF
  • frida-trace: flush queued events before exiting


  • frida-repl: improve REPL autocompletion
  • objc: add ObjC.registerProtocol() for dynamic protocol creation
  • objc: fix handling of class name conflicts
  • objc: allow proxies to be named


  • python: fix download fallback


  • python: improve the download fallback


  • python: fix the local fallback and look in home directory instead


  • core: fix handling of overlapping requests to attach to the same pid
  • core: (Darwin) fix spawn() without attach()
  • core: (Darwin) fix crash when shutdown requests overlap
  • frida-server: always recycle the same temporary directory


  • core: (Windows) upgrade from VS2013 to VS2015
  • node: add prebuild for Node.js 6.x
  • python: fix handling of unicode command-line arguments on Python 2.x
  • qml: use libc++ instead of libstdc++ on Mac


  • core: provide a proper error message when the remote Frida is incompatible
  • core: ignore attempts to detach from the system session
  • core: guard against create_script() and detach() overlapping
  • core: fix setTimeout() so the delay is optional and defaults to 0
  • core: (V8 runtime) fix crash when closed File object gets GCed
  • core: (Darwin) fix intermittent crash on teardown
  • core: (QNX) fix implementation of gum_module_find_export_by_name()
  • core: (QNX) implement temporary TLS storage
  • frida-repl: monitor the loaded script and auto-reload on change
  • node: take level into account when handling log messages so console.warn() and console.error() go to stderr instead of stdout
  • node: do not let sessions keep the runtime alive


  • core: fix the return value of Memory.readByteArray() for size = 0


  • core: (Linux/Android) fix export address calculation for libraries with a preferred base
  • core: fix Java API not available error on Android 6.0
  • java: improve ART support by taking OS version and arch into account
  • frida-repl: add –no-pause to not pause spawned process at startup


Frida 7.0 Released

It’s been a while since our last major release bump. This time we’re addressing the long-standing issue where 64-bit integers were represented as JavaScript Number values. This meant that values beyond 53 bits were problematic due to the fact that the underlying representation is a double.

The 64-bit types in the Memory, NativeFunction, and NativeCallback APIs are now properly represented by the newly introduced Int64 and UInt64 types, and their APIs are almost identical to NativePointer.

Now let’s cross our fingers that int64/uint64 make it into ES7.

So in closing, here’s a summary of the changes:


  • core: rework handling of 64-bit integers
  • core: improve strictness of constructors
  • core: improve QNX support
  • frida-repl: update the logo


  • core: fix Int64/UInt64 field capacity on 32-bit architectures


  • core: allow Int64 and UInt64 to be passed as-is to all relevant APIs
  • core: fix handling of $protocols on ObjC instances


  • core: fix race-condition where listener gets destroyed mid-call
  • core: fix handling of nested native exception scopes
  • core: improve QNX support
  • frida-repl: tweak the startup message


  • core: massively improve the function hooking success-rate on 32-bit ARM
  • core: improve the function hooking success-rate on 64-bit ARM
  • core: fix the sp value exposed by Interceptor on 32-bit ARM


  • core: spin the main CFRunLoop while waiting for Device#resume() when spawning iOS apps, allowing thread-sensitive early instrumentation to be applied from the main thread


  • core: fix hooking of half-word aligned functions on 32-bit ARM
  • core: fix thread enumeration on Linux
  • core: add simple hexdump() API to the Script runtimes
  • core: make the Duktape runtime’s CpuContext serializable to JSON


  • core: allow passing a NativePointer to hexdump()


  • core: fix handling of wrapper objects in retval.replace()
  • core: fix behavior of Memory.readUtf8String() when a size is specified
  • core: add support for the new task_for_pid(0) method on the iOS 9.1 JB
  • core: don’t use cbnz which is not available in ARM mode on some processors
  • core: implement enumerate_threads() and modify_thread() for QNX


  • core: fix early crash in FridaGadget.dylib on iOS when running with ios-deploy and other environments where we are loaded before CoreFoundation
  • core: run a CFRunLoop in the main thread of frida-helper on Darwin, allowing system session scripts to make use of even more Apple APIs
  • core: add stream APIs for working with GIO streams, for now only exposed through UnixInputStream and UnixOutputStream (UNIX), and Win32InputStream and Win32OutputStream (Windows)


  • core: fix deadlock on script unload when I/O operations are pending


  • core: spin the main CFRunLoop while FridaGadget.dylib is blocking waiting for Device#resume(), allowing thread-sensitive early instrumentation to be applied from the main thread
  • java: fix method type sanity-check


Frida 6.2 Released

It’s release o’clock, and this time we’re bringing you massive performance improvements on all platforms, a brand new API for looking up functions, and major stability improvements on iOS 9.

Let’s talk about the latter subject first. Some of you may have noticed weird bugs and deadlocks when using Frida on iOS 9. The root cause was simply that our inline hooking was causing the process to lose the CS_VALID bit of its code-signing status. This was not a problem on iOS 8 and older as jailbreaks were always able to patch the kernel to loosen up on its code-signing requirements. Starting with this release we have implemented some tricks to be able to do inline hooking without breaking the code-signing status. For the technically curious this means that we dynamically generate a .dylib as a temporary file, write out the new versions of the memory pages that we’d like to modify, e.g. the libc memory page containing open(), then pseudo-sign this binary, ask the kernel to F_ADDFILESIGS to it, and finally mmap() from this file on top of the original memory pages.

This brings us to the next topic: performance. The tricks that I just talked about do actually add quite a bit of extra overhead just to hook one single function. It is also a very different approach from what we can do on systems with support for read-write-execute memory-pages and relaxed code-signing requirements, so this obviously meant that major architectural changes were needed. I had also been thinking for a while about being able to apply a whole batch of hooks in one go, allowing us to be more efficient and have more control on exactly when hooks are activated.

Starting with this release, our Interceptor API now supports transactions. Simply call begin_transaction(), hook all the functions, and make them all active in one go by calling end_transaction(). This results in a massive performance boost, and you get all of this for free without any changes to your existing code. This is because we implicitly begin a transaction whenever we’re entering the JavaScript runtime, and end it when we’re leaving it (and just before we send() a message or return from an RPC method). So unless you’re attaching your hooks from timers or asynchronous APIs like Memory.scan(), they will all be batched into a single transaction and get a performance boost.

Here’s how we stack up to CydiaSubstrate in terms of performance:

Note that if you’re using our instrumentation engine from C or C++ you will have to call begin_transaction() and end_transaction() yourself to get this boost, but your code will still work even if you don’t, because every operation will implicitly contain a transaction, and the API allows nesting those calls.

That was function hooking performance, but we didn’t stop there. If you’ve ever used frida-trace to trace Objective-C APIs, or glob for functions across all loaded libraries, you may have noticed that it could take quite a while to resolve all the functions. If you combined this with early instrumentation it could even take so long that we exceeded the system’s launch timeout. All of this has now been optimized, and to give you an idea of the speed-up, a typical Objective-C case that used to take seconds is now completing in a few milliseconds.

Now to the final part of the news. Considering that dynamically discovering functions to hook is such a common use-case, and not just something that frida-trace does, we now have a brand new API for just that:

ApiResolver #1

ApiResolver #2

So in closing, here’s a summary of the changes:


  • core: improve Interceptor to avoid breaking dynamic code-signing on iOS 9
  • core: move to a transaction-based Interceptor API for improved performance
  • core: fix crash when scheduled callbacks are freed late (V8 and Duktape)
  • frida-trace: improve performance by removing setTimeout() logic, allowing many hooks to be applied in the same transaction
  • frida-trace: batch log events in 50 ms chunks to improve performance


  • core: add ApiResolver API
  • frida-trace: improve performance by using the new ApiResolver API


  • core: fix oops that prevented injection into Windows Store/Universal apps
  • core: fix crash on teardown on 32-bit ARM
  • core: add frida-inject, a tool to inject an agent into a running process with similar semantics to frida-gadget
  • core: (Linux) prevent libdl from unloading to work around TLS destructor bug
  • core: (Linux) fix race-condition on rapid uninject


  • core: fix source-map handling for eval code, which manifested itself as unhandled exceptions getting swallowed, e.g. when running frida-trace
  • core: fix Python 3.x build system regression
  • frida-trace: fix path escaping issue
  • frida-trace: improve error-handling for bad handlers


  • frida-trace: monitor handlers instead of polling them


  • core: add support for hooking arbitrary instructions by calling Interceptor.attach() with a function instead of a callbacks object
  • core: add support for detaching individual listeners added by Interceptor.attach(), even synchronously from their callbacks
  • core: add Memory.scanSync()
  • core: fix clobber by improving Interceptor to preserve r12 aka IP on ARM
  • core: expose r8 through r12 to the JavaScript runtimes
  • core: fix crash on architectures where unaligned word access is not supported
  • frida-repl: simplify logic by using the RPC feature
  • node: upgrade to prebuild 3.x


  • core: fix regression on non-jailbroken iOS systems
  • core: fix Interceptor regression in the Duktape runtime
  • core: fix module name of resolved imports
  • core: add API for specifying which host to connect to
  • core: improve QNX support and fix build regressions
  • core: fix the frida-inject build system on Mac
  • core: (Windows) fix crash when USB device location retrieval fails
  • frida-server: allow overriding the default listen address
  • frida-node: add addRemoteDevice() and removeRemoteDevice() to DeviceManager
  • frida-python: add -H switch for specifying the host to connect to
  • frida-python: add add_remote_device() and remove_remote_device() to DeviceManager
  • frida-python: fix compatibility issues with the Duktape runtime
  • frida-python: canonicalize the requested RPC method name


Frida 6.1 Released

Some time ago @s1341 ported Frida to QNX, and just a few weeks back he was running into memory footprint issues when using Frida on embedded ARM devices. This was right after he contributed pull-requests porting Frida to linux-arm. We started realizing that it might be time for a new JavaScript runtime, and agreed that Duktape seemed like a great fit for our needs.

This runtime has now landed, all tests are passing, and it even beats our V8 runtime on the measured overhead for a call to a hooked function with an empty onEnter/onLeave callback. To give you an idea:

…/interceptor_on_enter_performance: V8 min=2 max=31 avg=2 OK
…/interceptor_on_enter_performance: DUK min=1 max=2 avg=1 OK

(Numbers are in microseconds, measured on a 4 GHz i7 running OS X 10.11.2.)

Anyway, even if that comparison isn’t entirely fair, as we do some clever recycling and copy-on-write tricks that we don’t yet do in our V8 runtime, this new runtime is already quite impressive. It also allows us to run on really tiny devices, and the performance difference between a roaring JIT-powered monster like V8 and a pure interpreter might not really matter for most users of Frida.

So starting with this release we are also including this brand new runtime in all of our prebuilt binaries so you can try it out and tell us how it works for you. It only adds a few hundred kilobytes of footprint, which is nothing compared to the 6 MB that V8 adds per architecture slice. Please try it out by passing --disable-jit to the CLI tools, or calling session.disable_jit() before the first call to session.create_script().

Considering that this new runtime also solves some issues that would require a lot of work to fix in our JavaScriptCore runtime, like ignoring calls from background threads and avoid poisoning the app’s heap, we decided to get rid of that runtime and switch to this new Duktape-based runtime on OSes where V8 cannot currently run, like on iOS 9. We feature-detect this at runtime, so you still get to use V8 on iOS 8 like before – unless you explicitly --disable-jit as just mentioned.

So in closing, here’s a summary of the changes:


  • core: replace the JavaScriptCore runtime with its successor built on Duktape
  • core: add disable_jit() to allow users to try out the new Duktape engine
  • core: fix crash on Linux when injecting into processes where pthread_create has never been called/bound yet
  • core: add support for linux-armhf (e.g. Raspberry Pi)
  • python: add disable_jit() to Session
  • node: add disableJit() to Session
  • CLI tools: add –disable-jit switch
  • frida-repl: upgrade to latest prompt-toolkit
  • frida-trace: fix crash when attempting to trace partially resolved imports
  • frida-trace: stick to ES5 in the generated handlers for Duktape compatibility


  • core: fix synchronization logic and error-handling bugs in the Duktape runtime


  • core: fix Android regression resulting in crash on inject
  • core: fix Python 3.x build regression
  • clr: add DisableJit() to Session


  • core: give the iOS frida-helper all the entitlements that the Preferences app has, so system session scripts can read and write system configuration
  • core: changes to support AppContainer ACL on temporary directory/files within
  • node: fix pid check so it allows attaching to the system session


  • core: implement spawn() for console binaries on iOS
  • core: improve support for hooking low-level OS APIs
  • core: fix mapper issues preventing us from injecting into Mac processes where libraries frida-agent depends are not yet loaded
  • core: make InvocationContext available to replaced functions also


  • core: add support for generator functions in scripts generated by frida-load
  • frida-repl: fix race condition resulting in hang
  • frida-repl: fix spurious error message on exit


Frida 6.0 Released

Epic release this time, with brand new iOS 9 support and improvements all over the place. For some more background, check out my blog posts here and here.

There’s a lot of ground to cover here, but the summary is basically:


  • core: add support for OS X El Capitan
  • core: add support for iOS 9
  • core: fix launchd plist permissions in Cydia package
  • core: disable our dynamic linker on iOS for now
  • core: add new JavaScript runtime based on JavaScriptCore, as we cannot use V8 on iOS 9 with the current jailbreak
  • core: add brand new system session when attaching to pid=0
  • core: improve arm hooking, including support for early TBZ/TBNZ/IT/B.cond, and avoid relocating instructions that a later instruction loops back to
  • core: fix relocation of LDR.W instructions on arm64
  • core: abort when we’re stuck in an exception loop
  • core: fix AutoIgnorer-related deadlocks
  • core: drop our . prefix so temporary files are easier to discover
  • python: add support for running without ES6 support
  • python: tweak to allow offline installation
  • python: lock the prompt-toolkit version to 0.38 for now
  • frida-repl: fix display of raw buffers as returned by Memory.readByteArray()
  • frida-repl: fix crash in completion on error
  • node: add support for DeviceManager’s added and removed signals
  • node: add example showing how to watch available devices
  • node: use prebuild instead of node-pre-gyp
  • node: Babelify the source code read by frida.load()
  • node: remove frida.load() as it’s now in the frida-load module


  • python: stop providing 3.4 binaries and move to 3.5 instead
  • node: fix Linux linking issue where we fail to pick up our libffi
  • node: also produce prebuild for Node.js LTS


  • core: provide FridaGadget.dylib for instrumenting iOS apps without jailbreak
  • core: add support for the iOS Simulator
  • core: improve MemoryAccessMonitor to allow monitoring any combination of R, W or X operations on a page
  • python: fix UTF-8 fields being accidentally exposed as str on Python 2.x


  • core: fix spawn() on OS X


  • core: add partial support for using the gadget standalone
  • CLI tools: fix crash when the stdout encoding cannot represent all characters
  • frida-trace: always treat handler scripts as UTF-8


  • core: add logical shift right and left operations to NativePointer
  • core: improve Interceptor to support attaching to a replaced function
  • core: add support for hooking tiny functions on 32-bit ARM
  • core: emulate {Get/Set}LastErrror and TLS key access on Windows, allowing us to hook more low-level APIs


  • core: fix launchd / Jetsam issue on iOS 9
  • core: fix iOS 9 code signing issue
  • core: update security attributes on named pipe to allow us to inject into more Windows apps


  • core: add support for injecting into processes on linux-arm
  • core: fix crashes related to the DebugSymbol API on Mac and iOS
  • frida-trace: improve manpage parser


  • core: fix Linux compatibility issue caused by failing to link libstdc++ statically


  • core: add support for running frida-gadget standalone
  • core: add a temporary workaround for Windows compatibility regression
  • core: port the Fruity backend to Linux, allowing direct access to connected iOS devices
  • core: expose the InvocationContext context read-write in the JavaScriptCore runtime also
  • core: fix issue with InvocationContext’s CpuContext getting GCed prematurely


Re-release of 6.0.9 with a Windows build regression fix.


  • core: prevent stale HostSession objects in case of network errors
  • CLI tools: assume UTF-8 when the stdout encoding is unknown
  • node: fix double free caused by using the wrong Nan API


  • core: update security attributes on named pipe on Windows
  • core: add CreateProcessW flags to prevent IFEO loop on Windows
  • core: fix hooking of recursive functions on arm and arm64
  • python: fix Python 3 line endings regression
  • node: update prebuild dependency


Frida 5.0 Released

Wow, another major release! We decided to change the Device API to give you persistent IDs so you can easily tell different devices apart as they’re hotplugged.

But that’s just the beginning of it, we’re also bringing a ton of other improvements this time:


  • core: change to represent individual devices across reconnects
  • core: add new Droidy backend for interfacing with connected Android devices
  • core: adjust confusing iPhone 5+ device name on Darwin
  • core: normalize the fallback iOS device name for consistency with Android
  • core: upgrade V8 to
  • objc: include both class and instance methods in $methods and $ownMethods
  • python: add -D switch for specifying the device id to connect to
  • python: add frida-ls-devices CLI tool for listing devices
  • python: update to the new API
  • python: add get_local_device() and improve API consistency with frida-node
  • node: update to the new API
  • node: improve the top-level facade API
  • qml: update to the new API
  • clr: update to the new API
  • frida-ps: improve the output formatting


  • core: add support for source maps
  • node: add frida.load() for turning a CommonJS module into a script
  • node: upgrade Nan


  • core: add console.warn() and console.error()
  • core: add Module.enumerateImports() and implement on Darwin, Linux, and Windows
  • core: allow null module name when calling Module.findExportByName()
  • core: move Darwin.Module and Darwin.Mapper from frida-core to frida-gum, allowing easy Mach-O parsing and out-of-process dynamic linking
  • core: better handling of temporary files
  • frida-trace: add support for conveniently tracing imported functions
  • frida-trace: blacklist dyld_stub_binder from being traced
  • python: avoid logging getting overwritten by the status message changing


  • core: improve arm64 hooking, including support for hooking short functions


  • core: improve arm64 hooking, also taking care to avoid relocating instructions that other instructions depend on, including the next instruction after a BL/BLR/SVC instruction
  • core: port Arm64Writer and Arm64Relocator to Capstone


  • core: fix crash on teardown by using new API provided by our GLib patch
  • core: fix module name resolving on Linux
  • core: improve ELF handling to also consider ET_EXEC images as valid modules
  • core: improve arm64 hooking
  • core: port {Arm,Thumb}Writer and {Arm,Thumb}Relocator to Capstone
  • python: fix tests on OS X 10.11
  • node: fix tests on OS X 10.11


  • core: turn NativeFunction invocation crash into a JS exception when possible
  • core: add Process.setExceptionHandler() for handling native exceptions from JS
  • core: install a default exception handler that emits error messages
  • core: prevent apps from overriding our exception handler if we install ours early in the process life-time
  • core: gracefully handle it if we cannot replace native functions
  • core: allow RPC exports to return ArrayBuffer values
  • python: add support for rpc methods returning ArrayBuffer objects
  • node: add support for rpc methods returning ArrayBuffer objects


  • core: don’t install a default exception handler for now


Re-release of 5.0.7 due to build machine issues.


  • python: update to match new build server configuration


  • core: fix instrumentation of arm64 functions with early usage of IP registers


Frida 4.5 Released

Time for another packed release. This time we’re bringing a brand new spawn gating API that lets you catch processes spawned by the system, and tons of Android improvements and improvements all over the place.

So without further ado, the list of changes:


  • core: add Process.pageSize constant
  • core: let Memory.alloc() allocate raw pages when size >= page size
  • core: fix NativeFunction’s handling of small return types
  • core: fix PC alignment when rewriting BLX instructions
  • core: add spawn gating API
  • core: implement get_frontmost_application() on Android
  • core: implement enumerate_applications() on Android
  • core: add support for spawning Android apps
  • core: add support for injecting into arm64 processes on Android
  • core: add support for Android M
  • core: patch the kernel’s live SELinux policy
  • core: integrate with SuperSU to work around restrictions on Samsung kernels
  • core: work around broken sigsetjmp on Android, and many other Android fixes
  • core: fix crash when enumerating modules on Linux
  • core: optimize exports enumeration for remote processes on Darwin
  • dalvik: port to ART and deprecate Dalvik name, now known as Java
  • java: add Java.openClassFile() to allow loading classes at runtime
  • java: fixes for array conversions and field setters
  • python: add support for the new spawn gating API
  • python: allow script source and name to be unicode on Python 2.x also
  • python: fix error-propagation in Python 3.x
  • python: fix the Linux download URL computation
  • node: add support for the new spawn gating API
  • node: port to Nan 2.x


  • core: fix ensure_host_session() error propagation


Frida 4.4 Released

With 4.4 out the door, we can now offer you a brand new RPC API that makes it super-easy to communicate with your scripts and have them expose services to your application. We also got some amazing contributions from Adam Brady, who just ported frida-node to Nan, making it easy to build it for multiple versions of Node.js.

So to summarize this release:

  • core: add new RPC API
  • python: add support for calling RPC exports
  • node: add support for calling RPC exports
  • node: allow posted message value to be anything serializable to JSON
  • node: port to Nan


Frida 4.3 Released

It’s release o’clock, and this time we have a slew of improvements all over the place. In brief:


  • core: add support for getting details about the frontmost application, initially only for iOS
  • python: add Device.get_frontmost_application()
  • node: add Device.getFrontmostApplication()


  • core: add support for relocating PC-relative CBZ on arm64
  • frida-repl: fix crash and loading of script on Py3k


  • core: add support for launching an iOS app with a URL
  • dalvik: fix bug in field caching
  • frida-trace: color and indent events based on thread ID and depth
  • frida-ps: fix application listing on Py3k


  • core: re-enable the Darwin mapper after accidentally disabling it


  • core: gracefully handle attempts to replace functions
  • core: throw an exception when Interceptor’s attach() and replace() fail
  • core: fix clean-up of agent sessions
  • core: fix assertion logging and log to CFLog on Darwin
  • dalvik: add Dalvik.synchronized(), Dalvik.scheduleOnMainThread() and Dalvik.isMainThread()
  • dalvik: port Dalvik.androidVersion and Dalvik.choose() to Android 4.2.2
  • python: fix the PyPI download URL for windows-i386
  • frida-trace: handle attach() failures gracefully


  • frida-server: better resource tracking


  • core: fix for arm64 function hooking
  • dalvik: fix for Dalvik.enumerateLoadedClasses()


  • objc: add ObjC.Block for implementing and interacting with blocks


Frida 4.2 Released

The Frida co-conspirators have been cracking away on several fronts, so much lately that I figured it was worth jotting this down to get the word out.

In Dalvik land, @marc1006 contributed a really neat new feature – the ability to do object carving, essentially scanning the heap for objects of a certain type. Check this out:

const strings = [];
Dalvik.choose('java.lang.String', {
  onMatch(str) {
  onComplete() {
    console.log('Found ' + strings.length + ' strings!');

Meanwhile, @Tyilo has been rocking out adding the same feature for Objective-C:

const strings = [];
ObjC.choose(ObjC.classes.NSString, {
  onMatch(str) {
  onComplete() {
    console.log('Found ' + strings.length + ' strings!');

In other mobile news, @pancake added support for enumerating applications on Firefox OS. Sweet!

While all of this was going on, @s1341 has been hard at work stabilizing the QNX port, and it’s reportedly working really well now.

On my end I have been applying Frida to interesting challenges at NowSecure, and ran into quite a few bugs and limitations in the Objective-C integration. There’s now support for overriding methods that deal with struct types passed by value, e.g. -[UIView drawRect:], which means NativeFunction and NativeCallback also support these; so for declaring a struct simply start an array with the fields’ types specified sequentially. You can even nest them. So for the - drawRect: case where a struct is passed by value, and that struct is made out of two other structs, you’d declare it like this:

const f = new NativeFunction(ptr('0x1234'), 'void',
    [[['double', 'double'], ['double', 'double']]]);

Another thing worth mentioning is that a long-standing issue especially visible when instrumenting 32-bit iOS apps, but affecting all platforms, has finally been fixed.

So let’s run quickly through all the changes:


  • core: add support for enumerating applications on Firefox OS
  • core: add NativePointer.toMatchPattern() for use with Memory.scan()
  • core: fix QNX injector race condition
  • objc: massively improved handling of types
  • objc: fix implicit conversion from JS string to NSString
  • objc: fix crash during registration of second proxy or unnamed class
  • objc: new ObjC.Object properties: $className and $super
  • dalvik: add Dalvik.choose() for object carving


  • core: NativeFunction and NativeCallback now support functions passing struct types by value
  • core: fix accidental case-sensitivity in Process.getModuleByName()
  • dalvik: new object property: $className


  • core: add this.returnAddress to Interceptor’s onEnter and onLeave callbacks
  • objc: add ObjC.choose() for object carving


  • core: fix exports enumeration of stripped libraries on QNX
  • objc: new ObjC.Object property: $kind, a string that is either instance, class or meta-class
  • objc: fix the $class property so it also does the right thing for classes
  • objc: fix crash when looking up inexistent method
  • python: ensure graceful teardown of the reactor thread
  • frida-discover: fix regression
  • frida-repl: fix hang when target crashes during evaluation of expression


  • core: fix exception handling weirdness; very visible on ios-arm
  • core: QNX stability improvements
  • objc: add ObjC.api for direct access to the Objective-C runtime’s API
  • objc: new ObjC.Object properties: equals, $superClass and $methods
  • objc: fix iOS 7 compatibility
  • objc: fix toJSON() of ObjC.classes and ObjC.protocols
  • dalvik: fix handling of java.lang.CharSequence
  • frida-repl: add %time command for easy profiling


  • core: fix crash when handling exceptions without a message object
  • core: fix the life-time of CpuContext JS wrappers
  • core: expose the file mapping info to Process.enumerateRanges()
  • core: make it possible to coalesce neighboring ranges when enumerating
  • core: add convenience API for looking up modules and ranges
  • core: make the QNX mprotect read in a loop instead of just the once
  • dalvik: avoid crashing the process if a type conversion fails
  • dalvik: allow null as call parameter
  • objc: fix conversion of structs with simple field types
  • objc: speed up implicit string conversion by caching wrapper object


  • objc: fix crash when interacting with not-yet-realized classes


  • core: optimize Interceptor callback logic and make it twice as fast when onEnter and onLeave aren’t both specified
  • core: fix return-address seen by the invocation-context on arm64
  • core: add a fuzzy backtracer for arm64


  • core: fix access to arguments 4 through 7 on arm64
  • core: add Memory.readFloat(), Memory.writeFloat(), Memory.readDouble() and Memory.writeDouble()
  • dalvik: improved type checking
  • qnx: implement side-stack for calling onEnter()/onLeave() with the stack-hungry V8 engine


  • core: Darwin backend bug-fixes
  • core: optimize handling of the send() data payload
  • core: add APIs for interacting with the iOS kernel through task_for_pid(0), only available in the attach(pid=0) session
  • core: side-stack support for replaced functions on QNX
  • objc: add getOwnPropertyNames() to ObjC.classes
  • frida-repl: improved completion


  • python: fix Py3k regression


  • objc: add $ownMethods to ObjC.Object
  • dalvik: add support for primitive arrays and object arrays
  • python: improve compatibility between Python 2 and 3
  • frida-repl: better magic commands


  • core: fix Interceptor vector register clobbering issue on arm64
  • core: improve temporary directory handling on Android


  • dalvik: add support for accessing instance and static fields
  • dalvik: type conversion improvements
  • python: resolve python runtime lazily on Mac to allow our binaries to work with multiple Python distributions
  • python: pip support


  • python: fix Py3k regressions

That’s all for now. Please help spread the word by sharing this post across the inter-webs. We’re still quite small as an open source project, so word-of-mouth marketing means a lot to us.


Frida 4.1 Released

It’s release o’clock, and this time we’re taking the iOS support to the next level while also bringing some solid quality improvements. I’m also really excited to announce that I’ve recently joined NowSecure, and the awesomeness of this release is no conincidence.

Let’s start with a brand new iOS feature. It’s now possible to list installed apps, which frida-ps can do for you:

$ frida-ps -U -a
10582 Facebook    com.facebook.Facebook
11066 IRCCloud    com.irccloud.IRCCloud
  451 Mail
10339 Mailbox     com.orchestra.v2
 6866 Messages
10626 Messenger   com.facebook.Messenger
11043 Settings
10542 Skype
11218 Slack       com.tinyspeck.chatlyio
11052 Snapchat    com.toyopagroup.picaboo

Add the -i switch and it will also include all installed applications, and not just those of them that are currently running.

This is also available from your language binding of choice, e.g. from Python:

>>> import frida
>>> iphone = frida.get_usb_device()
>>> print("\n".join(map(repr, iphone.enumerate_applications())))
Application(identifier="", name="YouTube")
Application(identifier="com.toyopagroup.picaboo", name="Snapchat")
Application(identifier="", name="Skype", pid=10542)


That’s cool, but wouldn’t you like to do early instrumentation of those apps? Now you can do that too, by just asking us to spawn an app identifier:

$ frida-trace -U -f com.toyopagroup.picaboo -I "libcommonCrypto*"

Or at the API level:

>>> import frida
>>> iphone = frida.get_usb_device()
>>> pid = iphone.spawn(["com.toyopagroup.picaboo"])
>>> snapchat = iphone.attach(pid)
>>> apply instrumentation
>>> iphone.resume(pid)

Note that we piggy-back on Cydia Substrate for the early launch part in order to maximize interoperability; after all it’s not too good if multiple frameworks all inject code into launchd and risk stepping on each others’ toes. This dependency is however a soft one, so we’ll throw an exception if Substrate isn’t installed when trying to call spawn() with an app identifier.

So, early instrumentation of iOS apps is pretty cool. But, those applications are typically consuming tons of Objective-C APIs, and if we want to instrument them we often find ourselves having to create new Objective-C classes in order to create delegates to insert between the application and the API. Wouldn’t it be nice if such Objective-C classes could be created in pure JavaScript? Now they can:

const MyConnectionDelegateProxy = ObjC.registerClass({
  name: 'MyConnectionDelegateProxy',
  super: ObjC.classes.NSObject,
  protocols: [ObjC.protocols.NSURLConnectionDataDelegate],
  methods: {
    '- init': function () {
      const self = this.super.init();
      if (self !== null) {
        ObjC.bind(self, {
          foo: 1234
      return self;
    '- dealloc': function () {
    '- connection:didReceiveResponse:': function (conn, resp) {
      /* === 1234 */
     * But those previous methods are declared assuming that
     * either the super-class or a protocol we conform to has
     * the same method so we can grab its type information.
     * However, if that's not the case, you would write it
     * like this:
    '- connection:didReceiveResponse:': {
      retType: 'void',
      argTypes: ['object', 'object'],
      implementation(conn, resp) {
    /* Or grab it from an existing class: */
    '- connection:didReceiveResponse:': {
      types: ObjC.classes
          .Foo['- connection:didReceiveResponse:'].types,
      implementation(conn, resp) {
    /* Or from an existing protocol: */
    '- connection:didReceiveResponse:': {
      types: ObjC.protocols.NSURLConnectionDataDelegate
          .methods['- connection:didReceiveResponse:'].types,
      implementation(conn, resp) {
    /* Or write the signature by hand if you really want to: */
    '- connection:didReceiveResponse:': {
      types: 'v32@0:8@16@24',
      implementation(conn, resp) {

const proxy = MyConnectionDelegateProxy.alloc().init();
/* use `proxy`, and later: */

Though most of the time you’d like to build a proxy object where you pass on everything and only do some logging for the few methods you actually care about. Check this out:

const MyConnectionDelegateProxy = ObjC.registerProxy({
  protocols: [ObjC.protocols.NSURLConnectionDataDelegate],
  methods: {
    '- connection:didReceiveResponse:': function (conn, resp) {
      /* fancy logging code here */
      /* === 1234 */
          .connection_didReceiveResponse_(conn, resp);
    '- connection:didReceiveData:': function (conn, data) {
      /* other logging code here */
          .connection_didReceiveData_(conn, data);
  events: {
    forward(name) {
      console.log('*** forwarding: ' + name);

const method = ObjC.classes.NSURLConnection[
    '- initWithRequest:delegate:startImmediately:'];
Interceptor.attach(method.implementation, {
  onEnter(args) {
    args[3] = new MyConnectionDelegateProxy(args[3], {
      foo: 1234

So that’s Objective-C. The Dalvik integration also got some sweet new API for enumerating loaded classes thanks to @marc1006, who also fixed our handling of static methods and being able to return booleans from overriden implementations.

We also got lots of awesome improvements from @Tyilo who helped improve the ObjC integration, beat the REPL into better shape, added APIs for enumerating malloc ranges, and added some convenience APIs to NativePointer.

While all of this was going on, @s1341 has been hard at work doing an amazing job porting Frida to QNX, which is now really close to working like a charm.

Let’s run through the remaining changes:


  • objc: support for more types
  • frida-trace: fix ObjC tracing regression


  • frida-node: fix encoding of the pixels property


  • frida-repl: fix Windows regression


  • objc: support for more types and better type checking
  • objc: arm64 now working properly
  • frida-repl: allow variables to be created


  • platform: support passing a plain array of data to send()
  • arm: support for relocating cbz/cbnz instructions


  • platform: fix spawning of child processes that write to stdout
  • platform: fix NativeCallback’s handling of bool/int8/uint8 return values (this was preventing Dalvik method overrides from being able to return false).
  • platform: allow Memory.readByteArray() with length < 1
  • arm: support for relocating the ldrpc t2 instruction
  • arm: improved redirect resolver
  • arm64: fix relocation of the adrp instruction
  • arm64: support for relocating PC-relative ldr instruction
  • dalvik: add Dalvik.enumerateLoadedClasses()
  • dalvik: fix handling of static methods
  • python: fix console.log() on Windows
  • frida-repl: bugfixes and improvements
  • frida-trace: glob support for tracing ObjC methods


  • platform: add missing pid field in enumerate_applications()


  • objc: class and proxy creation APIs
  • objc: new ObjC.protocols API for enumerating protocols


  • platform: improved concurrency by releasing V8 lock while calling NativeFunction
  • platform: add Process.getModuleByName(name)
  • platform: faster and more robust detach
  • python: stability improvements in CLI tools
  • frida-repl: replace readline with prompt-toolkit


  • platform: faster and more robust teardown
  • frida-server: clean up on SIGINT and SIGTERM


  • frida-ps: add support for listing applications


  • platform: fix crash on spawn on Mac, iOS and Linux
  • platform: add and NativePointer.equals()
  • platform: add Process.enumerateMallocRanges{,Sync}()
  • frida-trace: switch from Enter to Ctrl+C for stopping
  • frida-trace: fix spawning of iOS apps
  • frida-repl: add prototype names to autocomplete


  • python: CLI tools stability improvements

That’s all for now. Please help spread the word by sharing this post across the inter-webs. We’re still quite small as an open source project, so word-of-mouth marketing means a lot to us.


Frida 4.0.0 Released

It’s time for an insane release with tons of improvements.

Let’s start with a user-facing change. The CLI tool called frida-repl has been renamed to just frida, and now does tab completion! This and some other awesome REPL goodies were contributed by @fitblip.

There is also integrated support for launching scripts straight from the shell:

$ frida Calculator -l calc.js
    |   |    Frida 4.0.0 - A world-class dynamic
    |   |                  instrumentation framework
    |   |    Commands:
    |   |        help      -> Displays the help system
    |   |        object?   -> Display information about 'object'
    |   |        exit/quit -> Exit
    |   |
    |   |    More info at

# The code in calc.js has now been loaded and executed
# Reload it from file at any time
[Local::ProcName::Calculator]-> %reload

Or, perhaps you’re tired of console.log() and would like to set some breakpoints in your scripts to help you understand what’s going on? Now you can, because Frida just got an integrated Node.js-compatible debugger.

(Cue “Yo Dawg” meme here.)

Yep yep, but it is actually quite useful, and all of the CLI tools provide the --debug switch to enable it:

# Connect Frida to a locally-running
# and load calc.js with the debugger enabled
$ frida Calculator -l calc.js --debug
    |   |    Frida 4.0.0 - A world-class dynamic
    |   |                  instrumentation framework
    |   |    Commands:
    |   |        help      -> Displays the help system
    |   |        object?   -> Display information about 'object'
    |   |        exit/quit -> Exit
    |   |
    |   |    More info at

Debugger listening on port 5858
# We can now run node-inspector and start debugging calc.js

Here’s what it looks like:

Frida Debugger Session

Ever found yourself wanting to frida-trace Objective-C APIs straight from the shell? Thanks to @Tyilo you now can:

# Trace ObjC method calls in Safari
$ frida-trace -m '-[NSView drawRect:]' Safari

There are also other goodies, like brand new support for generating backtraces and using debug symbols to symbolicate addresses:

const f = Module.getExportByName('libcommonCrypto.dylib',
Interceptor.attach(f, {
    onEnter(args) {
        console.log('CCCryptorCreate called from:\n' +
            Thread.backtrace(this.context, Backtracer.ACCURATE)
            .map(DebugSymbol.fromAddress).join('\n') + '\n');

Or perhaps you’re on Windows and trying to figure out who’s accessing certain memory regions? Yeah? Well check out the brand new MemoryAccessMonitor. Technically this code isn’t new, but it just hasn’t been exposed to the JavaScript API until now.

Another nice feature is that starting with this release it is no longer necessary to forward multiple TCP ports when using frida-server running on another device, e.g. Android.

There is now also much better error feedback propagated all the way from a remote process to different exceptions in for example Python. With the previous release attaching to an inexistent pid on Mac would give you:

SystemError: GDBus.Error:org.gtk.GDBus.UnmappedGError.Quark._g_2
dio_2derror_2dquark.Code0: task_for_pid() for remote pid failed w
hile trying to make pipe endpoints: (os/kern) failure (5)

Whoah, madness. This is now simply:

frida.ProcessNotFoundError: unable to find process with pid 1234

That’s better. Let’s talk about performance. Perhaps you used frida-trace and wondered why it spent so much time “Resolving functions…”? On a typical iOS app resolving just one function would typically take about 8 seconds. This is now down to ~1 second. While there were some optimizations possible, I quickly realized that no matter how fast we make the enumeration of function exports, we would still need to transfer the data, and the transfer time alone could be unreasonable. Solution? Just move the logic to the target process and transfer the logic instead of the data. Simple. Also, the Dalvik and ObjC interfaces have been optimized so seconds have been reduced to milliseconds. The short story here is further laziness in when we interrogate the language runtimes. We took this quite far in the ObjC interface, where we now use ES6 proxies to provide a more idiomatic and efficient API.

That brings us to the next topic. The ObjC interface has changed a bit. Essentially:

const NSString = ObjC.use("NSString");

is now:

const NSString = ObjC.classes.NSString;

You still use ObjC.classes for enumerating the currently loaded classes, but this is now behaving like an object mapping class name to a JavaScript ObjC binding.

Also, there’s no more casting, so instead of:

const NSSound = ObjC.use('NSSound');
const sound = ObjC.cast(ptr("0x1234"), NSSound);

You just go:

const sound = new ObjC.Object(ptr("0x1234"));

Yep, no more class hierarchies trying to mimic the ObjC one. Just a fully dynamic wrapper where method wrappers are built on the first access, and the list of methods isn’t fetched unless you try to enumerate the object’s properties.

Anyway, this is getting long, so let’s summarize the other key changes:

  • The Dalvik interface now handles varargs methods. Thanks to @dmchell for reporting and helping track this down.
  • NativePointer also provides .and(), .or() and .xor() thanks to @Tyilo.
  • The Interceptor’s onEnter/onLeave callbacks used to expose the CPU registers through this.registers, which has been renamed to this.context, and now allows you to write to the registers as well.
  • Process.enumerateThreads()’s thread objects got their CPU context field renamed from registers to context for consistency.
  • Synchronous versions of enumerateFoo() API available as enumerateFooSync() methods that simply return an array with all of the items.
  • Memory.readCString() is now available for reading ASCII C strings.
  • Frida.version can be interrogated to check which version you’re running, and this is also provided on the frida-core end, which for example is exposed by frida-python through frida.__version__.
  • Stalker now supports the jecxz and jrcxz instructions. This is good news for CryptoShark, which should soon provide some updated binaries to bundle the latest version of Frida.
  • V8 has been updated to 4.3.62, and a lot of ES6 features have been enabled.
  • We’re now using a development version of the upcoming Capstone 4.0.
  • All third-party dependencies have been updated to the latest and greatest.
  • Windows XP is now supported. This is not a joke. I realized that we didn’t actually use any post-XP APIs, and as I had to rebuild the dependencies on Windows I figured we might as well just lower our OS requirements to help those of you still instrumenting software on XP.


Frida 3.0.0 Released

You may have wondered:

Why a Python API, but JavaScript debugging logic?

Well, you can now do this:

$ npm install frida

We just brought you brand new Node.js bindings, and they are fully asynchronous:

Check out the examples to get an idea what the API looks like. It’s pretty much a 1:1 mapping of the API provided by the Python bindings, but following Node.js / JavaScript conventions like camelCased method-names, methods returning ES6 Promise objects instead of blocking, etc.

Now, combine this with NW.js and you can build your own desktop apps with HTML, CSS, and JavaScript all the way through.

So, brand new Node.js bindings; awesome! We did not stop there, however. But first, a few words about the future. I am excited to announce that I have just started a company with the goal of sponsoring part-time development of Frida. By offering reverse-engineering and software development expertise, the goal is to generate enough revenue to pay my bills and leave some time to work on Frida. Longer term I’m hoping there will also be demand for help adding features or integrating Frida into third-party products. In the meantime, however, if you know someone looking for reverse-engineering or software development expertise, I would really appreciate it if you could kindly refer them to get in touch. Please see my CV for details.

That aside, let’s get back to the release. Next up: 32-bit Linux support! Even Stalker has been ported. Not just that, the Linux backend can even do cross-architecture injection like we do on the other platforms. This means a 64-bit Frida process, e.g. your Python interpreter, can inject into a 32-bit process. The other direction works too.

Another awesome update is that Tyilo contributed improvements to frida-trace so it now uses man-pages for auto-generating the log handlers. Awesome, huh? But there’s even more goodies:

  • frida-server ports are now recycled, so if you’re using Frida on Android you won’t have to keep forwarding ports unless you’re actually attaching to multiple processes at the same time.
  • Linux and Android spawn() support has been improved to also support PIE binaries.
  • Android stability and compatibility improvements.
  • Mac and Linux build system have been revamped, and make it easy to build just the parts that you care about; and maybe even some components you didn’t even know were there that were previously not built by default.
  • Python bindings have a minor simplification so instead of frida.attach(pid).session.create_script() it’s simply just frida.attach(pid).create_script(). This is just like in the brand new Node.js bindings, and the reason we had to bump the major version.

That’s the gist of it. Please help spread the word by sharing this post across the inter-webs. We’re still quite small as an open source project, so word-of-mouth marketing means a lot to us.


Frida 2.0.2 Released

Thanks to your excellent feedback we just eliminated a crasher when using Frida on Windows with certain iOS device configurations. As this is a very important use-case we decided to do a hotfix release without any other changes.

Please keep the bug-reports coming!

Frida 2.0.1 Released

Just a quick bug-fix release to remedy an iOS issue that slipped through the final testing of 2.0.0. Enjoy!

Frida 2.0.0 Released

It’s time for a new and exciting release! Key changes include:

  • No more kernel panics on Mac and iOS! Read the full story here.
  • Mac and iOS injector performs manual mapping of Frida’s dylib. This means we’re able to attach to heavily sandboxed processes.
  • The CLI tools like frida-trace, frida-repl, etc., have brand new support for spawning processes:
$ frida-trace -i 'open*' -i 'read*' /bin/cat /etc/resolv.conf
    27 ms	open$NOCANCEL()
    28 ms	read$NOCANCEL()
    28 ms	read$NOCANCEL()
    28 ms	read$NOCANCEL()
Target process terminated.
  • Usability improvements in frida-repl and frida-discover.
  • First call to DeviceManager.enumerate_devices() does a better job and also gives you the currently connected iOS devices, so for simple applications or scripts you no longer have to subscribe to updates if you require the device to already be present.
  • The python API now provides you with frida.get_usb_device(timeout = 0) and frida.get_remote_device() for easy access to iOS and remote/Android devices.
  • The onEnter and onLeave callbacks passed to Interceptor.attach() may access this.registers to inspect CPU registers, which is really useful when dealing with custom calling conventions.
  • console.log() logs to the console on your application’s side instead of the target process. This change is actually why we had to bump the major version for this release.
  • Android 5.0 compatibility, modulo ART support.
  • Brand new support for Android/x86. Everything works except the Dalvik integration; please get in touch if you’d like to help out with a pull-request to fix that!

Want to help out? Have a look at our GSoC 2015 Ideas Page to get an overview of where we’d like to go next.


Update 2am: An iOS issue slipped through the final testing, so we just pushed 2.0.1 to remedy this.

Update 11pm: Thanks to your excellent feedback we found a critical bug when using Frida on Windows with certain iOS device configurations. Please upgrade to 2.0.2 and let us know if you run into any issues.

Frida 1.8.0 Released

This release introduced a serious regression on iOS and was quickly pulled from our Cydia repo, though it was available for Mac, Linux and Android while waiting to be replaced by 2.0.0.

Frida 1.6.7 Released

Tired of waiting for Frida to attach to 32-bit processes on 64-bit Mac or iOS systems? Or perhaps frida-trace takes a while to resolve functions? If any of the above, or none of it, then this release is for you!

Attaching to 32-bit processes on Mac/iOS hosts has been optimized, and instead of seconds this is now a matter of milliseconds. That’s however specific to Darwin OSes; this release also speeds up enumeration of module exports on all OSes. This is now 75% faster, and should be very noticable when using frida-trace and waiting for it to resolve functions.

Also, as an added bonus, teardown while attached to multiple processes no longer crashes on Darwin and Linux.


Frida 1.6.5 Released

It’s release o’clock, and time for some bug fixes:

  • iOS 8.1 is now supported, and the ARM64 support is better than ever.
  • The iOS USB transport no longer disconnects when sending a burst of data to the device. This would typically happen when using frida-trace and tracing a bunch of functions, resulting in a burst of data being sent over the wire. This was actually a generic networking issue affecting Mac and iOS, but was very reproducible when using Frida with a tethered iOS device.
  • Eliminated crashes on shutdown of the Python interpreter.
  • The onEnter and onLeave callbacks in frida-trace scripts are now called with this bound to the correct object, which means that it’s bound to an object specific to that thread and invocation, and not an object shared by all threads and invocations.

Frida 1.6.4 Released

It’s time for a bug-fix release!

Stalker improvements:

  • The engine no longer pre-allocates a fixed chunk of 256 MB per thread being traced, and now grows this dynamically in a reentrancy-safe manner.
  • Eliminated a bug in the cache lookup logic where certain blocks would always result in a cache miss. Those blocks thus got recompiled every time they were about to get executed, slowing down execution and clogging up the cache with more and more entries, and eventually running out of memory.
  • Relocation of RIP-relative cmpxchg instruction is now handled correctly.

Better Dalvik integration (Android):

  • App’s own classes can now be loaded.
  • Several marshalling bugs have been fixed.

Script runtime:

  • More than one NativeFunction with the same target address no longer results in use-after-free.

Also, CryptoShark 0.1.2 is out, with an upgraded Frida engine and lots of performance improvements so the GUI is able to keep up with the Stalker. Go get it while it’s hot!

Frida 1.6.3 Released

This latest release includes a bunch of enhancements and bug fixes. Some of the highlights:

  • The remainder of Frida’s internals have been migrated from udis86 to Capstone, which means that our Stalker is now able to trace binaries with very recent x86 instructions. Part of this work also included battle-testing it on 32- and 64-bit binaries on Windows and Mac, and all known issues have now been resolved.

  • Memory.protect() has been added to the JavaScript API, allowing you to easily change page protections. For example:

Memory.protect(ptr("0x1234"), 4096, 'rw-');
  • Process.enumerateThreads() omits Frida’s own threads so you don’t have to worry about them.

  • Python 3 binaries are now built against Python 3.4.

So with this release out, let’s talk about CryptoShark:

Grab a pre-built Windows binary here, or build it from source if you’d like to try it out on Mac or Linux.


Frida 1.6.2 Released

It’s release o’clock, and this time we’re bringing you more than just bugfixes. Meet Instruction.parse():

const a = Instruction.parse(ptr('0x1234'));
const b = Instruction.parse(;


push rbp
mov rbp, rsp

How is this implemented you ask? That’s the cool part. Frida already uses the amazing Capstone disassembly framework behind the scenes, and thus it makes perfect sense to make it available to the JavaScript runtime. Have a look at the JavaScript API Reference for all the details.


Frida 1.6.1 Released

It’s time for a bugfix release. Highlights:

  • Compatibility with the Pangu iOS jailbreak on ARM64. The issue is that RWX pages are not available like they used to be with the evad3rs jailbreak.
  • Fix occasional target process crash when detaching.
  • Fix crash when trying to attach to a process the second time after failing to establish the first time. This primarily affected Android users, but could happen on any OS when using frida-server.
  • Faster and more reliable injection on Linux/x86-64 and Android/ARM.
  • Fix issues preventing hooking of HeapFree and friends on Windows.
  • Upgraded GLib, libgee, json-glib and Vala dependencies for improved performance and bugfixes.
  • No more resource leaks. Please report if you find any.

Also new since 1.6.0, as covered in my blog post, there is now a full- featured binding for Qml. This should be of interest to those of you building graphical cross-platform tools.

Frida 1.6.0 Released

As some of you may have noticed, Frida recently got brand new Android support, allowing you to easily instrument code just like on Windows, Mac, Linux and iOS. This may sound cool and all, but Android does run a lot of Java code, which means you’d only be able to observe the native side-effects of whatever that code was doing. You could of course use Frida’s FFI API to poke your way into the VM, but hey, shouldn’t Frida just do that dirty plumbing for you? Of course it should!

Here’s what it looks like in action:

Dalvik.perform(() => {
    const Activity = Dalvik.use('');
    Activity.onResume.implementation = function () {
        send('onResume() got called! Let's call the original implementation');

The Dalvik.perform() call takes care of attaching your thread to the VM, and isn’t necessary in callbacks from Java. Also, the first time you call Dalvik.use() with a given class name, Frida will interrogate the VM and build a JavaScript wrapper on-the-fly. Above we ask for the Activity class and replace its implementation of onResume with our own version, and proceed to calling the original implementation after sending a message to the debugger (running on your Windows, Mac or Linux machine). You could of course choose to not call the original implementation at all, and emulate its behavior. Or, perhaps you’d like to simulate an error scenario:

Dalvik.perform(() => {
    const Activity = Dalvik.use('');
    const Exception = Dalvik.use('java.lang.Exception');
    Activity.onResume.implementation = function () {
        throw Exception.$new('Oh noes!');

So there you just instantiated a Java Exception and threw it straight from your JavaScript implementation of Activity.onResume.

This release also comes with some other runtime goodies:

  • Memory.copy(dst, src, n): just like memcpy
  • Memory.dup(mem, size): short-hand for Memory.alloc() followed by Memory.copy()
  • Memory.writeXXX(): the missing counterparts: S8, S16, U16, S32, U32, S64, U64, ByteArray, Utf16String and AnsiString
  • Process.pointerSize to make your scripts more portable
  • NativePointer instances now have a convenient isNull() method
  • NULL constant so you don’t have to do ptr("0") all over the place
  • WeakRef.bind(value, fn) and WeakRef.unbind(id) for the hardcore: The former monitors value so fn gets called as soon as value has been garbage-collected, or the script is about to get unloaded. It returns an id that you can pass to unbind() for explicit cleanup. This API is useful if you’re building a language-binding, where you need to free native resources when a JS value is no longer needed.


Frida 1.4.2 Released

Just a quick bugfix release to squash this annoying bug, which is reproducible on all supported x86 OSes. Thanks for Guillaume for tracking this one down.

As a bonus, frida-repl now also works on Windows. Happy REPLing!

Frida 1.4.1 Released

Interested in spawning processes on Windows or Linux, and not just on Mac? Or maybe you’ve been bitten by the Linux injector crashing your processes instead of letting you inject into them? Or maybe you had a function name that was so long that frida-trace overflowed the max filename length on Windows? Well, if any of the above, or none of it, then Frida 1.4.1 is for you!

Thanks to Guillaume and Pedro for making this release awesome. Keep those pull- requests and bug reports coming!

Frida 1.4.0 Released

Did anyone say Android? Frida 1.4.0 is out, with brand new Android support! Have a look at the documentation here to get started. Also new in this release is that Frida is now powered by the amazing Capstone Disassembly Engine, which means our cross-platform code instrumentation is even more powerful. It also paves the way for taking the x86-only stealth tracing to new architectures in future releases.


Frida 1.2.1 Released

Had some fun tracing Apple’s crypto APIs, which lead to the discovery of a few bugs. So here’s 1.2.1 bringing some critical ARM-related bugfixes:

  • ARM32: Fix crashes caused by register clobber issue in V8 on ARM32 due to an ABI difference regarding r9 in Apple’s ABI compared to AAPCS.
  • ARM32: Fix ARM32/Thumb relocator branch rewriting for immediate same-mode branches.
  • ARM64: Improve ARM64 relocator to support rewriting b and bl.

Frida 1.2.0 Released

It’s release-o-clock, and Frida 1.2.0 is finally out! Bugfixes aside, this release introduces brand new ARM64 support, which is quite useful for those of you using Frida on your iPhone 5S or iPad Air. You can now inject into both 64- and 32-bit processes, just like on Mac and Windows.

This release also improves stability on ARM32, where attaching to short functions used to result in undefined behavior.


Frida 1.0.11 Released

Some of you experienced issues injecting into processes on Windows, as well as crashes on iOS. Here’s a new release bringing some serious improvements to Frida’s internals:

  • V8 has been upgraded to 3.25 in order to fix the iOS stability issues. This also means new features (like ECMAScript 6) and improved performance on all platforms. Another nice aspect is that Frida now depends on a V8 version that runs on 64-bit ARM, which paves the way for porting Frida itself to AArch64.
  • The Windows injector has learned some new tricks and will get you into even more processes. A configuration error was also discovered in the Windows build system, which explains why some of you were unable to inject into some processes.
  • For those of you building Frida on Windows, the build system there now depends on VS2013. This means XP is no longer supported, though it is still possible to build with the v120_xp toolchain if any of you still depend on that, so let me know if this is a deal-breaker for you.
  • The recently added support for this.lastError (Windows) is now working correctly.

That’s all for now. Let us know what you think, and if you like Frida, please help spread the word! :)

Frida 1.0.10 Released

This release brings a few improvements:

  • Interceptor is now compatible with a lot more functions on iOS/ARM.
  • A new CLI tool called frida-repl provides you with a basic REPL to experiment with the JavaScript API from inside a target process.
  • onLeave callback passed to Interceptor.attach() is now able to replace the return value by calling retval.replace().
  • Both onEnter and onLeave callbacks passed to Interceptor.attach() can access this.errno (UNIX) or this.lastError (Windows) to inspect or manipulate the current thread’s last system error.

Here’s how you can combine the latter three to simulate network conditions for a specific process running on your Mac:

~ $ frida-repl TargetApp

Then paste in:

callbacks = { \
    onEnter(args) { \
        args[0] = ptr(-1); // Avoid side-effects on socket \
    }, \
    onLeave(retval) { \
        const ECONNREFUSED = 61; \
        this.errno = ECONNREFUSED; \
        retval.replace(-1); \
    } \
}; \
Module.enumerateExports("libsystem_kernel.dylib", { \
    onMatch(exp) { \
        if ("connect") === 0 &&"connectx") !== 0) { \
            Interceptor.attach(exp.address, callbacks); \
        } \
    }, \
    onComplete() {} \


Frida 1.0.9 Released

Another release — this time with some new features:

  • Objective-C integration for Mac and iOS. Here’s an example to whet your appetite:
const UIAlertView = ObjC.use('UIAlertView'); /* iOS */
ObjC.schedule(ObjC.mainQueue, () => {
    const view = UIAlertView.alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles_(
        "Hello from Frida",
  • Module.enumerateExports() now also enumerates exported variables and not just functions. The onMatch callback receives an exp object where the type field is either function or variable.

To get the full scoop on the ObjC integration, have a look at the JavaScript API reference.

Frida 1.0.8 Released

We just rolled out a bugfix release:

  • Support injection into Mac App Store apps
  • Eliminate iOS daemon auto-start issues
  • No more iOS crashes shortly after injecting

Frida 1.0.7 Released

This release brings USB device support in the command-line tools, and adds frida-ps for enumerating processes both locally and remotely.

For example to enumerate processes on your tethered iOS device:

$ frida-ps -U

The -U switch is also accepted by frida-trace and frida-discover.

Docs how to set this up on your iOS device will soon be added to the website.

However, that’s not the most exciting part. Starting with this release, Frida got its first contribution since the HN launch. Pete Morici dived in and contributed support for specifying module-relative functions in frida-trace:

$ frida-trace -a 'kernel32.dll+0x1234'


Frida 1.0.6 Released

This release simplifies the licensing and fixes bugs reported by the community since the HN launch.


  • Relicense remaining GPLv3+ Frida components to LGPLv2.1+ (same as frida-gum).
  • Tracer works on 64-bit with function addresses in the upper range
  • Linux build links with Frida’s own libraries instead of the build machine’s corresponding libraries.

Frida 1.0.5 Released

This release improves frida-trace with support for auto-generating, loading and reloading function handlers from scripts on the filesystem. Have a look at our Quick-start guide for a walkthrough. Also new in this release is Py3k support, which is available from PyPI on all platforms.