Compare commits

...
1359 Commits
Author SHA1 Message Date
Ole André Vadla Ravnås 2a41a960fe subprojects: Prepare for release 2026-11-07 15:45:32 +01:00
Ole André Vadla Ravnås 25bd49299a subprojects: Bump outdated 2026-11-07 15:44:13 +01:00
tubibibo c84323b6ac linux: Don't pass our stdin to crash logcat
Under `adb shell`, frida-server's stdin and stdout are the same socket.
The crash monitor's logcat inherited it, so when frida-server died
abnormally, logcat kept it open and `adb shell` never saw EOF.

Give logcat /dev/null for stdin instead.
2026-11-07 15:38:00 +01:00
Ole André Vadla Ravnås 45aef70f35 subprojects: Prepare for release 2026-11-06 13:15:10 +01:00
Ole André Vadla Ravnås eb8efdf40a subprojects: Bump outdated 2026-11-06 12:19:36 +01:00
Ole André Vadla Ravnås 025e5c433d submodules: Bump releng 2026-11-06 12:19:35 +01:00
Ole André Vadla Ravnås f9314073dc subprojects: Prepare for release 2026-11-05 22:45:53 +01:00
Ole André Vadla Ravnås 4011e34449 ci: Run QNX tests out of /tmp
On the emulated QNX 6.5 sabrelite board, threads that concurrently
take first-touch page faults on code mapped from the boot image's
/opt sometimes get SIGSEGV with SEGV_MAPERR, even though the pages
are mapped. A standalone dlopen() test with two or more threads
calling into a fresh library crashes on the first iteration, and
passes when the library lives in /tmp. This made the agent tests
crash intermittently once the reloaded agent spun up its threads.
2026-11-05 22:43:11 +01:00
Ole André Vadla Ravnås 2fef8d7d20 subprojects: Bump outdated 2026-11-05 21:59:57 +01:00
Ole André Vadla Ravnås 9f681e26f1 subprojects: Bump outdated 2026-11-05 21:00:52 +01:00
Ole André Vadla Ravnås 18a156817f barebone: Bump @types/frida-gum to 19.13.0 2026-11-05 20:58:58 +01:00
Ole André Vadla Ravnås 2ea8b6278e compiler: Bump @types/frida-gum to 19.13.0 2026-11-05 20:58:58 +01:00
washedbeast 40ce660b54 linux: Fix memfd check in code swap fallback
Gum.Linux.enumerate_ranges() gives paths as they appear in the maps,
like "/memfd:jit-zygote-cache (deleted)", so the "memfd:" prefix check
from 72a96226 never matched. When libc could not be found in the
target, the code swap then picked ART's read-only JIT zygote cache and
the first write failed with "unable to perform ptrace pokedata: I/O
error".
2026-11-05 11:43:12 +01:00
washedbeast 7913d880c2 linux: Parse /proc/<pid>/maps as raw bytes
Mapping names are not always valid UTF-8: file paths may hold any
bytes, and on Android kernels before 5.17 an anonymous mapping's name
is read from a user pointer whenever the maps are read, so a name whose
string got unmapped shows up as random bytes. We hit this with Magisk's
Zygisk, where apps inherit [anon:System property context nodes] from
zygote with a garbage name.

GRegex rejects a subject that is not valid UTF-8 unless the pattern is
raw, so one such line made ProcMapsIter yield no mappings at all. The
injector then could not find libc in the target, and attach failed.
2026-11-05 11:43:12 +01:00
washedbeast 34e718e4a3 linux: Make syscall-trace strings valid UTF-8
g_variant_new_string() returns NULL when given invalid UTF-8, and the
g_variant_ref_sink() that Vala adds around it then dereferences the
NULL, crashing frida-server.

Module paths in resolve-symbols, and string arguments and AF_UNIX
socket paths in read-events, come from /proc/<pid>/maps and from the
kernel, so they can hold any byte. Replace invalid sequences before
they go into a GVariant.
2026-11-05 11:39:13 +01:00
tubibibo 10d6f866b1 agent: Retry emulated load through LoadLibraryExt
Houdini v3 rejects the deprecated NativeBridgeLoadLibrary() entrypoint
and returns NULL, which surfaces as "Process is not using emulation".
When the plain call fails and the extended entrypoint is available,
retry through it with a null namespace.
2026-11-05 11:31:24 +01:00
Ole André Vadla Ravnås d5bc484072 subprojects: Bump outdated 2026-11-04 12:53:53 +01:00
Ole André Vadla Ravnås 04bd09b7b7 submodules: Bump releng 2026-11-04 12:53:52 +01:00
Ole André Vadla Ravnås ef7dd9701c agent: Mirror native cloak in emulated realm
The emulated agent sees the native agent's threads when it walks the
host's thread list, as they are only cloaked in the native agent's Gum.

Have the native agent share its cloaked threads with the emulated agent,
as a snapshot followed by changes, applied to the emulated agent's own
cloak.

Depends on the CloakObserver interface in frida-gum.
2026-11-03 23:11:43 +01:00
Ole André Vadla Ravnås 8777bbe8af agent: Feed emulated thread registry from host
On Android's native bridge, threads are created and torn down by the
host's Bionic, which the emulated agent's Interceptor cannot hook. This
broke Process.attachThreadObserver() in the emulated realm.

Have the native agent expose its own thread registry to the emulated
agent, which plugs it into Gum as a ThreadProvider: a snapshot on
activation, followed by added, removed and renamed events.

Depends on the ThreadProvider interface in frida-gum.
2026-11-03 23:11:43 +01:00
Francesco Tamagni 8734d9875a fruity: Fix kill() fallback (#1282)
LLDB errors are Efni.Error since the move to idun, so catching
Frida.Error skipped the ProcessControlService fallback.
2026-11-03 17:35:54 +01:00
Francesco Tamagni d043c66c75 channel: Keep streams alive until linked
The registry only holds a weak reference, so a stream nobody else owns,
such as one relayed by idund, was gone before link().
2026-11-03 17:29:07 +01:00
Ole André Vadla Ravnås 317363ce80 subprojects: Prepare for release 2026-11-02 22:10:32 +01:00
Ole André Vadla Ravnås f1aca3182f subprojects: Bump outdated 2026-11-02 22:08:48 +01:00
Ole André Vadla Ravnås 92bc074641 linux: Skip libraries of a foreign ABI in mapper
On x86_64 Android with a native bridge, the arm64 guest libraries may
be mapped before the host ones, and since the mapper looks up
dependencies by basename, the x86_64 agent got bound to the guest libc.

Only accept libraries whose machine and pointer size match the agent.

Fixes #1281.
2026-11-02 21:01:10 +01:00
Ole André Vadla Ravnås 8c98e9b6c6 linux: Find module bases using the ELF layout
Since 00d8ba24, the mapper picks library bases through
find_module_by_path(), whose heuristic starts a new candidate at every
mapping with file offset 0 and prefers executable ones. This breaks
libraries whose code shares file page 0 with their headers, such as
Android's libdl.so on x86_64, where the code mapping was picked as the
base, one page too high.

Instead, accept a mapping at file offset 0 as the base only if the
library's code segments are mapped executable where the linker would
have put them. This also rejects stray mappings of the file, so the
Android-specific libc workaround is no longer needed.

Fixes #1280.
2026-11-02 21:00:51 +01:00
Ole André Vadla Ravnås b61866bf84 subprojects: Prepare for release 2026-11-01 15:11:39 +01:00
Ole André Vadla Ravnås 67d80c40a3 subprojects: Bump outdated 2026-11-01 15:10:19 +01:00
Ole André Vadla Ravnås 22b43c980f agent: Shut down Gum while still able to patch
When code memory is delegated to the host, Gum's early destructors need
to run while the host connection is still up, as some of them revert
hooks. Do so as the last step of removing instrumentation, which happens
inside unload() for agents that delegate.
2026-11-01 15:06:45 +01:00
Ole André Vadla Ravnås 00d8ba24d0 linux: Ignore stray library mappings in mapper
A process may map a library file outside of the dynamic linker. When
such a mapping came before the loaded image, the mapper used it as the
library's base and bound imports into non-executable memory.

Pick each library's base through find_module_by_path(), which prefers
the mapping that the linker actually loaded.

Fixes #1278.
2026-11-01 14:42:47 +01:00
Ole André Vadla Ravnås bf5d2e0516 windows: Include winternl.h for NTSTATUS
The helper backend glue relied on NTSTATUS being pulled in indirectly,
which is no longer the case with recent SDKs.
2026-11-01 14:14:55 +01:00
Ole André Vadla Ravnås cb997b4131 windows: Keep agent mapped while target exits
When the target exits, its other threads are terminated before DLL
detach notifications run, including the agent's. We then saw the
injector thread end without the agent having asked to stay resident,
and unmapped it while hooks such as the unwind broker's were still in
place. Anything running during the remainder of the shutdown could then
call into unmapped memory, e.g. the shim engine walking the stack.

Leave the agent mapped when the process is already exiting.

Fixes frida/frida-gum#1200.
2026-11-01 14:12:34 +01:00
Ole André Vadla Ravnås e106e11a07 subprojects: Bump outdated 2026-10-31 00:48:09 +01:00
Ole André Vadla Ravnås fa87aae7e4 subprojects: Bump outdated 2026-10-30 23:12:48 +01:00
Ole André Vadla Ravnås 340cca8edc subprojects: Bump outdated 2026-10-30 21:59:02 +01:00
Ole André Vadla Ravnås 275ce5a663 submodules: Bump releng 2026-10-30 21:59:01 +01:00
Ole André Vadla Ravnås aa97282d85 Update URLs of repos moved to frida-forks 2026-10-30 21:56:44 +01:00
Leo Conforti 6f68beba34 tests: Check that Linux spawn leaves SIGCHLD alone
A spawned process used to make GLib replace the host's SIGCHLD handler
with its own, and reset it to SIG_DFL once the process was gone, which
kept Node.js from reaping its child_process children. And if the host
reset SIGCHLD in the meantime, the spawned process was never reaped.

Both rely on GLib using pidfd for child watches, as it does since
frida/glib#17, so they are skipped on kernels without pidfd.

See frida/frida-node#121.
2026-10-30 20:40:24 +01:00
Ole André Vadla Ravnås f915561295 subprojects: Prepare for release 2026-10-30 14:33:53 +01:00
Ole André Vadla Ravnås 6429170a01 subprojects: Bump outdated 2026-10-30 14:01:20 +01:00
Ole André Vadla Ravnås 256ff4ee4f host-session: Unload delegating agents on close
Wait for in-flight agent unloads before cancelling pending I/O, so the
AgentController stays registered while agents that delegate code memory
to us revert their instrumentation. Also unload such agents instead of
just dropping their connection, so they don't stay resident.
2026-10-30 12:43:25 +01:00
Ole André Vadla Ravnås a5da784ccb windows: Guard tempdir state behind !HAVE_MAPPER 2026-10-30 11:17:04 +01:00
Ole André Vadla Ravnås 5666eb9043 subprojects: Bump outdated 2026-10-30 07:08:27 +01:00
Ole André Vadla Ravnås 8dd3c63a63 subprojects: Bump outdated 2026-10-30 06:59:55 +01:00
Ole André Vadla Ravnås 678f2710dc submodules: Bump releng 2026-10-30 06:59:54 +01:00
Ole André Vadla Ravnås 40820e1edf subprojects: Bump outdated 2026-10-29 23:02:08 +01:00
Ole André Vadla Ravnås 80b07d161e base: Use efni's GCD and XPC bindings
efni now ships identical bindings on all Darwin OSes, so drop our
duplicates to fix the redefinition errors.
2026-10-29 22:46:21 +01:00
Ole André Vadla Ravnås d4d2f603da subprojects: Bump outdated 2026-10-29 21:52:59 +01:00
Ole André Vadla Ravnås 09d80667e0 subprojects: Bump outdated 2026-10-29 21:28:10 +01:00
Ole André Vadla Ravnås 42d5e9ec9a submodules: Bump releng 2026-10-29 21:28:10 +01:00
Ole André Vadla Ravnås b816986f65 barebone: Sync the agent build docs with CI
The XNU agent is built for none-arm64-softfloat_pic with a target spec
from its directory, and the SDKs are published prebuilt. The kext is
built for macos-arm64e-kernel.
2026-10-29 21:24:32 +01:00
Ole André Vadla Ravnås 5f67e4665c barebone: Port the agent to GumMachoModule
The XNU agent still used the GumDarwinModule API, which was renamed.
2026-10-29 21:17:08 +01:00
Ole André Vadla Ravnås 24f9b838f3 agent: Delegate code memory to the host if needed
When the process can't create code itself, e.g. due to ACG on Windows,
plug in Gum memory hooks that have the host allocate and protect code
pages through the AgentController. Tear down instrumentation before
replying to unload(), while the host is still able to serve requests.
2026-10-29 19:33:57 +01:00
Ole André Vadla Ravnås 4833152542 windows: Drop unused derive_basename() binding
It's only called from the C glue.
2026-10-29 19:33:57 +01:00
Ole André Vadla Ravnås 3c0c3a61b9 windows: Remove unreachable catch clause 2026-10-29 19:33:57 +01:00
Ole André Vadla Ravnås 14b506d31c linux: Inject agents through Gum.Mapper
Map the agent out-of-process instead of having the target dlopen() it.
The loader loads any of the agent's dependencies that the target lacks,
reserves memory, and reports its address back. Imports are then
resolved against the target's libraries, and the loader is sent a plan
that it applies: the read-only segments are mapped straight from the
agent's file, so their pages are shared across targets, while the rest
is streamed over the control socket. The loader also applies
protections, runs the constructor, and on unload runs the destructor
and unmaps.

The target's libraries are opened through /proc/<pid>/map_files when
permitted, then through /proc/<pid>/root with an inode check, so
targets in containers resolve against their own files. When neither
works, e.g. because a library was replaced on disk, the image is copied
from the target's memory.

Gated by the mapper option, now enabled by default on Linux, and used
by the ptrace, procmem and kernel injectors alike. No memfd support is
required.
2026-10-29 19:33:57 +01:00
Ole André Vadla Ravnås 299b18ea4b linux: Tolerate the target exiting before detach
An agent may make its process exit right away, e.g. through exit() in
its entrypoint, and if that happens before we detach, the detach fails
with ESRCH even though the injection succeeded.
2026-10-29 19:33:57 +01:00
Ole André Vadla Ravnås ad89867ffd agent-container: Pass ctrlfd through parameters
The Linux container handed the agent its control socket through a
LinuxInjectorState, making it the only non-injected caller that passes
an injector state. Encode it in the transport URI instead and pass NULL
like on the other OSes.
2026-10-29 19:33:57 +01:00
Ole André Vadla Ravnås dbfb39c7d5 windows: Inject agents filelessly via Gum.Mapper
Map the agent straight into the target with the cross-platform
Gum.Mapper instead of writing a temporary DLL and loading it through
LoadLibrary. A small emitted stub runs the mapped images' constructors,
calls the agent's entrypoint, and runs the destructors when it is not
staying resident, in which case the mapper then unmaps it.

The agent is copied once into a pagefile-backed section per
architecture, whose handle helpers duplicate to map it, so its code
pages are shared across every target. The dbghelp and symsrv DLLs are
mapped alongside it and handed to the agent, so symbolication doesn't
load them either. The agent cloaks these mappings.

Gated by the existing mapper option, now enabled by default on
Windows, so nothing touches the filesystem; the LoadLibrary path stays
for builds with the mapper disabled. On either path the agent is handed
the range of the stub, so it stays cloaked for as long as it runs.
2026-10-29 19:33:57 +01:00
Ole André Vadla Ravnås 51a7762e97 darwin: Adopt the cross-platform Gum.Mapper API
The Mach-O parser and mapper were renamed from GumDarwin* to GumMacho*
in frida-gum, and the mapper now allocates the payload itself, with the
image first, followed by the runtime and the extra memory holding our
stub code, data and stack. It also no longer drives Mach APIs on the
resolver's task, so give it a GumNativeProcessMemory for the target
task.
2026-10-29 19:04:01 +01:00
Ole André Vadla Ravnås 2d4291add2 barebone: Adopt renamed Gum.Macho module API
The Mach-O parser types were renamed from Gum.Darwin* to Gum.Macho* in
frida-gum; follow suit here.
2026-10-29 19:04:01 +01:00
Ole André Vadla Ravnås 0ac43adad3 fruity: Adopt renamed Gum.Macho module API
The Mach-O parser types were renamed from Gum.Darwin* to Gum.Macho* in
frida-gum; follow suit here.
2026-10-29 19:04:01 +01:00
Ole André Vadla Ravnås 74c60230a4 build: Pass along Vala packages of efni and idun
When they're subprojects their vapis are passed by path, so valac never
sees the packages they refer to, such as json-glib-1.0 and libsoup-3.0.
Use the packages they export through "vala_packages".
2026-10-29 19:04:01 +01:00
Ole André Vadla Ravnås 1d3d7beaf8 tests: Use the right format modifier for uint64 2026-10-29 19:04:01 +01:00
Ole André Vadla Ravnås c44d56f175 tests: Close the device manager inside the try
Otherwise its IOError goes unhandled.
2026-10-29 19:04:01 +01:00
Ole André Vadla Ravnås 2986e7f8a8 build: Pass the Go config to Meson by reference
Have Meson write go-config.json through configure_file(), instead of
referring to the file detect.py wrote by name, which Meson warns about
and will reject in 2.0.
2026-10-29 19:04:01 +01:00
Ole André Vadla Ravnås 74e9d14a5d compat: Use parent releng when symlinked
Resolving REPO_ROOT followed a symlinked subprojects/frida-core to the
frida-core checkout, so the parent's releng was not found and
frida-core's own was used instead. When that pinned a different deps
version, it wiped the shared toolchain in the middle of the build.
2026-10-29 10:23:36 +01:00
Håvard Sørbø c817b9b2f4 server: Expand system session entitlements (#1274) 2026-10-26 15:24:48 +01:00
Ole André Vadla Ravnås 61708ddb0c subprojects: Bump outdated 2026-10-23 13:51:27 +02:00
Ole André Vadla Ravnås 4d55da3b5f submodules: Bump releng 2026-10-23 13:51:26 +02:00
Ole André Vadla Ravnås e0819e7917 subprojects: Bump outdated 2026-10-20 21:57:01 +02:00
Ole André Vadla Ravnås d9a1a4de1e subprojects: Bump outdated 2026-10-20 21:39:11 +02:00
Ole André Vadla Ravnås c0ac0832b8 submodules: Bump releng 2026-10-20 21:39:11 +02:00
Ole André Vadla Ravnås 802c13ea18 subprojects: Bump outdated 2026-10-20 21:33:12 +02:00
Ole André Vadla Ravnås 18f6f47e18 subprojects: Bump outdated 2026-10-20 20:23:35 +02:00
Ole André Vadla Ravnås ac41ddece4 build: Sync wrappers with releng 2026-10-20 20:21:18 +02:00
Ole André Vadla Ravnås 67621c8022 submodules: Bump releng 2026-10-20 20:21:18 +02:00
Ole André Vadla Ravnås 63033655d3 fruity: Embed idund for embedded assets
Idun now always goes through idund on Windows and Linux, so ship the
daemon inside the library when assets are embedded, extracting it to a
temporary file before starting the device monitor. With installed
assets Idun installs idund next to the library and resolves it from
there. A daemon that fails to start is reported on stderr and leaves
Apple devices unavailable instead of failing the whole backend.
2026-10-20 14:29:46 +02:00
Ole André Vadla Ravnås 9de990422c fruity: Migrate pairing records to Idun
Idun keeps its pairing identity and peers under its own config
directory now. Move existing records over on first use so pairings
survive the upgrade. Drop this on the next major bump.
2026-10-20 14:29:46 +02:00
Ole André Vadla Ravnås 819a797f31 Split out Efni and Idun as subprojects
Move the generic building blocks in lib/base into Efni, and the Apple
device protocol stack in src/fruity into Idun, both consumed as Meson
subprojects. The Gum-dependent injector and the host session stay as
the fruity backend on top of libidun. Tests for the moved code go
with it.

Efni has its own error domain; frida-core translates it to Frida.Error
at interface boundaries, and awaits go through a Promise facade so the
rest of the code keeps seeing Frida.Error. Barebone builds directly on
the Efni debugger abstraction and uses Efni.Error throughout.
2026-10-20 14:29:46 +02:00
Ole André Vadla Ravnås 6fb6bb6f05 subprojects: Bump outdated 2026-10-20 14:25:13 +02:00
Ole André Vadla Ravnås 43a961a6da submodules: Bump releng 2026-10-20 14:25:13 +02:00
Ole André Vadla Ravnås 943ebe2b92 build: Disable sysprof unconditionally in GLib
sysprof is unused and fails to build on several targets, so disable it
for every machine in the GLib fallback options instead of only QNX.
While here, order the options to follow GLib's meson.options, with the
conditional ones grouped last in the same order.
2026-10-20 13:52:21 +02:00
Ole André Vadla Ravnås 30c276063b socket: Resume WebSocket input on its own thread
Dispatch resume_input() to the main context that owns the WebSocket.
libsoup reads synchronously from it, which raced with that context's
thread over the incoming buffer and crashed in process_frame().
2026-10-19 21:31:35 +02:00
Ole André Vadla Ravnås 741dbe0642 fruity: Fix crash stopping Linux pairing browser
Skip the backend and don't stop it when stop() arrives before start()
has created it.
2026-10-19 20:52:10 +02:00
Ole André Vadla Ravnås 09968c4c5a fruity: Build SystemNetworkStack on macOS
Split VirtualNetworkStack, which needs lwIP, out of network-stack.vala
and build the rest on all hosts. The macOS tunnel uses UdpSocket and
SystemNetworkStack.
2026-10-19 20:52:10 +02:00
Håvard Sørbø 418f4727d1 socket: Cap total WebSocket message size
Newer libsoup also limits the size of reassembled fragmented messages
through max-total-message-size. Set it to the same limit as the
incoming payload size when the property is available.
2026-10-19 20:49:52 +02:00
Ole André Vadla Ravnås 3b1fea7623 fruity: Add UDP service session 2026-10-19 17:08:16 +02:00
Ole André Vadla Ravnås 0fac460ce9 fruity: Support uint64 and double in XPC requests 2026-10-19 17:08:16 +02:00
Ole André Vadla Ravnås 8e289a29d5 meson: Pass fallbacks the options deps.toml does
Drop the stale QuickJS bignum option, which broke configure.
2026-10-19 12:27:21 +02:00
Ole André Vadla Ravnås 57161b0ddd subprojects: Prepare for release 2026-10-19 10:33:25 +02:00
Ole André Vadla Ravnås 940e7d7277 subprojects: Bump outdated 2026-10-19 10:30:42 +02:00
Ole André Vadla Ravnås 0d793d22be submodules: Bump releng 2026-10-19 10:30:41 +02:00
Ole André Vadla Ravnås 81fac8f2ac fruity: Suggest firewall on mDNS reply timeout 2026-10-18 23:48:35 +02:00
Ole André Vadla Ravnås 8ff7b81b95 fruity: Fall back to Avahi for mDNS browsing 2026-10-18 23:48:35 +02:00
Ole André Vadla Ravnås 22fb4aecff fruity: Fix XPC flow control for large replies 2026-10-18 23:33:20 +02:00
Ole André Vadla Ravnås 38e33a9517 fruity: Decode double and date XPC objects 2026-10-18 23:33:20 +02:00
Ole André Vadla Ravnås 295c11cafc subprojects: Prepare for release 2026-10-18 13:53:35 +02:00
Ole André Vadla Ravnås 0ab2372653 subprojects: Bump outdated 2026-10-18 13:52:06 +02:00
Håvard Sørbø 4993c7e24c darwin: Don't leak control socket to helper 2026-10-18 13:11:19 +02:00
Ole André Vadla Ravnås 0d8d6def25 subprojects: Prepare for release 2026-10-17 12:54:25 +02:00
Ole André Vadla Ravnås a0c4988578 subprojects: Bump outdated 2026-10-17 12:53:30 +02:00
Ole André Vadla Ravnås 775237d99c submodules: Bump releng 2026-10-17 12:53:29 +02:00
Ole André Vadla Ravnås 522193f030 compiler: Inject global as globalThis
Node.js code and packages commonly refer to it.
2026-10-17 12:31:52 +02:00
Ole André Vadla Ravnås fbdfce3b12 compiler: Update package-lock.json 2026-10-17 12:26:34 +02:00
Ole André Vadla Ravnås 6dba2c4fa9 compiler: Bump @frida/process to 1.3.0 2026-10-17 12:19:19 +02:00
Ole André Vadla Ravnås 1a0cf99335 compiler: Inject Blob as a global 2026-10-17 12:19:19 +02:00
Ole André Vadla Ravnås 92bf833ebe compiler: Inject Response as a global 2026-10-17 12:19:19 +02:00
Ole André Vadla Ravnås 072613ac5f compiler: Add stream/web shim
Also inject ReadableStream as a global.
2026-10-17 12:19:19 +02:00
Ole André Vadla Ravnås a08a756dfb compiler: Add depd shim
The real one needs V8 stack traces.
2026-10-17 12:19:19 +02:00
Ole André Vadla Ravnås 3407bc73c2 compiler: Return default exports from require()
Node hands out module.exports, which for modules such as events is
the default export itself. Merge the named exports into it.
2026-10-17 12:19:19 +02:00
Ole André Vadla Ravnås 4115121519 compiler: Stub out unsupported Node builtins
Resolve them to an empty module, like frida-compile used to, so that
code which only touches them on optional paths can still load.
2026-10-17 12:19:19 +02:00
Ole André Vadla Ravnås eed070336e compiler: Bump @frida/http-parser-js to 1.1.0 2026-10-17 11:37:23 +02:00
Ole André Vadla Ravnås 070e46703e compiler: Bump @frida/crypto to 1.1.0 2026-10-17 11:21:11 +02:00
Ole André Vadla Ravnås 107335fe7b subprojects: Prepare for release 2026-10-16 15:37:39 +02:00
Ole André Vadla Ravnås df4fda9e9e subprojects: Bump outdated 2026-10-16 15:36:15 +02:00
Håvard Sørbø 7013560bb5 patterns: Fix std::mem::base_address()
Return the address the data is loaded at, as ImHex does, so a pattern
can turn an absolute pointer into an offset. A section is its own
address space and keeps 0.
2026-10-16 13:54:04 +02:00
Håvard Sørbø 394d285192 patterns: Skip pointer_base fields in views
A view cannot run the base function, so it read such a field as an
absolute pointer.

Leave these fields out of the view, toJSON(), pattern() and the
declarations, and warn that they are only resolved when parsing.
2026-10-16 13:54:04 +02:00
Håvard Sørbø c6b5dc3d26 patterns: Fix pointer_base offset resolution
A base function returns an offset in pattern space, like $ and
addressof(parent), but the decoder and parse() added it to the raw
value as an address. That only worked at address zero.

Resolve the target as the raw value plus the base, with $ at the
pointer field while the base function runs. Sign-extend sized
pointers such as T *p : s32, and treat zero as no target.

Add ImHex's std::ptr::relative_to_* functions and report the base
function in the type description.
2026-10-16 13:54:04 +02:00
Ole André Vadla Ravnås aebfac99ff subprojects: Bump outdated 2026-10-16 12:42:33 +02:00
Ole André Vadla Ravnås e36eb4e27a submodules: Bump releng 2026-10-16 12:42:33 +02:00
Ole André Vadla Ravnås 47d6c50906 meson: Disable zstd in libsoup fallback
Match the SDK's libsoup build.
2026-10-15 19:25:49 +02:00
Ole André Vadla Ravnås 0ee52b15b4 subprojects: Bump outdated 2026-10-15 18:18:02 +02:00
Ole André Vadla Ravnås c9867a73b0 subprojects: Bump outdated 2026-10-15 15:45:54 +02:00
Ole André Vadla Ravnås 793ef53bd5 submodules: Bump releng 2026-10-15 15:45:54 +02:00
Ole André Vadla Ravnås 345ce8c1a3 subprojects: Bump outdated 2026-10-15 15:13:03 +02:00
Ole André Vadla Ravnås de16aacccb barebone: Drop the g_io_watch_funcs stub
GLib now guards its IO watch references in gsourceclosure.c, so the
freestanding build no longer leaves the symbol undefined.
2026-10-15 14:44:08 +02:00
Ole André Vadla Ravnås 584b985c61 compiler: Serialize library output captures
The emitter writes files from several goroutines at once, so capturing
its output into a plain map crashed the process with concurrent map
writes.
2026-10-15 14:44:08 +02:00
Ole André Vadla Ravnås c731da6148 compiler: Tolerate library builds without sources
A watch rebuild that runs after the project's files are gone has no
sources and no patterns, and commonDirectory() then indexed an empty
slice, panicking the whole process.
2026-10-15 14:44:08 +02:00
Ole André Vadla Ravnås 1abfff3d16 libc-shim: Provide QNX's stdio objects
QNX's stdout is &_Stdout rather than a pointer variable, so code built
against its stdio.h handed libc's FILE to our fprintf(), which crashed
on its first log message. Define _Stdin, _Stdout and _Stderr ourselves,
and bind the slots through stdin, stdout and stderr.
2026-10-15 14:44:08 +02:00
Ole André Vadla Ravnås f3285577c1 gadget: Adapt to minizip-ng 4.x 2026-10-15 14:44:08 +02:00
Ole André Vadla Ravnås 0d348a800b Adapt to UnixSocketAddress.with_type changes
The new GIO bindings take the path as a byte array.
2026-10-15 14:44:08 +02:00
Ole André Vadla Ravnås 8c9f7928f4 agent: Drop the unused agent_dep
Newer Meson rejects link_with on a custom target whose output lacks a
library suffix, as the per-ABI agent does when building universal.
2026-10-15 14:44:08 +02:00
Ole André Vadla Ravnås 5480f6c5ed subprojects: Bump outdated 2026-10-15 14:44:08 +02:00
Ole André Vadla Ravnås d6b95cd124 compat: Adapt to Meson's option store
Meson now keeps options in an OptionStore rather than on CoreData, and
OptionKey no longer knows whether it is a project or builtin option.
2026-10-15 14:24:36 +02:00
Ole André Vadla Ravnås 63e5a4cf68 subprojects: Bump outdated 2026-10-15 14:24:36 +02:00
Ole André Vadla Ravnås a37ec4e5c1 submodules: Bump releng 2026-10-15 14:24:36 +02:00
Ole André Vadla Ravnås 2a1ed339d9 tests: Reuse remove_tree() in pattern tests 2026-10-14 10:50:36 +02:00
Ole André Vadla Ravnås 38dc1a7030 tests: Order compiler tests by dependency 2026-10-14 10:50:36 +02:00
Ole André Vadla Ravnås ee6b0a8eb3 tests: Drop static from namespace functions 2026-10-14 10:50:36 +02:00
Peter A. ce74a3c90d compiler: Stop watch session on dispose
Releasing a Compiler right after starting a watch could re-enter
finalization while a build callback took a reference, deleting the same
cgo.Handle twice. Stop the watch in dispose() and clear the handle
before calling into the backend.

Hold the session lock while starting the watch, so callbacks only see a
fully initialized session, and make disposal terminal so a queued
rebuild cannot recreate a context after the delegates are gone. Release
the delegates when construction fails.
2026-10-14 10:50:36 +02:00
Ole André Vadla Ravnås b03488ac72 subprojects: Prepare for release 2026-10-09 18:57:29 +02:00
Ole André Vadla Ravnås b62b0e5d7d subprojects: Bump outdated 2026-10-09 18:56:24 +02:00
Ole André Vadla Ravnås 78028da11b subprojects: Prepare for release 2026-10-09 14:19:16 +02:00
Ole André Vadla Ravnås 61ce698601 subprojects: Bump outdated 2026-10-09 14:14:52 +02:00
Ole André Vadla Ravnås 95b9cd3bae barebone: Stub g_io_watch_funcs in the Linux .ko
A freestanding GLib builds neither giounix.c nor giowin32.c, so
g_io_watch_funcs is defined nowhere, yet gobject's gsourceclosure.c
takes its address. The kernel module must resolve every symbol, and
the agent never creates a GIOChannel watch, so provide a weak
placeholder that GLib's own definition overrides once the freestanding
build ships one.
2026-10-08 17:01:18 +02:00
Ole André Vadla Ravnås d8a73dae8b android: Stop truncating app icons
The Base64OutputStream was never closed, so its final group was never
written.
2026-10-08 16:37:42 +02:00
Ole André Vadla Ravnås 8947659b2f subprojects: Prepare for release 2026-10-08 14:48:53 +02:00
Ole André Vadla Ravnås d0895b4026 subprojects: Bump outdated 2026-10-08 14:29:30 +02:00
Ole André Vadla Ravnås 8d0e8ad85e compiler: Bump frida-fs to 7.1.1 2026-10-08 14:04:26 +02:00
Ole André Vadla Ravnås cd0df43cdc barebone: Report the Linux copy's errno
SystemFunction reads the system error through gum_thread_get_system_error,
which linux-injected left to the stub returning 0, so failures came back
with errno 0. Read the C library's thread-local errno instead, resolving
__errno_location on glibc and musl or __errno on Bionic.
2026-10-08 13:08:19 +02:00
Ole André Vadla Ravnås 40d053dd51 barebone: Drop the Linux copy-status logging
The kernel half already detects a copy's exit through the
sched_process_exit tracepoint and detaches the session, so logging
what each copy hit was only noise. Remove it, along with the helpers
left unused.
2026-10-08 12:52:13 +02:00
Ole André Vadla Ravnås 7962abb3a3 barebone: Resolve a Linux copy's own exports
gum_module_find_global_export_by_name fell through to the kernel
symbol table for a linux-injected copy, so userspace names like
opendir never resolved and frida-fs could not list a directory.
Search the copy's mapped modules instead, as the Windows and XNU
copies already do.
2026-10-08 11:26:49 +02:00
Ole André Vadla Ravnås a94cd60e60 barebone: Keep the Linux kernel symbols alive
The upcast to LinuxKernelSymbols inside the ?: freed the System.map
before set_kernel stored it, leaving image.kernel dangling and the
session reading freed memory once the config was parsed. Assign each
image kind directly instead.
2026-10-08 11:03:14 +02:00
Ole André Vadla Ravnås d784b73d12 compiler: Bump frida-fs to 7.1.0 2026-10-08 09:07:23 +02:00
Ole André Vadla Ravnås f0bd948a71 barebone: Warm async I/O before the loop runs
GLib brings its worker context and GTask pool up lazily, on the
first async GIO call. Inside the agent's live loop that wedges it,
so warm them at copy startup while still single-threaded.
2026-10-08 05:13:36 +02:00
Ole André Vadla Ravnås fc2c0101a1 barebone: Give the win9x loop its own wake event
The main loop must be wakeable even when GLib watches no handles
and no timer is due, where it sleeps INFINITE. Wait on a dedicated
event in poll_handles instead of a token slot shared with the send
path, and set it from poke_loop_wakeup.
2026-10-08 04:12:42 +02:00
Ole André Vadla Ravnås f911715ebf barebone: Enable GLib's IO features for the agent
The diet GLib leaves the private vtable's IO entries null until
glib_enable_io_features() runs, so the first GIO user faulted on a
null g_get_worker_context. Streams are that first user.
2026-10-08 02:49:56 +02:00
Ole André Vadla Ravnås 8c206ea6cf barebone: Back streams and ANSI in the copies
Hand Gum the poll, read, write and close of the user copies so
scripts get Win32InputStream, Win32OutputStream and their UNIX
counterparts, and convert ANSI strings through the code page of
the process. The Win9x copy waits on handles like the NT one.
2026-10-08 00:41:42 +02:00
Ole André Vadla Ravnås 677f9c8379 barebone: Share the windows_fn! macro
The NT kernel half and the user copies each had a copy; the Win9x
copy wants it too.
2026-10-08 00:41:42 +02:00
Ole André Vadla Ravnås 474d8aab5a barebone: Expose the last error of Windows copies
Back gum_thread_get_system_error() and its setter with
GetLastError() and SetLastError() when running in the userspace
copy, so SystemFunction and Interceptor report lastError.
2026-10-08 00:41:42 +02:00
Ole André Vadla Ravnås 3302340a48 barebone: Enumerate the imports of Windows modules
Walk the import directory of a module in the process the copy runs
in, the way exports already are, so Module.enumerateImports() stops
coming back empty on Win9x and NT.
2026-10-08 00:41:42 +02:00
Ole André Vadla Ravnås b2161e02aa compiler: Bump frida-fs to 7.0.1
Its stat field reads passed the path to NativePointer#read*(), which
now take an optional offset.
2026-10-07 20:35:36 +02:00
Ole André Vadla Ravnås 6880660481 subprojects: Revert the termux-elf-cleaner bump
Its new code needs C++20, which the host tool build does not use.
2026-10-07 15:46:21 +02:00
Ole André Vadla Ravnås 94dc97155f subprojects: Prepare for release 2026-10-07 14:26:53 +02:00
Ole André Vadla Ravnås 16c98b23d0 subprojects: Bump outdated 2026-10-07 14:23:37 +02:00
Ole André Vadla Ravnås 9f9428208e submodules: Bump releng 2026-10-07 14:23:36 +02:00
Ole André Vadla Ravnås ce8d8889a7 compiler: Fix the pattern bundle tests
The library fixture declared Vec3 beside the geometry import meant to
provide it, and the alias assertion predates shared imported units,
which bind the alias to the shared class instead of a copy.
2026-10-07 14:10:00 +02:00
Ole André Vadla Ravnås 1d571ec980 patterns: Let hosts define preprocessor macros
Add a defines dictionary to PatternCompileOptions, carried by the
module into decode and call, and take the language server's from
settings.patterns.defines on workspace/didChangeConfiguration. A
pattern can then branch on where its data comes from, such as a
file or a mapped image, without knowing its host.
2026-10-07 14:05:05 +02:00
Ole André Vadla Ravnås 904a4a12ed patterns: Report endless template recursion
A template that instantiates itself with a growing argument never
reaches a cached shell, so the resolver recursed until the stack ran
out. Stop after 64 nested instantiations and say so.
2026-10-07 14:05:05 +02:00
Ole André Vadla Ravnås 7ada909cf8 patterns: Hand ref parameters the pattern
A ref parameter typed auto received the field's value, so a function
formatting a bitfield or struct found no members. Pass the pattern as
is done for parameters of a composite type.
2026-10-07 14:05:05 +02:00
Ole André Vadla Ravnås cc56d552ad patterns: Find template locals by name as fallback
A template instantiated from two enclosing templates with a parameter
of the same name shares one shell, so the argument it captured names
the other instantiation's local. Fall back to the local of that name.
2026-10-07 14:05:05 +02:00
Ole André Vadla Ravnås 1f28539920 patterns: Answer array_index() inside an element
The index lived on the frame that owns the array, so a member of the
element type asked its own frame and always got zero. Walk up to the
frame that is iterating.
2026-10-07 14:05:05 +02:00
Ole André Vadla Ravnås 37dc612b12 patterns: Keep enum names through member access
Only a field named directly was labelled with its enumerator; a member
of a struct, a bitfield member, an element or a ref parameter gave the
bare number. Hand builtins that take a pattern the pattern itself, so
formatted_value() formats scalars and bitfields too.
2026-10-07 14:05:05 +02:00
Ole André Vadla Ravnås d26d4c88d4 patterns: Look up fields by name as a fallback
A template instantiated with a field argument is shared by every
enclosing type that spells the argument the same way, so the field it
captured belongs to whichever instantiation came first. Fall back to
the decoded field of that name.
2026-10-07 14:05:05 +02:00
Ole André Vadla Ravnås b69b0ebd61 barebone: Stop scribbling on the Win9x gone ring
on_module_load bumped the words at arena 0xb0 and 0xb4, which are
the head and tail of the departed-thread ring. A target that
loaded a library while attached shifted the ring under the copy,
which then read a stale slot and reported a thread that never left.
2026-10-07 09:55:05 +02:00
Ole André Vadla Ravnås d5ad5e288f barebone: Let the Win9x copy's main thread exit
Return from the main thread and let it run ExitThread on the
injection stack. KERNEL32 made the thread's own stack in the
process that created it, thus point the TIB and the TDB at the
injection stack first. Free the memory only once VMM destroys the
thread.

The thread used to park in the image and on the injection stack,
which detach freed. It crashed the process once a later injection
reused that memory.
2026-10-07 02:16:22 +02:00
Ole André Vadla Ravnås 557824d30b barebone: Free the Win9x copy's start stack
The injected thread leaves it as soon as the trampoline switches to
the injection stack, which is before it reports that it runs.
2026-10-07 02:16:22 +02:00
Ole André Vadla Ravnås 276b45047d barebone: Wake the Win9x copy when detaching
The copy's worker sleeps in an alertable wait until an APC wakes it,
thus it never saw the stop request. Detach timed out after ten
seconds, the host stopped waiting for the agent to leave, and the
next session ran alongside the old agent and its VxD descriptor.
That bluescreened the guest.
2026-10-07 02:16:22 +02:00
Ole André Vadla Ravnås 53eac9e6ae barebone: Let Win9x page in what the agent reads
Hand a fault on an absent page back to Windows when Gum does not
claim it. The agent reads the memory of other processes, and such
a page is paged out, not invalid. Explorer's image headers were
one, thus attaching to it parked the agent while it was switched
into Explorer's memory context, and the guest bluescreened.
2026-10-07 00:51:57 +02:00
Ole André Vadla Ravnås d1f5966ca6 subprojects: Prepare for release 2026-10-05 10:00:26 +02:00
Ole André Vadla Ravnås 9748fa155a subprojects: Bump outdated 2026-10-05 09:56:55 +02:00
Ole André Vadla Ravnås 8eab4bb94b submodules: Bump releng 2026-10-05 09:56:54 +02:00
mbv06 5a2f81d600 android: Scan boot-image-methods.art for setArgV0
Newer ART copies the boot image's methods section into its own memfd
mapping (see Jit::MapBootImageMethods()), so look for the setArgV0()
JNI slot there too.
2026-10-05 09:54:50 +02:00
Ole André Vadla Ravnås 308b99f1e0 subprojects: Prepare for release 2026-10-04 11:35:27 +02:00
Ole André Vadla Ravnås 9b748ea649 subprojects: Bump outdated 2026-10-04 11:34:39 +02:00
Ole André Vadla Ravnås ea9efa1e24 subprojects: Prepare for release 2026-10-03 07:56:08 +02:00
Ole André Vadla Ravnås bb012fa0bb subprojects: Bump outdated 2026-10-03 07:55:12 +02:00
Ole André Vadla Ravnås 4f49ef529e compiler: Add library builds
Let Compiler.build_library() emit a package's modules, declaration
files and source maps, one set per source, with the patterns they
import copied alongside and their declarations beside them, so a
library needs no tsc of its own and its consumers compile the patterns
themselves. Add watch_library() to keep the output fresh as the
sources change.
2026-10-02 22:38:22 +02:00
Ole André Vadla Ravnås f3e525f6b0 subprojects: Prepare for release 2026-10-02 20:52:52 +02:00
Ole André Vadla Ravnås 065c8ed0dc compiler: Absolutize project roots
A relative root crashed builds in the TypeScript VFS, and left pattern
file names relative to the working directory.
2026-10-02 20:52:52 +02:00
Ole André Vadla Ravnås 5f454c7281 subprojects: Bump outdated 2026-10-02 20:26:50 +02:00
Ole André Vadla Ravnås 1963f365a4 compiler: Compile patterns from files
Take an entrypoint and a project root, like Compiler.build(), so imports
and includes resolve the way they do in agent builds, and name the file
of each type and diagnostic relative to the project root. Resolve the
same way in the language server, with open documents as overlays.
2026-10-02 19:16:46 +02:00
Ole André Vadla Ravnås ba1ab6c76f barebone: Fix the agent's warnings
Gate what only some flavors use, drop what none use, and take GPollFD
from the bindings instead of redeclaring it.
2026-10-02 03:11:47 +02:00
Ole André Vadla Ravnås 0b6733f7a9 barebone: Zero Arm vector state in CPU contexts
The kernel's CpuState has no v[], which broke the 32-bit Arm build.
2026-10-02 03:11:47 +02:00
Ole André Vadla Ravnås 9bd22b4a3e compiler: Probe for the MinGW compiler per flavor
MSYS2 ships every flavor's directory, so an empty ucrt64/ shadowed a
MINGW64-only toolchain. Pick the first flavor that has the compiler,
else fall back to PATH.
2026-10-02 03:11:47 +02:00
Ole André Vadla Ravnås 1a069a5d6c subprojects: Prepare for release 2026-10-02 01:36:34 +02:00
Ole André Vadla Ravnås a9c70dd907 subprojects: Bump outdated 2026-10-02 01:32:02 +02:00
Ole André Vadla Ravnås 3c51ca3563 ci: Use UCRT64 for the Windows cgo toolchain 2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 8f03b3a4a9 compiler: Prefer UCRT64 when building with cgo
MSYS2 deprecates MINGW64, and UCRT matches the CRT that MSVC links
against. Fall back to MINGW64 where UCRT64 is missing.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 75e5310101 compiler: Trim redundant work from $parse
Hoist the environment's base, compute each field's offset once, and
check each run of static fields once. Start such runs after dynamic
fields too in packed structs, and let constant attributes stay on them.
Parse structs and fixed arrays without allocating pairs, and derive a
pattern's address on demand.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås a766c09cbe compiler: Take NativePointers as given
Stop wrapping addresses in ptr(), and declare NativePointer rather than
NativePointerValue.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 8c430cff4e compiler: Share imported types and functions
Emit each imported unit once, keyed by its path and incoming macros,
and bind its declarations in importers, also under an alias.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 9e4c5cb215 compiler: Honour the byte order of casts
Swap be u16(x) and its kin when the order differs from the default.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 98cc87e1d5 compiler: Check bounds before reading pattern data
Report a read past the data as truncation rather than faulting, except
in growable sections. Let std::mem's readers read zeros there, as ImHex
does.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 79a17eedfd compiler: Place patterns at 64-bit addresses
GumJS reads a u64 as a UInt64, which was taken for a section pointer.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås b3255dd311 compiler: Format patterns once decoding ends
ImHex formats lazily, so a format may refer to what follows. Format
the untransformed pattern, and let formatted_value() format on demand.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 9f83790b47 compiler: Expose patterns while they are decoded
So members can refer to it by name, as in ImHex.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 4130d6e57c compiler: Pass patterns to auto and struct params
Read a pattern as its value where a number or condition is expected.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 0918e8d70a compiler: Match ImHex's std::time
Exchange times as ImHex's packed u128, and implement the library
functions over them.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 55f88343db compiler: Copy patterns assigned to struct members 2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås bf33ca6184 compiler: Store values wider than 64 bits 2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås bd7d1857f0 compiler: Resolve builtin:: calls to the builtin
So a library function of the same name cannot shadow it.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 7594907131 compiler: Size strings held in locals 2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 653c876d8a compiler: Let the shim read zero bytes at the end 2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 005712427a compiler: Hash patterns through their extent 2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 75217cd95b compiler: Size static types from their layout
Decoding at the cursor ran the type's attributes on unrelated data.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 6aa0955e97 compiler: Apply alias attributes to struct fields
Such as the transform of type::uLEB128.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 2bfff35610 compiler: Read patterns used as numbers
Let patterns convert to their value, and make array lengths numbers.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 94d02474c1 compiler: Pass patterns by reference to attributes
Wrap the argument when the function's parameter is ref.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 9684d98b51 compiler: End struct parsing on a bare return 2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 2a155c6101 compiler: Keep the cursor across placed fields
A nested decode moved it, stretching the enclosing struct.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 8212dc7065 compiler: Pass template arguments to bitfields 2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 73a03e6f97 compiler: Resolve pattern members through pointers
Dereference pointer fields on member access, and resolve members the
compiler cannot type at runtime.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 2748be9323 compiler: Keep all bytes of fixed-size strings
So they compare equal to magics such as "SQLite format 3\x00". Decode
bytes that aren't UTF-8 as \uDCxx, as literals already are.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 26a5c2fd0f compiler: Follow ImHex's operator precedence
Bind bitwise operators tighter than comparisons.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås aba0cbf19a compiler: Read static pattern fields directly
Read fields with a known layout at literal offsets, and skip the pair
destructuring for fixed-width reads.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 3c8b2824c2 compiler: Decode patterns into $Pattern instances
Keep the type, arguments, parent and fields behind getters, rather
than defining them on every decoded value.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås bc1c2cf1e8 compiler: Track the pattern cursor as an offset
Describe field extents with a $Span, which derives the address on
demand.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 47edda06b2 compiler: Access pattern fields at an offset
Avoid allocating a NativePointer per field access.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 72bdfbc0d7 compiler: Order emitted patterns by abstraction
Emit callers before callees and drop unreachable functions. Load each
corpus module under node, as the order now affects evaluation.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås 6c1dd8e31f compiler: Share the pattern runtime across modules
Serve the helpers as a virtual module, so a bundle holds one copy. Give
each host constant a module of its own, so unused ones are dropped.
2026-10-02 01:26:51 +02:00
Ole André Vadla Ravnås ab0df9b070 barebone: Add version to the kernel Module stub 2026-10-02 00:03:43 +02:00
Ole André Vadla Ravnås 0c8081bdb1 barebone: Bump @types/frida-gum to 19.12.0 2026-10-02 00:03:43 +02:00
Ole André Vadla Ravnås 9c1ef0f10e compiler: Bump @types/frida-gum to 19.12.0 2026-10-02 00:01:29 +02:00
Ole André Vadla Ravnås 4859422d65 base: Keep Posix types out of the API
Consumers of frida-base should not need posix.vapi.
2026-10-01 21:03:20 +02:00
Ole André Vadla Ravnås dd0970bc72 ci: Set up MSVC without a Node 20 action 2026-10-01 20:44:53 +02:00
Ole André Vadla Ravnås 43a6e33ffd compiler: Add ImHex pattern language support
Lex, parse, resolve and lay out .hexpat sources in Go, evaluate them
against bytes, and emit JavaScript that does the same in an agent,
with TypeScript declarations. Embed the std library, with its builtins
on both sides.

Let agents import .pat and .hexpat files as ES modules. Add
PatternCompiler, which compiles a source into a PatternModule that
describes its types and decodes memory against them, visualizers
included. Serve .pat and .hexpat documents in the language server.

Share parses and compiled modules between builds, queries and the
language server. Like ImHex, fail on unresolved names only when the
code using them runs.

Run the ImHex-Patterns corpus and the PatternLanguage tests as opt-in
suites: 303 of 312 corpus files compile, the rest needing ImHex itself
or failing there too; all 78 reference cases pass, and all 55 under
node.
2026-10-01 20:44:53 +02:00
Ole André Vadla Ravnås a12a300a99 barebone: Fix build without the Droidy backend
Lift the ADB client out of the Droidy backend as src/adb.vala, with its
types renamed to Adb*, and build it with the base sources. Build the
Android helper client whenever the Barebone backend is enabled.
2026-10-01 20:44:53 +02:00
Ole André Vadla Ravnås a9cfb2c310 payload: Create stdio registries on first use
Gum's Interceptor now looks up the module of each function it hooks,
and gum_init_embedded() hooks the unwinder. That activates the module
registry, which reads /proc/self/auxv through fdopen() before the libc
shim has created its stream registry, crashing every Android agent on
load.
2026-10-01 19:08:53 +02:00
Ole André Vadla Ravnås 563de2b426 barebone: Serve macOS Android-emulator debug regs
Arming the hardware breakpoint sets MDCR_EL2.TDE so HVF routes the
guest's debug exceptions to the host, but TDE also forces TDA, so the
guest's own debug-register accesses trap too. QEMU's HVF path has no
handler for them and aborts the whole VM -- a rare crash on kernels
that touch those registers under injection, such as Android 16's 6.12.

Perform the trapped access against the vcpu and step over it, so the
guest keeps running and QEMU never sees an unhandled system register.
2026-10-01 01:04:16 +02:00
Ole André Vadla Ravnås 950b5c832d barebone: Filter macOS Android-emulator idle hits
run_until_schedule waits for a real task to hit the scheduler
breakpoint, but only one core carried it, and on an idle guest
that core mostly ran the idle task, so the search flaked. Arming
every core instead floods the search: the idle loop enters the
scheduler constantly and each hit costs a GDB round-trip.

Have each core arm the breakpoint on its own thread from the
exception handler, so there are no cross-core register writes,
and drop the idle hits natively. A scheduler hit taken with IRQ
masked is the idle loop or an interrupt-context reschedule;
rewrite it to a benign exit and resume without waking the
debugger, so only a real task's hit reaches Frida.
2026-10-01 00:03:32 +02:00
Ole André Vadla Ravnås 74508ad276 barebone: Resume macOS Android-emulator cores
The emulator's gdbstub resumes only the current core on a
continue, so the other cores stay parked and the guest makes
no progress: run_until_schedule waits forever for a task that
never runs. Resume every vcpu from the emulator's own
cpu_resume so the whole guest advances.
2026-09-30 14:59:14 +02:00
Ole André Vadla Ravnås 0b65db9f08 barebone: Push macOS Android-emulator regs to vcpu
Setting the CPUState dirty flag did not reliably push gdb
register writes into the HVF vcpu, so an injected call ran
from a stale pc and never returned, hanging every
machine.invoke before the agent could start. Write x0-x30,
pc and cpsr straight to the vcpu with hv_vcpu_set_reg.
2026-09-30 14:59:14 +02:00
Ole André Vadla Ravnås 3e3e8226e9 barebone: Rewrite macOS Android-emulator shim
The shim's per-exit hooks ran under the JS lock, serialising
every core and starving the guest's own faults once all cores
were live. Move breakpoint programming and exception handling
into a CModule so the hot path runs lock-free, and hold the
mutable state in a shared buffer since a CModule's globals are
read-only.
2026-09-30 14:59:14 +02:00
Ole André Vadla Ravnås eae2380196 barebone: Skip Corellium probe on Android emulator
The Corellium probe runs "monitor info", which the emulator
answers with its entire HMP help text — pointless work on a
stub that is never Corellium. Skip it like the VZ and
Parallels stubs do, keeping the vCont and PhyMemMode probes
the emulator needs.
2026-09-30 14:59:14 +02:00
Ole André Vadla Ravnås df7eb6c805 barebone: Borrow one core to park on SMP
Stepping past an unwanted scheduler hit corrupts a translation block
under multi-threaded TCG, dropping later hits and hanging the search.
Borrow one core for it and leave the rest stopped.
2026-09-28 19:30:18 +02:00
Ole André Vadla Ravnås 127fbde6c6 barebone: Order commands to match interfaces 2026-09-28 18:05:04 +02:00
Ole André Vadla Ravnås 8420a6faaa barebone: Force-include stdint in agent bindgen 2026-09-28 17:39:21 +02:00
Ole André Vadla Ravnås b9e7573b5d barebone: Hold a named spawn in the agent 2026-09-28 17:39:21 +02:00
Ole André Vadla Ravnås 18db169e04 barebone: Read process details in the agent 2026-09-28 17:39:21 +02:00
Ole André Vadla Ravnås 8ec0c08622 barebone: Enumerate and spawn emulator apps 2026-09-28 17:39:21 +02:00
Ole André Vadla Ravnås 73c9ba0faa gdb: Drop a vestigial register read 2026-09-28 17:24:07 +02:00
Ole André Vadla Ravnås 845b686967 linux-kernel-image: Drop unused helpers 2026-09-28 17:24:07 +02:00
Ole André Vadla Ravnås 27d98acfe7 barebone: Inject the agent into SMP arm64 Linux
Read current from per-cpu memory rather than an allocating stub,
skip the idle task when borrowing one at schedule, and confine
each invoked call to its core so a sleeping call keeps its task.
Start the agent with all cores live for kthread_create. Treat any
state past SYSTEM_BOOTING as running, for kernels without
SYSTEM_SCHEDULING.
2026-09-28 17:22:35 +02:00
Ole André Vadla Ravnås 8bb5783cf1 barebone: Name Linux processes by their cmdline
Read the name from argv[0] rather than the truncated comm,
and skip kernel threads.
2026-09-28 00:38:57 +02:00
Ole André Vadla Ravnås 17fcaec4c8 barebone: Add kallsyms mining regression test
Open a kernel image named by FRIDA_TEST_KERNEL_IMAGE and resolve
a symbol list, reporting parse time and hit count.
2026-09-27 22:10:13 +02:00
Ole André Vadla Ravnås c89f1e8da9 linux-kernel-image: Mine 32-bit and VA48 kallsyms
Add finders for arm64 VA_BITS=48 bases, Rust symbol names past
80 chars, 32-bit absolute tables, and 32-bit relative offsets in
plain and percpu-absolute form.

Run the 32-bit finders only after the 64-bit paths fail, so
64-bit kernels are unaffected.
2026-09-27 22:10:09 +02:00
Ole André Vadla Ravnås 31c6d7d081 barebone: Return no applications on Linux
Linux has no application registry, so answer the enumerate command
with an empty list rather than letting it fall through to "Unknown
command".
2026-09-27 15:41:44 +02:00
Ole André Vadla Ravnås e872f8a99d barebone: Report Linux thread state
Fill in each thread's run state so ThreadDetails carries it. The
copy reads it from /proc/<pid>/task/<tid>/stat; the kernel half
reads task->__state. Both map to a GumThreadState.
2026-09-27 15:41:44 +02:00
Ole André Vadla Ravnås af22a4b53f barebone: Capture Linux thread registers
A copy runs in the target's userspace and cannot read a thread's
saved registers itself, so it asks the kernel half over the arena;
the kernel half reads the pt_regs off the task's kernel stack and
hands them back. find_task_by_vpid resolves any thread by id.
2026-09-27 15:34:43 +02:00
Ole André Vadla Ravnås 394c932f8d barebone: Keep the arm64 copy off huge pages
The copy's code is made executable a page at a time, which faults when
the kernel backs the mapping with a huge page: the leaf spans both
halves and has no page table to walk. A THP-enabled guest folds the
fresh mapping into one, so injection timed out. Ask for small pages
before faulting the copy in.
2026-09-26 22:41:24 +02:00
Ole André Vadla Ravnås b66a485f7c subprojects: Prepare for release 2026-09-25 13:15:40 +02:00
Ole André Vadla Ravnås b39661d192 subprojects: Bump outdated 2026-09-25 13:13:56 +02:00
Ole André Vadla Ravnås cabf50e98d barebone: Bump @types/frida-gum to 19.11.0 2026-09-25 13:13:35 +02:00
Ole André Vadla Ravnås 5748488e23 compiler: Bump @types/frida-gum to 19.11.0 2026-09-25 13:13:35 +02:00
Ole André Vadla Ravnås c1d3207caf subprojects: Bump outdated 2026-09-25 12:18:43 +02:00
Ole André Vadla Ravnås 9830a8db74 linux: Build the bootstrap exit via Arm64Writer
Emit the exit with the writer's put_ldr_reg_u64 and put_svc_imm and
Frida's LinuxSyscall.EXIT, instead of a hand-encoded MOVZ and SVC.
2026-09-25 12:05:29 +02:00
Ole André Vadla Ravnås ddde9cd975 linux: Drop kernel-injector comments
They restated what the code already says.
2026-09-25 10:49:33 +02:00
Ole André Vadla Ravnås 0d19d6719d linux: Reclaim the loader region on agent unload
Kernel-injected agents ride the shared RemoteAgent monitor, so their
unload policy and process status already reach on_agent_stopped. Carry
the loader region in the BootstrapResult and free it through the kernel
primitive instead of a ptrace CleanupSession.

The region is then reclaimed on an immediate unload, and via the cloned
result in a fork child that unloads too, while a resident agent or a
pending exec keeps it mapped.
2026-09-25 10:45:53 +02:00
Ole André Vadla Ravnås 9c3deaaded linux: Silence kernel-injector warnings
The request structs are written here and read only by the module
through the pointer, so valac flagged every field as unused. Reference
them from a static initialiser to settle it, and drop the unused
read_memory()/read_pointer() primitives.
2026-09-25 10:45:37 +02:00
Ole André Vadla Ravnås c6b8176c03 linux: Free the bootstrap stack and TLS
The kernel-assisted injector spawns a bootstrap thread on a dedicated
stack with a synthesised TLS block, both mapped into the target. The
thread exits once it has handed off to the agent's own pthread, but
its stack and TLS were left mapped, leaking 136 KiB per injection.

Reclaim them once the agent has settled, by when the bootstrap thread
is long gone. The loader region stays mapped, since the agent keeps
running from it.
2026-09-25 10:23:26 +02:00
Ole André Vadla Ravnås a95b627bf9 barebone: Exclude Android from emulator glue
Android defines LINUX too, so the guards pulled in
EmulatorInstrumentation, which meson compiles only for the desktop
(host_os == 'linux'). The Android build then broke on the missing
type. Narrow the guards to LINUX && !ANDROID to match.
2026-09-25 09:46:13 +02:00
Ole André Vadla Ravnås 5fae7f0dab fruity: Fix RSD port discovery on macOS 27
macOS 27 only reveals other processes' sockets to platform binaries
whose non-platform ancestors are all Developer ID or Apple signed.
Read the PCB list through /usr/sbin/sysctl, orphaned so launchd adopts
it, to not depend on how our own process was signed and launched.
2026-09-25 09:37:16 +02:00
Ole André Vadla Ravnås a1cf475801 subprojects: Bump outdated 2026-09-24 21:34:38 +02:00
Ole André Vadla Ravnås 3bfc37ebb4 barebone: Leave macOS emulator breakpoints to stub
Adopting breakpoints sets breakpoints_provided_externally, which drops
the Z packets to the stub. That suits Windows, whose shim watches the
debug registers itself, but starves the macOS shim: it learns each
address only from the stub's own cpu_breakpoint_insert and reports
stops through gdb_set_stop_cpu, so nothing ever gets planted.

The macOS inclusion was never meant: adoption was wired for Windows
and later fenced with macOS to fix the build. Restrict the call, and
the adoption machinery it needs, to Windows.
2026-09-24 21:31:08 +02:00
Ole André Vadla Ravnås c715362609 barebone: Base emulator shims on InternalAgent
Make EmulatorInstrumentation an InternalAgent, which brings the RPC
client, attach, and script lifecycle along. The shims expose RPC
exports instead of recv() handlers: allowPipePath on macOS and Linux,
setBreakpoints on Windows, whose hits now arrive as an on_event array.

Reach the host process through hub.resolve_host_session ("local"), as
Simmy does, rather than spinning up a private DeviceManager.
2026-09-24 21:31:08 +02:00
Ole André Vadla Ravnås 71c1d83659 barebone: Support the Android emulator on Linux
On Linux the emulator accelerates with KVM, whose gdbstub commits
register writes, inserts breakpoints, and handles vcpu exits on its
own. The shim thus only whitelists the pipe-over-vsock socket path,
and breakpoints go straight to the stub.
2026-09-24 21:31:08 +02:00
Ole André Vadla Ravnås 637d2d1103 barebone: Implement gum_memory_query_region()
Replace the gum_memory_query_protection() override, which now clashes
with Gum's own wrapper. Report the whole region, since the Interceptor
uses its extent to tell C stacks apart.
2026-09-24 15:08:30 +02:00
Ole André Vadla Ravnås 7157acae00 barebone: Accept thread flags when enumerating
Match the signature gum_barebone_enumerate_threads() gained upstream.
2026-09-24 15:08:30 +02:00
Ole André Vadla Ravnås 2327849c71 barebone: Give WinNtLayout a single kernel module
d30e8fbb narrowed the host to reading only the kernel, but left
WinNtLayout carrying a modules list of one and the host test still
expecting the old whole-list walk. Fold the layout down to a single
kernel module, and fail if it cannot be found rather than returning
an empty layout. The agent enumerates the rest in situ.
2026-09-24 13:17:19 +02:00
Francesco Tamagni f4d7a22aac fruity: Add force_transport=usbmux channel option
So that specific lockdown endpoints can be forcefully reached through
the usbmux transport even when a tunnel is available.
2026-09-24 11:55:26 +02:00
Akshit Singh dd3ed8907c socket: Tolerate peer vanishing before accept
on_websocket_opened() aborted the process through
assert_not_reached() when get_remote_address() failed, which
happens when the peer disconnects between the WebSocket handshake
and this handler running. Drop the half-open connection and carry
on instead.
2026-09-24 11:48:17 +02:00
Jiska Classen fe8934f868 darwin: Drop VM_PROT_COPY when mmapping the blob
frida_darwin_helper_backend_mmap() remapped the agent blob with
copy = TRUE and then reprotected it with VM_PROT_COPY. On iOS 27
VM_PROT_COPY produces a permanent VM entry that mach_vm_deallocate()
no longer removes but merely strips of permissions, while still
reporting success. The agent blob is allocated on every injection, so
frida-server retained a 40 MB blob per injection for the lifetime of a
process, until every attach failed with "unable to mmap ((os/kern) no
space available)".

The blob is remapped from our own heap, whose max_protection permits
writing, so a plain VM_PROT_READ | VM_PROT_WRITE reprotect always
succeeds and VM_PROT_COPY was never needed here.

Tested on an SRD with iOS 27.0 beta 7.
2026-09-24 11:15:20 +02:00
Ole André Vadla Ravnås d27e5c51cc barebone: Restore page protection before freeing
protect_unless_already asks the allocator what it grants, not what the
pages currently hold. The allocator grants read-write, so restoring
read-write on the way out returned early and did nothing -- while the
text pages had been made read-only during injection.

set_memory_ro also updates the direct-map alias, so freeing them that
way hands the page allocator a read-only page, and whoever gets it next
faults clearing it. Over eight runs: fourteen oopses before, none after.
2026-09-23 14:08:13 +02:00
jiska c3d8d94ded darwin: Preserve suspension across policy softening 2026-09-23 00:17:08 +02:00
Ole André Vadla Ravnås f53ffd72c8 gdb: Forget the selected thread when the stub stops
The stub picks its own thread when it reports a stop, so remembering
which one was asked for last is only good until the next stop or
resume. Held across one, the group packets skip the selection they
need and answer for, or write to, whatever the stub chose.

Clear it on both transitions. Under load, where a stop lands between
the selection and its use far more often, this takes the failure rate
back from 47 to 25 percent.
2026-09-23 00:13:00 +02:00
Ole André Vadla Ravnås 2ef1277ccc subprojects: Bump outdated 2026-09-23 00:02:42 +02:00
Ole André Vadla Ravnås 151a4587a6 submodules: Bump releng 2026-09-23 00:02:41 +02:00
Ole André Vadla Ravnås 4141a1d0f4 subprojects: Bump outdated 2026-09-22 23:47:04 +02:00
Ole André Vadla Ravnås cfd234c65a submodules: Bump releng 2026-09-22 23:47:04 +02:00
Ole André Vadla Ravnås a84cf817a7 barebone: Give the 32-bit agents their vsock slots
The pipe hostlink's socket calls were only listed in the 64-bit
scripts, so x86 and arm failed to link. List them there too, and
let the 32-bit shim reach fget_task.
2026-09-22 20:55:03 +02:00
Ole André Vadla Ravnås 4769387ac2 barebone: Fix the 32-bit x86 Linux agent build
The shim supplies ioremap, pci_irq_vector and ktime_get_real_ts64
itself, so declaring them again as patched symbols clashed. Reach the
latter two through accessors that hand back the same shape on every
architecture, and give x86 its own map_io, the shim having no nocache
fallback.

kernel_write is a pointer there and an integer elsewhere, so split the
two callers as well, passing what the other architectures already pass.
2026-09-22 20:55:03 +02:00
Ole André Vadla Ravnås b76ba67971 barebone: Space the shim's pointer declarations
Put a space on either side of the star, as the rest of the file does.
2026-09-22 20:55:03 +02:00
Ole André Vadla Ravnås ad865bf14d barebone: Drop the winnt resume block thunk
Its record went away when faults started going through the handler,
and nothing calls it.
2026-09-22 20:55:03 +02:00
Ole André Vadla Ravnås da0aae3579 barebone: Gate the Linux hostlink pipes
Both modules reach for native and layout, which only exist in an
injected build, so a kernel module failed to compile. Declare them
alongside the rest of that flavour.
2026-09-22 20:55:03 +02:00
Ole André Vadla Ravnås fc692bb122 gdb: Trust the advertised binary write support
The stub tells us up front whether it takes X packets, so keep the
flag with the rest of the connection state and drop the degrade on
failure, which only ever masked a stub lying about what it accepts.
2026-09-22 17:29:51 +02:00
Ole André Vadla Ravnås d44dba6f95 tests: Wait for the Fruity device to appear
The manual tests looked the device up with no timeout, which means
not waiting at all, so they failed before USB discovery had run.
2026-09-22 17:22:07 +02:00
Ole André Vadla Ravnås a6549a6692 gdb: Ask for stop info until it arrives
State starts out stopped, so the loop guarding the connect-time
request never ran and the first stop was never asked for. Loop
until the stop info itself lands.
2026-09-22 17:22:03 +02:00
Ole André Vadla Ravnås 19b4af7c04 gdb: Write binary only when the stub allows it
Apple's debugserver does not advertise binary-upload, and answers X
packets by writing the ",<len>:" header into target memory ahead of
the payload instead of rejecting them, so the hex fallback never
fired and the injected code was corrupted.
2026-09-22 17:21:57 +02:00
Ole André Vadla Ravnås 8a0564bcd2 fruity: Chain LLDB.Thread to base constructor
The id and name properties lost their setters when construct-only
interface properties went away, so passing them to Object() left
both null and packet building crashed on the first thread query.
2026-09-22 17:21:48 +02:00
Ole André Vadla Ravnås 3d43d64c49 subprojects: Bump outdated 2026-09-22 15:57:08 +02:00
Ole André Vadla Ravnås c6fac8cbca submodules: Bump releng 2026-09-22 15:57:07 +02:00
Ole André Vadla Ravnås 62c666492b prospero: Add backend for the PS5
The console is FreeBSD underneath, so it reuses the FreeBSD host
session, but nothing else carries over: it refuses to exec an unsigned
binary, hands out no password database, and numbers a jailed process's
modules from 0x2000.

Spawn a program by exec'ing a stock system app and mapping the real
ELF over it, and spawn an app by asking the shell to launch it while
kqueue watches for the fork. Drive the target through the raw ptrace
syscall, borrowing the debugger authid, and hand the agent kernel
access through a pair of overlapped sockets. Enumerate apps from the
metadata the console stores on disk, icons included.

Link the agent against crt1 so it doubles as a payload, and reach it
over a socketpair rather than a named pipe, which the console does not
support.
2026-09-22 15:51:06 +02:00
Ole André Vadla Ravnås 302d0dcd59 freebsd: Stride process records by their own size
The kernel reports each record's size up front, and it need not match
the struct the headers describe, so dividing the reply by sizeof left
the list empty.
2026-09-22 15:51:06 +02:00
Ole André Vadla Ravnås b81b48c376 debugger: Drop construct-only interface properties
valac routes such a property through the interface setter, which it
defines but never declares, so callers in other units end up with an
implicit declaration. Give the implementations their own storage and
expose only a getter.
2026-09-22 15:51:06 +02:00
Ole André Vadla Ravnås f043b61f02 barebone: Rename local colliding with a macro
Compilers targeting Linux predefine linux, so the generated C did not
build. Name it config instead.
2026-09-22 15:51:06 +02:00
Ole André Vadla Ravnås b7d36ce56a barebone: Guard emulator breakpoint adoption
The field only exists on Windows and macOS, so building anywhere else
failed. Guard the use the way its declaration and close() already are.
2026-09-22 15:51:06 +02:00
Ole André Vadla Ravnås 367e1d7e99 Share the non-blocking waitpid helper
Move ChildProcess.wait_for_next_stop, WaitResult, WaitKind and
PosixStatus into lib/base so other POSIX backends can wait without
blocking the main loop. The ptrace-specific parts of a stop stay in
the Linux backend, as PtraceStatus.
2026-09-22 15:51:06 +02:00
Ole André Vadla Ravnås 1d2bc226e7 Fix pointer spacing in declarations
Write the type, a space, the asterisk, a space, and then the name,
the way the rest of the codebase does. Vala and VAPI sources follow
the same rule as C.
2026-09-22 15:51:06 +02:00
Ole André Vadla Ravnås 71b9ee120d fruity: Refresh RSD snapshot on service miss
The RemoteXPC handshake snapshots the services that exist when the
tunnel opens. Services published later, such as those brought in by
mounting a Developer Disk Image, never show up.

Resolve services through the tunnel, so a miss reopens the RSD
connection and retries against a fresh snapshot.

Kudos to @hsorbo for reporting.
2026-09-22 15:22:25 +02:00
Ole André Vadla Ravnås 1f965e5282 gdb: Read and write the thread that was asked for
Reading or writing the whole register file sent its packet without
saying which thread it meant, so both landed on whichever one the stub
had selected last. A single register says what it means, through the
thread suffix or by selecting first; the group packets did not, and on
a target with more than one processor they answered for, and wrote to,
the wrong one.

Select before the group packets too, remembering the choice so the
landing loop does not pay for a round trip it does not need.
2026-09-22 13:05:00 +02:00
Ole André Vadla Ravnås 115245595a gdb: Give the stub's answers a deadline
A query sends its packet and waits with nothing but a cancellable, so
an answer that never arrives takes the session with it. The waits that
follow a stop were bounded one at a time; the path they all run
through was not.

Carry a deadline on each pending response and sweep them from a single
timer, which starts with the first query outstanding and stops once
none are. A timer per query is far too dear: the landing loop asks
after every arrival, and paying for a timeout source each time takes
the instrumentation phase from 2.6 to 18 seconds.
2026-09-22 12:52:53 +02:00
Ole André Vadla Ravnås 7d4afc0b59 barebone: Repair the vcpu that trapped
QEMU reads registers back from the first vcpu only, so the shim's
hflags repair never reached the others. The gdbstub then reported
them in 32-bit form, and a landing on any other processor was
compared against a truncated stack pointer and rejected. Under load
that spun until the borrowed task migrated back, costing twenty
seconds or the call's own deadline.

Pull the trapped vcpu's registers through QEMU's reader before
reporting it, and repair it there. Its CPUState is taken from the
register the run loop carries it in, learned by matching against
the one vcpu QEMU does read.

Rejections on the other processors go from 240 to none, and no run
is slow any more: the instrumentation phase averages 5 s and peaks
at 6.5 s, against 7.7 to 16.7 s and peaks of 21 to 24 s.
2026-09-21 23:48:52 +02:00
Ole André Vadla Ravnås 15814adcf5 kallsyms: Read the whole range of kernels
Only four of the thirteen emulator kernels parsed, and the ones that
did took up to forty seconds. Both failings came from the same place:
each part of the table was found by scanning the whole image for it in
turn, against a layout only the middle generation of kernels has.

Find the token table first, since it ends the region the names and the
addresses live in, then the percpu prefix, and take the names in one
backward pass that records where every run of symbols begins. Kernels
storing offsets from a relative base, and those keeping percpu symbols
absolute, fall out of that same pass. All thirteen now parse, in 92 to
246 ms.
2026-09-21 18:48:08 +02:00
Ole André Vadla Ravnås 3b6aa6b315 gdb: Bound the wait for the stub's first stop
Connecting asks for stop info and waited for the answer forever, so a
stub that never sends one takes the whole connection with it before
anything else has run.

Ask again, bounded, and give up with a timeout.
2026-09-21 08:39:36 +02:00
Ole André Vadla Ravnås 9c907a9fed barebone: Give a guest call a deadline
The wait for a call to land runs until the return address is reached
on the stack we supplied. The rest of the kernel passes through that
address too, so the loop keeps going, and a call that never returns
keeps it going forever. Every backend waits this way.

A minute is far past any call that is coming back: on a machine that
is not starved, injection finishes inside three seconds.
2026-09-20 09:19:55 +02:00
Ole André Vadla Ravnås 4b717bccbd gdb: Ask again when a stop goes missing
Where breakpoints come from outside the stub, the stop that follows a
hit is reported out of band and can go missing, leaving nothing on the
wire to wait for. Three waits assumed one always arrives: for an
exception, for a stop, and for a step.

The step is the costly one. Stepping over a breakpoint moves a single
processor with vCont;s and holds every other one stopped, so a
completion that never comes freezes the machine, and the guest looks
deadlocked from the inside.

Wait in bounded rounds and ask again, giving up with a timeout instead
of never returning. Stubs reporting their own breakpoints are
unaffected.
2026-09-19 16:49:40 +02:00
Ole André Vadla Ravnås 873d90c4d5 barebone: Report breakpoint hits faithfully
The shim claimed every debug exception before checking whether one of
the planted registers had matched. Ones that match none are the
guest's own, single steps included, and the stub itself steps to get
past a breakpoint.

Hits were also reported at most once every 50 ms per address. The call
mechanism continues past every arrival at the return address until it
finds the one on the stack it supplied, so a second arrival inside the
window went missing.
2026-09-19 16:47:09 +02:00
Ole André Vadla Ravnås ea4f15e528 barebone: Give the hostlink device its IRQ line
Nothing claims the emulator's virtio-serial controller, so the kernel
leaves its interrupt line at zero and the chipset keeps the link off.
Routing asked the kernel for the line and unwrapped the answer, which
panicked the guest.

Pick the line instead: take what config space names, else share one
another link already carries, and write it back so the device and the
handler agree. Message interrupts are silenced on the way in, so that
line is the one the device raises.
2026-09-19 16:47:09 +02:00
Ole André Vadla Ravnås f0f272a47c barebone: Carry the hostlink over the QEMU pipe
The guest kernel's own driver owns the virtio-serial device and holds
its interrupt unshared, so an injected agent gets none. The emulator's
pipe carries adb the same way and hands out a channel per open, so the
agent names the pipe:unix service on it and the emulator joins that
stream to the socket frida-core listens on.

That listener is a UNIX socket on Windows as well, so what stood behind
the guard is now built everywhere.
2026-09-18 09:36:55 +02:00
Ole André Vadla Ravnås b5485d71e2 agent: Ask the kernel for the hostlink line again
The firmware leaves the config byte at zero on this platform, so taking
it named no interrupt at all. Keep the guard that ignores a line shared
with another device until there is a transport to wake.
2026-09-18 04:12:02 +02:00
Ole André Vadla Ravnås 23f0953088 barebone: Hand the agent per-CPU slots verbatim
A per-CPU symbol names an offset from the processor's own base, not an
address in the image, and the agent reads it through gs. Adding the
kernel base gave it one that is neither, so current came out as rubbish
and waking a task on it faulted in interrupt context.
2026-09-18 03:39:34 +02:00
Ole André Vadla Ravnås 676e6d6058 agent: Take the hostlink interrupt on x86
Vector zero is the message-signalled interrupt the guest's own virtio
driver holds, and it does not share, so take the line the firmware
programmed instead. That one is shared, and arrives before there is a
transport to wake, so leave it alone until there is.
2026-09-18 02:21:59 +02:00
Ole André Vadla Ravnås 6d14d92d03 agent: Map I/O where ioremap is a macro
Kernels before 5.6 export ioremap_nocache and nothing named ioremap, so
the slot stayed empty and mapping a device called through it.
2026-09-18 02:06:12 +02:00
Ole André Vadla Ravnås f97166b8ce agent: Take the heap from vmalloc
The slab allocator cannot hand out 32 MiB in one piece, so asking it
for the heap fails and the panic that follows allocates as well.
2026-09-18 02:06:06 +02:00
Ole André Vadla Ravnås 55a4a40aa5 agent: Name the kernel slots x86_64 links against
The vsock hostlink and the socket primitives it calls were given slots
on arm64 only, so the x86_64 agent named symbols nothing defined and
would not link.
2026-09-18 02:05:52 +02:00
Ole André Vadla Ravnås bf1f308443 barebone: Break on the Windows emulator
WHPX reports no breakpoint trap to the stub, so the shim arms the
partition's exception exits and watches through the debug registers.
Arming touches partition state, which deadlocks from the run path, so
it happens on the script's own thread; a processor already inside a run
is cancelled to pick up what was planted since.
2026-09-18 02:05:33 +02:00
Ole André Vadla Ravnås dec5561943 gdb: Let breakpoints come from outside the stub
A stub that plants none of its own can have them provided for it: the
client publishes what it wants watched and is told when one is hit.
2026-09-18 02:05:33 +02:00
Ole André Vadla Ravnås 39fee15e3f barebone: Relax CFI while the agent is in
Android builds the kernel with Clang CFI, and an indirect call into the
agent belongs to no module, so the scheduler calling in panics. Make the
check itself return, not its failure path: the caller does not expect
that one to come back.
2026-09-18 02:05:26 +02:00
Ole André Vadla Ravnås 1f9410251e barebone: Give the agent a stack of its own
A kernel stack is 16 KiB and the agent wants far more than the borrowed
task has left, so it runs on memory taken for the purpose.
2026-09-18 02:05:18 +02:00
Ole André Vadla Ravnås 22323e48b9 barebone: Borrow a task on x86
The scheduler is reached by making it spin where it stands and catching
whoever arrives, since stopping the guest on every arrival costs more
than the arrivals are worth.

Reading current takes the per-CPU slot rather than a probe that has to
be allocated first, the allocator being what needs a task to begin
with. Page protection goes through the kernel's own set_memory_*, which
reaches pages the debugger cannot walk.

SYSTEM_RUNNING is 2 on some kernels and 3 on others, so test for a
system that is past scheduling instead.
2026-09-18 02:05:10 +02:00
Ole André Vadla Ravnås ee827b9e9a barebone: Bound the kernel a sampled pc can be in
The landing sweep took any aligned address below the pc whose banner
matched, so a dormant copy of the image elsewhere in memory won the
race. Require the pc to fall inside the span the symbols cover.

Per-CPU symbols carry their own small values rather than addresses in
the image, so leave those alone when rebasing.
2026-09-18 02:04:28 +02:00
Ole André Vadla Ravnås d7167c5a16 barebone: Disarm PatchGuard on x86-64
Hooking the dispatcher is noticed, and the guest bug-checks within a
minute of the agent leaving. Sweep the pool around our own allocation
for the deferred calls that do the checking, the same way arm64 does,
and clear the context each one carries.

Skip whole regions the page tables say are absent rather than asking
the kernel about every page, which keeps the sweep near a second.
2026-09-18 01:24:34 +02:00
Ole André Vadla Ravnås 0697d4e19d barebone: Report winnt faults through the handler
The fault vectors are read-only on Windows 11, and a table of our own
is reloaded from under us, thus the gates cannot be taken over. Replace
the routine the vectors dispatch to instead, which the gates lead to in
two hops, and take the fault from the trap frame it is given.
2026-09-18 01:24:25 +02:00
Ole André Vadla Ravnås 99e62dddf4 barebone: Walk the winnt page tables as mapped
Windows randomises where it maps the page tables into themselves, thus
the address the walk assumed has been wrong since Windows 10 and every
lookup read memory that is not there. Find the self-map by the entry
that names the table it lives in.

The kernel maps its own text with large pages, which the walk skipped
on the assumption that such a mapping is already writable. It is not,
so apply the change to the entry that maps it.
2026-09-18 01:24:25 +02:00
Ole André Vadla Ravnås 110fc7c96c barebone: Find winnt guest state in the agent
The host would sweep the guest for the loaded module list and the
process list head, spending a round trip per candidate on data the
agent can walk in a few loads.

Walk the loaded module list from the note the host leaves, which the
process copies already do through the same entry layout, and find the
process list head from the system process on first use.
2026-09-18 01:22:44 +02:00
Ole André Vadla Ravnås 0ba1fc9ed0 barebone: Resolve the winnt layout in a second
Fifteen seconds went on three sweeps that a stub pays for one round
trip at a time.

The syscall entry MSR already points into the kernel image, which beats
hunting the stack for a pointer to it, and the image is mapped on large
pages, thus sweeping on that boundary finds it in a handful of probes
rather than a hundred. Carry the export directory out of the sweep
instead of reading and parsing it again for the module list and once
more for the symbols.

That leaves the process list head, which cost nine seconds of probing
and which injection never reads; the agent knows how to find it.
2026-09-18 01:22:44 +02:00
Ole André Vadla Ravnås d490a5f923 barebone: Stop calling the winnt pool executable
Windows 11 hands out non-executable pool, thus claiming it is already
executable skips the protection change the agent needs and the guest
bug-checks on the first instruction. Let the host protect the pages,
which it can now that the console reaches physical memory.
2026-09-18 01:22:44 +02:00
Ole André Vadla Ravnås 5804ad8b22 barebone: Land calls on a cold routine on x86-64
invoke() detects its call returning by watching the address the thread
was hijacked at, which on winnt is a system service the whole kernel
runs through. With a spin standing in for a breakpoint that wedges
every processor, so point x86-64 at the bug-check routine the way arm64
already does.
2026-09-18 01:22:44 +02:00
Ole André Vadla Ravnås 22d5528285 barebone: Release processors parked on a spin
A spin holds whoever arrives first. A caller that rejects an arrival --
because it wants its own call returning on its own stack -- would then
wait on a processor that can never leave, and every other processor
piles up behind it.

Lift the spins before resuming, let the guest run for a moment, and arm
them again, which is what a breakpoint gives a caller for free.
2026-09-18 01:22:44 +02:00
Ole André Vadla Ravnås f732450cad barebone: Address physical memory on VirtualBox
The console takes a physical address with a prefix of its own, thus it
can serve the page-table walking that changing protection needs. A
staged write still goes through virtual memory, because writegstmem
knows no other kind.
2026-09-18 01:22:44 +02:00
Ole André Vadla Ravnås 44248c1ad2 barebone: Cache what the console reports
Halting to read registers costs round trips, thus hold on to what a
stop reported until the guest runs again, and fetch the wider set only
when a caller asks for a register outside it.

Write back only the registers whose value changed, which keeps the
console from setting ones it cannot.
2026-09-18 01:22:44 +02:00
Ole André Vadla Ravnås e7b20adfad barebone: Speed up the VirtualBox console
One command per round trip made the console unusable for bulk work: the
4.7 MB agent took eight minutes to upload and the kernel layout fifteen
seconds to resolve.

Batch reads, and stage a large write through a file that writegstmem
loads in one go, which the console reads in chunks of its own and thus
wants a whole number of them. Read the program counter, stack pointer
and code selector of every processor in one turn rather than every
register of each, and leave out the ones that belong to the processor
rather than the thread, which a caller must not restore onto whichever
processor a call lands on.

Wait for the console to go quiet after halting, because each processor
reports its halt on its own, long after the command that asked for it
was acknowledged.
2026-09-17 21:49:51 +02:00
Ole André Vadla Ravnås 52bfb579ba barebone: Add VirtualBox console stub flavor
VirtualBox only lets one debugger attach, and its GDB stub cannot do
what the winnt flavor needs. Drive its native debugger console instead,
selected as the "virtualbox-console" flavor.
2026-09-17 21:47:55 +02:00
Ole André Vadla Ravnås 99bad7b7d3 barebone: Implement Debugger on VirtualBox
VirtualBox on NEM sets breakpoints that never fire, but it does let
us write to resident kernel code. So stand a breakpoint on a jump
to itself: whichever thread arrives parks there, and the debugger
notices by polling the program counter.
2026-09-17 15:16:26 +02:00
Ole André Vadla Ravnås 7c158e692f barebone: Add a VirtualBox console client
Its GDB stub cannot write memory, and on NEM its breakpoints never
fire. The console can do both, but only one debugger may attach at
a time, so this has to stand on its own.

Framing is the awkward part: each halting CPU announces itself with
a prompt of its own, echo is not implemented, and a state change
discards pending input. So a command is followed by one the console
does not know, and read up to the complaint that names it.
2026-09-17 15:16:26 +02:00
Ole André Vadla Ravnås 182963a8ec barebone: Open a named pipe for serial transport
The serial transport assumed a Unix socket and refused to work on
Windows, where a guest's serial port shows up as a named pipe.
Give lib/base an IOStream over an overlapped handle and reach the
pipe through it.
2026-09-17 15:16:26 +02:00
Ole André Vadla Ravnås d30e8fbbb2 barebone: Read only the kernel module on the host
Walking the whole module list and every module's exports over the
wire cost the better part of a minute, and served nobody: the host
needs the allocator's two symbols, and the rest is for the agent,
which can read it in the guest for free. Pass the list head along
instead.
2026-09-17 15:16:26 +02:00
Ole André Vadla Ravnås 746a297542 barebone: Find the winnt kernel without the PCR
A stub that withholds the GS base leaves the kernel image as the
only way in, which is also arm64's situation. Decide on what the
stub exposes rather than on the architecture, and seed the scan
from the program counter, which in kernel mode already sits inside
an image.
2026-09-17 15:16:26 +02:00
Ole André Vadla Ravnås 05c2df24ac barebone: Add VirtualBox stub flavor
Its GDB stub answers one request at a time, and greets a client
with a stop packet per CPU. Serialise requests and let the burst
settle before the handshake starts.
2026-09-17 15:16:26 +02:00
Ole André Vadla Ravnås 169580b22a barebone: Read a present export directory at once
Reading it page by page cost a round trip per page. Try the whole
range first and fall back to pages only when part of it is out.
2026-09-17 15:16:26 +02:00
Ole André Vadla Ravnås 51153487f9 barebone: Rename $gdb to $debugger
The script runtime reaches a Debugger now, whatever speaks to the
target underneath.
2026-09-17 15:16:26 +02:00
Ole André Vadla Ravnås 218ae7b3ea gdb: Extract Debugger interface from client
Barebone talked to a GDB.Client throughout. Name what it actually
needs instead, so a hypervisor with a debugger of its own can serve
it. GDB.Client, Thread and Breakpoint implement the interface;
TargetArch, the run state and the breakpoint kind move out of
Frida.GDB and into Frida.

continue becomes resume: as an interface method it would name a
field in the generated C, where continue is a keyword.

Raw packets stay with the protocol that has them, reached through
a cast. Physical addressing was a capability behind a packet, so it
becomes one.
2026-09-17 15:16:26 +02:00
Ole André Vadla Ravnås b27d371df6 gdb: Drop FRIDA_GDB_TRACE 2026-09-17 15:16:26 +02:00
Ole André Vadla Ravnås d7eb1a4df1 gdb: Let a stub ignore stops while connecting 2026-09-17 15:16:26 +02:00
Ole André Vadla Ravnås 374bf7c896 gdb: Let a stub opt out of pipelining 2026-09-17 15:16:26 +02:00
Ole André Vadla Ravnås 04d78bb826 gdb: Handle stop notifications one at a time 2026-09-17 15:16:26 +02:00
Ole André Vadla Ravnås e43c01d159 gdb: Bound the feature read by packet size 2026-09-17 15:16:26 +02:00
Ole André Vadla Ravnås 149f005446 gdb: Send supported features with qSupported 2026-09-17 15:16:26 +02:00
Ole André Vadla Ravnås 1fbded1079 api: Publish the kernel image types
The types the image config names were not in the public API at all:
the generator takes it from the sources it is pointed at, and the two
new ones were not among them. A binding generator reading the gir got
as far as the config and then had nothing to resolve:

    AssertionError: unable to resolve type XnuKernelcache

Naming them there brought out a second leak. Vala keeps a virtual
method in the class struct whatever its visibility, so the internal
to_list() carried Gee into the public gir, which nothing outside the
library can resolve either. It answers for both kinds itself now, and
no longer needs to be virtual.
2026-09-17 15:09:27 +02:00
Ole André Vadla Ravnås d3fcc932c5 barebone: Take the kernel image as an object
An image was named by path, and read again by everyone who wanted
something from it: the Linux kallsyms parse sat behind the barebone
gate where nothing else could reach it, and the XNU kernelcache was
read and parsed twice over, once for the relocation and once for the
layout. A caller that wanted to know what a kernel names before
launching anything -- which transport a guest supports, say -- had no
way to ask.

So the images become objects the caller holds:

    LinuxKernelImage.from_blob (blob)
    LinuxSystemMap.from_blob (blob)
    XnuKernelcache.from_blob (blob)

Both Linux kinds answer has_symbol() and try_find_symbol(), and the
config carries whichever the caller has rather than a path, so an
image and a map can no longer both be supplied. Nothing reads the
filesystem to find out what it was handed: the kind is named, not
sniffed.

The kallsyms tables are located up front and the names are decoded
only when asked, so a lookup walks the tables comparing in place
instead of building the whole symbol list to answer one question.

FRIDA_BAREBONE_CONFIG names the kind it has:

    "image": { "type": "xnu-kernelcache",    "file": "..." }
    "image": { "type": "linux-kernel-image", "file": "..." }
    "image": { "type": "linux-system-map",   "file": "..." }
2026-09-17 14:43:48 +02:00
Ole André Vadla Ravnås e68cdcd229 barebone: Guard a field Windows never uses
The pipe-vsock service is only ever set where there is one, so a
Windows build warned that it is never used.
2026-09-17 14:34:27 +02:00
Ole André Vadla Ravnås 69af5a12a8 resource-compiler: Terminate blobs on MSVC too
Only the assembler path ended a blob with a NUL, so on the Microsoft
toolchain the last one in a section ran into whatever followed:

    GLib-CRITICAL: g_variant_new_string(): requires valid UTF-8

Terminate there as well, and skip it for anything that is plainly not
a string: the blob's own magic says whether it is a PE, an ELF or a
Mach-O, which also tells the assembler path what to align, so neither
has to guess from the extension.
2026-09-17 14:34:27 +02:00
Ole André Vadla Ravnås 61b3e3b8f6 barebone: Carry QEMU's channels over TCP
QEMU's Windows pipe chardev writes but never reads, so the QMP
handshake cannot complete over one; the hostlink's chardev would have
failed the same way. Both move to a loopback port.

With server,nowait QEMU no longer holds the machine still waiting to
be connected to, so the handle handover goes away with it.
2026-09-17 14:34:27 +02:00
Ole André Vadla Ravnås 637383261e barebone: Reach QEMU's hostlink on Windows
QEMU has no UNIX socket to listen on there, so the QMP channel could
not be opened at all and the config check rejected every address.
Serve both it and the hostlink's chardev over a named pipe.
2026-09-17 14:34:27 +02:00
Ole André Vadla Ravnås da558f706d barebone: Make the emulator's WHPX gdbstub usable
whpx_get_registers() never recomputes env->hflags, so QEMU believes
the guest is a 32-bit CPU outside long mode: registers come back
truncated to their low half, and every kernel address fails to
translate with E14. Recompute the long-mode bits after each sync.

Aimed rather than pinned: the function carries its own __func__
string, and the fields it writes give away the register file.
2026-09-17 14:34:27 +02:00
Ole André Vadla Ravnås 776a3a0e58 barebone: Sweep the x86 KASLR window
Stepping back from the PC the guest was caught at only reaches the
base when that PC is in the kernel's own text. x86_64 slides the base
across a gigabyte, so sweep the window -- 512 landings -- when
stepping back has not found the banner.
2026-09-17 14:33:23 +02:00
Ole André Vadla Ravnås 442293e122 barebone: Read an absolute-percpu kallsyms table
x86 kernels set CONFIG_KALLSYMS_ABSOLUTE_PERCPU, whose offsets are
signed and therefore do not ascend, so the ascending-run search cannot
find the table. Anchor on the layout instead: relative_base, then
num_syms, then the names. The run search is still tried first.

Recovers all 140881 symbols from an android-30 x86_64 kernel.
2026-09-17 14:33:23 +02:00
Ole André Vadla Ravnås dbe35e5247 barebone: Unpack an LZ4-packed kernel payload
Android builds its x86 kernels with CONFIG_KERNEL_LZ4, so the bzImage
payload sits behind the legacy LZ4 frame and the kallsyms scan found
nothing to read. Inflate it first.
2026-09-17 14:33:23 +02:00
Ole André Vadla Ravnås af0be008d5 gdb: Carry a built-in i386-64bit feature document
The emulator's stub names i386-64bit.xml in its target.xml and then
declines to serve it, as it already does for the aarch64 documents.
Carry our own so an x86_64 guest can be attached.
2026-09-17 14:33:23 +02:00
Ole André Vadla Ravnås 09a425f107 barebone: Instrument the emulator on macOS only
The gdbstub quirks worked around here are macOS ones, so skip the
instrumentation elsewhere until another OS is shown to need it.
2026-09-17 14:33:23 +02:00
Ole André Vadla Ravnås d3196950ff barebone: Fix copy reply wakeup on Linux
Declare kernel_read with its real argument order (buf, count, pos)
and call it that way, so wait_for_a_word reads the copy's says pipe
instead of faulting on a null buffer. The reply doorbell exited at
its first read on every architecture, and replies only reached the
host when the kernel half happened to still be polling, so
create_script timed out at random.

Wake the loop through nudge_the_loop, matching the hostlink doorbell,
so a reply that lands between the work check and the sleep is not
lost.
2026-09-17 12:29:00 +02:00
Ole André Vadla Ravnås e91795569a barebone: Separate with a single blank line
Two or more in a row say nothing the one says.
2026-09-17 01:24:49 +02:00
Ole André Vadla Ravnås f8d14d0b1d barebone: Catch winnt arm64 faults at the handler
The agent gave every CPU a vector table of its own and pointed VBAR at
it. Windows points it back from three places: a hardcoded pair of
instructions when a CPU starts, the architectural state a CPU resumes
from, and the return to user mode. A CPU that had been sent back thus
sent the script's fault to Windows, which bugchecked the machine on an
exception nobody handled, and the host saw only a command that timed
out.

Replace the handler the vector entries call instead. Every table leads
there, so no CPU can be sent back behind our back, and the trap frame it
receives carries the state to read and the state to resume with.
2026-09-17 01:24:49 +02:00
Ole André Vadla Ravnås 6d206eedcc subprojects: Bump outdated 2026-09-16 19:28:37 +02:00
Ole André Vadla Ravnås 2a1ca2d126 barebone: Poll handles in the winnt copy
The barebone GLib reaches a descriptor only through the poll func the
embedder installs, and the copy installed none, so a handle a script
opened was never waited on.

Wait on the handles with NtWaitForMultipleObjects, and keep the event
the kernel half sets among them, so the one loop serves both. Every
kernel gains a poke that breaks the wait, since a token says nothing
to a thread parked in the object manager.
2026-09-16 19:24:22 +02:00
Ole André Vadla Ravnås e640d2b5a2 barebone: Wake every winnt sleeper at once
The kernel half sets one event to tell a copy that something arrived,
and that event was auto-reset, so a single set released one waiter.
The copies have more than one: the loop, and any thread held back by a
full ring. The one left asleep was never told again, and a command sat
unserved for as long as the process lived.

Make it a notification event, which releases all of them, and let the
copy reset it once it wakes.
2026-09-16 19:22:46 +02:00
Ole André Vadla Ravnås 33a17a1e74 barebone: Enumerate in the winnt arm64 test
The test that says it sees modules and threads sent two constants and
asserted they came back, thus it proved only that a script loads in the
copy. Enumerate the modules of the process, an export of ntdll, and the
threads, and assert on what comes back.
2026-09-16 19:22:46 +02:00
Ole André Vadla Ravnås 5abdfe6f5f barebone: Test an in-process REPL in a live guest
Attach to a real guest process, load a script and check that an
evaluate RPC replies, covering the userspace injection path that
the kernel/pid-0 tests miss.
2026-09-16 19:16:19 +02:00
Ole André Vadla Ravnås d9779bd956 barebone: Poll the copy's wakeup in its own loop
Drop the copy's doorbell thread. Its single GMainContext now
polls a wakeup pipe through an installed poll func, so one loop
waits on descriptors without a helper. Resolve that pipe against
the copy's own task rather than the command thread's, since the
two do not share a descriptor table, and fence the ring publish
before waking so the copy never reads a stale head.
2026-09-16 19:16:19 +02:00
Ole André Vadla Ravnås 2098ff6925 barebone: Let the copy request executable memory
The copy runs in a domain that cannot make its own memory
executable, so gum's protect hands the range to the kernel half
over the arena and waits. The kernel half relaxes it below the
LSM and answers back.
2026-09-16 19:16:19 +02:00
Ole André Vadla Ravnås 3efec68374 barebone: Map the Linux copy's code past execmem
A hardened GKI kernel denies executable mappings to the copy's
domain and verifies indirect calls (kCFI). Map the image writable
and turn its code half read-execute by flipping the page tables
below the LSM, reaching the pte callback through a type-id
trampoline. Keep gum W^X so it never asks for RWX. Skip the sched
tracepoint probes until they carry CFI-safe trampolines.
2026-09-16 19:16:19 +02:00
Ole André Vadla Ravnås 1fcdd7a016 barebone: Correct arm64 block splitting
A level-3 descriptor parses as a block, so the walk treated every mapped
page as one to split; only split below level 3. Split the blocks under
the flush stub too, since it can land in one the agent's range did not
cover. Resolve every table's address before splitting any block, so a
table sitting in an already-split block still resolves through the
original mapping.
2026-09-16 18:55:13 +02:00
Ole André Vadla Ravnås ef431465b9 barebone: Wait for userspace before injecting
A task reaches the scheduler with interrupts on long before userspace
exists, and run_until_schedule borrowed the first such task -- during
early boot, when the kernel's allocators are not yet fit to hand out an
agent. execmem then returned huge-block-mapped, overlapping memory, and
the injection corrupted the guest.

Gate the borrowed task on system_state having reached SYSTEM_RUNNING, so
execmem gives page-mapped, non-overlapping memory and injection works.
2026-09-16 18:55:13 +02:00
Ole André Vadla Ravnås 1a497bc1c4 barebone: Split blocks to protect arm64 pages
The protect walk only descended table-to-table down to a level-3 page,
so a kernel that huge-maps the region it hands out -- the execmem ROX
cache backs the agent with a 2 MB block at level 2 -- made it throw
"Walk failed at level 2".

Before protecting, break each block covering the range into a
next-level table that maps the same memory with the same attributes,
so the pages beneath it can be protected one by one. Protection runs
with the MMU off and the guest halted, so the tables are rewritten in
place and the existing flush invalidates the TLB afterwards.
2026-09-16 17:41:26 +02:00
Ole André Vadla Ravnås be3b3d95ae device: Report connection progress
A Device connects on the first call that needs it, and with the
Barebone backend that means waiting for the target, injecting the agent
and waiting for it to come up. Add Device.connecting (status, progress)
and Device.connected, so a UI can show what a call is waiting on, and
how far along it is, whichever method made it. Providers report through
a callback handed to create(), as the socket provider serves every
remote device. Report each Barebone and socket stage, with the agent
upload advancing the fraction as it is written.
2026-09-16 17:41:26 +02:00
Ole André Vadla Ravnås ad69de758d barebone: Give the winnt x86 halves their slots
Both lagged arm64 by five kernel addresses, and the 32-bit half has
not linked since the serial hostlink started writing to a port.
2026-09-16 17:35:13 +02:00
Ole André Vadla Ravnås 7efce2f879 barebone: Cover the winnt arm64 agent with tests
Place a copy in a process and talk to it, ask it about the modules
and threads of the process it landed in, list the kernel's own
threads, and let a timer fire.
2026-09-16 17:35:13 +02:00
Ole André Vadla Ravnås d959ad23a6 barebone: Run a copy in a Windows arm64 process
Four things stood between a placed copy and a running one.

The copy asked the translation control register for the page size,
which only the kernel may do. Tell it the answer instead.

Making written code visible asked for cache maintenance a process may
not do, thus ask the system. The kernel half asked for the whole
instruction cache, which says nothing about the other processors nor
about writes still in the data cache, thus do it line by line.

The guard refuses a call to an address the memory manager never
handed out, and a thread starts by way of such a call. Give the copy
memory the manager knows about, say which addresses in it may be
called, and start the thread on a jump the guard accepts.

Give the ground back only once the thread that carried the copy is
gone.

Listing threads is the asking half's own business: walk the list the
process keeps rather than every list on the machine, ask the manager
before reading an address the walk worked out, and fill in the
registers only for a caller that asked for them.

Leave the session server alone: it refuses code of its own and is
critical, thus a copy placed there stops the machine.
2026-09-16 17:34:12 +02:00
Ole André Vadla Ravnås 287c75d8a7 barebone: Walk the right table for user pages
A lower-half address lives in the table TTBR0 names, not the one
TTBR1 names, so protecting a mapping placed in a process read and
wrote a kernel descriptor instead.

Tell the two never-execute bits apart while there: EL0 is governed
by bit 54 and EL1 by bit 53, thus a page mapped for a process read
back as non-executable even once it was made executable.
2026-09-16 17:34:12 +02:00
Ole André Vadla Ravnås 6b2e45e136 barebone: Ask the system for the thread it makes
Starting the copy read the loader's exports by dereference, in the other
process, where a page is often not resident. A failed read answered with
whatever it left behind, so the service index for one name differed from
one run to the next, and the call went to whichever service the misread
index named. That is where the access-denied answer came from.

Read them through the memory manager as well, and the index is the same
every time. NtCreateThread resolves, but the system refuses it across
processes even from the kernel, so take NtCreateThreadEx, which the
system uses itself: it takes the entry and its argument directly, and
gives the thread a stack and a block of its own.
2026-09-16 17:34:12 +02:00
Ole André Vadla Ravnås 7e9b5b8c1a barebone: Read another process through the manager
Listing processes attached to each one and read its command line through
the fault path, a pointer at a time. The pages of a process that is not
running are rarely resident, so nearly every one of those reads faulted:
a single listing took over two hundred, each answered as a failed read,
and a path came back empty far more often than it came back whole.

Ask MmCopyVirtualMemory for the string instead. It brings the pages in
and reports a status, so the reads no longer go anywhere near the
exception path, and the whole string arrives in one call.
2026-09-16 17:34:02 +02:00
Ole André Vadla Ravnås d4c1981166 barebone: Restyle the emulator gdbstub shim
Match the agent JS in this repo: two-space indent, one statement per
line, named helpers in newspaper order. No behavioural change.
2026-09-15 18:00:45 +02:00
Ole André Vadla Ravnås 0056a0d734 barebone: Read the wall clock on old kernels
ktime_get_real_ts64 arrived in 4.18; on older kernels the slot is
empty, so seeding Math.random through gettimeofday jumped to NULL and
took the agent down on the first script. Fall back to getnstimeofday64,
which has the same shape.
2026-09-15 17:01:18 +02:00
Ole André Vadla Ravnås 95a45760fe barebone: Keep the hostlink socket path short
A UNIX socket path must fit sun_path, and the system temp directory is
already long on some hosts; a full UUID pushed it over.
2026-09-15 17:01:18 +02:00
Ole André Vadla Ravnås 82dce5ddd0 barebone: Report agent faults on arm64 Linux
Record an unrecoverable fault in our own pages in frida_agent_fault and
park the thread, matching the win9x path, so the guest stays up and a
timed-out command can name where we stood. Without a reference the
record was stripped from the agent.
2026-09-15 17:01:18 +02:00
Ole André Vadla Ravnås 778462bc30 barebone: Wake the borrowed task on old kernels
A task caught entering schedule() has set itself to sleep, so a call
made in its context that sleeps -- module_alloc, kmalloc reclaim -- is
dequeued there and never resumed. Force it runnable first on kernels
predating execmem.

current lives in SP_EL0, which the stub does not surface as a register,
so read it by invoking a two-instruction stub that moves it into x0.
2026-09-15 17:01:18 +02:00
Ole André Vadla Ravnås b7d2722734 barebone: Skip the writable-half copy if absent
The pristine copy only feeds process-copies, and its few-hundred-KiB
kmalloc can fail on a fragmented guest. Proceed without it rather than
copy into a NULL destination.
2026-09-15 17:01:18 +02:00
Ole André Vadla Ravnås ec0d002537 barebone: Adapt the agent to pre-4.9 arm64 kernels
Base virt_to_phys on the classic linear map, at the top of the kernel
half, when the VA space predates the 5.4 flip; a queue ring handed to
the device otherwise lands 256 GB off and never advances.

Mark device mappings dirty so their first store does not trap, and map
through __ioremap where generic_ioremap_prot is absent.

Resolve a legacy PCI line through of_irq_parse_and_map_pci where
pci_irq_vector has not arrived, and fall back to __kmalloc where kmalloc
is inline. kernel_read and kernel_write took the offset by value before
4.14, so pass it that way.
2026-09-15 17:01:18 +02:00
Ole André Vadla Ravnås cd3b5b3f6c barebone: Fall back to module_alloc on old kernels
execmem_alloc arrived in 6.x; kernels before it hand out
executable memory through module_alloc/module_memfree.
2026-09-15 17:01:18 +02:00
Ole André Vadla Ravnås d750d38b4d barebone: Mask only IRQ when picking a context
Linux on arm64 keeps FIQ masked throughout, so the old IRQ|FIQ
test rejected every sleepable task on such kernels and injection
never found a context. Check the IRQ bit alone.
2026-09-15 17:01:18 +02:00
Ole André Vadla Ravnås 408ca82484 barebone: Harden kallsyms parsing on old kernels
Reject a token index whose entries are almost all empty, a lookalike
that turns up before the real table, by requiring the reconstructed
table to hold enough non-empty fragments. And anchor the names' start
on the position that decodes exactly as many symbols as the address
table has, so names stay paired with addresses instead of latching onto
an earlier clean-looking run.
2026-09-15 17:01:18 +02:00
Ole André Vadla Ravnås 34dfaf782b barebone: Support absolute kallsyms addresses
An older kernel stores each symbol's full address (kallsyms_addresses)
rather than a signed offset from a base, so fall back to reading the
absolute table when no relative base follows the offsets. Vet the base
as a page-aligned kernel pointer, and seed the names on a long clean
run so it cannot latch onto lookalike token data.
2026-09-15 17:01:18 +02:00
Ole André Vadla Ravnås f646ba93bd gdb: Fall back to built-in feature documents
Some stubs advertise a target.xml that includes the standard register
documents but decline to serve them; the Android emulator's does. Keep
a snapshot of the aarch64 core and fpu features and use it when the
stub cannot, so the register layout stays known.
2026-09-15 17:01:18 +02:00
Ole André Vadla Ravnås 9462ffb30f barebone: Recover kernel symbols from the image
Mine the kernel image's embedded kallsyms when the configured path is a
gzip or raw Image rather than a System.map, so no separate symbol file
is needed. Locate the token table, offsets and names, and pair each
name with relative_base + offset -- the address it was linked for.
2026-09-15 17:01:18 +02:00
Ole André Vadla Ravnås 3f4d81a7ba barebone: Instrument the Android emulator gdbstub
The emulator's gdbstub cannot commit register writes, crashes on
breakpoint insertion, and aborts on an unknown vcpu exit. Add an
ANDROID_EMULATOR stub flavor that requires the hosting process at
connection.pid; attach to it and load an embedded script that fixes all
three in place before driving the stub. The script verifies the
instruction at each patch site and refuses to patch a build it does not
recognise. Whitelist the pipe-over-vsock socket path there too.
2026-09-15 17:01:18 +02:00
Ole André Vadla Ravnås 9e210316ae barebone: Detect stub capabilities from registers
Reach a call's return through one run_until_pc helper shared by invoke
and execute, replacing the breakpoint dance each open-coded.

Detect the arm64 MMU system registers from the stub's advertised
register set rather than a config flag, choosing host page-table walking
versus the kernel's set_memory_* helpers. Read guest physical memory
from the local process at pid for a stub that needs it.
2026-09-15 17:01:18 +02:00
Ole André Vadla Ravnås 597e493ae8 barebone: Add a pipe-over-vsock Linux hostlink
On a full Android guest the agent cannot own a virtio device,
so reach the host over the guest kernel's own vsock stack:
connect to the emulator's connector port and hand it a
"pipe:unix:<path>" request, which it bridges to a UNIX socket
frida-core listens on. Wake the loop from a kCFI-thunked
sk_data_ready hook.
2026-09-15 17:01:18 +02:00
Ole André Vadla Ravnås 5040551e7d barebone: Make the Linux agent CFI/SCS/UBSAN-safe
Android kernels enforce kCFI, a shadow call stack and UBSAN
on the injected agent. Reserve x18 so we stop clobbering the
SCS pointer; hand the kernel kCFI thunks for the kthread and
IRQ callbacks (type id copied from a matching kernel fn, via
a range-independent indirect branch); skip cloak_task, whose
detach_pid arity varies and trips UBSAN.
2026-09-15 17:01:18 +02:00
Ole André Vadla Ravnås e78b371d32 barebone: Protect pages via kernel set_memory_*
When the stub lacks the MMU system registers, change page
protection by calling the kernel's set_memory_ro/rw/x/nx and
take the page size as 4096, rather than walking the tables.
Gate on connection.mmu_registers_available; allocate agent
code from EXECMEM_MODULE_TEXT on that path.
2026-09-15 17:01:18 +02:00
Ole André Vadla Ravnås 81d5b29bd9 barebone: Find the arm64 kernel by its banner
The arm64 kernel find hunts the Image header magic, but a loader that
zeroes the header leaves nothing to match, and walking back from the
hijacked pc misses a kernel the pc sits above in a module. Anchor on
linux_banner instead, as the x86 path already does: confirm it at the
linked address first for a kernel that was not slid, then sweep.
2026-09-15 16:40:24 +02:00
Ole André Vadla Ravnås afd70d834e barebone: Disarm PatchGuard on arm64 winnt
PatchGuard stops the machine with bugcheck 0x109 about a minute and a
half after anything patches kernel .text, which is every hook the
Interceptor installs. It reaches its checks by queueing its own KDPCs
whose DeferredContext is a non-canonical pointer: the host routines it
borrows test for exactly that and branch into PG. Write a canonical
context and the routine takes its own path instead.

Find them by shape, since none of the host routines are exported: walk
the pool window the kernel allocates from, asking MmIsAddressValid
before each page, and look for a routine in ntoskrnl's .text beside a
non-canonical context. Insist on the object being a KDPC as well. A
queued work item carries the same pair at the same spacing, and writing
to one is itself reported -- that alone bugchecks in the usual minute
and a half, naming the routine it borrowed.

Do this before anything patches .text, so there is nothing to find in
the meantime.

Also give each processor back the vector table it had. They do not all
boot on the same one, so restoring a single value left some of them on
a table that was never theirs.
2026-09-15 15:19:28 +02:00
Ole André Vadla Ravnås de22bb5ecb barebone: Tell an absent module from an empty one
An export directory whose pages are all absent read back as zeroes,
which parses exactly like a module that exports nothing. The kernel
image was then rejected for lacking PsLoadedModuleList, and the only
symptom was "Unable to find the kernel image".

Say so instead, and let the two callers that are merely looking decide
to move on: image discovery tries the next candidate, and symbol
enumeration skips the module.
2026-09-15 15:19:28 +02:00
Ole André Vadla Ravnås c1c7a406dd barebone: Take arm64 faults from a shadow table
PatchGuard hashes the exception vectors, so patching them stops the
machine with bugcheck 0x109. It only reads their contents, though, so
leave those alone and run from a table of our own.

Forward every entry to the kernel's rather than copying it: the entries
are full of pc-relative branches, which a copy this far away would send
into nothing. The veneer needs a register and only x18 is ever free.
Coming from EL1 the kernel overwrites it before reading it; coming from
EL0 it holds the user's TEB and the entry's second instruction saves it,
so there the two saving instructions are copied and the veneer resumes
past them.

Take the first slot of the table array in CFGRO, not the one VBAR_EL1
names: a processor boots on whichever its mitigations pick, while
KiPreflightReturnToUserMode reloads from the first. Writing the slot
only converts the processor that reloads next, so install the table on
all of them through KeIpiGenericCall, and give each one back the table
it had.

The handler moves to its own stack before building a frame, since a
fault can arrive on an interrupt or DPC stack with nothing left, and
restores x18 after aligning the stack, or every kernel call it makes
reads the wrong processor block.
2026-09-15 15:19:28 +02:00
Ole André Vadla Ravnås 2eb5f9a3a7 barebone: Let callers take turns handing over work
Work that must run on the thread's own stack goes through a single
slot, so two callers at once would lose one another's, and each would
then wait out its slices for an answer that is not coming.
2026-09-15 15:19:23 +02:00
Ole André Vadla Ravnås 7a4f4bdd83 barebone: Bound the wait for the stack helper
Work that has to run on the thread's own stack is handed to a helper
thread, which is started the first time anyone asks. The requester then
waited for it without a deadline, so a helper that is slow to appear --
the kernel still finding pages for its stack -- or one that never
appears at all left the agent stopped there for good.

The loop already re-reads the slot, so wait in slices and look again,
the way the GLib primitives do.
2026-09-15 15:19:23 +02:00
Ole André Vadla Ravnås 8e9fc804d9 barebone: Carry on when the port refuses a setting
A port bridged to a socket has no use for a baud rate, and the driver
is free to turn the request down. Ask for the settings, wait for each
answer so none is still in flight when the first frame goes out, and
keep the port either way.
2026-09-15 15:19:18 +02:00
Ole André Vadla Ravnås b204633ef4 barebone: Give each serial direction its own event
One event cannot carry two requests: a notification event stays raised,
so the next wait returns on a signal that was not its own, and an
auto-reset one is taken by whichever waiter arrives first. Either way a
read and a write on the same port answer for each other, which is why a
reply came back only now and then.

Give the reader and the writer an event each, made once and cleared
before every request, so a synchronous completion cannot leave one
raised for the next. Wait for the port's own settings the same way,
rather than leaving them in flight while the first frame goes out.

Ask the port to hold a read until a byte arrives: with the interval and
total multiplier both at their maximum, a read completes on the first
byte received, so nothing wakes up in between.
2026-09-15 15:19:18 +02:00
Ole André Vadla Ravnås 5e71497c64 barebone: Overlap serial reads and writes
The port was opened for synchronous I/O, which makes the kernel let
one request at a time near the file object. The reader spends its life
parked in a read, so nothing could ever be written back: whether the
greeting escaped at all came down to whether it was issued before the
reader got going.

Open the port for overlapped I/O instead and give each direction an
event to wait on, so a reply can go out while a read is outstanding.
2026-09-15 15:19:13 +02:00
Ole André Vadla Ravnås 434c8c02dd barebone: Wait to be greeted over a serial link
A serial port discards whatever is written to it while the guest side
is closed, and the agent opens its end well after we have connected
ours, so the first command went nowhere. The other transports connect
to us and give us that ordering for free.

Have the agent announce itself once its transport is up, and hold the
serial link's setup until the greeting arrives.
2026-09-15 15:19:07 +02:00
Ole André Vadla Ravnås 035aa392c4 barebone: Let the loop notice a serial frame
The blob flavour does not drain its transport from the main loop; it
waits until the kernel half says it has work. That question was only
ever put to the virtio, character device and vsock links, so a frame
the serial reader had queued sat there and the host waited for a reply
that was never composed. Have the serial link answer it too.
2026-09-15 15:19:07 +02:00
Ole André Vadla Ravnås ac5c84009e barebone: Order the serial line control correctly
SERIAL_LINE_CONTROL asks for the stop bits first, then the parity,
then the word length, so the port was told to use eight stop bits and
a word length of zero. The driver refused the request and left the
line at whatever it had.
2026-09-15 15:19:07 +02:00
Ole André Vadla Ravnås 8b808ad576 barebone: Give an event the room the kernel needs
The events behind the wait primitives were allocated 16 bytes, but a
KEVENT holds a word of type and size, the signal state, and a list
head for its waiters. KeInitializeEvent points that list head at
itself, so on a 64-bit kernel the last eight bytes landed outside the
block and overwrote the neighbouring pool chunk's header, which the
kernel later reported as a corrupt variable-size subsegment.
2026-09-15 15:19:07 +02:00
Ole André Vadla Ravnås 55ce119f63 barebone: Let the kernel grow the worker's stack
A system thread gets 32 KiB and the script runtime needs more, so the
body ran on a stack of our own. The kernel checks the stack pointer
against the bounds it recorded for the thread, though, and fast-fails
outside them, which is what stopped the machine in RtlpGetStackLimits.

KeExpandKernelStackAndCalloutEx does the same thing with the kernel's
knowledge, so ask for the room and run the body in the callout. The
previous commit reached this by rewriting the bounds in the thread
object, which needed offsets that move between releases.
2026-09-15 15:19:07 +02:00
Ole André Vadla Ravnås c4323db441 barebone: Declare the stack the worker runs on
A system thread gets 32 KiB and the script runtime needs more, so the
body has always run on a stack of our own. The kernel, though, checks
the stack pointer against the bounds it recorded for the thread and
fast-fails when it falls outside them, which is what stopped the
machine in RtlpGetStackLimits.

Rewrite those two bounds for as long as we are on our stack, and put
the thread's own back afterwards. Find them as the page-aligned
pointers nearest either side of the stack the kernel handed us, rather
than at offsets that move between releases.
2026-09-15 15:19:07 +02:00
Ole André Vadla Ravnås e7da82bc65 barebone: Turn foreign faults away in the vector
Windows takes every kernel-mode synchronous exception through the entry
we hook, so nearly all of what arrives belongs to the kernel. Deciding
that in the handler meant each of those faults first switched stacks
and ran our code with interrupts masked, on whichever processor and at
whatever IRQL it happened to be.

Compare the return address against our own image in the entry itself,
which takes a handful of instructions and the two registers already
saved there, and branch to the handler we replaced when it does not
match.
2026-09-15 15:19:07 +02:00
Ole André Vadla Ravnås a7dcfac8e0 barebone: Publish the fault entry atomically
The entry was overwritten a word at a time while every processor kept
taking its faults through it, so one that faulted mid-copy ran a half
written trampoline. Build the trampoline in the free bytes the entry
has to spare, make it visible, and only then replace the one
instruction at the top, which is a single store.
2026-09-15 15:19:07 +02:00
Ole André Vadla Ravnås 3b8f649fde barebone: Give the fault thunk its own stack
The thunk saved the context and then ran the Exceptor on the stack of
the thread that faulted. A kernel thread has little room to spare, and
the handler needs as much as the script asks for, so the pointer left
the thread's stack behind and the kernel stopped with a stack switch
panic. Keep the save where it is, bounded, and move to a stack of our
own, one per processor, for the handler itself.
2026-09-15 15:19:07 +02:00
Ole André Vadla Ravnås a6800681ae barebone: Claim only faults from our own code
Windows runs its kernel threads at EL1t, so the synchronous entry we
hook is where every thread in the system takes its faults, most of
them the kernel's own business: a demand-paging fault it is about to
service. Handing those to the Exceptor first ran the agent on a
foreign thread and swallowed work the kernel was waiting for.

Restore the range check, take the arm64 bounds from the linker rather
than the config so the gate holds from the first instruction, and let
everything else go straight to the handler we replaced.
2026-09-15 15:19:07 +02:00
Ole André Vadla Ravnås d72b3dbe82 barebone: Reserve every page an allocation spans
A kernel allocator hands out a block with less than page alignment, so
the allocation starts on the page boundary above it. Ask for enough
that every page the result spans is ours: the caller changes the
protection of those pages and writes all of them, and the tail of the
last one belonged to whoever came next.
2026-09-15 15:19:07 +02:00
Ole André Vadla Ravnås a9c7694e21 barebone: Clear what an image leaves uninitialised
An ELF keeps its zero-initialised data out of the file, so writing the
file over an allocation leaves that part holding whatever the previous
owner put there. A pool hands out memory as it finds it, and both the
Rust and the C halves read statics expecting zero.
2026-09-15 15:19:07 +02:00
Ole André Vadla Ravnås bd0c4fc4cd barebone: Stop asking the monitor for MMU regs
Parallels serves no system register, so the arm64 machine read them
through a monitor command instead. That command aborts the VM whenever
another processor happens to be somewhere the monitor cannot read it
from, which it does often enough to end a run at random and to make
every measurement around it untrustworthy.

Say NOT_SUPPORTED there instead, and answer the page size from the
granule the kernel uses, which is the only thing the injection path
wanted the registers for. Route the remaining readers through the
cache as well, so a target that does serve them is asked once.
2026-09-15 15:19:07 +02:00
Ole André Vadla Ravnås 42069c36a9 barebone: Leave a migrated core its own x18
A call may reschedule onto another core, and both invoke() and execute()
match the landing thread rather than the one they started on. Each then
wrote back every register it had saved, x18 among them. That register
holds the KPCR on Windows, so a core that took the call over was given
the processor data of the core that began it, and every later access
through it reached the wrong processor.

Restore the platform register only where the call stayed put, through
one helper both share.
2026-09-15 15:18:57 +02:00
Ole André Vadla Ravnås 50f9f3fa40 barebone: Fault on the interrupted thread's stack
Taking an exception selects SP_EL1, which Windows does not keep as a
stack a handler may spend. The replacement vector parked registers
below it and the thunk then took a frame there and called on, which
scribbled over the kernel stack region and showed up much later as a
FAST_FAIL_CORRUPT_LIST_ENTRY on an unrelated thread.

Select SP_EL0 on the way in, which is the kernel stack the interrupted
thread was already running on, and select SP_EL1 again before chaining,
so the kernel's own handler is entered as the hardware would.
2026-09-15 14:42:14 +02:00
Ole André Vadla Ravnås 9cf631c522 barebone: Test the arm64 WinNT agent
Map out a live arm64 guest, and run a script in one. Both read where
to find the guest from the environment, as the other live-guest tests
do.
2026-09-15 14:42:13 +02:00
Ole André Vadla Ravnås 907ba95f38 barebone: Add a serial hostlink transport
A Parallels guest has no virtio and no vsock, but it does have a PL011
that the host sees as a Unix socket. Carry the hostlink over it.

Find the port by enumerating SERIALCOMM, configure it, and read on a
thread of its own, so the loop wakes on a frame rather than polling.

Connect from the host before the guest resumes: the emulated port
drops what it writes while no peer is attached.
2026-09-15 14:42:13 +02:00
Ole André Vadla Ravnås 0f2dc534e6 barebone: Align what the Rust allocator hands out
The global allocator asked the kernel for the size and ignored the
alignment, so anything wanting more than the pool happens to give came
back misaligned, and dealloc freed a pointer that was never the block.

Go through the C shims, which already over-allocate and keep a header,
and hold the floor at two pointers.
2026-09-15 14:40:58 +02:00
Ole André Vadla Ravnås 3176508870 barebone: Port the WinNT agent to arm64
Teach the agent the other calling convention, the wider CONTEXT, and
the shape of a Zw stub, which is a movz of the service index and a
branch to the dispatcher.

There is no self-map on arm64, so walk the tables from TTBR1_EL1 by way
of MmGetVirtualForPhysical. The permission bits read the other way
around: a descriptor says read-only and execute-never, where an x86
entry says writable and no-execute.

Take faults from the vector table, which the kernel offers no interface
to replace. Patch the entry for SP_EL0, the stack a Windows kernel
thread runs on; the SP_ELx entry is never taken.

The kernel maps its own image with blocks, which no page table breaks
up, so reprotect the descriptor that maps the address at whatever level
it sits, and tell the caller how far that reached. Skipping such a
mapping left the vector page read-only and the write bugchecked.

Reserve x18. It holds the KPCR, and the compiler is otherwise free to
use it as a scratch register.
2026-09-15 14:40:58 +02:00
Ole André Vadla Ravnås c4c4b70280 barebone: Map out an arm64 WinNT kernel
The version block that the x86 flavor reads is not there on arm64, so
find the anchors from the running kernel instead: x18 holds the KPCR,
the image lies below it at a 64 KiB boundary, and it exports the module
list. Walk from PsInitialSystemProcess to reach the process list.

Set the landing zone for a call to KeBugCheckEx, and size a breakpoint
by the target's architecture rather than assuming one byte. Without
either, a run on more than one processor comes apart.

Read the MMU registers through the monitor on a Parallels guest, whose
stub serves no system register.
2026-09-15 14:40:58 +02:00
Ole André Vadla Ravnås 25a10dae9f barebone: Add a Parallels GDB stub client
Parallels Desktop serves a GDB remote from prl_vm_app once vm.debug is
set. It names its architecture by feature rather than in target.xml,
answers qAttached with nothing, and serves memory only after a stop
reply, so the generic client cannot drive it.

Halt from enable_extensions rather than while preparing the connection:
the stop reply carries register values, and nothing can parse those
until the register set is installed.

The stub honours guest page protection, and a failed write aborts the
VM, so promote a soft breakpoint to a hardware one wherever a target
reports protected code.
2026-09-15 14:40:57 +02:00
Ole André Vadla Ravnås 01ba812d82 barebone: Let an allocator declare protection
A pool that already hands out executable memory needs no page-table
walk to make the agent runnable. Let the allocator say what it grants,
and skip the reprotect when it covers what the caller wants.

Declare the Windows pool executable, as it is.
2026-09-15 14:40:55 +02:00
Ole André Vadla Ravnås a1211f3b24 barebone: Stop gating faults on our own range
The Exceptor lets a user handle any fault, not only one taken in the
agent's own code. Drop the range checks that hid every other fault
from it, and the idle-slab helper that only those checks needed.
2026-09-15 14:40:54 +02:00
Ole André Vadla Ravnås 6c96a18b0b barebone: Find the headers when built on Windows
Canonicalizing an include path there hands back the extended-length
form, and the separators inside one are never normalized -- so the
compiler found <limits.h> and then lost <sys/cdefs.h>, and no
bindings could be generated at all.

Hand on the path as it is written instead.
2026-09-15 14:02:35 +02:00
Ole André Vadla Ravnås c28a321e97 barebone: Keep a queue within the device's maximum
Every queue was set up with room for 64, which virtio-serial's control
queues do not have -- they hold 32, where the data queues hold 128.
The check that would have caught it was a debug_assert, and the agent
ships released, so it wrote 64 over a queue half that size.

QEMU used to let that pass; since the fix for CVE-2026-50626 it
refuses the size and marks the device broken, and a broken device
never announces the port the host is waiting for.

Ask what the device allows and stay within it, which is what the spec
asked for all along.
2026-09-15 14:02:35 +02:00
Ole André Vadla Ravnås 74462098a5 barebone: Open the hostlink without passing fds
Handing QEMU a socket over SCM_RIGHTS is the one thing that cannot be
made to work on Windows: AF_UNIX is there, but ancillary data is not,
so getfd had no answer and the whole hostlink was shut out.

Let QEMU serve the socket and connect to it instead. The chardev was
already a socket backend; only the address changes from a file
descriptor to a path. A socket that stays put for the life of the
chardev can be looked at while it is in use, which is worth more than
it sounds when a guest will not come up. One way in on every
platform, and the last of the guards in these two files goes with it.
2026-09-15 14:02:35 +02:00
Ole André Vadla Ravnås 36048b9747 barebone: Reach QMP over a UNIX socket on Windows
Windows has had AF_UNIX for years and GLib builds the socket address
for it there, headers and symbols alike -- only the Vala bindings
were missing, as the backend asks for gio-windows-2.0 alone.

Ask for gio-unix-2.0 as well, and let the address be made the way it
is everywhere else. Vala emits an include per symbol, so nothing
Windows lacks comes along with it.
2026-09-15 14:02:35 +02:00
Ole André Vadla Ravnås 3f1315f81d barebone: Report agent faults to the host
Park the thread on any fault our own code cannot handle, not
only on an invalid opcode, and record the vector, the program
counter and the address in frida_agent_fault. The host reads
that record when a command times out and names the function the
agent stood in.

Our pages are fixed and locked, thus a fault that reaches this
point is ours to answer for, and Windows answers it by taking
the guest down. Parking keeps the guest up and leaves the fault
where the host can read it.

The record belongs to every kernel the agent runs on; Win9x is
the only one that writes it so far.
2026-09-12 01:25:49 +02:00
Ole André Vadla Ravnås 1ee4e76dbf barebone: Give Gum a stack it may scribble on
Hand Gum the stack the thunk saved, not the stack the faulting
code ran on.

Gum's x86 recovery keeps no stack pointer of its own: it takes
the one in the context, backs off, and writes its scope and a
return address there. The faulting stack pointer sits above the
frame that VMM and the thunk are still standing on, thus those
writes landed in the middle of them, and a caught read wedged
the agent instead of returning.
2026-09-12 01:25:38 +02:00
Ole André Vadla Ravnås ab66f9caf4 barebone: Take only our own faults on Win9x
Read the faulting registers from the frame at VMM's EBP instead
of the thunk's own pushad block, and ask Gum only for the faults
of our ring zero code. Every other fault goes to Windows.

Gum answers for the thread that faulted, thus a fault the agent's
thread took elsewhere -- inside VMM, or in an interrupt that
preempted it -- was claimed by an open exception scope and resumed
on registers that were never the faulting ones. Moving the mouse
supplies such faults at a steady rate, which bluescreened the
guest while the agent was attached.
2026-09-11 16:54:56 +02:00
Ole André Vadla Ravnås 0602e5dca4 subprojects: Prepare for release 2026-09-09 20:37:12 +02:00
Ole André Vadla Ravnås cbcb39b724 barebone: Stop copying export names
The name a module hands out lives as long as the callback needs
it, so pass it straight through instead of leaving a copy behind
for every export.
2026-09-09 20:31:40 +02:00
Ole André Vadla Ravnås 30e00071be subprojects: Bump outdated 2026-09-09 20:16:23 +02:00
Ole André Vadla Ravnås 9712f1021b barebone: Expose BTF to scripts
Register a Btf namespace with the script API registry, so scripts
can ask the kernel's own type information about a structure, an
enum or a function instead of hard-coding what it says.

    const m = Btf.getStruct("module");
    m.size;                     // 1280
    m.getOffsetOf("name");      // 24
    m.fields.name;              // { offset: 24, size: 56,
                                //   type: "char[56]" }
    Btf.getConstant("MODULE_STATE_LIVE");   // 0
    Btf.getFunction("__symbol_get");        // void * (const char *)

Index the types while walking them, so a lookup resumes where the
last one stopped, and remember only what was found.
2026-09-09 20:09:39 +02:00
Ole André Vadla Ravnås 8ba21583ca barebone: Let an x86 run span leaf tables again
Scanning the upper half from the top stopped a run at every root
entry, so nothing longer than one leaf table could be mapped.
Start one root entry lower on each attempt instead, and let the
run carry on into the entries above it.
2026-09-09 20:09:39 +02:00
Ole André Vadla Ravnås d363476d24 barebone: Leave the module version unset
Report no version rather than an empty string when there is
none to give, so scripts see null.
2026-09-09 15:20:33 +02:00
Ole André Vadla Ravnås 87a3e60980 barebone: Keep the copy out of the kernel
Symbol enumeration walked the kernel's module list from the copy,
whose BTF pointer is a kernel address, so the process died on
SIGSEGV as soon as a script asked an image for its symbols.

Answer from the copy's own images instead, through GumElfModule.
2026-09-09 15:11:32 +02:00
Ole André Vadla Ravnås e340e0a36a barebone: Read the copy's images through Gum
Drop the agent's own ELF walker and hand the mapped image to
GumElfModule.

Read the file the mapping came from rather than the image in
memory, so the parse sees what the loader left out.
2026-09-09 15:00:00 +02:00
Ole André Vadla Ravnås c3d661f10f barebone: Expose Linux module symbols
Enumerate the symbols a loaded module keeps in its kallsyms
table, and the symbols it exports, so scripts can resolve names
inside a driver instead of only the kernel.

Read both through BTF where the kernel carries it, and probe for
them otherwise, following only pointers the module's own memory
holds and reading through Gum, which recovers from a fault.
2026-09-09 14:35:31 +02:00
Ole André Vadla Ravnås 783ce27c15 barebone: Size the Linux kernel from _end
Symbols above _end name fixed mappings the kernel keeps
elsewhere, such as the vector page a 32-bit ARM kernel puts near
the top of memory. Taking the highest symbol instead grew the
kernel to 2 GiB there.
2026-09-09 14:35:31 +02:00
Ole André Vadla Ravnås 9e8e3f07f4 barebone: Expose kernel modules on Linux
Enumerate the loaded modules and watch for load and unload
events, so the module registry stays in sync and scripts can
observe drivers coming and going.

Share the publishing logic between the injected agent and the
kernel module. When injected, reach the module list through BTF,
falling back to probing the list itself on kernels built without
it, and go through the regparm thunks on i386.
2026-09-09 14:35:31 +02:00
Ole André Vadla Ravnås 5b780434b8 subprojects: Bump outdated 2026-09-09 00:51:54 +02:00
Ole André Vadla Ravnås 1443659088 submodules: Bump releng 2026-09-09 00:51:53 +02:00
Ole André Vadla Ravnås 86c50f1c2d subprojects: Bump outdated 2026-09-09 00:46:47 +02:00
Ole André Vadla Ravnås 6560175dc3 barebone: Report RWX support on Linux
The agent owns its own pages now, so the allocator can take them
writable and executable instead of flipping each slab twice per patch.
2026-09-09 00:37:35 +02:00
Ole André Vadla Ravnås eb489b0d7c barebone: Flush caches without a syscall on arm
__clear_cache() went through SVC, which is what a process does. Ours
is the kernel; a handful of CP15 writes is all it takes.
2026-09-09 00:37:35 +02:00
Ole André Vadla Ravnås f49536ce1c barebone: Do Linux memory work in the guest
The pages the agent allocates are its own, and set_memory_rw() makes
them writable where the host used to map an alias. Kernel text is the
one place the kernel refuses, and it offers its own patch primitives.
2026-09-09 00:37:35 +02:00
Ole André Vadla Ravnås 507c513ecb barebone: Start the kernel scan at the half on arm
Three quarters in assumes a 3G/1G split. Kernel space starts at or
above the half on either, so start there.
2026-09-09 00:37:35 +02:00
Ole André Vadla Ravnås 33aaecea7d subprojects: Bump outdated 2026-09-08 22:33:52 +02:00
Ole André Vadla Ravnås e6b3859b94 submodules: Bump releng 2026-09-08 22:33:51 +02:00
Ole André Vadla Ravnås a4738780e7 barebone: Translate and alias in Arm's kernel half
Arm had no address translation, so remapping was refused outright, and
the run it allocated came from whichever half the scan reached first.
Walk the table the kernel uses, and prefer the kernel's own range.
2026-09-08 18:25:09 +02:00
Ole André Vadla Ravnås 3921730cae barebone: Widen the pages a 32-bit agent asks about
The remap request carries 64-bit addresses, and the array of pointers
went out as-is: on a 32-bit target the host read two of them per
element and translated whatever came out. Widen them first.
2026-09-08 18:25:09 +02:00
Ole André Vadla Ravnås cebc16fdff barebone: Map x86 aliases where the kernel lives
The scan started at the bottom of the address space and took the first
free run, which is user space and belongs to whichever process happens
to be current. A CModule kept its data there and faulted on the first
reference; the module space at the top is both valid and within reach
of a 32-bit displacement.
2026-09-08 18:25:09 +02:00
Ole André Vadla Ravnås 2a5eb32b39 barebone: Measure the stack the module runs on too
The kernel module half never said how much stack it had, leaving the
runtime with the backend's fixed floor rather than what is left below
the caller. Same kernel thread, same 16 KiB, same answer as the
injected half already gives.
2026-09-08 17:18:35 +02:00
Ole André Vadla Ravnås 47c3b0818c barebone: Serve memory RPCs on any architecture
Remapping and patching were written for Arm64 and refused elsewhere,
so on x86 the agent got a null alias back and wrote a trampoline
through it, taking the guest down. Translation is a machine's own
business, and a kernel the host can write to virtually needs no walk.

The three memory commands also touch the guest, so halt it for them:
the stub answers nothing while the guest runs.
2026-09-08 17:18:35 +02:00
Ole André Vadla Ravnås dd1d1b5a1b barebone: Share the halt around physical access
Every architecture switches the stub into physical addressing the same
way, and every one of them needs the guest halted for it. One pair of
helpers, and Arm's and x86's paths get the fix Arm64 had.
2026-09-08 17:18:34 +02:00
Ole André Vadla Ravnås 5b23bcdb38 barebone: Know a shadow page by its own record
Shadow pages were recognized by address, on the assumption that the
kernel heap lives above 0xfffff000_00000000. Linux hands out kmalloc
memory from the linear map far below that, so every patch to kernel
text was quietly dropped instead of being committed: hooks reported
success and never landed.
2026-09-08 16:48:55 +02:00
Ole André Vadla Ravnås 9d1fe85ab2 barebone: Halt the guest to reach physical memory
QEMU answers Qqemu.PhyMemMode only while the target is stopped, and
after injection nobody stops it, so the first Memory.protect() from a
script waited for a reply the stub was never going to send, taking the
session with it.
2026-09-08 16:48:55 +02:00
Ole André Vadla Ravnås 6c538f2262 tests: Cover Barebone's arm64 Linux guest
The live-guest tests skipped arm64, and none of them made a script
answer: rpc is a message in and a message out, which is what a REPL
does and what the one-way send() above never reached.
2026-09-08 15:21:49 +02:00
Ole André Vadla Ravnås 5f88059819 barebone: Report the real kernel stack size
The kernel half claimed the megabyte that a copy in a process runs on,
so the runtime's own limit sat far past the end of the 16 KiB a kernel
thread has, and a script a few frames deep panicked the guest instead
of throwing. Measure what is left below the entry into JavaScript.
2026-09-08 15:21:49 +02:00
Ole André Vadla Ravnås b067b3ea24 compiler: Emit ES modules for bundling
Node16 made tsgo emit CommonJS, which esbuild rewrapped in interop
and a default export. Consumers that eval a bundle as a plain script
then choke on the export. Emit ESM with bundler resolution instead,
rewriting relative import extensions so .ts imports still resolve.
2026-09-08 14:17:06 +02:00
Ole André Vadla Ravnås 300e8aeedf barebone: Bracket the kmod's init array
kbuild owns the final link and no script defines the constructor
brackets, so the prelinked object left them undefined. Give the
prelink a script that does.
2026-09-08 12:09:02 +02:00
Ole André Vadla Ravnås 93022f9e82 barebone: Fix the Linux kernel module build
The module half reached for the injected half's heap, symbol table
and arena forwarding, and had no process id of its own. Gate each
on the flavour that has it, and give the module one.
2026-09-08 11:37:47 +02:00
Ole André Vadla Ravnås bb0550a900 tests: Build LSP file URIs properly
Concatenating "file://" with a Windows path put backslashes in
the JSON, which the language server rejects.
2026-09-07 17:06:32 +02:00
Ole André Vadla Ravnås d53eade5b1 compiler: Initialize the backend only once
Both Compiler and LanguageServer initialize it from their static
construct, and starting the Go runtime twice aborts the process
with "randinit twice".
2026-09-07 16:50:10 +02:00
Ole André Vadla Ravnås 3447b29a34 compiler: Drop the duplicate Text field
BackendEvent declared it twice, which some Go toolchains reject.
2026-09-07 16:50:10 +02:00
Ole André Vadla Ravnås 9219ec7526 ci: Disable V8 on windows-arm64
To avoid issues with our prebuilt V8 being built with two different MSVC
versions (Intel vs arm64), whose STLs differ.
2026-09-07 16:47:09 +02:00
Ole André Vadla Ravnås fa16655e54 tests: Fix the x64 RELATIVE relocation test
Give it an addend, as that is where such a relocation carries
its value.
2026-09-07 16:19:45 +02:00
Ole André Vadla Ravnås e6e2b1d1aa Fix builds without the Barebone backend
Guard remove_barebone_device() the way add_barebone_device()
already is, and keep resolve_mmio_base() out of the Windows
build, where the QOM property getters it needs are absent.
2026-09-07 16:11:37 +02:00
Ole André Vadla Ravnås 39583a2761 barebone: Cast the range size XNU reports
The shared range reporter takes a u64, which is what every other
kernel's enumerator hands it; XNU's uses a usize.
2026-09-06 22:15:04 +02:00
Ole André Vadla Ravnås 9198961cb5 compiler: Share parsed files with LanguageServer
Put the build's program and the LSP session on one ParseCache, so a
file is parsed once per content hash however many of them are open.
The compiler refs each program file and derefs on replace or dispose.
2026-09-06 20:37:28 +02:00
Ole André Vadla Ravnås a987284afa compiler: Add LanguageServer
Run the TypeScript language server in-process, speaking LSP through
post() and the message signal, with the typings and options a build
sees. macOS links CoreServices for its file watcher.
2026-09-06 20:37:28 +02:00
Ole André Vadla Ravnås 3000eeffd8 compiler: Stat the embedded typings
Program reuse between builds stats every source file, and a typing
without one forced a fresh program.
2026-09-06 20:37:28 +02:00
Ole André Vadla Ravnås 5a7e94c75a compiler: Move to TypeScript 7's Go compiler
Track the tsc module in microsoft/TypeScript through our fork, which
also hosts what typescript-go used to. Ask for every @types package,
as 7 only includes them on request.
2026-09-06 20:37:28 +02:00
Ole André Vadla Ravnås 7e3d7a1d78 barebone: Match the Linux kmod detach_pid to 6.18
The 6.18 detach_pid collects freed pids into a caller array that
free_pids releases; take that shape when reparenting a spawned
thread, the way the injected agent already does.
2026-09-06 19:20:21 +02:00
Ole André Vadla Ravnås 2977e3dbac barebone: Hide the Linux copy's own footprint
Filter the copy's own threads and mapped ranges out of what it lists,
and record the relay FDs it holds so those can be hidden too.
2026-09-06 19:20:21 +02:00
Ole André Vadla Ravnås 865e7b512d barebone: Hide the Linux kernel half's own threads
Unlink each spawned thread from the task list and the pid hashes so
neither the guest nor our own enumeration reports it. On i386 route
the tasklist write lock and pid detach through the regparm shim, the
way its read lock already goes.
2026-09-06 19:20:21 +02:00
Ole André Vadla Ravnås b2353b0281 barebone: Enumerate the Linux kernel half natively
Listing ranges and threads went through the copy's /proc paths, whose
syscalls fault a kernel thread. Walk the kernel's own modules and task
list instead.
2026-09-06 15:52:20 +02:00
Ole André Vadla Ravnås 29545abf8b api: Drop async constructors from the gir
Vala emits them as sync-looking entries whose C symbol does not
exist, so the bindings cannot bind them anyway.
2026-09-06 11:52:49 +02:00
Ole André Vadla Ravnås 522af7bc22 barebone: Model the agent config as a sum type
Split it into injected and resident cases, and the transports to
match, so no invalid combination can be expressed. Carry the image
as bytes and name the hostlink fabric.
2026-09-06 11:52:49 +02:00
Ole André Vadla Ravnås 60af24b496 barebone: Resolve a virtio-mmio hostlink's base
Map it from the QOM bus topology so the agent need not find it
itself.
2026-09-06 11:52:49 +02:00
Ole André Vadla Ravnås 4e1b650ee0 barebone: Keep Linux's blob changes to itself
The Linux kernel-half work reached into the shared blob worker two
ways the other blobs cannot follow.

Install the fault reporter before the constructors again on the
non-Linux blobs, so winnt and win9x still catch a fault during init;
Linux alone waits until Gum and its own range are up, as its
recovery needs both.

Look up the virtio-mmio IRQ only on Linux; the call reached a
primitive the other kernels do not carry, so their blobs no longer
built.
2026-09-05 18:47:07 +02:00
Ole André Vadla Ravnås 907a997112 barebone: Recover Linux kernel guarded faults
Let a script on the kernel half guard a read the way one in a copy
does: an invalid access inside PID 0 unwinds to the catch, not a
dead kernel.

i386 and x86-64 halt the oops from a die notifier; 32-bit arm and
arm64 die past it, so detour fixup_exception through the kernel's
own text patcher. The i386 die frame carries no stack pointer and
restores only eip, so resume through a stub and stage Gum's longjmp
on a private scratch.
2026-09-05 18:18:42 +02:00
Ole André Vadla Ravnås 1cf143e07e barebone: Map a virtio-mmio hostlink's interrupt
A virtio-mmio device the guest kernel never drives has no interrupt
line mapped, so hooking one needs it made first. Decode the slot's
device-tree node -- named by its address -- to make the line and
learn its number, the way the PCI half asks the kernel for its own.
2026-09-05 18:18:30 +02:00
Ole André Vadla Ravnås 527d644526 barebone: Read arm's SCTLR under arm64 name too
QEMU's 32-bit arm stub names the banked system registers after
their arm64 aliases, so the control register comes back as
SCTLR_EL1 and the old sctlr lookup finds nothing. Fall back to the
aliased name when the plain one is absent.
2026-09-05 18:18:30 +02:00
Ole André Vadla Ravnås cda85394d4 barebone: Supply arm's unaligned access helpers
The compiler calls __aeabi_uread4, __aeabi_uread8 and
__aeabi_uwrite8 for the unaligned loads and stores a packed field
wants, and compiler_builtins does not carry them the way libgcc
would, so the 32-bit arm blob would not link. Copy the bytes
across by hand.
2026-09-05 18:18:30 +02:00
Ole André Vadla Ravnås 1c4c490106 barebone: Find a Linux copy's registers on x86-64
The copy's first user register file sat at the top of its kernel
stack, found by rounding the running stack pointer up. x86-64 keeps
that top sixteen bytes lower, in a per-cpu word, so every register
written landed past the frame the return reads. The copy took its
first interrupt with a stale segment and per-cpu base and brought
the kernel down.

Read cpu_current_top_of_stack and seat the frame under it.
2026-09-05 18:18:30 +02:00
Ole André Vadla Ravnås b7f6e48e76 barebone: Ask Linux for the virtio-pci IRQ
The x86 half read the interrupt line from PCI config space, which
holds nothing when the guest boots straight into the kernel with
no firmware to route the pins. The interrupt never arrived, so the
control queue went unserviced, the port never opened, and the host
waited forever.

Take the line from the kernel, the way the arm halves already do;
the config-space read stays for the Windows guests, whose firmware
fills it in.
2026-09-05 18:18:30 +02:00
Ole André Vadla Ravnås 64bc34ab4e barebone: Put the Linux agent in module data
execmem_alloc's module text is mapped read-only in 2 MB pages on
6.12, and relaxing the agent's data to writable hits a large page
the backend cannot split. vmalloc gives 4 KB pages but sits more
than two gigabytes from the kernel's text, out of reach of the
rel32 calls a tracepoint patches in to reach a probe.

Module data is mapped in the module range in 4 KB pages: near
enough for the direct calls, fine enough to protect a page at a
time.
2026-09-05 18:18:30 +02:00
Ole André Vadla Ravnås 7d1b616228 barebone: Resolve the Linux current task by name
The per-cpu current is exported as current_task on these kernels,
not folded into the pcpu_hot struct older ones carried. Point the
.kernel_addrs slot at it; left unresolved the slot stayed zero, so
x86-64 read the per-cpu base by luck and i386 faulted on it.
2026-09-05 18:18:30 +02:00
Ole André Vadla Ravnås c4f1898a87 barebone: Find a relocated Linux kernel on x86
x86 slides the whole image by a random aligned amount, so its
symbols no longer sit where System.map linked them. Bound the
search by the pc caught in the kernel and confirm each aligned
landing by the banner every build carries, the way arm64 already
walks back to its image header.

Drop the assumption that x86 runs unrelocated; only 32-bit arm
still does.
2026-09-05 18:18:30 +02:00
Ole André Vadla Ravnås 32b0c99ed7 barebone: Recover a Linux copy's guarded faults
Hand each fault to Gum before reporting it, so one a script guards
with the Exceptor recovers and the copy carries on, as the Windows
copies already do. A signal restorer lets the handler resume on the
mended context.

A fault Gum declines goes to the target's own handler where it has
one, and is reported as a copy's death otherwise.

Carry the register file to and from Gum on x86, x86-64, arm and
arm64, and give 32-bit arm the signal-frame offsets that the arm64
ones had stood in for.
2026-09-05 18:18:30 +02:00
Ole André Vadla Ravnås 6573e9c064 barebone: Locate a Linux copy's crash in its image
Log the fault pc and lr as an offset from the copy's image base
so a screenshot of the death line symbolizes on its own, and keep
the absolute address for anything mapped outside the image.
2026-09-05 18:18:30 +02:00
Ole André Vadla Ravnås 6f9630e281 barebone: Read the identifier selector once
Pull the whole selector out with g_variant_get_strv when the
handler starts, rather than fetching and freeing a child per
candidate on every membership test.
2026-09-04 22:08:34 +02:00
Ole André Vadla Ravnås ef60e23191 barebone: List the kernel, apply selectors
Report the running kernel as the first process entry, named after
its image and attachable at pid 0.

Push the process and application selectors down to the agent so it
skips what the caller did not ask for, rather than enumerating
everything and filtering afterwards.
2026-09-04 21:47:02 +02:00
Ole André Vadla Ravnås ddc30dc96e barebone: Report Linux and XNU's stack to GumJS
GumJS caps its stack at a floor sized for a bare kernel thread unless
the platform reports its own. The Windows agents do; Linux and XNU
did not, so their runtimes stayed at that floor, far below the stack
they run on, and deep evaluation overflowed. Report it there too.
2026-09-04 21:00:58 +02:00
Ole André Vadla Ravnås 67caf023ab barebone: Take an empty version as none at all
An unset variable falls back, but an empty one did not, and it
reached the kext as a bundle version no loader would take.
2026-09-04 17:57:34 +02:00
Ole André Vadla Ravnås 5eea0c010d barebone: Assemble the note for the right target
The compiler the agents are built with is handed no target of its
own, so the note came out for the host and the link refused it
everywhere but x86_64. Take the triple from the target spec.
2026-09-04 17:40:21 +02:00
Ole André Vadla Ravnås c6f4c66fc1 barebone: Stamp the agents with the version
The blobs get an ELF note that the usual tools read, and the kext
its bundle keys. A kext version has to be plain numbers, so only
the short string carries the whole of it.

Take the version from the environment, since Cargo and the kext
Makefile are driven directly rather than through Meson.
2026-09-04 17:25:20 +02:00
Ole André Vadla Ravnås aa5142034b barebone: Give Win9x injection a stack of its own
KERNEL32 commits a new thread's startup stack in whichever process
runs the creator, and injection runs it on an arbitrary thread. When
that is not the target, the thread's own stack is left merely
reserved there, so its startup faulted -- taking the guest down on
attach.

Send the thread past that startup to the trampoline on a shared
stack every process holds committed alike, wearing the user
selectors the startup would have restored. Park the main thread in
the arena rather than exit through a startup it never entered.
2026-09-04 17:11:12 +02:00
Ole André Vadla Ravnås 3a9e34e1f7 barebone: Name a timed-out command by its nick
The full enum constant made for a long, noisy message. Use the value's
nick, the way the other enums here do.
2026-09-04 12:37:51 +02:00
Ole André Vadla Ravnås 05556a11ca barebone: Give devices a stable, caller-set id
A caller managing several targets, or persisting what it attached to,
needs to name a device and get the same one back. Take an id in the
options and give each device its own provider, rather than wrapping a
shared one under a fresh serial each time; adding an id that is
already present is an error.
2026-09-04 11:49:40 +02:00
Ole André Vadla Ravnås df33bd86f8 subprojects: Bump outdated 2026-09-03 22:39:55 +02:00
Ole André Vadla Ravnås b02acdabaf barebone: Right-size the injected XNU stack
The thread the agent injects had an 8 MiB stack, the main thread's
size. It is a secondary thread, so match Darwin's default of 512 KiB.
2026-09-03 22:37:24 +02:00
Ole André Vadla Ravnås f890354853 barebone: Give Gum the JS worker's stack size
GumJS runs on the worker the agent spawns, which runs on a 64 KiB
stack of its own on the Windows guests. Answer Gum's stack-size query
with it while a thread is on that worker, so the limit fits the stack
that is there, and fall back to the floor otherwise -- an Interceptor
handler runs on the target thread's stack, not ours.
2026-09-03 22:30:25 +02:00
Ole André Vadla Ravnås 4480b51a84 tests: Handle what open and close may throw
Close the manager in a finally that swallows the error, the way the
other barebone tests do, and give the LPAE test the outer catch its
siblings have.
2026-09-03 19:46:27 +02:00
Ole André Vadla Ravnås 85fbbb67d5 barebone: Fold the loop wakeup into one call
The two calls look redundant. They are not. kernel::wake wakes every
waiter, but does not set the flag GLib polls. g_main_context_wakeup
sets that flag, but wakes nobody unless the loop sits in g_poll.

Fold both into nudge_the_loop, and set the flag before the wake. The
cookie is the address of a static, so drop the null check.
2026-09-03 15:07:17 +02:00
Ole André Vadla Ravnås 1d26330510 barebone: Wake the loop from every link
The work source asks for an infinite timeout and polls no file. Once
it reports nothing pending, the loop sleeps until the context itself
is woken. The virtio link does this now. The socket upcall and the two
Windows wake paths only set their flag and woke the token, so the loop
stayed asleep with work pending.
2026-09-03 14:27:38 +02:00
Ole André Vadla Ravnås 31a248fa78 barebone: Wake the loop when the link has a turn
data_tx_push stops when the transmit ring runs out of descriptors. It
leaves the rest of the frame queued, and waits for the completion
interrupt to bring a turn. The interrupt set the flag and woke the
kernel wait. But the loop sleeps in g_main_context_iteration, and that
sleeper asks GLib whether its own token is set. The wake was spent,
the answer was still no, and the frames stayed queued.

Wake the context the loop sleeps in, as the kext half does.
2026-09-03 14:27:38 +02:00
Ole André Vadla Ravnås fe6c94b5b5 barebone: Free whole descriptor chains
free_chain walked to the tail and published the tail as the new free
head. Every descriptor between head and tail left the list, but
free_cnt still counted them. The count outlived the list, so alloc
could return 0xFFFF and write past the descriptor table.

Read and write the shared rings with volatile accesses. Order the used
index against the element it describes.
2026-09-03 14:27:22 +02:00
Ole André Vadla Ravnås 59d3a03d80 tests: Cover injection in the live Linux guests
Put a copy in a process of the running guest, and talk to it both
ways, on arm, x86 and x86_64. Coverage stopped at the kernel half.
That is where every architecture but arm64 let a task into userland
with no registers.
2026-09-03 14:27:22 +02:00
Ole André Vadla Ravnås 6ee0f9b341 tests: Wait for the guest to release the link
The unbind and the ready marker went out together. The host injected
while the driver was still leaving, and the reset that follows took
the device from the agent. This failed on the Intel guests. On arm it
moved with load and with anything that shifted the timing.

Make the command report when it is done. Hold the guest until then.
2026-09-03 14:27:22 +02:00
Ole André Vadla Ravnås fb1537828c barebone: Support injection on every Linux arch
Placing a copy in a process was written for arm64. It asked for arm64
registers by name. Every other architecture wrote none, and let the
task into userland unset.

Each architecture now says where a task keeps its start registers.
Find that from the stack the entry stands on, not from a field only
BTF names. Neither 32-bit guest carries BTF.

Each one needs more than that. IRET needs the selectors the kernel
zeroed. A 32-bit stack is half the assumed size. arm leaves eight
bytes above the registers. Start flags are not zero outside arm64:
x86 wants interrupts enabled, arm wants the mode and the thumb bit.
An i386 entry takes its argument on the stack, so leave a frame where
the copy looks. Reading the arena back no longer walks the borrowed
mm, which needed two more BTF offsets.

On arm, copy the pages with user access open. The kernel guards it
with a domain, and the resolved routine leaves the switch to its
caller.
2026-09-03 14:27:22 +02:00
Ole André Vadla Ravnås 32d30045d3 barebone: Disable MSI-X when claiming a device
A virtio reset leaves the PCI capability alone. A guest driver that
put the device on MSI-X keeps it there, so the device never raises the
line the agent listens on. The handshake still completed, because the
host drives it. Then nothing arrived.

Disable MSI and MSI-X in configuration space on the way in. This puts
the device back on INTx.
2026-09-03 14:27:22 +02:00
Ole André Vadla Ravnås 8b1a799baa barebone: Add the Linux x86 and x86_64 agents
The slots came from arm64. An x86 kernel differs. current sits in the
per-cpu block. Translation and I/O mapping are inline, or named
differently. The copy into a process has another name again. One slot
was read instead of the value the host put in it.

A 32-bit x86 kernel is built with -mregparm=3. Rust names no such
convention, so the calls go through a shim built for the target. The
shim also holds the thunks the kernel calls back through: a thread
entry, an interrupt handler, the tracepoint probes, and the entry a
task runs before userland. printk and panic are exempt. They are
variadic.

No symbol names PAGE_OFFSET, so read it off the kernel base. The
32-bit user side spells out its syscalls. Check the mmap result as the
kernel means it. A user address on i386 is above 0x80000000, and is
negative as a signed word.
2026-09-03 14:27:22 +02:00
Ole André Vadla Ravnås 18956864c8 barebone: Let the guest run when attaching fails
Opening the GDB connection halts the guest, and a failure on the way
to a session -- a Win9x guest still in DOS mode, say, with nothing
paged in to enumerate -- left it halted, with the connection still
held. Resume and close on the way out, the way a session's close
does.
2026-09-03 14:05:36 +02:00
Ole André Vadla Ravnås 059b6cfe53 barebone: Name the Rust ABI the arm64 agent uses
Rustc now wants a softfloat target to say so twice: once for the
cfg and once for its own ABI. Without the second it refuses to load
the target at all.
2026-09-02 22:40:53 +02:00
Ole André Vadla Ravnås c0b90ef744 subprojects: Bump outdated 2026-09-02 18:42:16 +02:00
Ole André Vadla Ravnås f9394cf092 submodules: Bump releng 2026-09-02 18:42:15 +02:00
Ole André Vadla Ravnås a9b5787ebf barebone: Keep code pages in memory and in place
The kernel map hands out pageable memory, so a trampoline could be
reclaimed and a kernel thread branching into it took a translation
fault. Wire every page the agent runs from or keeps its books in.

Returning a page that has run wedges the agent, so keep those for
its lifetime and hand back only what Gum never saw.
2026-09-02 15:47:52 +02:00
Ole André Vadla Ravnås 1f6273a0b9 barebone: Honor the address Gum asks to be near
Dropping the spec left Gum believing a far trampoline was in reach,
and its ADRP was silently refused. Allocate above the wanted address,
and give up when nothing lands close enough.

Keep bookkeeping on pages that stay writable.
2026-09-02 15:47:52 +02:00
Ole André Vadla Ravnås bd08388460 barebone: Give code pages a name to write through
Kernel pages can be made to run or to be written, never both, and a
protection is only ever taken away. Hand out pages that run from
birth, and remap each run through the kernel map to write to it.

Pages the agent does not own keep the device mapping, in its own
book, since that refuses unaligned stores.
2026-09-02 15:16:52 +02:00
Ole André Vadla Ravnås e833563480 barebone: Sign what the kernel half calls back
The C half signs its function pointers now, so a Rust one handed
across is answered with a fault. Sign each where it crosses, the
way a proc callout already was.

Let the compiler say what to sign with: the discriminators come
from the real types, in a file that has their headers, rather than
from a guess written out by hand.
2026-09-02 15:16:52 +02:00
Ole André Vadla Ravnås 405b9be350 tests: Run the ARM agent in a live guest
The quiescent guest parks on a panic, which leaves no scheduler to run
an agent on and no processes to find. This one keeps the ramdisk the
distribution ships beside the kernel, and carries the GDB stub, QMP and
the hostlink together.

The board has no host bridge its kernel can drive, so the hostlink goes
over virtio-mmio. The slot and the interrupt come from the guest, the
transport is asked for outright, and the driver holding the device lets
go of it once it has been asked.
2026-09-02 12:07:50 +02:00
Ole André Vadla Ravnås 986a9d0f9e tests: Cover the ARM machine
The walk over short descriptors, the runs it hands out and the
protections it changes, against a stub whose tables are known, and
against a guest whose kernel QEMU can be asked about independently.
2026-09-02 12:07:50 +02:00
Ole André Vadla Ravnås feec63051d barebone: Let the hostlink be told where it is
The transport was chosen by whether a bus had been named, which left the
MMIO one to guests whose QEMU publishes hostlink-mmio. An embedder that
knows the address can now say so, the way it can already say where
configuration space is, and a bus is wanted either way for the port.
2026-09-02 12:07:50 +02:00
Ole André Vadla Ravnås be4de9b9ad barebone: Answer rather than abort off arm64
Two of the calls the agent can make were written as casts, so a machine
that is not arm64 took the process down instead of being told the call
is not implemented for it.
2026-09-02 12:07:50 +02:00
Ole André Vadla Ravnås 29f442c257 barebone: Write the config through the machine
Its neighbours protect and call through the machine; this one went
straight to the stub, and so missed whatever the machine knows about
reaching an address.
2026-09-02 12:07:50 +02:00
Ole André Vadla Ravnås d0b591c13b barebone: Support ARM in the agent
Reads current from TPIDRURO, maps I/O through ioremap(), takes its page
attributes from pgprot_kernel, translates addresses with __pv_offset,
and hands memory to a process through arm_copy_to_user().

Sizes an unsigned long as the kernel does while it is here: declaring
one as u64 costs nothing where a word is 64 bits wide and everything
where it is not, a 64-bit argument taking an even-odd register pair on
EABI. Where the kernel half of the address space begins is a build-time
choice as well, so it is taken from an address known to be in it, and
the tasklist lock is taken with whichever pair the kernel exports.
2026-09-02 12:07:50 +02:00
Ole André Vadla Ravnås b6ab15fec5 barebone: Find a 32-bit ARM kernel where it linked
The walk back to an image header only ever found an arm64 kernel, that
magic being the one it looks for. Nothing relocates a 32-bit ARM kernel,
so its System.map already says where it is.
2026-09-02 12:07:50 +02:00
Ole André Vadla Ravnås ee3698bc39 barebone: Land ARM calls where nothing else runs
Only the arm64 machine was told to return through panic, so a call on
ARM returned through whatever the guest happened to be executing --
schedule, on a kernel with a userspace to run. The next thread through
it was taken for the call coming back, and its r0 for the answer.
2026-09-02 12:07:50 +02:00
Ole André Vadla Ravnås 3b083e26e6 barebone: Give the ARM machine its kernel table
Which the machine has no way of finding on its own, the address being
one the kernel names.
2026-09-02 12:07:50 +02:00
Ole André Vadla Ravnås 790c468681 barebone: Implement the ARM machine
Walks the short-descriptor tables, maps and protects what they
describe, calls into the guest and relocates what is loaded into it.

A run has to be contiguous in virtual address space but not in the
tables describing it, so anything longer than a leaf table holds is
gathered a segment at a time. TTBCR.N splits that space, and an entry
read from the wrong half of it describes nothing.
2026-09-02 12:07:50 +02:00
Ole André Vadla Ravnås 64a7f5727e barebone: Build the agent for 32-bit ARM
The target it is built for, and the name the build gives that target
when it asks which architecture it is compiling for.
2026-09-02 12:07:50 +02:00
Ole André Vadla Ravnås 3f95474842 subprojects: Bump outdated 2026-09-02 00:30:25 +02:00
Ole André Vadla Ravnås 53f0308e52 submodules: Bump releng 2026-09-02 00:30:24 +02:00
Ole André Vadla Ravnås 4fe86fd7c8 subprojects: Bump outdated 2026-09-01 21:38:34 +02:00
Ole André Vadla Ravnås 03a6db3e56 submodules: Bump releng 2026-09-01 21:38:33 +02:00
Ole André Vadla Ravnås 674db7644e subprojects: Bump outdated 2026-09-01 15:15:49 +02:00
Ole André Vadla Ravnås 917a3bbf24 submodules: Bump releng 2026-09-01 15:15:48 +02:00
Ole André Vadla Ravnås 0b49424ed4 subprojects: Bump outdated 2026-09-01 14:05:00 +02:00
Ole André Vadla Ravnås 494521d93b submodules: Bump releng 2026-09-01 14:04:59 +02:00
xoffio c2146c42ab barebone: Allow wildcards at scan pattern edges
Instead of rejecting scan patterns starting or ending with a wildcard,
only reject those consisting entirely of wildcards, where the guest-side
scanner has no exact or masked token to use as a needle.
2026-09-01 14:02:45 +02:00
Bruno Abreu 18435c9470 compiler: Update Go toolchain to 1.24.13
Address CVEs reported in Go 1.24.3 standard library by bumping the
required Go toolchain version to 1.24.13.
2026-09-01 13:49:29 +02:00
Ole André Vadla Ravnås 4d12cc2225 barebone: Run a snippet in the live guest
Read the script from FRIDA_PROBE_JS and print what it sends, so
the kernel can be asked something without building a test for it.

Skip when the variable is unset.
2026-09-01 12:50:56 +02:00
Ole André Vadla Ravnås a842b69328 barebone: Wake the kext agent, do not poll it
The transport had no way to say it wanted a turn, so the loop
pumped it on every pass. Let the char device answer that, the
way the other transports do, and drive it from the source that
already serves the kernel half.

Wake the main context rather than the loop thread: a write that
lands before the thread blocks would otherwise leave nothing
behind for it to find.
2026-09-01 12:49:03 +02:00
Ole André Vadla Ravnås dd87557f3c barebone: Run code the kernel map hands out
Take code memory from mach_vm_allocate and tell the pages they
may run before anything has touched them: pmap_protect only takes
permissions away, so a page that starts writable never becomes
one that runs.

Write through a second mapping of the same pages, since a page
may not both take a write and run.
2026-09-01 12:29:29 +02:00
Ole André Vadla Ravnås f709d99f84 barebone: Add a kernel patcher for XNU
Find the guards from panic strings and instruction encodings, so
a kernel update does not move them out from under us.

Undo three refusals: pages the pmap has locked down, executable
mappings in the kernel's own pmap, and the table template that
forbids running what lies under it.

Write nothing unless every pass finds something.
2026-09-01 12:29:29 +02:00
Ole André Vadla Ravnås d114e7106e barebone: Hand out page-aligned code memory
The code allocator carves pages out of what it is given, so kalloc's
alignment leaves the slices short.
2026-08-30 12:57:32 +02:00
Ole André Vadla Ravnås 27d4d728e2 barebone: Patch from the kext, not the host
A kext is the kernel, so it protects, remaps and patches on its own.
Asking the host blocks until it answers, which it cannot do while it
waits for the script to load.
2026-08-30 09:34:28 +02:00
Ole André Vadla Ravnås a1959507e0 barebone: Write kernel text through an alias
Kernel text is mapped read-only and its pages cannot be remapped
writable. Map the same physical page a second time instead, and
store whole words: the alias is device memory.
2026-08-30 09:34:28 +02:00
Ole André Vadla Ravnås bfe6235970 barebone: Keep a resident agent alive on detach
Stop tears the agent down where the host injected it. The kext stays
loaded, so the loop must survive the host going away.
2026-08-29 18:31:16 +02:00
Ole André Vadla Ravnås f52fc9abc3 barebone: Sign the proc_iterate callback
The kernel branches to the callout with blraa, so an unsigned
pointer fails authentication and panics.
2026-08-29 15:56:11 +02:00
Ole André Vadla Ravnås 0b1716ee59 barebone: Wake the kext agent on a write
A character device has no interrupt to rouse the main loop, so the
write handler wakes it once the frame is queued.
2026-08-29 15:56:11 +02:00
Ole André Vadla Ravnås e8b3008d53 barebone: Poll the transport in resident agents
The kext implies xnu-core, so the poll that drives a resident
agent's transport was compiled out and the loop only ever slept.
2026-08-29 15:56:04 +02:00
Ole André Vadla Ravnås 1cc5353eeb barebone: Strip the constructors before calling
A kernel collection signs what its init sections hold, and branching
to one as it stands goes nowhere the machine can fetch from.
2026-08-29 14:28:45 +02:00
Ole André Vadla Ravnås 4e96620e52 barebone: Say where the kext was loaded
The agent is handed the kernel's own addresses, and what those are
depends on where the kernel landed. Nothing in userland says. The
kext knows where it is itself, and the two moved together.
2026-08-29 14:28:35 +02:00
Ole André Vadla Ravnås df93339430 barebone: Mark the kext as boot-required
A boot collection takes only what declares itself needed at boot.
An auxiliary one does not ask, which is why this went unnoticed.
2026-08-29 14:28:25 +02:00
Ole André Vadla Ravnås 7c27ea59db barebone: Keep the interrupt route out of a kext
Only the virtio transport installs an interrupt handler, and only a
blob carries that. What it reaches for is IOKit, which no KPI
exports, so a kext holding it cannot be linked.
2026-08-27 19:55:35 +02:00
Ole André Vadla Ravnås 9fbada363d barebone: Give the kext its kmod_info
A kernel collection binds a kext through _kmod_info, and one built
without it is turned away before anything else is looked at.
2026-08-27 19:54:56 +02:00
Ole André Vadla Ravnås 79f5f464c0 subprojects: Bump outdated 2026-08-27 18:25:53 +02:00
Ole André Vadla Ravnås 5fb1d6274f submodules: Bump releng 2026-08-27 18:25:53 +02:00
Ole André Vadla Ravnås b861f2a1f5 barebone: Build the XNU agent as a kext
Split the flavor from its packaging: xnu-core is what the agent
does, xnu wraps it as a blob, xnu-kext puts it in a kext. Add a
start and stop for the kernel's loader, and macos/ holding the glue,
the bundle and how to build it.

What a kernel exports to a kext is not what this agent calls, so the
addresses arrive through /dev/frida before the agent is started, and
that device then carries the frames.
2026-08-27 18:10:00 +02:00
Ole André Vadla Ravnås 463a285a7a barebone: Reuse the image left in a process
A page here is sixteen kilobytes and the image ended its runnable
half on four, so one page was granted both running and writing, and
the monitor keeps such a page for good.

End that half where a page ends, and put a copy going into a process
into the image it left. Three rounds in and out cost 98 KB, not
twelve megabytes.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 60a504b36b barebone: Stop reading past a thing's page
Where a port keeps what it names is found by reading through the
port, and a pointer that looks like the kernel's is not a pointer to
anything mapped. Reading past the page took the machine down, which
a copy listing its own threads did every time.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås ce5b43a054 barebone: Test what a copy sees in its process
What is mapped there, two processes kept apart, a hook in its own
process, and what it leaves behind: everything but the image, which
this kernel keeps once the process has been let run it.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås c44e8467c9 barebone: Give back what a copy takes
A copy takes memory of the process it lands in, and wrote it down
only to be left out of what the process is told is mapped. Give it
back when the copy leaves: four megabytes a time.

Hold what is taken before the shared pages exist until there is
somewhere to say it.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 6630e27b25 barebone: Let a config serve more than one device
Bringing a device up rewrote the caller's allocator configuration
with runtime addresses, so a second device translated an address
already translated and refused it as outside the kernelcache.

Keep what was worked out to ourselves.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 59bd5b3e58 barebone: Cover XNU like the other flavors
Four the Windows flavors have and this one did not: the processes it
lists, getting into one, what it says is loaded there, and what that
process can find out about us -- the threads it is told it has against
the ones it may act on, and the list the system keeps in its own
memory.

What a script reports as its platform is asserted too.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 6a9920fb05 barebone: Say less of what a task is holding
What the kernel says a task has mapped counted ours, so it disagreed
with the walk of it by what we had put there -- sixteen megabytes of
disagreement, which is more telling than either number alone. Take
ours out of both, and out of the count of ranges it took to say so.

Which process a port names is learned the way the rest is: a word is
taken as the task only when it holds a task we put a copy into. What
the kernel offers for that hangs when asked in the middle of its own
answer.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås ba895fbd9d barebone: Answer the other way a task asks
A process may ask how many threads it has and what is mapped in it
without asking either of the calls already answered for, and what came
back disagreed with them: two answers to the same question that
disagree is the whole of what something looking for us wants. Take our
threads out of the count, and ask past a range of ours the same way as
there.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 1d3a4d59d1 barebone: Say a port of ours names no thread
A process holds a name for the port of every thread, ours among them,
and everything it could do with such a name begins by turning the port
into the thread it names. Told that ours names nothing, the rest fails
as it would for a thread that has ended. Where a port keeps the thread
it names is learned on its own, a word being taken as that only when
it holds a thread already known to be ours.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 554a6727d3 barebone: Take our threads off the system's list
The system keeps every thread it made on a list in its own memory,
which a process walks without asking the kernel anything, so what the
other half hides does not cover it. Read where that list is and what
guards it out of the routine that walks it, and have each thread of
ours leave the list while it runs and rejoin before it ends, ending
being when the system takes it off itself.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 168139ad9e barebone: Keep what we map out of a process's map
A process may ask what is mapped in it, a range at a time, each answer
saying where to ask next. What is ours is not answered for: the asking
is done again from the far end of it, so what comes back is the next
range the process owns. Nothing is rewritten, and a range nobody
mentions is a range nobody looks twice at.

Both ways of asking are answered this way, and a copy asking about the
process it is in is answered whole, as with the threads.

Walked from inside a process we are in, against the same walk done by
us: five hundred and twenty-one ranges against five hundred and
twenty-five, and the four it does not have are ours.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 9749dbf479 barebone: Note which threads in a process are ours
Each thread the copy runs on writes down what the kernel calls it, and
the other half takes note, so that what a process is told about itself
can leave them out.

Asking for thread numbers is answered in memory the caller named, read
out of the call's arguments. Asking for the threads themselves is
answered with a port apiece, and ours are known by the ports the copy
holds. Both are shortened, and so is the count the runtime asks for.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås bcc7dd08c6 barebone: Do not count our threads as theirs
A process is held once again, where its library first speaks to the
kernel, and given its copy there: before a line of its own code has
run, before its libraries have finished starting, and with every image
it asked for already in place.

What stood in the way was the runtime laying claim to something the
kernel grants only a process with one thread. It counted ours, was
refused, and ended the process saying so. The threads a process did
not make are not its to count, so they are not counted while it asks.

That takes the second place a process was held at back out again,
along with working out whether it has a runtime at all: a process
without one asks nothing and is held and given its copy the same way.

Where a task counts its threads is not something the kernel says, so
it is found from a change whose meaning is known: a thread is made,
and the two numbers that were the same and are now the same again, one
higher, are how many threads there are and how many are running.

The loader now comes last among the modules rather than first, since
what comes first is the program itself and everything expects that.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 511bc5804e barebone: Match an application to what is running
The host looked for the last backslash in a path to find the name at
the end of it, which finds nothing in a path that leans the other way:
no application on XNU was ever matched to the process running it.

Asking a copy something also woke it only by accident. It sleeps until
there is something to do, and being given something to do is exactly
that, so say so.

The waits around starting a program are bounded by the clock rather
than by a count of turns, since together they have to leave the host
an answer before it stops waiting for one.

Both come from covering XNU with two of the tests the other flavours
already have: hooking a program's entry from its LC_MAIN before it has
run a line and catching that line once let go, and listing what is
installed and starting one of them by name.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 5a67bdd08b barebone: Give a new process a copy it survives
A process we put a copy into hardly ever survived being let go, and
the reason turned out to be written down by the process itself: its
crash report says the runtime ended it on purpose, from _objc_init.
It counts what it finds on the way up and will not come up beside a
thread it did not make.

So a process is now held twice. The first time is where it says how it
will make threads, which every process says and says early: it is held
there so that nothing gets away, but nothing may be put into it yet.
When the host asks to go in, it is let go as far as the first time it
asks the kernel for somewhere to run work -- by which time the runtime
has started -- and held again there, where a copy is safe to place.

Measured by instrumenting every other held spawn, so a process either
way is the control: 13 of 14 we were in are still there, against 12 of
14 we were not. They keep working, too, and see more of themselves as
they go: 145 modules while held, 247 once running.

Waiting on a copy is also bounded now. A copy in a process that has
died answers nothing, and the half that waited on it was the half that
serves every other process.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås d7d5097df0 barebone: Hold a new process at its first word
A process was held the moment it was made, which is too early to be
useful: nothing is loaded yet, so there is nothing to be asked about,
and a copy put there can reach none of what the system keeps beside a
thread -- asking it anything never comes back.

Hold it instead where its library first speaks to the kernel, saying
how it will make threads. That is still before a line of the process's
own code, and by then the loader has put everything in place: a script
in a held process now sees the 141 modules of a daemon, or the 1540 of
an application, where it saw none.

The word is answered before anything is kept back, so the library is
registered either way, and the thread that said it waits in the kernel
until the host says to let it go.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås fc0a5f191a tests: Say what becomes of a process we were in
Instrument every other held spawn and leave the rest alone, then count
what is still there afterwards. A process either way is the control,
so the two numbers can be read against each other in the run that
produced them: 2 of 7 we were in, 15 of 16 we were not.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 7b06d618ad barebone: Ask the system nothing on a bare thread
A copy in a process that has not run serves from the thread the kernel
made it, and a thread the system does not know cannot send the kernel
a message: asking where the loader keeps its list never came back, so
enumerating modules in a held process hung until the host gave up.

Nothing is loaded in such a process anyway, the loader not having run
either, so say so and say it at once.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 0fd8093d55 barebone: Start applications on XNU
An application is not a program to be run but a name the system knows,
and what it takes to bring one up -- which container, which services,
an unlocked device -- is what the system does when asked properly. So
a name with nothing of a path about it is asked of the process that
shows applications, and anything else of the one that starts programs.

What the system starts it starts in its own time and says nothing of
what came of it, so the copy says what a process of it will be called
and the other half, holding every new process anyway, waits for one by
that name to appear.

A process that becomes another one is held once as each, so letting it
go means saying every word owed to it, not the first.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 5a6efd128a tests: Measure XNU response time and throughput
Both in the kernel and through a copy in a process, the way the other
flavours measure their pace. Round trips are reported in microseconds
now: on a guest that answers in well under a millisecond, milliseconds
round to nothing.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 12e181e58b barebone: Take one write at a time on the wire
A stream takes one write at a time, and there is more than one of us:
posting a script's messages is a command going out while a reply is,
and GIO refuses both. Posting sixty-four frames landed one and lost
sixty-three, each saying so where nobody was listening.

Nor may the stream go while it is still being read from, since what
polls it is then polling something that is no longer there.

And what the agent was given is taken back through the debugger, so
where the debugger has gone -- as it has on a kernel that panics on any
synchronous exception while one is attached -- nothing can be, and
waiting for it to happen waits forever.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 608d738ea9 barebone: Deliver every frame on XNU
Measuring what goes through found three ways it did not.

A frame is written to the ring a piece at a time, and the writer gave
up when the ring filled. What that loses is not the frame but the next
one: the reader is still waiting for the rest of the abandoned one when
the next begins. Wait for room instead, the way the Linux half does,
now that there is something to end the wait.

The socket takes what it has room for and says how much that was, and
what it said was thrown away. Offer the rest again.

The copy being taken back is the one most in need of waking, and it had
just been struck off the list the waking looks in.

What was taken over in the kernel's own code is given back when the
host says to stop, rather than on the way out: giving it back means
changing that code, which is asked of the host, and by then there is no
loop left to hear the answer on. A second agent otherwise hangs its
bell where the first one's is still standing.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 04e0eb67b3 barebone: Let the halves wake each other on XNU
Neither half could reach the other's way of waiting, so both ticked:
the copy's loop woke every millisecond to look at its rings, and the
kernel half's loop never slept at all.

Give each the one thing it lacked. The kernel ends the copy's wait by
asking the kernel, which is what a process asks anyway, on a word in
the pages they share. The copy says there is something to look at
through the call that ends a wait, naming a word nothing waits on and
saying with it what nothing else in the system says -- so one
comparison tells a copy from every other caller.

A number the kernel no longer offers would have been the plainer bell,
but a process may only make the calls it is allowed to make, and that
is not one of them.

Reading the transport now happens before the wait is armed rather than
inside it: a thread may have one wait armed at a time, and the reading
can wait itself.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 5364fc4841 barebone: Read Mach-O with gum on XNU
A list entry that is not an image says how many parts it has as
readily as one that is, and following that number through memory that
is mapped either way ends nowhere: enumerating modules never returned
in SpringBoard, and a script loaded there never ran a line.

Check what an image begins with, and let gum's Darwin module reader
walk it rather than following the parts by hand.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 27cc0a3de7 barebone: List what is installed on XNU
What reads a bundle is a framework, and a framework lives in a
process, so the copy is asked. The one that starts everything else
answers none of this: the libraries that would answer talk to the
daemons it is still starting, and the asking waits for what the
answering waits for. An ordinary process is asked instead.

Where the system shows applications itself, its name for one is
preferred over what the bundle says, the way the Darwin backend
prefers it.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 6aa6143c49 barebone: Answer detach on XNU
The handler was there; the command was not routed to it, and the host
swallows what detaching says, so nothing showed for it.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 57f1659b7d barebone: Start programs on XNU
Nothing in the kernel starts a program: what starts one is a library,
and a library lives in a process. Ask the copy in the process that
starts everything else, and hold what comes of it the way any other
new process is held.

The asking is left to the loop, since putting the copy there asks the
host something, which the host cannot answer while it waits for an
answer of its own.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås f241bceb0b barebone: Get into a process that has not run
Holding a process by its task held every thread of it, including
the one the agent puts there to be its copy, so getting in waited
for itself. What is held now is the process's own threads: they are
stopped, and the task is then told it may go, so it wakes to find
its threads stopped and runs nothing of its own while the agent has
the run of it.

Which threads those are is not something the kernel says. It is
worked out from a case where the answer is known: the agent's own
thread, in the task it belongs to, is somewhere in that task's list,
and the only thing in a task that points into a thread is the list
that holds it. That gives both where the list is kept and where the
link sits, and the same answer holds for every task.

A process held before it has run has not started its own library
either, so nothing there can be asked for a thread, and asking hangs
where the asking is done. The half in the kernel knows which
processes it is holding, so it says so, and the copy serves from the
thread it was given instead.

Twenty-three of twenty-five processes held through a boot were got
into and given a script, which ran and said something back.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 67235d4fe0 barebone: Let the manual test watch spawns
Asking for gating by name attaches to the ones whose name matches
before letting them go, which is what holding them was for. That
part does not work yet: a process is held by a flag on its task,
and every thread entering user space in that task waits on it,
including the one the agent puts there. Attaching to a held process
therefore waits for itself.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 5fbb3d551a barebone: Let XNU gate spawns without a helper
Gating on Windows needs something in userland to hold what is
spawned, and the host went looking for explorer.exe. The XNU agent
holds them in the kernel, where they are made, so it needs nothing
of the sort -- and resuming one is the agent's to do as well.

The manual test grows a way to ask for it, which is how this was
watched: fifty spawns held and let go through twenty seconds of a
running phone, each named as the kernel names it. A process that
goes on to become something else is seen twice, once as xpcproxy
and once as what it became.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås b029af0ca5 barebone: Answer for a held XNU process
What is held was being worked out afterwards: the loop asked the
kernel what was running, twenty milliseconds at a time, and matched
what it found against the tasks being kept back. A process held that
long is a process the system is waiting on, and holding everything
that starts while the system is still starting is how launchd gives
up.

Which process a task is, and what it is called, are two questions
the kernel answers from the task without looking anything up. Both
are asked where the word to go was going to be said, into room set
aside beforehand, so nothing is asked for and nothing is allocated
on the thread that is making the process. The host hears about it on
the loop's next turn.

get_bsdtask_info is not named in this kernel, but the same function
in one that names it compiles to the same instructions once the
addresses are blanked. Reading proc_best_name that way answers with
the address already in use, which is what says the reading is right.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 4727278403 barebone: Hold XNU processes before they have run
A process is made and its threads wait until the kernel says they
may go. That word is the last thing said to it, after everything it
needs is in place and before one of its own instructions has run,
and it is said with the task in hand. While the host is gating, the
word is kept back and the task noted instead, and it is said
afterwards, exactly as it would have been, when the host has seen
enough. Holding what appeared and hoping to be quick about it is
gone.

The word is task_clear_return_wait, which the task table does not
name: it is the one thing fork calls with both waits at once that
the spawn path calls too, and its own code agrees -- it tests the
first wait's bit and takes the event from the task, as the sources
read.

Two things had to give way first.

Changing what memory may be had the host stop the machine and run a
stub in the guest to make the machine agree. That cannot finish for
a request the guest itself made, and on kernels that keep no
debugger attached it cannot finish at all. The host now leaves that
to whoever asked, and the half in the kernel does it where it
stands, which is where it was always going to be cheapest.

And a wait that was not taken took the kernel down. Whoever waits is
looking again anyway.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 95d5cea9f6 barebone: Hold XNU processes as they appear
The host may ask for new processes to be held so that it can see
them before they have got anywhere, and say when each may go on.
XNU had none of that.

The kernel is not asked to say when something starts; it is asked
often enough what is running that anything new is noticed while it
is still finding its feet. A process here is spawned with the
program it is to run already named, so what is seen the first time
is what it is, and the first time is as early as looking can be.
What is new is stopped where it stands, and the loop says so once
it is back where sending a frame belongs.

task_suspend and task_resume are what the task table names at 3407
and 3408. The same rule reading that table answers thread_create
with the address already in use, which is what says it is being
read right.

Spawning a process is not done yet; this is holding what others
spawn.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 1201fb3093 barebone: Detach from one XNU process
The host may be done with one process while it is still attached to
others, and until now XNU could only be done with all of them at
once. Taking one copy out is what taking them all out is made of,
so that is what it is made of now.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås e04f171bc2 barebone: Take back what the XNU copies were given
Nothing was ever given back: each copy kept its image, its arena and
its stack in the process, and the kernel half kept its own view of
two of them, for as long as the process lived.

Now a copy is asked to stop and given a moment to say it has, the
thread the kernel made is stopped where it waits, and everything
either half was holding is handed back. A process that has had a
copy taken out of it takes another and runs it.

thread_terminate is the routine the thread_act table names first,
and the one call its stub makes that no other stub makes. The same
rule answers thread_resume with the address already in use, which
is what says the rule is being read right.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 8f22aa50b2 barebone: Instrument system code from the copy
The copy reports what the process has loaded, rather than what the
kernel is made of: the kernel says where the loader keeps its list,
and the list says where each image begins and what file it came
from. What an image offers is read out of its own tree of names,
and a name asked for without an image is the loader's to answer,
which it does through dlsym.

Hooking the system's code then wanted one more thing. Pages shared
with every other process are not a process's own to write, and
asking to write them plainly is refused; asking for a copy of its
own is granted. The trampoline is taken as near its mark as the
kernel will place it, so the jump to it reaches.

A script in the copy can now look up open, hook it, call it, and be
told the path it was called with.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 618a8576c5 barebone: Stand the XNU copy on libSystem
A thread the kernel made is not a thread the system knows: it has
none of what the system keeps beside a thread, and the first thing
that reaches for it falls over, which is what killed every attempt
to ask the kernel anything by message.

So the copy finds the library every process shares -- the kernel
says where the shared code begins, and what begins there says which
images are in it and where -- and reads out of that library what it
offers and where each thing is. With that it asks the system to make
a thread of its own out of the bare one it was given, and serves the
host from there. The bare thread then waits on a message that never
comes, which is somewhere it may be stopped from without harm.

Threads the copy wants afterwards it asks for the ordinary way, and
what it asks about them -- which there are, what each is doing, and
what each should do instead -- it asks the system rather than the
half in the kernel, which no longer has to make threads for it.

The addresses handed over are signed, this being a machine that
signs the ones it jumps to.

Asking which threads there are is refused on this system, in every
process tried, so what comes back is nothing at all.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 568a30f925 barebone: Let the XNU copy instrument its process
Interceptor works there now: it hooks a function, the hook is
reached, and the process carries on.

Two things a process may not do for itself stood in the way. It may
take the execute away from its own memory but not give it, since
nothing signed what it wrote, so it asks the half that is not asked
that question -- the same way it asks for a thread. And Darwin will
not let it read how the caches are kept, though it may act on them,
so the shape comes from the half that may ask, as the page size
already did.

The copy also stops trying to hand the host a second view of a page
for patching, which it has no way to ask for and no need of: it
changes what its own memory may be instead, as the Linux copy does.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås c2ff29f97b barebone: Show the copy what its process maps
A process may ask the kernel about its own regions without sending
it a message, one region at a time, each the one holding the
address asked about or the next one after it. Walking that from
zero is the whole map, which is what the copy reports rather than
only the memory it took itself.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås c0be69928c barebone: Run a script inside an XNU process
The copy answers the host now: it is given a script, compiles it,
runs it, and what the script sends comes back through the kernel
half. A script in a process reports that process and the thread it
is on.

Three things stood between it and that. Asking what time it is went
to the kernel's calendar, which a process may not call, so it goes
through the group the copy runs on like the rest. The kernel half
slept on its loop with nothing able to wake it -- what fills its
rings is a process, and a process cannot wake a thread of the
kernel's -- so it looks instead, as it already does with the
hostlink. And a message from a copy has to say which process it
speaks for, or the host has nowhere to put it.

Enumerating the ranges of the process is still the copy reporting
only what it took, since asking the kernel for the rest is a
message a process is denied.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås edd5c988f0 barebone: Run the agent's runtime in the XNU copy
The copy brings itself up the way the kernel half does: its own
primitives, its own memory, the runtime the script engine needs.
It then serves the host on the thread it was started on, and the
frames the host addresses to that process reach it there and its
answers go back.

Three things the kernel half may do and a process may not turned
up on the way. Asking the machine how it is paging reads a
register of the kernel's, so the copy is told instead. Throwing
away the whole instruction cache is likewise the kernel's, and the
line-by-line way both halves may ask for was already written for
Linux. Memory that will be run has to be written first, so what
the copy hands back is writable and turns when it is asked to.

A process is also denied the message that starts a thread, so the
copy asks the half that started its first one.

The heap the Linux copy carves its memory out of is the same heap
this one needs, so it moves up beside the agent rather than living
under one flavor.

Creating a script in the copy does not finish yet.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 2db7bba1f3 barebone: Give the XNU copy threads of its own
What a process may ask about its threads is a message to a port
rather than a call, so write out as much of MIG as the copy needs:
a header saying where it goes and what is being asked, and a reply
on a port made for the purpose.

The copy enumerates the threads in the process it landed in, reads
what each is doing, writes a thread's registers back while holding
it still, and asks for threads of its own. That completes the group
it runs on.

Each was checked in the guest against what the process really has:
every thread answers with a stack and an instruction inside the
process, a change taken and let go leaves it running, and a thread
asked for turns up in the count.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås f2fdbfbe7a barebone: Build the XNU agent where it is placed
The copy runs at an address of the process's own, and a group of
primitives is function pointers held in data, so the copy read the
addresses the kernel runs at and went there.

Build the XNU agent the way the other flavors are built: position
independent, against the pic sdk, with the relocations kept and the
image split into the half that is only ever read and the half each
copy needs its own of. Placing a copy now relocates that half to
where the copy sits, and the copy stands on its own group.

Asked which process it is in, and asked to take a page, the copy
answers through that group.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås ba043cea15 barebone: Gather what the XNU halves differ on
The image is the same in the kernel and in the copy, and most of it
does not care which it is. What differs is who answers when it asks
for memory, for a thread or for the time, so put those in a group
the way the other flavors do, with one for each side.

The copy cannot swap its group in yet. A group is function pointers
held in data, and the XNU agent is linked without relocations, so
data in the copy still names the addresses the kernel runs at. The
calls written out beside the group work regardless of where the
copy sits, which is what it uses meanwhile.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 56429e31dc barebone: Let the XNU copy ask the kernel itself
The copy is the agent's image and nothing else, with no library
loaded for it and no symbol resolved on its behalf. It needs none:
a process asks the kernel through the same instruction whatever it
went through to get there, so the calls are written out.

Asked which process it is in, the copy answers the one the kernel
half put it in.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 61ddffb0c3 barebone: Carry frames between the XNU halves
The pages the two halves share hold a ring running each way, and
each half reaches them at an address of its own, so a frame is
written on one side and read on the other with nothing asked of
anybody.

The copy has no allocator yet -- what it would reach for lives in
the kernel -- so its side reads into a buffer it already has.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås b42f1e73bc barebone: Give the XNU copy its own half
The copy is the same image at an address of the process's own, so
the memory is taken from the process and the kernel takes a view
of the same pages to write the image through.

What the halves leave for each other lives in those pages too,
which is why neither has to ask the other to read or write them.
The copy writes there to say it reached its own code, and the
kernel half waits for that word before answering.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 7d23de749e barebone: Put the agent into an XNU process
Reach the process through its task and the map it keeps, take
memory from that map, lay the code out in it, make it runnable,
and give it a thread that is told where to begin.

None of what this needs is exported to kernel extensions. They are
Mach routines, so their addresses come from the tables MIG leaves
behind, and the host writes them into the slots.

The manual test can now attach to a process rather than only the
kernel, which is how this was watched: the code that lands there
runs.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås e476f59baf barebone: Let the flush stub be run
Making pages executable ends in a stub that flushes the
translations, and the stub itself was never granted what it asks
for on behalf of everything else.

Kernels that hand out memory for data alone -- which is all a
modern XNU exports -- therefore fault on the very first flush, and
the agent never starts. Grant it directly rather than through
protect_pages, which is what asks for the flush.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 028a0b1fd2 barebone: Let a session stay attached to the guest
Apple's VZ stub takes the whole virtual machine down when told to
let go, which ends the session the moment the agent is in.

The research kernel needs the opposite -- it panics on a
synchronous exception while a debugger is attached -- so say which
until there is a way to tell the two apart.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås e6fe301a38 barebone: Say what an XNU kernel has mapped
Gum had no ranges and no protections on XNU, so a script asking
what is mapped got nothing back.

The loader scatters the images a kernel is made of, and the agent
cannot read their headers from where it runs -- one that faults
takes the kernel with it. The host reads them while it is working
out where everything landed, and passes on what it found.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås 5f9ec7495a barebone: Let the manual test say what to run
It asked for a symbol only iOS has, so on any other kernel the
session came up and the script could not.

Name a script through the environment, and say what the target has
running.
2026-08-27 18:09:36 +02:00
Ole André Vadla Ravnås fc926151c1 barebone: Skip XNU processes that cannot be there
Numbers are handed out in order, so a long enough run of unused
ones means the end of what the kernel is holding. Asking after all
of them takes long enough for the host to give up waiting.

Drop what read the kernel's own images for their segments: those
addresses are not the agent's to read, and one that faults takes
the kernel with it.
2026-08-23 12:59:11 +02:00
Ole André Vadla Ravnås cdb35a0496 barebone: Find where a scattered XNU kernel landed
Three things stood between a macOS guest and its own layout.

Apple's stub takes the whole virtual machine down when asked to
read what the guest keeps beyond its reach, so read through the
bridge where there is one, and never ask about a device.

A collection header sits at a page of its own somewhere inside a
range rather than at the range's beginning, so look at every page
of one.

The header belongs to none of the sets inside it, and neither does
anything else the loader left where it was; let the collection
stand for whatever the sets did not claim.
2026-08-23 12:59:11 +02:00
Ole André Vadla Ravnås 1d67d48aa4 barebone: Say what a stub turned down
A rejected command arrived as the bare word "09", which says
neither what was asked nor of what.
2026-08-23 11:58:23 +02:00
Ole André Vadla Ravnås 48482c1c33 barebone: Let go of an XNU agent asked to stop
The host asks every agent to stop and waits for it to say it has
left, and XNU answered that it did not know the command.

Take it, and give XNU the part of the teardown it has: the scripts
go, the transport shuts down, and the flag the host waits for is
set.
2026-08-23 01:39:00 +02:00
Ole André Vadla Ravnås 70a835028d barebone: Say what the XNU kernel is made of
Gum had no ranges and no protections on XNU, so a script asking
what is mapped got nothing back.

Read the segments of every image the host named, and move them by
the distance between where each was linked and where it landed.
2026-08-23 01:07:17 +02:00
Ole André Vadla Ravnås 78a02a2680 barebone: Read XNU processes on either kernel
macOS exports what reads a process and iOS does not; iOS walks its
own list and macOS leaves that to whoever asks by number.

Take whichever the kernel offers, on both sides, and only look for
where a process keeps its number and its name when the kernel will
not say.

Fall back to thread_wakeup_prim and ml_static_vtop where the plain
names are absent.
2026-08-23 00:55:42 +02:00
Ole André Vadla Ravnås 6261536b3e barebone: Find where XNU keeps a process's name
XNU ships no type information and inlines the small accessors
away, so neither half can ask where a process keeps its number
and its name.

The host asks the kernel for its processes, looks at what they
hold, and picks the two places out: one of them is launchd, and
launchd says so. What it finds is passed on, along with anything
else a kernel leaves nowhere to read.
2026-08-22 22:49:07 +02:00
Ole André Vadla Ravnås 8416b2f935 barebone: Enumerate processes on XNU
The flavor could see the kernel and nothing running on it. The
kernel walks its own list and hands out what is in it, so the
agent asks rather than knowing how a proc is laid out.
2026-08-22 22:39:01 +02:00
Ole André Vadla Ravnås da56982691 barebone: Carry data with a script message
The post and the message that answers it left the binary payload
behind: a TODO on the host half, and a NULL where Gum takes the bytes.
Thus an rpc export could be called, but nothing could be sent with the
call, and the data a script sends never left the guest.

Both carry an array of bytes now. A test calls an export with four
chunks of 4 MiB, one at a time, and says what that costs: 4.1 MiB/s on
Win9x, 4.0 on WinNT and 2.6 on its x64.
2026-08-22 17:55:36 +02:00
Ole André Vadla Ravnås 4b5718e58e barebone: Say which system an agent runs on
Each flavor answers gum_barebone_query_platform with the name of the
system it is on: windows for the two Windows ones, darwin for XNU, and
linux for the kernel module and the half the host injects. Both halves
of a flavor answer the same, thus a script in a copy and one in the
kernel agree.

The tests ask each Windows flavor twice, once in the kernel half and
once in a copy.
2026-08-22 13:46:40 +02:00
Ole André Vadla Ravnås 43d341c243 barebone: Move the WinNT loader library word
The word holds a pointer, thus it takes eight bytes, and the four after
it are where a copy says it has left. One wrote over the other, and
only the order of the writes kept that from mattering.

It sits at a word of its own now, aligned as a pointer wants.
2026-08-22 12:44:53 +02:00
Ole André Vadla Ravnås c90ce9fadd barebone: Watch what the Linux loader maps
A script was told what the process had mapped when it asked and
never again. The copy now replaces the empty function the loader
calls whenever it has changed something -- what a debugger waits
on -- and asks the kernel again from there.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås 9c756d4192 barebone: Gate spawns on Linux
The host could hold what it spawned itself, but not what the
guest started on its own. Every exec is held while gating is on,
and the loop tells the host which process it was, since the exec
path is not where a frame may be sent from.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås 604c399e5b barebone: Spawn and resume a program on Linux
Spawning was the Windows flavors' alone, and it went through a
process of the target's that could do it: the kernel here starts
programs itself, so the host asks the kernel half directly.

What is started is stopped the moment it has execed, thus the
host is there before the program runs anything of its own, and
resuming lets it go.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås fbfdda8742 barebone: Let the Linux agent be told to stop
The command was the Windows flavors' alone, so the kernel half
stayed where it was with its copies still placed. It now sees
every copy out, lets go of the tracepoints and the interrupt,
and leaves nothing of itself behind.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås 6d039faaed barebone: Watch Linux threads come and go
A script asked to be told and heard only what was there when it
asked. The kernel half now watches the tracepoints the scheduler
already has and leaves what it sees in the arena, and the copy
hands it to Gum's registry.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås d82ad877c2 barebone: Take the Linux copy back out
A session that ends left the copy running, its pipes open in the
file table the kernel half shares, and its pages in the target.
The copy now closes what it opened and goes when it is asked,
and the kernel half unmaps the rest and forgets it; attaching
again after that works.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås 68425814f7 barebone: Stand the Linux copy on a thread pointer
A task the kernel half makes has none, and code of the process
keeps things below it: musl's errno lives there, so the first
call into the C library the copy was placed beside killed it,
hooks included. Each thread of the copy now has a page of its
own to stand on.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås 259d718ca4 barebone: Wake the Linux halves through a pipe
Waiting on a futex from a kernel thread spins in the retry loop
that guards the private hash, and on a kernel built without
preemption that spin holds off the grace period it waits for:
the guest locked up under a target that forks.

The copy opens a pipe each way as it comes up and leaves the
descriptors in the arena; the two halves share a file table, so
the kernel half takes the ends it needs and reads and writes them
as the kernel does. What the copy has to say about a fault, a
panic or a message of its own now reaches the kernel half too,
since the file it would have written to is nobody's.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås 6a5b87f6b1 barebone: Let the Linux copy patch what it runs
Invalidating the whole instruction cache is the kernel's to do,
and the copy runs where that instruction faults. Both halves can
ask line by line instead.

A process may also make its own pages executable, so the copy
writes its patches itself rather than asking the host to remap
what it already owns.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås 1019cd4d90 barebone: List the threads of the Linux copy
The copy is a task beside the process, thus what runs there is
the process's own task list, not its.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås 90ceb856b4 barebone: Answer for what the Linux copy sees
Gum asked the flavor for modules, ranges and protection and got
the kernel's, which are nobody's business inside a process. The
copy answers from /proc/self/maps, as the images the process has
mapped, what is mapped where, and what may be done with it.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås 2c265b649b barebone: Say which process the Linux copy is in
The copy is a task beside the one it was placed in, thus its own
id says nothing about what a script is instrumenting.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås f370ab5986 barebone: Carry frames between the halves on Linux
A command for a process the host attached to goes to the copy living
there and its answer comes back the same way, each side waking the
other on the futex it sleeps on. Which session a frame belongs to is
the process the copy was placed in, which here is not the process it
is: the copy has a task of its own.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås b4168c9a1a barebone: Give the Linux copy memory of its own
The C library asks for memory of its own while it is taking the lock
that guards its allocator, so an allocator that hands out what the C
library gives never finishes the first call: the copy died on its
first allocation, and a blown stack leaves no room for the handler
that would have said so.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås e1d5570993 barebone: Give the Linux copy its own primitives
The half that runs in a process cannot do what the kernel half does:
it allocates through the C library and mmap, changes protection with
mprotect, sleeps and wakes on a futex, and makes threads with clone.
Which of the two is answering is now a table the copy switches on
its way up, as the Windows flavours already do.

A fault there is nobody else's to report, since the kernel kills the
task and says nothing, so what it was is written where the kernel
half can read it.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås 970744a9d5 barebone: Give the kernel half a view of the arena
The pages the copy was handed are pinned and mapped again for the
kernel half, so what the two leave for each other is reached by an
address of its own: neither borrowing the address space nor copying
through it. The address the copy sees is kept for what is keyed by
it, which is the futex the two sleep on.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås 316625f1e4 barebone: Take memory attributes from the kernel
Which index carries device memory and which carries ordinary memory
differs between kernel versions, so the register the kernel filled
in says which is which. The index this was written with turned out
to be tagged normal memory, which a machine less forgiving than an
emulator would not have let pass.

Bring the linker scripts back in step with the slots the agent
declares while here.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås d477446c1e barebone: Find fields the kernel lays out itself
A structure whose fields the kernel is free to reorder keeps them in
a member with no name of its own, so what is asked for is looked for
through those as well.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås 7ed3d4d9a0 barebone: Let the two Linux halves talk
Each side sleeps on a word the other bumps, so neither looks at a
clock: the copy waits on a futex the kernel half wakes while it
borrows the address space, and a thread of the kernel half's own
sleeps on the futex the copy wakes.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås 4129ebdf99 barebone: Give the Linux copy a task of its own
The copy is given a task that shares the address space it was mapped
into, and enters userland at the address the kernel is told to give
it. Nothing of the target is borrowed, so a target whose threads are
all asleep is reached as readily as a busy one.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås 95134fd30c barebone: Place a copy in a Linux process
Borrow the target's memory long enough to map a copy of the image
into it: the half that is only read as it stands, and the half that
is written to as it was before this agent touched it, with every
address in it moved to where the copy runs.
2026-08-22 12:35:48 +02:00
Ole André Vadla Ravnås 6cf068f833 barebone: Read the Linux layout from the kernel
A kernel built with BTF says where every field of every structure
sits, which the task walk was guessing at. Take it from there when
it is present, and keep the guesswork for kernels without it.
2026-08-22 12:35:29 +02:00
Ole André Vadla Ravnås 207f8321dd barebone: Drive the Linux transport by interrupt
Ask the kernel for the line it gave the device and take that, so the
queues are read when a frame lands rather than looked at over and
over. The loop waits on a kernel wait queue the interrupt wakes.
2026-08-22 12:35:29 +02:00
Ole André Vadla Ravnås b8c4735767 barebone: Enumerate processes on Linux
Nothing states where a task keeps its name, number or list: the
offsets move with the version and the configuration. Find them from
the idle task, whose name is known, and take the task list to be
the longest circular one whose every node is a task.

Walk it under the lock that keeps it still, taking a reference to
each task's executable there, and resolve the paths once the lock
is dropped.
2026-08-22 12:35:29 +02:00
Ole André Vadla Ravnås a771bb7b48 barebone: Let the flavor name a process
Which name a process is listed under is the flavor's to decide,
not the handler's.
2026-08-22 12:35:29 +02:00
Ole André Vadla Ravnås 7c571d5ac3 barebone: Find virtio-pci through ECAM on arm64
Configuration space is reached through I/O ports on x86 and through
a window in memory elsewhere, so the host passes where that window
is and the bus maps it once for every function on it.

No line the kernel's interrupt API accepts comes with the device
there, thus the waiter is left to poll for what arrives.
2026-08-22 12:35:29 +02:00
Ole André Vadla Ravnås 2da849ff35 barebone: Give DMA pages memory a device can reach
A device is told where to look in physical terms, and only what the
kernel maps linearly can be told. Executable memory is not, so the
virtqueues pointed the device at nothing on Linux.
2026-08-22 12:35:29 +02:00
Ole André Vadla Ravnås 2b4e66b4f0 barebone: Let the Linux agent write its own code
Executable memory comes back read-only, so the agent faults on the
first instruction it lays down. Ask the kernel to make those pages
writable again, which on arm64 leaves them executable.
2026-08-22 12:35:29 +02:00
Ole André Vadla Ravnås 66bd8e3ff1 barebone: Reach the Linux kernel as arm64 has it
The host resolves a slot by the symbol name with its first character
dropped, so a kernel function whose own name begins with an
underscore needs one more. Call printk and panic with the message as
the format, since a variadic call through a slot marshals nothing.

Take the clock and the mapping helper this kernel exports, and work
out a physical address the way the kernel's macro does: the linear
map's base, from what the translation control register was set up
with, and where memory starts.
2026-08-22 12:35:29 +02:00
Ole André Vadla Ravnås 10cc617e1d barebone: Hijack Linux where a sleep is woken
A thread reaching the scheduler from a path that masked interrupts
cannot be borrowed: the agent goes on to call kernel functions that
sleep, and a sleep taken with interrupts masked is one nothing ever
wakes. Wait for one that got there with them on.
2026-08-22 12:35:29 +02:00
Ole André Vadla Ravnås dfffea35c2 barebone: Give the injected image usable pages
Executable memory is handed out read-only, so the agent could not
write to its own data, nor to the config the host leaves for it.
Protect what the image says is writable, taken from its segments
rather than from wherever text happens to end, and take the Linux
image from execmem so its text is executable at all.
2026-08-22 12:35:29 +02:00
Ole André Vadla Ravnås 793d592362 barebone: Make arm64 page protection take effect
A descriptor's permission bits mean what the kernel decided they
mean: Apple remaps them through SPRR, and a kernel using permission
indirection reads an index out of them, leaving AP to say nothing.
Grant writable pages the way executable ones already were, by
sampling a page the kernel made that way -- the running thread's
stack -- and replaying its encoding.

Editing a descriptor also leaves whatever the TLB cached in force,
so run the maintenance the change needs from a stub of our own.
2026-08-22 12:35:29 +02:00
Ole André Vadla Ravnås 6f194f12df barebone: Read what a QEMU stub says about arm64
Three things stood between the Barebone backend and a Linux guest under
QEMU. TTBR1 carries an ASID above the table's address and CnP in the bit
below, so mask both off before walking. A stub that keeps some of its
registers out of the group packet -- SVE, on a QEMU that has them -- is
written to one register at a time. And only the VZ stub has a link
register of its own; QEMU knows only x30.

Infer the allocator for a Linux kernel from the entry point it exports,
whichever of the two names this version uses.
2026-08-22 12:35:29 +02:00
Ole André Vadla Ravnås 06be91a508 barebone: Inject into a running Linux kernel
Take LINUX as a kernel kind, and find what the agent needs the way the
kernel itself leaves it lying about: the symbols come from the
System.map named as the image, at the addresses it was linked for, and
where it actually landed is read back out of the guest -- walking down
from where it is executing until the header every arm64 image carries
turns up. The distance between the two moves every symbol.

Bring it to a standstill in the scheduler, which every kernel thread
passes through on a stack of its own.
2026-08-22 12:35:29 +02:00
Ole André Vadla Ravnås 4b8b9afa23 barebone: Let the Linux agent be injected too
The agent reaches Linux from the inside as a kernel module, or from the
outside the way it reaches XNU and Windows. Split the backend in two so
everything above the primitives is shared: the facade picks one, the
module one calls into the shim kbuild compiles, and the injected one
takes its addresses from .kernel_addrs.

Rename the XNU half of Gum's platform backend, which was never about
XNU: it is what an injected agent needs on a kernel that keeps its page
tables and its executable regions to itself, so Linux wants it as well.
Give it the arch it lacked and the paths each kernel names its modules
by.
2026-08-22 12:35:29 +02:00
Ole André Vadla Ravnås b6e6a29801 barebone: Stalk a thread on WinNT as well
The test wrote a body of 32 bit code, thus the thread of the x64 guest
ran nonsense and faulted before the Stalker ever saw it, and the one
before it asked whether the pc had left the page of the body, which
says nothing about a thread that sleeps.

It writes the body each machine takes now, and WinNT answers the same
way Win9x does: the blocks arrive and the thread lives.
2026-08-22 12:32:01 +02:00
Ole André Vadla Ravnås 4c33f94595 barebone: Say why the Stalker skips KERNEL32
The line that keeps it out looks arbitrary without the reason.
2026-08-22 12:22:05 +02:00
Ole André Vadla Ravnås a590b53798 barebone: Stalk a thread that calls the system
The test followed a thread that spins in code of its own, thus it never
showed whether the events arrive. It follows one that sleeps now, and
counts the blocks the sink hands over: 376 of them in five seconds.

A stalked thread that calls a service of Win9x faults, since the call
goes through a gate that the Stalker cannot follow, thus the test keeps
it out of KERNEL32.
2026-08-22 12:13:18 +02:00
Ole André Vadla Ravnås 9962d0ad3d barebone: Keep a copy's thread storage in slots
Every GPrivate went through one lock and one map keyed by thread, which
the Interceptor reads on each call it stands in front of, and the code
the Stalker writes reads on each block it leaves.

A copy takes a slot of the system for each GPrivate now and keeps the
number in the key itself. The kernel halves keep the map, since they
have no slots to take.
2026-08-22 02:39:14 +02:00
Ole André Vadla Ravnås fe3496fd48 barebone: Tell a Windows copy where it runs
A copy took the range of the image it runs in from the half that placed
it there, and that half never said. Thus the range stayed empty, and
what the Stalker keeps out of covered nothing: it instrumented the
runtime it runs on, and a thread it followed took the copy with it.

The half that maps the image writes the base and the size into the
arena now, and the copy reads them before Gum starts. A test follows a
thread of the target on Win9x and sees it run the code the Stalker
wrote.
2026-08-22 02:34:44 +02:00
Ole André Vadla Ravnås 3acacc41a0 barebone: Send the rest of a big WinNT frame
The kernel half keeps what does not fit in the ring, and its loop
writes the rest as the copy makes room. Both calls that do so were
left to Win9x alone, thus a frame bigger than the ring waited for
good on NT and a script of 42 kB never arrived.
2026-08-21 22:42:41 +02:00
Ole André Vadla Ravnås daa9e2a553 barebone: Drop unreachable code in the Win9x copy
The tail of patch() sits after the return, thus none of it runs, and
the slot it gives back is given back above.
2026-08-21 22:24:31 +02:00
Ole André Vadla Ravnås ef1be27f04 barebone: Recover from a fault in a Windows copy
A copy heard nothing about the faults of its own process: the fault
hooks of the kernel half are the VMM's and the machine's, which see
ring 0 only. Thus a script that read an address that is not there took
the whole target down, which on Win9x is a blue screen, rather than
throwing where it was read.

Each copy feeds its own Gum now: Win9x through an unhandled exception
filter, chained to the one it replaced, and NT through a vectored
handler that runs ahead of the rest.
2026-08-21 22:24:31 +02:00
Ole André Vadla Ravnås 5d56093b92 barebone: Keep the WinNT loop free while it sends
A frame bigger than the ring made the kernel half wait for room in
the loop that drains what the copy sends, so two full rings left
each half waiting for the other.

What does not fit is kept, as on Win9x, and the loop writes the
rest as the copy makes room.
2026-08-21 22:12:18 +02:00
Ole André Vadla Ravnås e76a336ee7 barebone: Keep the Win9x loop free while it sends
A frame bigger than the ring made the kernel half wait for room,
and it waited inside the loop that answers the patches the copy
asks for on its way up, before it serves the ring at all. The two
then waited for each other, and a script of 42 kB never arrived.

What does not fit is kept, and the loop writes the rest as the
copy makes room.
2026-08-21 20:11:19 +02:00
Ole André Vadla Ravnås ee471ef122 barebone: Wait for the guest to open the hostlink
A port the guest has not opened is one the emulator throws writes
away on, so the first command was lost and the reply waited for
forever. QEMU says when the guest opens it.
2026-08-21 20:10:43 +02:00
Ole André Vadla Ravnås f6b7dff649 barebone: Keep the transport's state off the stack
A kernel thread on Linux has 16 KiB, and the queues' page tables
alone are copied past it as the state is moved out of start().
2026-08-21 20:10:43 +02:00
Ole André Vadla Ravnås 8fe9438b9c barebone: Find and change Windows threads by id
Gum asks the agent for both through hooks of its own, and no Windows
flavor answered either. All four halves answer now.

A lookup walks the list of threads and takes the registers of the one
it wants, rather than of every thread. Changing them goes through
VWIN32's Get and Set_Thread_Context on Win9x, with the handle the
kernel keeps, since that system has no OpenThread and thus no way to
suspend a thread it did not make; NT sets the context from ring 0, and
suspends, sets and resumes in a copy.

The tests follow a thread of the target with Stalker, which is what
uses the change, and look a thread up by id in both halves.
2026-08-21 19:22:47 +02:00
Ole André Vadla Ravnås da40616292 barebone: Hook the real Win9x thread entry
A new thread first runs a stub of KERNEL32 that restores its registers
and then sleeps until the one that made it lets it go. The observer
hooked that stub and announced the thread from there, which kept the
thread asleep for good: the release goes missing while the callback
runs, thus the body never ran and ExitThread was never reached.

Take the address the stub returns to from the frame of the suspended
thread that finds it, and hook that instead. It is the routine that
calls the body and then ExitThread, thus the routine and the parameter
move one slot up the frame.
2026-08-21 18:12:05 +02:00
Ole André Vadla Ravnås cddacfd341 barebone: Take the Windows ring lock per frame
A writer took the lock for each piece it put in the ring, so a second
writer could put a frame of its own between the pieces of a large one.
The reader joins pieces into one buffer, and thus mixed the two.

A writer now holds the lock until the whole frame is in, the wait for
room included.
2026-08-21 16:58:42 +02:00
Ole André Vadla Ravnås 63f5a7c13e barebone: Carry frames in a ring on Windows
A script in a copy answered in 137 ms on Win9x and 187 ms on
WinNT, and answers in 1 ms now. The wire was never the cost:
each half slept in slices of 50 ms, and the kernel half of
Win9x served what a copy asked from a timer of 20 ms.

The arena holds a ring per direction: two indices, a lock
for the side with several writing threads, a word for a
writer that wants room, and a record that says how long its
piece is and whether more of the frame follows. Several
frames may be on the way at once. The single slot with its
sequence and its acknowledgement is gone, and so are the
queue, the pump and the reassembly around it.

GLib does the waiting. Both halves block in an iteration of
their context, and a source of ours answers whether the
transport, a ring, a request to write shared code or a
change of gating wants a turn. Every one of those now wakes
the other side: WinNT sets an event, Win9x calls VWIN32 to
reach a thread of ring 3 and signals a semaphore of VMM the
other way, and the answer to a request goes to the thread
that asked rather than to the loop.

VMM says which threads end, which the code that ends one
does not always reach, thus a copy hears of its own that way
and the device stays registered for as long as the agent is.
2026-08-21 16:18:16 +02:00
Ole André Vadla Ravnås d6c61fcc7d barebone: Name the words of a Win9x arena
The word that says whether a spawn is held was written by
its offset alone, thus the next word taken landed on it and
the gating of spawns stopped working. It has a name now, as
do the words that follow. The arena is also cleared whole
rather than only its handshake, since what a copy reads
above that was whatever the memory held before.
2026-08-21 16:18:16 +02:00
Ole André Vadla Ravnås 39e012d836 barebone: Borrow the virtio state per step
Taking a frame off the ring of the device held a borrow of
that state across the callback, and what the callback leads
to sends, which writes the same state. The compiler is free
to keep the fields of the one and of the other in registers
and to write back what it kept. Each step takes the borrow
it needs and gives it back now.
2026-08-21 16:18:16 +02:00
Ole André Vadla Ravnås 65477911ce barebone: Run what the linker gathered for GLib
GLib and GObject each leave a routine behind for a loader to
run before anything uses them, and no loader ever touches
this image. The section they land in has a name now, and
both halves walk it before Gum starts.
2026-08-21 16:18:16 +02:00
Ole André Vadla Ravnås 68b36e6066 barebone: Keep EBP across a Win9x service call
The macro every service call goes through kept the table of
services in EBP and never put the caller's value back. A
thunk that makes a frame of its own hid that; one that does
not, which is what enumerating processes calls, handed the
wreck on. Whether it mattered depended on whether the
compiler kept anything in EBP, thus an unrelated change to
the code flipped it, and the loop of the kernel half then
answered nothing at all.
2026-08-21 16:18:16 +02:00
Ole André Vadla Ravnås 77d1edd259 barebone: Measure the pace of a script on Windows
A session with a script answers one message at a time, and
the tests said nothing about how long that takes. The new
test asks twenty times over, in the kernel and again in a
copy in explorer, and reports the quickest, the median, the
slowest and the spread between them.

The kernel answers in about 1 ms on every flavor. A copy
takes 137 ms on Win9x and 187 ms on WinNT, since its loop
sleeps in slices of 50 ms instead of waiting to be woken.
The test holds the median to a quarter of a second.
2026-08-21 16:18:16 +02:00
Ole André Vadla Ravnås 4af4a8cd2f barebone: Let a WinNT copy wake the kernel half
A frame that finds the channel full waits for the next turn
of the kernel loop, and nothing woke that loop once the copy
took the frame before it, thus the frame stayed where it was
and the command it carried never arrived.

A copy sets the event when it acknowledges a frame now, the
way the other half already does when it takes one. Win9x
still leans on its poll for this.
2026-08-20 23:56:33 +02:00
Ole André Vadla Ravnås a0c051cad8 barebone: Let only the loop wait on one event
A copy in a process can set one event alone, and it sets it
once, thus a second thread waiting on that event took the
wake meant for the loop, and a reply sat in the channel
until the host gave up.

The thread that owns the loop keeps that event now. Another
thread that asks the host something waits on a token of its
own, and the arrival of its reply wakes it there.
2026-08-20 23:49:13 +02:00
Ole André Vadla Ravnås ed3870d6d7 barebone: Name the gating of spawns once
The two ways in carried the body between them, thus a call
crossed the interface where an error is any error at all,
and the compiler said so. Both ways lead to one private
method now.
2026-08-20 14:48:32 +02:00
Ole André Vadla Ravnås b5cc4504b4 barebone: Report where a new Win9x thread begins
What a thread is to run, and the value to hand it, wait on
its own stack above the place the startup stub returns to,
where the starter of KERNEL32 reads them. A script hears
both as the thread arrives, as it does on the other flavor.

A process of this system is also named the way its author
named the program on WinNT now, since that half can read a
version resource, thus the tests find the shell by its file
rather than by its name.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 9b63436fef barebone: Enumerate applications on WinNT
The host could list the programs of a machine, and start
one by name, on Win9x alone. WinNT answers the same way
now: the kernel half reads what the system registers under
App Paths, in the folders the registry itself names, and a
copy walks the menu of the shell for everyone and for the
user who is logged in.

The walk of the menu was written for the other flavor and
is the same work here, thus it moves to a module of its
own, which each half drives with the calls of its process.
The half that reads on the stack of the kernel also lends
that stack to the work it is already doing, since naming an
image happens inside such a walk.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 98902b8c59 barebone: Let go of a wake slot when the wait ends
A token names one wait of one thread, and the next wait has
a token of its own. The table of thirty two slots never let
go of one, and waking a token nobody waited on took a slot
as well, thus the table filled after a few dozen waits and
every token after that shared the first slot: a wake meant
for one thread released another, and the thread that waited
without a limit never woke.

A slot belongs to a token only while somebody waits on it
now, and a wake looks for an owner instead of making one.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 09782eb7a7 barebone: Show where a WinNT thread waits
A copy named the threads of its process and said nothing
more about them, thus a script could not read where one of
them waits. It opens each thread and asks the system, as
the other flavor does. A thread cannot ask about itself,
and says so by leaving the registers out.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås bd8d31658c barebone: Forget the queue of a copy that left
The memory of a copy goes back to the system when it
leaves, and the pump kept a queue named after its arena,
thus every turn read and wrote a page that was nobody's.
The agent then answered nothing more, and letting go of a
session cost the host fifty seconds of waiting. Both
flavors had it.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 2275790507 barebone: Read x64 registers with the x64 flags
The copy asked for a thread's registers with the flags of
the other width, thus the kernel refused every reading and
a program was never seen to reach the instruction that
holds it. Spawning then ran until the host gave up. It now
answers in under half a second.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås b63d04d7da barebone: Report where a new WinNT thread begins
The starter of this system is entered with the routine and
its parameter, thus a script hears both when the thread
arrives and can stand in front of the routine before it
runs.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 59bddab584 barebone: Watch a Win9x copy's own threads
A thread of this system begins in a stub of KERNEL32 that
puts back the registers the kernel gave it and returns to
the start of the program. The copy asks where that stub is
by making a thread that returns at once, and stands in
front of the place where the registers are back, thus a
script hears of a thread on that thread itself.

The slot that holds what a hook stood in front of moves to
the two flavors, since the stub of 9x needs to reach it.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås cda842b89e barebone: Watch a WinNT copy's own threads
A copy answered with the threads of ring 0 and heard of none
that arrived. It walks a ToolHelp32 snapshot now and stands
in front of the thread starter of kernel32 and ExitThread,
thus a script hears of a thread on that thread itself before
it runs any code of the program.

The starter is not exported on x86, thus it is found through
the thunk that leads to it.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 2227140bbc barebone: List a Win9x copy's own threads
A copy answered with the threads of ring 0, since the
router had no entry of its own and called itself. Ring 3 of
this system reaches both the services and the memory the
kernel keeps, thus the copy walks the same list the kernel
half walks and keeps what belongs to it, registers and all.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 3b81e137d6 barebone: Watch threads on a Windows guest
Process.enumerateThreads answered from the flavor already, but nothing
watched, thus a script could not hear of a thread as it arrives.

Win9x hears it from the system: the agent joins the list of devices,
and the message that a thread is ready is delivered on that thread,
before it runs what it was made for. Thus the observer runs there too,
and the thread waits for it. Measured on Win95: the id the observer is
called with is the id of the thread it is called on.

WinNT hears it from PsSetCreateThreadNotifyRoutine, which speaks on the
thread that asked for the new one, thus the observer runs there. The
new thread cannot be held from that call: it is not in the table of
thread ids yet, and looking it up answers STATUS_INVALID_PARAMETER.

A copy in a process leaves the watching to the kernel half.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås aa52adb41a barebone: Identify a Win9x program by its maker
A program was known by its file name, which says nothing of what it is.
It is now named the way the other backends name one: the maker and the
program, in the order a domain is written, from what the image says of
itself. CompanyName and InternalName give it, ProductName and
OriginalFilename stand in where those are missing, and a 16 bit image
is read from its description, which tends to begin with the maker.
A program that says nothing of itself keeps its file name.

The shell's menu is the other half of the list. Only ring 3 can walk a
directory, thus a copy reads the menu, follows each shortcut to the
program it opens, and reads that image as well. The two lists do not
meet on this guest: 21 programs are registered and 30 more are in the
menu, thus both are answered with.

Two things the version resource asks for: a string says how long its
value is in the six bytes before its key, and a value of nothing leaves
the next key where the value would be. A description that names no
maker, "Utility for easily selecting special characters", is no name
for a program, thus such an image keeps its file name too.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 50c220c2f0 barebone: List the programs of a Win9x guest
The system keeps the programs a user can ask for under App Paths, thus
read that list from ring 0 with the registry services of VMM. A program
is known by the name it is registered under, by what its author called
it, and by the file it runs, which goes in the metadata as it does for
a process. One that runs is given the process it runs as, matched by
file name: the registry keeps a path in the short form, and a process
says where it was started from.

spawn takes either now. A name with no separator in it is looked up in
that list, and anything else is a path as before.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 7f9e77b44b barebone: Let one asker at a time reach ring 0
The arena carries one request at a time, and any thread of a process
can ask: a script that hooks shared code, and the maker of processes
that the agent stands in front of once the gate is open. Two askers
would read each other's answers, and a program held on the wrong
answer either keeps the jump to itself or is let go unheld.

Each request now takes the slot for as long as the answer takes.

The new test starts two programs from two threads at the same moment
and holds both: letting one go leaves the other where it was, at the
first instruction of its own image. It passes without this change as
well, since the maker of processes serves one caller at a time, thus
that pair never meets in the slot. A script that hooks while a program
is held is the pair that does.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås a0aac6d179 barebone: Gate spawns on a Win9x guest
A copy of the agent stands in front of the maker of processes while the
host asks for it, and asks for the program to be held whatever the
caller wanted. The program is then held at its first instruction the
way a program the host spawns is, and the host hears of it as it
happens.

Every copy hears the gate through its own arena, thus a copy placed
later stands in front of the maker as well, and the shell gets a copy
when the gate opens, since that is where a user starts a program.

The host keeps what is held, answers enumerate_pending_spawn from it,
and lets resume find the copy that holds each program. A program that
nobody takes is let go by SpawnGatingWatchdog, as on the other backends.

Measured on Win95: a program started from a script in the shell, with
no flag of its own, is held, told to the host, listed, and resumed. The
program waits at the first instruction of its own image: its thread
reads eip 0x401000 and stays there.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 63e605746b barebone: Spawn a 16 bit program on Win9x
Spawning one never answered. The half that holds a process asks the
kernel where its first instruction is, and the kernel takes the lowest
module of the process and reads a PE header from it. A 16 bit program
has a process record that names KERNEL32 and nothing else, thus the
answer was KERNEL32's entry point: eb fe went into the library every
process shares, and the wait for a thread to arrive there never ended.

The kernel says there is nothing to hold for such a program now, and
the half that spawns takes it as it is. This system starts a 16 bit
program whatever the caller asks for, thus spawn answers with it
running, where a 32 bit one is held.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås b97c03e398 barebone: Name a Win9x process from its image
A program was called by its file name, which for a 16 bit one is worse
still: its process record names KERNEL32 and nothing else. The image
says what the program is: FileDescription in the version resource of a
32 bit image, and the first entry of the non-resident name table of a
16 bit one. The file name is left for the programs that carry neither.

The path is already in the metadata, thus what looks for a program by
its file goes there. The helper for spawning is found that way now.

Measured on Win95: Microsoft Solitaire, Windows Explorer, System Tray
Applet, WIN32 Network Interface Service Process, and WELCOME.EXE by
its file name.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 9cb99e12aa barebone: Name a 16 bit program on Win9x
A 16 bit program has a process record that names KERNEL32 and nothing
else, thus every one of them was called KERNEL32.DLL. Such a record
says so in its flags, and the line the program was started with holds
the path. Take the path from there, and keep the module list for the
rest.

Measured on Win95: SOL.EXE is an NE image, and reads as SOL.EXE now.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 4ac4d1ad4c barebone: Carry a frame of any length to a copy
The channel between the halves holds 16 KB, and a longer frame was
dropped where it was written, thus the host waited for an answer that
never came. A real agent is longer: 24 KB hangs, and so does 47 KB.

Win9x runs every process on one page directory, thus what the kernel
half allocates a copy can already read: a frame that does not fit the
buffer is left in memory of its own, and the channel says where. WinNT
cannot lend memory that cheaply, thus a long frame goes in pieces, and
each side tells the other as it goes.

Neither side waits on the other now. A frame is queued and the loop
puts it across as the channel frees, where publishing used to turn on
the spot in the loop that answers the host.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 979316b47c barebone: Give each Win9x wake token its own event
A token names one wait of one thread, and the tokens were hashed into
64 slots. Two of them sharing a semaphore means a wake meant for one
thread lets another through, which reads its own reason, finds nothing
and waits again, while the thread the wake was for sleeps on. The host
then waits for an answer that never comes. Which tokens meet depends on
where they are allocated, thus a guest answered one agent and not the
next.

Each token owns its slot now, on both sides: the kernel half, which
waits on a semaphore, and the copy in a process, which waits on an
event. A wake is given to every thread waiting on the token, since both
let one thread through each time. The line is answered at interrupt
time, where this system makes nothing, thus a wake that arrives before
there is a semaphore is written down for the waiter to read.

The same fault was found and fixed on WinNT in August. Measured on
Win95: with every token in one slot, two tests fail every time before
this change and pass after it; 20 agents in reused guests, where about
one in eight used to hang.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås f1b105b53b barebone: Let go of a guard when its process does
A guard belongs to one process. The one a process leaves behind keeps
its place in the chain and leads straight on, but the next process took
a guard of its own even so, and a page holds 120 of them. An unclaimed
guard is taken over now, and the guards of a copy that has left are
forgotten with it: this system gives the number of a process that is
gone to another one later.

The code also read as a jump to the chain while nobody claimed it, and
Gum follows a jump it finds where a function starts. It is written back
as soon as the last process lets go. The guards stay: a thread of
another process can be in one of them.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås b8fc2947d9 barebone: Put the shared Win9x code back
A hook in a system library is the same memory in every process, thus
the agent puts a chain of guards in front of it, one for each process
that asked. Those guards lead into the image of the agent, and they
stayed there after the image was given back.

The code a hook stands in front of is written the way it was found
before the image goes. The pages that carry the guards stay: a thread
of another process can be in one of them, and from there it reaches the
code that was moved and then the rest of the library, whichever way the
entry reads.

Measured on Win95: the bytes at each of the three places KERNEL32 was
hooked read the same after a session as on a guest that never had an
agent.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 6ee742201e barebone: Take the IRQ line before the device runs
An agent that met a guest where another one had been never came back
from VPICD_Virtualize_IRQ, thus it never reached its own loop and the
host saw every command time out.

The device was told to go before the line was asked for. A device that
is going draws the line, and VPICD does not hand over a line that is
already drawn. The first agent leaves the line quiet, but the host
plugs the port again for the next one, and the device answers that
before the agent asks for the line.

The line is taken first now, and the device is stopped before the line
goes back. Measured on Win95: four agents in one guest, each one giving
the image back, where the second used to hang.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås b9c2c2a37c barebone: End a WinNT agent thread by returning
The agent runs its body on a stack of its own, since the one the kernel
gives a system thread is a few pages and cannot grow. That switch never
came back: it ended where the body returns, thus the stack stayed and
the thread had to be terminated by name.

It puts the stack pointer back and returns now, which lets the stack go
and lets the thread end the way any thread does. PsTerminateSystemThread
is gone from both images.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 6cee8a230d barebone: Give a Win9x guest its memory back
Three things kept a guest from taking the image back, and disassembling
the services showed them.

Unhook_VMM_Fault knows a handler of its own by a head of twelve bytes,
eb 0a ff 25 and the address after it, and reads the way back out of the
handler's first word. The system enters the handler at that word, thus
it is a jump over itself while the handler is in the table, and names
the next handler for as long as the unhooking takes, interrupts off.

A ring 0 thread ends by returning; the thunk spun there, from the days
when the agent never left. VPICD_Force_Default_Behavior does give the
line back for the handle VPICD_Virtualize_IRQ gave.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås a56c6fa706 barebone: Yield to the host only from ring 0
The yield point is code of the kernel, thus the machine is meant to be
in ring 0 when it is reached. Nothing said so: any stop at that address
was taken, and a stop in another ring would have the host call into the
kernel from a place that cannot. The selector is read now and the
machine goes on until it is ring 0. Measured: 0x08 on NT and 0x28 on
Win9x, both of them ring 0.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås e1fd72ddfb barebone: Give the memory back in ring 0
The allocator of a guest is code of its kernel, thus call it where that
kernel runs: the machine is taken to the same yield point the injection
uses, and the memory goes back from there. Calling it wherever the
machine happened to stand still is what a device did before.

Win9x reboots a little after its heap is handed a block of ours back,
from the yield point as well, thus the image stays there for now.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 10cbf957c1 barebone: Detach from a WinNT process
Detaching from a process was answered by Win9x alone, thus on NT the
copy stayed and everything made for it with it. The copy now takes
itself out of the loader and says when it is off its own code; the half
that placed it then unmaps what the process saw, frees the mdls, the
image and the arena, and closes the event, so that a process that is
attached to again is given a copy the way it was the first time.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås b92d8eee1a barebone: Unhook the agent from the fault handlers
The agent puts itself in front of three faults and stayed there: on NT
the gates of the table still named its code, and on Win9x so did the
chain, thus the first fault after the memory went back took the machine
with it. Both are put back the way they were found before the agent says
it has left.

Win9x also asks _HeapFree for what it wants: the second argument is
flags, and the size that is passed by default is read as such. Giving
the memory back there still takes the machine down and is left for
later; NT gives it back.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 37b512de90 barebone: Give the guest its memory back on close
The code the host wrote into the guest stayed there for as long as the
machine ran. The agent says when it is off that code, in a word the host
reads over the stub, and the memory goes back to the guest once it does.

Two more things a closing device owes the guest: a thread of Win9x ends
with VMMTerminateThread wherever it returns, since the thunk that VMM
enters it through cannot, and a machine left standing still stays that
way, thus set it going before letting go of the stub.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås db4fc849b0 barebone: Place one agent copy in a process
A process was given a copy each time one was asked for, thus a session
that came after another one had left added a second image, a second
thread and a second arena to it, and both stood in front of the loader.
The half that places them answers with the copy the process already has.

Win9x has a service for a thread that is done: VMMTerminateThread ends
it and gives back what it held, which the thunk VMM enters it through
cannot do.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 9edfacb5d8 barebone: Let the agent leave when the device goes
The agent stayed in the guest after the device that put it there was
gone, thus the next one found two of them and never got an answer. The
closing device asks it to leave: it answers, unloads the scripts, tells
the copies to leave, gives the line back and resets the device, so that
nothing points into the image. The host lets go of the link, the
monitor and the stub, which take one client at a time.

A ring 0 thread of Win9x cannot end yet: the thunk VMM calls spins
where the thread would return, thus the half that is done blocks on a
token nothing signals.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 8e391bcee1 tests: Wake the barebone test only while it waits
The handler woke the test on every message from the script, and it went
on waking it after the wait it was written for. A message that came in
while the test was closing the manager thus carried it past the close,
which then ran on with nobody waiting for it: the device was still
being taken apart when the test said it was done.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås a6b948712c barebone: Remove a hostlink left by an earlier run
The machine outlives the runs that plug into it, thus a hostlink that
was never unplugged keeps the names the next run needs, and opening the
next one says the chardev already exists. Take the leftovers out before
adding: unplugging is asked for and not done at once, so the chardev is
tried until the guest has let the port go.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 5cc7117c77 barebone: Snapshot Win9x processes in an agent
CreateToolhelp32Snapshot used to fault when a thread the agent made
called it, thus the kernel half had to hand the list over. It works now,
and a test says so: the snapshot walks six processes and finds the one
the copy is in.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås e8a9d6d1ea barebone: Find an export of a module by name
The module of a copy answered only when its exports were enumerated:
Gum asks the module itself for a name and the agent left that out, thus
getExportByName found nothing that enumerateExports lists.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås ff7f70bcea barebone: Watch the Win9x loader with Interceptor
A hook in shared code belongs to one process now, thus the copy attaches
to LoadLibraryA, LoadLibraryExA and FreeLibrary with the Interceptor,
the way the NT copy does. The stub that was written by hand, its table
of processes and the two arena words that named it are gone.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 0ae07cbdaa barebone: Hook Win9x shared code for one process
The code of a library on this system is the same memory in every
process, thus a hook that Gum wrote into one was run by them all, and
ring 3 cannot even make such a page writable. Gum now writes on a page
of its own, and the kernel half puts a guard in front of the code: the
guard names a process and leads to that process's hook, and every other
process goes on to the guard before it, the last of which runs the code
that was there before. Gum's own relocator moves that code, which the
devkit does not declare, thus the agent names it itself.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås e34002579e barebone: Hold a spawned Win9x process at entry
Do here what NT does: let the process run its own loader and stop it at
its first instruction, where none of its code has run yet. Ring 3 cannot
write the private space of another process on this system, thus the copy
asks the kernel half through the arena, and the kernel half finds the
first instruction in the headers of the image as well.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 6db5917ed6 barebone: Hold a spawned WinNT process at entry
The loader must run for a process to be usable, and it takes the
process down if a thread of ours runs it first. Thus make the process
the way the system does, with the maker in kernel32 that tells the
session server about it, and let it start itself with a jump to itself
written over its first instruction. It is stopped there, before any of
its own code, and attaching now works while it is held.
2026-08-20 13:50:05 +02:00
Ole André Vadla Ravnås 3fb975a32b tests: Let the Barebone device options default 2026-08-18 21:16:08 +02:00
Ole André Vadla Ravnås 63a23371ae api: Carry toplevel functions into the public API
Only the base VAPI was scanned for them, and the prototype pattern
predates VALA_EXTERN, so a function declared in frida.vala reached
neither the header nor the VAPI nor the GIR.

List init_with_runtime by hand alongside the rest of the lifetime
API, and leave the merge to icon_from_png and icon_from_rgba.
2026-08-18 21:16:08 +02:00
Ole André Vadla Ravnås 02c6631782 device-manager: Let a Barebone device be presented
Take a name and an icon through options, the way a remote device takes
its own, so an embedder can say what the machine behind the stub is.

Build the icon with icon_from_png or icon_from_rgba, which is what a
provider's own icon is made of.
2026-08-18 21:16:08 +02:00
Ole André Vadla Ravnås 5fdb3d5402 barebone: Reach the loader in a held NT process
A process that is still held has neither a list of its libraries nor a
heap of its own. Take the loader's address from a process that runs,
since this system puts it at the same place in every process, and pass
it through the arena. The copy makes a heap of its own when it finds
none, and the thread that carries the copy no longer walks a list that
is not there.
2026-08-18 17:23:37 +02:00
Ole André Vadla Ravnås e4f17a9802 device-manager: Take a name for a Barebone device
The caller knows what the target is, whereas the backend has only its
own name to offer.
2026-08-18 16:43:33 +02:00
Ole André Vadla Ravnås b6ef27640a barebone: Leave the module registry its reference
The registry is a singleton the caller does not own, and every
module we told it about let go of a reference all the same. It
was thus taken apart under our feet, which unhooked the loader
from inside its own stand-in and left the thread there for good:
CreateProcessW never came back in a process the agent was in.

Making a process the way the loader does is no longer needed,
thus that stand-in goes.
2026-08-18 16:03:58 +02:00
Ole André Vadla Ravnås a400720771 barebone: Sleep and wake on the token in a copy
A copy of the agent has more threads than one, and both entry
points threw the token away and used the event that the kernel
half sets. A wake thus released whichever thread happened to be
waiting. Keep an event for each token, release every sleeper on
it, and let the loop keep the event the kernel half can set.
2026-08-18 16:03:58 +02:00
Ole André Vadla Ravnås cb36d358a0 barebone: Watch the Win9x loader for modules
The loader of this system lives in memory that every process
shares, thus a stand-in on it runs in all of them and a copy of
the agent cannot own it. Put a stub in the shared arena instead:
it asks who is running, and either leads to the copy that the
process has or goes straight on to the loader. The copy then
tells the registry from the thread that loads, before the
library is handed back to whoever asked for it.

Both entry points that load by name are covered. The wide ones
are stubs on this system: they set ERROR_CALL_NOT_IMPLEMENTED
and give back nothing.
2026-08-18 15:36:00 +02:00
Ole André Vadla Ravnås 2d0825b126 tests: Hook a Win9x process before it runs
Spawn holds the process, thus a stand-in put in place from a
script is there before the program's first instruction.
2026-08-18 15:36:00 +02:00
Ole André Vadla Ravnås 3dfb79dbe9 barebone: Query memory from a Win9x copy
A copy of the agent runs in ring 3, where the service that reads
the page tables belongs to the ring 0 half alone. Asking for the
protection of an address, or for the ranges of the process, thus
faulted in a copy. Take both from VirtualQuery instead.
2026-08-18 15:36:00 +02:00
Ole André Vadla Ravnås 7cfaeb8cf0 subprojects: Bump outdated 2026-08-18 15:32:05 +02:00
Ole André Vadla Ravnås 59e736fad7 submodules: Bump releng 2026-08-18 15:32:04 +02:00
Ole André Vadla Ravnås 78518b7b0c Require the platform's GIO half
Keep pkg-config's view of the API the same as the devkit's, which
now carries GUnixFDMessage.
2026-08-18 15:25:03 +02:00
Ole André Vadla Ravnås df8247bca2 Compile the device serial only for barebone
The counter is only used when adding a Barebone device, so it
became an unused field whenever that backend is off.
2026-08-18 13:20:33 +02:00
Ole André Vadla Ravnås 56ccdc87b3 barebone: Drop unowned where it is default 2026-08-18 13:19:14 +02:00
Ole André Vadla Ravnås 03cde6eb1a tests: Drop static from methods of a namespace
A method that is not in a class has nothing to be static about.
2026-08-18 12:42:39 +02:00
Ole André Vadla Ravnås cbb2f13976 barebone: Tell the registry of a module once
The loader calls in again for a module the process already has,
and each call put the module in the registry anew. Keep the ones
we told about, and hold the registry while telling it, the way
the Windows backend of Gum does.
2026-08-18 12:41:56 +02:00
Ole André Vadla Ravnås da6d0d8521 barebone: Send every byte of a frame to the host
A frame that did not fit the ring went out cut short, and the
rest was thrown away. The host then waited for bytes that never
came, and every command after that timed out. Send what fits and
leave the rest at the head of the queue, since the host reads a
length and then that many bytes.
2026-08-18 12:41:56 +02:00
Ole André Vadla Ravnås e92c387472 barebone: Defer the wake out of the interrupt
The routine of an interrupt runs above the level at which the
dispatcher of the system may be touched, and it set the event
there. That left the thread the wake was for asleep now and
then, and the command the host waited for timed out. Queue a
deferred call from the routine and set the event from there.

This was the flake: a sweep that missed one run in two or four
now passes.
2026-08-18 12:41:56 +02:00
Ole André Vadla Ravnås d4e65ef7a1 barebone: Sleep and wake on the token given
A token names one wait of one thread, and both entry points of
the loop library threw it away: one event served every waiter,
so a wake released whichever thread happened to be waiting and
left the thread it was meant for asleep. Two tokens could also
fall in the same slot and share an event.

Keep an event for each token, release every sleeper on it, and
let the loop keep the event a copy in a process can set.
2026-08-18 12:41:56 +02:00
Ole André Vadla Ravnås 30363e1e65 barebone: Let the session server know our threads
Making a thread asks the session server to take it on, and that
server answers only the threads it has a record of. It never
heard of a thread the agent made, thus such a thread waited
inside CsrClientCallServer for an answer that never came, and a
script calling CreateThread hung.

Put a copy of the agent in the server, and let it make the
record with the server's own CsrCreateThread, under the lock
that CsrLockProcessByClientId takes. The kernel half names each
thread it starts in that copy's arena. Threads then reach the
server as any other thread does.

Making a process still goes the way the loader does it, since
CreateProcessW does not come back in a process the agent is in.
2026-08-18 12:41:56 +02:00
Ole André Vadla Ravnås abfd615a6c barebone: Watch the loader from where it takes
A copy counts as running in a process only once its frames are
routed, and that happens after Gum starts. The watch on the loader
asked before then and never went on, thus the stand-ins were never
put in place and the registry alone reported a module.
2026-08-18 12:41:56 +02:00
Ole André Vadla Ravnås 1edd01be5c barebone: Document the agent's flavors
The README still spoke of two kernels, and named the arm64 SDK throughout
even though each flavor has one of its own.

Say which machine and Rust target each is built against, and how the
Windows ones ask for a target Rust does not name. Also fix the SDK name
the Linux Makefile suggests.
2026-08-18 10:05:12 +02:00
Ole André Vadla Ravnås 88871f7ad3 Guard the QMP client's UNIX socket use
UnixSocketAddress does not exist on Windows, so the build broke there.
Refuse a unix: address like the other transports do.
2026-08-18 00:12:09 +02:00
Ole André Vadla Ravnås d252d7f8b1 subprojects: Bump outdated 2026-08-17 21:10:40 +02:00
Ole André Vadla Ravnås ae5f2e49bc submodules: Bump releng 2026-08-17 21:10:40 +02:00
Ole André Vadla Ravnås 6bf69ace8e Make the generated vapi self-contained
Delegates the public API refers to were never emitted, so the vapi did
not resolve WebRequestHeaderFunc, and RpcClient.call needs json-glib.

Emit the referenced delegates, and declare json-glib as a dependency.
2026-08-17 20:48:03 +02:00
Ole André Vadla Ravnås 34e930c4cc Keep Gee and Json out of the public API
The Barebone config types exposed Gee.List and Gee.Map through their
properties, and implemented Json.Serializable. Bindings cannot resolve
either.

Make those properties internal and reach them through clear/add/enumerate
methods, like PeerOptions does for relays. Drop Json.Serializable from
the generated gir and vapi.
2026-08-17 20:48:03 +02:00
Ole André Vadla Ravnås 98b3b82037 Say how a Barebone config reaches the backend
A device added by the user is configured through the config itself,
the environment variable applying only to the default device.
2026-08-17 19:13:13 +02:00
Ole André Vadla Ravnås 5972ab3a7d Add API for removing a Barebone device
Give each such device an ID of its own, device IDs having to be
unique, and keep the Barebone entry points together after the remote
ones.
2026-08-17 19:12:50 +02:00
Ole André Vadla Ravnås 3ca20ab9e8 Move the Barebone config types to the public API
The device manager's entry point takes a config, thus its type must
exist even where the backend does not, or the build breaks on every
target without it.
2026-08-17 18:58:24 +02:00
Ole André Vadla Ravnås 3e1b189109 api: Keep abstract classes in the public API
The generator dropped every abstract class, so a subclass reached the
GIR with a parent that was not there.
2026-08-17 18:57:44 +02:00
Ole André Vadla Ravnås 4b3e1b2dc7 barebone: Watch the NT loader for modules
Stand in front of the loader's way in and way out, thus a script
hears about a library the moment it arrives and can instrument it
before the process uses it.

Win9x keeps to the list it gets at injection: its loader lives in
memory that every process shares, so standing in front of it would
send every process in the system through the agent.
2026-08-17 17:38:56 +02:00
Ole André Vadla Ravnås 8a6759e1b5 barebone: Enumerate an NT process's modules
The loader keeps the list in the process itself, thus the copy reads
it where it runs, and each module says what it exports through its
own headers. A script in a process now finds a function by name, as
it does on Win9x.
2026-08-17 17:24:24 +02:00
Ole André Vadla Ravnås c989617b16 barebone: Spawn and resume on Windows NT
Make the process through the loader's own entry points, which take
wide strings and leave the first thread held. The ones in kernel32
ask a thread for state that only the loader gives the threads it
starts, and a thread that the agent made has none of it.

Reading what a held process is called faults, nothing having touched
those pages yet. Thus read it the way that recovers from a fault,
where a plain read took the system down.
2026-08-17 16:43:02 +02:00
Ole André Vadla Ravnås e531cafe57 barebone: Enumerate a Win9x process's modules
What a process has mapped is written down where only ring 0 can read
it, thus the kernel half hands the list over when it injects, and the
copy reads it from memory of its own ring. Each module then says what
it exports through its own headers, so a script can find a function
by name.

The list is what the process had when the copy arrived. Also take a
backslash for a separator when naming a module.
2026-08-17 16:19:07 +02:00
Ole André Vadla Ravnås 9bd2f246c4 barebone: Tell script messages apart by process
Each copy numbers its scripts from one, thus two attached processes
gave the host the same name twice and each session heard both. A
message now says which process it comes from.
2026-08-17 15:52:43 +02:00
Ole André Vadla Ravnås 6b2d5e76c0 barebone: Spawn and resume on Win9x
Only ring 3 can make a process. Thus one process holds a copy of the
agent, and that copy makes each new process held and resumes it when
asked.

A held process runs none of its own code yet, thus a session in it
starts before the program does.
2026-08-17 14:48:52 +02:00
Ole André Vadla Ravnås 8eb7d3e5dd barebone: Name the linker scripts alike
Each script says which kernel and which word size it is for.
2026-08-17 14:48:52 +02:00
Ole André Vadla Ravnås 6021127a17 barebone: Inject from the guest, not the host
The agent keeps the writable half as it was before it ran, and it
keeps its own relocations in the image. Thus it can make a copy for
a process itself: it copies that half and moves each address in it
to where the copy runs, on both kernels.

The host now asks only for the process. It reads no image, stops the
guest for nothing, and writes no memory.
2026-08-17 14:48:52 +02:00
Ole André Vadla Ravnås e7ba5aa46f barebone: Lend the Win9x code to ring 3
Show the pages of the kernel half at a second address instead of
putting a second copy of the image there. The process then runs the
same code, and only the half that gets written to is new memory,
which takes 5 MB less and leaves the host 134 KB to write in place
of 5.2 MB.

A position-independent image cannot name its data in an instruction,
thus the service wrappers and the fault thunk read the program
counter and reach the table from there. A second address for a page
that the memory manager does not own takes the user bit only.
2026-08-17 14:48:52 +02:00
Ole André Vadla Ravnås 93c0d173d9 barebone: Build the XNU agent against the SDK
Use the bare soft-float SDK and picolibc, as the other flavors do,
in place of an external toolchain and newlib. This retires the libc
stubs that only newlib asks for, and the symbol lookup that the blob
flavors already share.

The flavor did not build: the kernel facade had grown a fault
reporter and a CPU state that this backend never got. It reports no
fault yet, the exception vectors being unhooked.
2026-08-17 14:48:52 +02:00
Ole André Vadla Ravnås 9219ee6bd8 barebone: Attach to a process on 32-bit NT
Manufacture the ZwCreateThread stub for both word sizes, seed the
stack the way a cdecl entry reads it, and set the data segments,
which the kernel takes from the context on x86.

The agent is position-independent now, thus the process runs the
same code as the kernel half, as it does on x86_64. A fault thunk
cannot name data in such an image, thus it asks for the record it
resumes from, and it returns to the previous handler through the
stack, which also retires a global.
2026-08-17 14:48:51 +02:00
Ole André Vadla Ravnås 99c29a9891 barebone: Run scripts in the attached process on NT
The copy answered nothing because two things still went to the
kernel half's world: replies went to the hostlink, which belongs
to that half and is not reachable from ring 3, and the process id
came from a flavor gate that only Win9x passed. Both now follow
the ring the code runs in.

attach(pid) works: the script runs in the target, says which
process it is in, and messages reach it and come back.
2026-08-17 09:18:46 +02:00
Ole André Vadla Ravnås e6f078e196 barebone: Make the NT thread the way the system does
This kernel exports no ZwCreateThread, and a thread of the
system's that is sent to ring 3 by hand makes Windows tear down
the hostlink's interrupt. But a Zw stub is only a service number:
it builds the frame the dispatcher expects, loads an index and
jumps to it. Read those two addresses out of an exported stub,
take the index from ntdll's own stub for the service, and write a
stub with an indirect jump, which pool memory needs.

The copy now runs on a thread the kernel made, with a block of
its own. It comes up through Gum, says which process it woke up
in, and takes the frames the kernel forwards. Lending its text
read-only is enough, so nothing writable is shared.

What it does not do yet is answer: the script it is asked for
never finishes being created.
2026-08-17 08:53:14 +02:00
Ole André Vadla Ravnås df59e43004 barebone: Bootstrap the NT copy through ntdll
The thread that descends has no block of its own, thus the copy
lost gs at its first syscall. ntdll's NtCreateThread is a syscall
stub, which needs no block, so the descended thread now only asks
for a real thread and retires. The kernel gives that thread a
block, and the copy runs through Gum and the JS context on it.

The descent underneath is unsound, though: a thread made by
PsCreateSystemThread that executes in ring 3 makes Windows tear
down the hostlink's interrupt, with the copy doing nothing but
spin. That is the next thing to replace.
2026-08-16 22:59:22 +02:00
Ole André Vadla Ravnås f613a8788f subprojects: Bump outdated 2026-08-16 22:23:24 +02:00
Ole André Vadla Ravnås d12e6146ba submodules: Bump releng 2026-08-16 22:23:23 +02:00
Ole André Vadla Ravnås 1d7d1ff878 barebone: Select the agent's primitives once
Each primitive that differs between the two halves examined the
privilege level and then did one of two things. Put the two sets
in groups instead, and let the entry point of the half that runs
in a process select its group before it does anything else. Each
function is now one thing, and each flavor keeps that half in a
file of its own.
2026-08-16 21:39:41 +02:00
Ole André Vadla Ravnås c7160ba8ff barebone: Stand the NT copy up in ring 3
Gum comes up in the injected copy now, and the JS context with it.
What stood in the way was a handful of primitives still reaching
for kernel addresses from ring 3: the page tables it may not read,
the thread id it may not ask the kernel for, and yielding. Each
now goes through the front door instead.

Placement no longer hangs either. The event the loop sleeps on was
only recorded if it won a race for a slot in the hashed table, and
a token it collided with could take that slot first, leaving the
copy to be handed a null object to open.
2026-08-16 20:14:38 +02:00
Ole André Vadla Ravnås c2d0c59acf barebone: Start the NT agent's copy in a process
This kernel exports no way to make a user thread, so one is made
the long way: a thread of the system's inside the target, which
hands itself down a ring through a frame laid out by hand. What it
lacks on the way down is a block of its own, so it is given one.

The copy gets that far and finds its way around ntdll; what it
does not survive yet is bringing up Gum, so the test that asks for
all of it is red.
2026-08-16 20:14:38 +02:00
Ole André Vadla Ravnås 4092a9c185 barebone: Add the NT agent's ring 3 half
The copy the host places in a process runs the same code as the
kernel half and tells itself apart by the ring it is in, since
that is the one thing the shared image cannot carry. What it needs
underneath comes from ntdll, found through the loader's own list.
2026-08-16 20:14:38 +02:00
Ole André Vadla Ravnås 16e215c466 barebone: Let the NT halves wake each other
Neither side can reach anything of the other's, but a handle
travels: the loop's event is made one the object manager knows,
and each attached process gets one of its own to wait on. Both
handles are left in the arena.
2026-08-16 20:14:38 +02:00
Ole André Vadla Ravnås 82ee4438cb barebone: Route the hostlink's interrupt on NT
The device raised its line from the start and nothing ever came of
it: the chipset leaves the link of a device no driver claimed
switched off, so the assertion reached no controller at all. Point
the link at the interrupt its config space already names.
2026-08-16 20:14:38 +02:00
Ole André Vadla Ravnås bd0e97e31f barebone: Pump the NT relay from the main loop
A GLib timeout source at the rate the relay wants leaves the NT
agent alive but never answering, even with an empty callback, while
the loop itself keeps coming around at that very rate. Drive the
relay from there instead.
2026-08-16 20:14:38 +02:00
Ole André Vadla Ravnås 9653f6fd83 barebone: Give the NT halves a channel
The two copies do not share an address space, so what passes
between them is a region each sees at an address of its own, and
everything in it is self-relative: a pointer stored there would
mean nothing on the other side. Placing a copy now sets one up
and answers with both views of it.

The routing that carries a frame to the copy running inside a
process is no longer one flavor's, so its gates widen to cover
this one.
2026-08-16 20:14:38 +02:00
Ole André Vadla Ravnås d042b214c1 barebone: Place the NT agent in a process
The code is lent to the process as it stands, since code is only
ever read and the image is position independent; the half that
gets written to is made fresh, which is what keeps the two
copies from sharing state. Where the code lands is the memory
manager's to choose, so nothing is at a fixed address, but how
far the writable half sits from it belongs to the image and is
asked for by address.

The fresh half is pool the kernel can still name, so the host
fills it in where it is, having relocated the image for where
the process will run it.
2026-08-16 20:14:38 +02:00
Ole André Vadla Ravnås 294f9a64d7 barebone: Close the handle to a thread we start
Nothing here waits for one, and a handle left open keeps the
thread around long after it has seen itself out.
2026-08-16 20:14:38 +02:00
Ole André Vadla Ravnås b93de2d8c8 barebone: Build the Windows agent PIC
Its text is to be mapped a second time into each process being
instrumented, shared, since only what is written to has to be
private, and text read from two addresses at once cannot hold
either of them. That settles the code model too: reaching the
rest of the image RIP-relative needs none.

Such an image states its relocations twice, the ones it was built
from and the dynamic ones a loader applies, and only the latter
covers the tables the linker synthesized, so on a position-
independent image it is the whole story. It also says what it
wants in its addend rather than in the slot, which is left empty
-- taking the slot instead leaves every pointer in the image
holding the base and nothing else.
2026-08-16 20:14:38 +02:00
Ole André Vadla Ravnås 88b4c57c9b barebone: Report what an NT thread was doing
Win9x asks a service for a thread's registers rather than
reading them out of anything, and NT has one too, exported since
the kernel was young. It will only answer about a thread with a
user-mode half to report: asked about one living entirely in the
kernel, it walks off the end of that thread's stack and takes
the machine down with a 0x50. A process without a block of its
own has no such threads, which is what tells the two apart, and
a thread cannot be asked about itself.

The shape of a CONTEXT is part of the architecture's own
contract, so unlike everything else read out of the kernel here
those offsets are simply known.
2026-08-16 20:14:38 +02:00
Ole André Vadla Ravnås 8284f0d0d4 tests: Run the NT suite against both word sizes
The same guest is described the same way whatever its width, so
the tests take the prefix that names it and a second suite aims
them at a 64-bit one. One of them is new: the compiler backend
has to agree with the kernel about where arguments go, which is
a different answer per word size and one nothing else here would
notice getting wrong.
2026-08-16 20:14:38 +02:00
Ole André Vadla Ravnås 67e4bd2c9e barebone: Widen the NT agent to 64 bits
Every kernel call goes through the convention of the word size it
was built for, and the pointers the agent reads out of the kernel
follow suit. A gate is twice as wide and keeps its handler in
three pieces; a long-mode interrupt frame carries the stack
pointer, so a caught fault resumes through the frame rather than
through a record of its own, and one carrying an error code has
that taken off as well before iret reads it. The self-map walk
moves out of winnt.rs, where the 32-bit form asks about each page
in turn and the 64-bit one descends four levels instead.

The image is built large-model, so its sections come l-prefixed
and the linker script has to say where they go, and the target
Rust ships is no use: it is kernel-model and position
independent, which the C archives cannot be linked against.

Two things a 64-bit kernel is stricter about. It bugchecks with
0xc4 (0x91) if a system service is entered from a stack it did
not hand out, and the agent has run on one of its own since
bring-up to have room for the script runtime, so anything reached
that way is now done by a thread which never left. And its
processes and threads are about twice the size, which is past
where the walk deriving their list looked.

While here, two things that were wrong at either width: a process
reports where it came from in whichever form it was given, and
only a drive letter wants the directory of symbolic links in
front of it; and the two forms of the optional header put their
data directories in different places, which is why a 64-bit
image appeared to have no resources at all.
2026-08-16 20:14:38 +02:00
Ole André Vadla Ravnås 1d097f2f5a barebone: Teach the host to drive a Windows kernel
Three things it had to learn, none of which a System V target
ever asks for.

Windows passes the first four arguments in rcx, rdx, r8 and r9,
and has the caller leave four spill slots beneath the return
address rather than a red zone above it, so the machine takes
the convention from the kernel it is talking to. While here,
skip past the return address when reaching for a stacked
argument.

The address a call returns to is one the rest of the kernel runs
through as well, so arriving there was never enough on its own:
only the call we made comes back on the stack we handed it.
Both x86 machines now say so, the 32-bit one allowing for a
callee that took the arguments off.

And a relocation against the text section is only nothing to do
where the code reaches its own image with an instruction-encoded
displacement. Reaching it by absolute address, as the large code
model does, needs every one of them applied.
2026-08-16 20:14:38 +02:00
Ole André Vadla Ravnås 7730f1c65d barebone: Map out a 64-bit kernel on Windows NT
The control region is no longer at an address fixed since NT was
young: a 64-bit kernel puts it wherever it likes and reaches it
through GS, which points at it in kernel mode and at the current
thread's own block in user mode. Only the half that is in GS
right now can be read back, so a processor caught in user mode is
let run on and caught again.

Everything the walk reads afterwards is the same structure at
twice the width, save the version block, which was the 64-bit
form on a 32-bit kernel all along.
2026-08-16 20:14:38 +02:00
Ole André Vadla Ravnås 21ec918bfa barebone: Read both forms of the optional header
The data directories start in different places depending on
which form an image was built in, and a 64-bit kernel is
built in the one we were not reading.
2026-08-16 20:14:38 +02:00
Ole André Vadla Ravnås 3171b8bade barebone: Widen a module's and symbol's address
Both were kept in 32 bits, which is all a kernel below the
4 GiB line ever needs, and all any flavor has needed so far.
The 64-bit Windows kernels sit far above it, and the address
is absolute wherever no image was configured, so the field
has to hold one.

The width reaches the agent through the config it is handed
and the symbol table packed alongside it, so both change
with it. The entry laid over that table says what it is
worth: it must pack exactly as written, or the name that
follows moves.
2026-08-16 20:14:38 +02:00
Ole André Vadla Ravnås cf3a9616ff barebone: Answer questions about symbols
The host already hands over a symbol table, so the calls
that turn an address into a name were answering with
nothing for want of asking it. Wire up the four of them,
plus the global export lookup, for every flavor the host
injects.

Also say what a live-guest script sent when it fails to
match, since the message is the only account of what
happened in the guest.
2026-08-16 20:14:38 +02:00
Ole André Vadla Ravnås 5c7bff1561 barebone: Enumerate ranges on both Windows flavors
Process.enumerateRanges answered with nothing, so Memory.scan
had nowhere to look. There is no VirtualQuery to lean on for
kernel space, but both flavors can read their own page
tables: NT through the tables it maps into itself, Win9x by
asking VMM to copy an entry.

Only the half above the split is worth reporting, since the
other one belongs to whichever process happens to be
current. Answering gum_memory_query_protection falls out of
the same read.
2026-08-16 20:13:04 +02:00
Ole André Vadla Ravnås 05c46c9e50 barebone: Add a Windows NT flavor
Run scripts in ring 0 on Windows NT, starting with XP on
x86, alongside the Win9x flavor.

The host finds its way around without a PDB: the processor
control region sits at a fixed address and points at the
kernel debugger's version block, which names the loaded
module list, and every module's exports become symbols. The
agent is brought up on a thread entering
NtWaitForSingleObject, at PASSIVE_LEVEL on its own stack.

NT's exports are ordinary stdcall rather than VMM's
register-based services, so the slots are called directly
with no thunks. Memory comes from the non-paged pool, where
a driver's own code lives; threads from PsCreateSystemThread,
running on a stack of the agent's own, since a system thread
gets a few pages and cannot be asked for more.

Faults are caught by taking the gates for #UD, #GP and #PF.
The kernel takes paging faults constantly, so anything
raised outside the agent goes straight on to the handler we
displaced; a caught one resumes through a record, since Gum
unwinds onto a stack of its own that iret would not reload.

Processes come from the list head the debugger's data block
publishes and the accessors the kernel exports, their path
and argv from each one's own user-mode block, their icons
from the image on disk. Threads follow the caller's own
links round to its process.

The pool is mapped with large pages, which cannot be
narrowed and need not be, and hands out blocks aligned to
less than a page, so the allocator now names each argument
its target expects.
2026-08-16 20:13:04 +02:00
Ole André Vadla Ravnås 1f5e54c8d9 tests: Boot a Windows XP guest from vm.py
Same treatment Windows 95 gets, so the two share everything
but their name, their processor, and where their disk hangs.
The image exported from VMware puts the system disk on a
SCSI adapter, and handed over as IDE the kernel bugchecks.
2026-08-16 20:13:04 +02:00
Ole André Vadla Ravnås 3aa6ea65d5 barebone: Extract a PE reader from win9x
The Windows NT flavor walks the same export directories, and
Vala flattens namespace constants, so two copies of the PE
layout will not compile. Turn the targeted lookup into an
enumerator both can use.

Read the directory a page at a time: part of an image may be
paged out, and taking it in one go turns one absent page in
the middle into no exports at all.
2026-08-16 20:13:04 +02:00
Ole André Vadla Ravnås f8c576118c barebone: Close the handle to a thread we start
The same on Win9x as on NT: nothing here waits for one, and a handle
left open keeps the thread around long after it has seen itself out.
2026-08-16 20:13:04 +02:00
Ole André Vadla Ravnås 1a5f90f5ca barebone: Let the agent's loop sleep until woken
The loop came around every 50 ms whether or not anything had
happened, because that slice was what actually delivered frames.
Now that the hostlink's interrupt arrives, iterate blocking and
let the wake do it. The copy injected into a target keeps a slice
of its own: nothing on the other side can wake it.
2026-08-16 20:13:04 +02:00
Ole André Vadla Ravnås 22f8823838 barebone: Reset receive state before dispatch
Handling a command can issue a synchronous host RPC, which re-enters
process() to collect its reply. The virtio hostlink dispatched a
completed frame while its receive state still said one was complete, so
the nested pass could hand the same frame over again.

The other two hostlinks already detach the frame and reset first; this
makes virtio agree with them.
2026-08-16 20:13:03 +02:00
Ole André Vadla Ravnås 3efdb6d821 barebone: Park on an invalid opcode in the agent
Windows answers a fault in its own kernel by retrying the instruction.
An opcode never becomes valid, so one of ours left to it stops the
machine with nothing to show for it.

Faults arrive with EBP pointing at the frame the processor and VMM's
dispatcher built -- registers, error code, EIP, CS, flags -- so the
faulting address and ring are there to read. When an invalid opcode in
ring 0 turns out to be ours and Gum does not want it, say so and park
the thread.

Everything else still goes to Windows: a page fault on a page it has yet
to bring in is how paging works, and declining that one is what lets
Gum's own recovery see it.
2026-08-16 20:13:03 +02:00
Ole André Vadla Ravnås a4e48811f4 barebone: Scan memory ranges on x86
Memory.scan said the machine did not support it. The other backends
place a scanner in the guest and run it there; x86 reads the ranges and
matches them here instead, which for the sizes a script asks about costs
less than getting a scanner in place would.

Masked tokens kept the value where the mask belonged, so they matched
whatever the value happened to be.
2026-08-16 20:13:03 +02:00
Ole André Vadla Ravnås b552b3a157 barebone: Find VMM without sweeping the arena
Locating VMM's descriptor block asked the machine to enumerate its
ranges, which walks the whole address space a table at a time over GDB,
and then swept every range in the system arena.

Windows maps the page tables into every context, so the pages at the
bottom of the arena -- where VMM is, and nothing else needs looking at --
are one read away. Targets without that mapping, such as the synthetic
one the unit test builds, still go the long way round.

2.9s to 0.1s on a live guest.
2026-08-16 20:13:03 +02:00
Ole André Vadla Ravnås e35d1f9973 barebone: Reclaim the injected agent's memory
Detaching left the copy's arena, stack and image behind, and handing
them back broke the next injection into that process.

The thread was still in them. It reaches the agent by way of a stub that
ends in a spin, so returning from the entry point parked it there for
good; it now leaves through ExitThread instead. With both threads really
gone, the pages go back, and the shared arena hands the very same ones
out again.
2026-08-16 20:13:03 +02:00
Ole André Vadla Ravnås 93b46273d0 tests: Snapshot and rewind the Windows 95 guest
Booting takes minutes, so the live tests want a snapshot to rewind onto,
and getting one has so far been a matter of knowing which QMP calls to
make in which order.

Readiness is something to look at rather than wait out: the mode change
out of text is the shell coming up, and the adapter Windows cannot find
leaves a dialog over the desktop that has to go first.

Rewinding has an order to it too. The snapshot is of a machine that never
had a hostlink port, and the port a run leaves behind goes away
asynchronously, so restoring too early leaves QEMU unable to load the
device's state -- and every rewind after that runs a wedged machine.
2026-08-16 20:13:03 +02:00
Ole André Vadla Ravnås 3235509874 barebone: Tear down the injected agent on detach
A session held the copy in its target for good: its scripts stayed
loaded and both of its threads kept running. Sessions now own the
scripts they create and discard those when they close, and the copy
itself goes when the last session watching that process does.

Each attached process gets its own copy, too. They are placed in the
shared arena, so the one image the host used to reuse had two processes
writing the same globals.

The copy's pages stay behind. Handing them back leaves the target unable
to take another injection, so they are kept rather than freed into a
state that breaks the next attach.
2026-08-16 20:13:03 +02:00
Ole André Vadla Ravnås 437756a2a0 barebone: Route agent frames to the target
Scripts in an attached process needed their own commands, and posting to
one would have needed a third. The framing gains a destination instead:
zero is the agent in the kernel, anything else a process it injected a
copy into, and that half passes the frame along and relays whatever
comes back without looking inside.

The copy already knows how to answer, since it runs the same dispatch
over an arena channel rather than the hostlink, so create, load, destroy
and post all work by addressing them at it.
2026-08-16 20:13:03 +02:00
Ole André Vadla Ravnås 87ff1f02cb barebone: Run scripts in the target on Win9x
Scripts for an attached pid ran in the kernel half, which is the wrong
process to be inspecting. Pass the source to the copy injected there
instead, and carry its messages back through the shared arena, since
that copy has no hostlink of its own.

Loading stays a step of its own: a script that ran the moment it was
created would be sending before the host had anywhere to put it.
2026-08-16 20:13:03 +02:00
Ole André Vadla Ravnås 965e8d1142 barebone: Report the target's process id on Win9x
Gum's barebone backend has no process to speak of, so it answered zero.
The injected copy knows better: KERNEL32 tells it which process it woke
up in.
2026-08-16 20:13:03 +02:00
Ole André Vadla Ravnås f82e921410 barebone: Run Gum in the injected agent
The hostlink belongs to the kernel half: the injected copy has its own
transport global, never initialized, and driving the same virtio queue
from both halves wedged the one the host talks to. Give the injected
copy a loop that only dispatches its own work.

The Exceptor stays behind too, since fault handling is machine-wide and
seizing it from the half already using it is not the injected copy's
call. Reporting moves to the worker, so an answer means the thread that
runs scripts came up.
2026-08-16 20:13:03 +02:00
Ole André Vadla Ravnås 7b8e6c401a barebone: Give the injected agent its own image
Both halves shared one image, so they shared its globals: bringing the
user-mode side up tore down the state the kernel side was running on.
Place a second, separately relocated copy in the target instead, and
start the injected thread at its entry point.
2026-08-16 20:13:03 +02:00
Ole André Vadla Ravnås da0badaa57 barebone: Add an allocator over reserved memory
Injection halts the guest to write, so a region that has to be asked for
while it runs cannot be allocated from inside.
2026-08-16 20:13:03 +02:00
Ole André Vadla Ravnås efa84266aa barebone: Tolerate paged-out KERNEL32 exports
Sweeping for the image means most candidates are not KERNEL32, and one
carrying a PE header points the export directory anywhere at all. Parts
of the real name table are paged out too, and a debugger read cannot
fault those in.
2026-08-16 20:13:03 +02:00
Ole André Vadla Ravnås 8ccf2ab2b4 barebone: Bring up a worker in the injected agent
Everything the user-mode half goes on to do runs on threads
of its own, so it starts one before reporting in. attach()
answering now means the agent is able to work, not merely
that it was placed.
2026-08-16 00:16:02 +02:00
Ole André Vadla Ravnås 65c7d261b1 barebone: Create the agent thread in the target
CreateThread builds a thread against whichever process
KERNEL32 believes it is in, which is the worker, not the
target. Its inner creator takes the process explicitly, so
the thread's own records belong to where it will run and
nothing has to be patched afterwards.

Create it suspended: the first frame is seeded on a stack
carved from the calling process, so the target needs those
bytes at the same address before the thread may run. The
seeded frame comes from Get_Thread_Context; TCB+0x1c is
only filled in once a thread has run.

Neither the creator nor the resume behind ResumeThread is
exported, so both are found by the shape of the code that
calls them.
2026-08-15 23:25:22 +02:00
Ole André Vadla Ravnås 2646e54ce0 barebone: Wait and wake in Win9x user mode
One auto-reset event per token slot, mirroring the
semaphores the kernel half uses.

spawn_thread stays kernel-only: creating a thread from the
injected one runs with a mismatched identity and takes the
guest down, though the thread itself does run.
2026-08-15 22:17:47 +02:00
Ole André Vadla Ravnås 7807fdfc39 barebone: Support attach(pid) on Win9x
Inject the agent's own user-mode half into the target and
run it there. The image is shared, so a thread created in
KERNEL32's worker is retargeted at the target's memory
context and enters the same code the kernel half runs.

Which half is asking is read off the current privilege
level; a stored flag would have each answering for the
other, since both share one image.
2026-08-15 22:00:05 +02:00
Ole André Vadla Ravnås 2d8b24aadf barebone: Resolve KERNEL32 exports in the agent
Parts of the export name table are paged out, and a GDB
read cannot fault them in, so the host only publishes the
base and the agent walks the directory itself.
2026-08-15 22:00:05 +02:00
Ole André Vadla Ravnås 92a822d60a barebone: Report each thread's registers on Win9x
VWIN32 hands out a thread's ring-3 context the way GetThreadContext
does, so ask it, and tell Gum the context is there only for the
threads it answered for.
2026-08-15 11:16:30 +02:00
Ole André Vadla Ravnås 8c55c83596 barebone: Restore newspaper order in win9x.vala
Adding the KERNEL32 lookups landed them between a comment and the
function it belonged to, and ahead of two callees that run before
them. Pure movement, no change in behaviour.
2026-08-15 09:25:56 +02:00
Ole André Vadla Ravnås 81c5919f30 barebone: Enumerate ring 0 threads on Win9x
Walk the scheduler's own list and report each thread by its handle,
which is what current_thread_id already hands out. The ring-3 half
of a Win32 thread belongs to its process, not to us.
2026-08-15 08:54:53 +02:00
Ole André Vadla Ravnås 7632e8cf1d barebone: Report each process's argv on Win9x
Now that the name comes from the module table, the command line is
free to be what it is. Split it the way Windows does, where a run
of backslashes escapes only a quote and then only half of them
survive.
2026-08-15 08:36:58 +02:00
Ole André Vadla Ravnås 00792d83e4 barebone: Name processes the way Windows does
The command line is empty for everything Windows starts itself, so
WELCOME.EXE, SYSTRAY.EXE and both KERNEL32 processes came back
nameless. Take the path from the module table instead, reached as
GetModuleFileName reaches it: the process's own MODREF at PDB+0x94,
whose index picks the entry holding the name.
2026-08-15 08:08:36 +02:00
Ole André Vadla Ravnås db71c4f876 barebone: Report real process ids on Win9x
We handed out the process database pointer, but Windows XORs that
with a per-boot value before anyone sees it. Ask KERNEL32 where it
keeps the value rather than hardcoding: GetCurrentProcessId()
tail-calls a helper that loads it, and both ids go through there.
2026-08-14 23:10:58 +02:00
Ole André Vadla Ravnås 635247442d barebone: Follow VMM's chain to reach every VxD
Sweeping the arena only recognises a VxD by its service table, so
the 23 that export nothing were invisible, and the name check
rejected lowercase, losing VNetSup, mmdevldr, DiskTSD, voltrack
and scsi1hlp. Sweep only until VMM turns up, then walk DDB_Next:
68 VxDs instead of 42, and far less to read.

VXDLDR knows the objects it scattered each dynamically loaded VxD
into, so take the extent from the object holding the descriptor
block. Statically linked VxDs sit inside VMM32.VXD and have no
image to ask about; they keep the span of their services.
2026-08-14 22:51:46 +02:00
Ole André Vadla Ravnås c7567aa977 barebone: Enumerate the guest's VxDs on Win9x
The descriptor block scan already knows every VxD and every
service it exports, so hand that to the agent as modules and let
Gum resolve exports out of the symbol table it already carries.
VxDs we have no ordinal list for get numbered service names.

Each module spans its own service addresses. Note VxD code is
split across the arena, so those spans overlap and an export list
reaches beyond the VxD that owns it.
2026-08-14 21:40:06 +02:00
Ole André Vadla Ravnås e0af18d591 barebone: Say when the guest has not booted
Attaching before Windows reaches protected mode found no page
tables to scan, hence no VxD, and the failure surfaced three
layers down as a missing Get_System_Time.
2026-08-14 21:29:22 +02:00
Ole André Vadla Ravnås 4a85e30cf5 Ignore the kernel module build output 2026-08-14 20:46:58 +02:00
Ole André Vadla Ravnås 1b51fea313 barebone: Report process icons on Win9x
Read the icon group straight out of the executable's PE resources
over IFSMgr's ring-0 file I/O, and hand the raw resources to the
host, which turns the DIBs into RGBA.

Resolving IFSMgr needs its ordinals: the DDK does ship them, in an
assembly include declaring the table without parentheses, which is
why the earlier sweep for service tables missed it.
2026-08-14 20:36:59 +02:00
Ole André Vadla Ravnås b61402808b barebone: Create the agent thread via VWIN32
VMMCreateThread leaves TCB_ClientPtr pointing at raw stack memory.
The moment VMM simulates a call down into the VM, it pushes onto
that garbage client stack and spins in an endless fault loop,
wedging the guest. Any file read not served from cache does so.

VWIN32 builds the thread properly and gives it a stack, so the
segment helpers go too.
2026-08-14 20:35:32 +02:00
Ole André Vadla Ravnås f1998c37e1 barebone: Enumerate the guest's processes on Win9x
Win32 threads name the process they belong to in VWIN32's
per-thread block, so the list is the deduplicated set of
those, and the image path is where the command line starts.
The kernel's own processes have neither and stay unnamed.
2026-08-14 17:21:42 +02:00
Ole André Vadla Ravnås 95d8293b39 barebone: Start the agent when the kernel is ready
VMM will not build a thread from the borrowed context the
host enters us on, and only says so by never returning, so
a booted guest left the agent with no worker at all and
every request timed out. Deferring belongs to how we are
entered rather than to making a thread, so _start asks to
run when the kernel is ready; where entry is already safe
that is a direct call.
2026-08-14 17:21:42 +02:00
Ole André Vadla Ravnås ad226053f0 barebone: Resolve VWIN32 and thread services
Name VWIN32's ordinals so its services resolve like VMM's,
and add slots and thunks for walking VMs and threads and
for switching memory context. Together these are what
process enumeration needs from the guest.
2026-08-14 15:13:44 +02:00
Ole André Vadla Ravnås dcbdb0d092 barebone: Run scripts in the Windows 95 kernel
Resolve VMM services by walking the DDB chain and its
service table, enter the agent from a thread already
calling Get_System_Time, and carry the hostlink over
virtio-serial on PCI. Wake on the hostlink IRQ, and hand
ring-0 faults to Exceptor before the kernel sees them.
2026-08-14 12:54:48 +02:00
Ole André Vadla Ravnås 4f125feacb gdb: Write memory with binary packets
Halves what goes on the wire, with a fallback to the hex
form for stubs that decline X.
2026-08-14 12:54:48 +02:00
Ole André Vadla Ravnås e9cd6e5120 barebone: Select kernel flavor from config
Flavor was inferred from the presence of an image config, so any
target supplying symbols got XNU's bring-up dance and its demand
for a kernel_base. Win9x needs neither, yet does need symbols to
fill the agent's service slots.

Add an explicit "kernel" field, defaulting to the old inference.
2026-08-14 12:54:28 +02:00
Ole André Vadla Ravnås deeb15aa24 tests: Cover mappings that outgrow a leaf table
Two adjacent tables, the first all but full, so a run of three has to
carry on into the second. Checks the entries land in both and that
releasing puts each back, and that reprotecting across the boundary
still touches only what was asked for.

The guest cases prove this at agent size, but only when pointed at one.
2026-08-14 12:54:28 +02:00
Ole André Vadla Ravnås 4d1ad3fa81 barebone: Reprotect a leaf table at a time
Every page was costing two walks from the root and a write of its own,
so protecting an agent-sized range spent fifty seconds asking the same
questions. A range covers whole tables, so walk to each one once and
rewrite the entries it holds in a single go.

Five megabytes now takes a second and a half.
2026-08-14 12:54:28 +02:00
Ole André Vadla Ravnås cc96b2d886 barebone: Map runs that outgrow a leaf table
A run has to be contiguous in virtual address space, not in the tables
describing it, so gather it a segment at a time and put each segment
back where it came from on release. A gap anywhere above the leaves
still starts the search over, the addresses either side of it not being
adjacent.

Five megabytes of agent needs twelve hundred pages, where a table holds
a thousand and twenty-four, or half that under PAE.
2026-08-14 12:54:28 +02:00
Ole André Vadla Ravnås b21fc1247f tests: Load a whole agent into an x86 guest
Everything the loader had been shown so far was a page or two, which
says nothing about an image of thousands. Point it at a real agent and
it turns out one cannot be placed at all: the search for somewhere to
put it never leaves a single leaf table, and a table only reaches four
megabytes, or two under PAE.

Skipped unless pointed at a blob, since none is small enough to keep.
2026-08-14 12:54:28 +02:00
Ole André Vadla Ravnås e6bcc3c8af barebone: Add a Win9x flavor of the injected agent
What the host injects is no longer XNU's alone, so the feature naming a
flavor is what asks for one at all, and the linker script laying out its
slots follows the flavor rather than the word size.

VMM answers the kernel facade through slots the host fills in, as it
does for XNU. Nothing here is specific to 95: the same services carry
98 and Me.

Its Gum backend is the short one: these processors have no execute
permission to withhold and no write protection worth the name, so code
is writable where it lies and the aliasing the others do falls away.
Page permissions go through VMM, and there are no exports to walk.

What was XNU's only by accident becomes every injected flavor's: the
entrypoint, the config it parses, and the C library shims that answer
picolibc wherever it is linked. Vsock stays XNU's.
2026-08-14 12:54:23 +02:00
Ole André Vadla Ravnås d7b330d06d barebone: Name virtio's kernel calls in the facade
We drive the device ourselves, so what the host kernel offers has no
bearing on it. Only three calls underneath were XNU's — mapping the
registers, resolving a ring's physical address, and taking the
interrupt — so the facade names them and each kernel answers.

The write barrier is the one place the architecture shows through.
2026-08-13 18:33:02 +02:00
Ole André Vadla Ravnås af6f1eec42 tests: Inject a real ELF into an x86 guest
The relocations an image carries had only ever been applied to made-up
data, and the loader had only ever run against AArch64. Put a genuine
one through the whole path — inject, relocate, call its entry point —
and let the guest report where it ended up.
2026-08-13 18:16:13 +02:00
Ole André Vadla Ravnås 1e4abea59e barebone: Add an x86 helper that reports its bias
Something small enough to inject and prove nothing more than that the
image arrived: it hands back the address of a static, which is only
worth reading once the load bias has been applied, along with what it
finds there.
2026-08-13 18:16:13 +02:00
Ole André Vadla Ravnås bb809b4dbf barebone: Tell ld which x86 to prelink for
A relocatable link defaults to the host's own format, so prelinking a
32-bit agent on a 64-bit builder is refused outright. The arm64 branch
gets its answer from the cross linker and needs no such hint.
2026-08-13 17:57:18 +02:00
Ole André Vadla Ravnås 8ddd84061c tests: Boot a Windows 95 guest for the stub
The image is whatever the caller points at, since it is not ours to
fetch. TCG rather than KVM: the stub behaves there, and it sidesteps
the divide overflow a fast processor provokes in this kernel.

Nothing reaches the serial line to say the boot finished, so give it a
budget and leave the verdict to whoever attaches.
2026-08-13 17:57:18 +02:00
Ole André Vadla Ravnås f0c69bb108 barebone: Separate kernel flavor from injection
Quiescing the target and letting it go again are the kernel's business,
not the injector's: XNU wants EL1, a thread parked in thread_block and
a permission template read off it, and the VZ bridge wants a detach
rather than a continue. A target that is already halted wants none of
this, so it no longer has to supply a kernelcache, a load base or a
thread_block just to be injected into.
2026-08-13 17:57:18 +02:00
Ole André Vadla Ravnås ac65b7213b subprojects: Bump outdated 2026-08-13 17:54:51 +02:00
Ole André Vadla Ravnås 694964fffd submodules: Bump releng 2026-08-13 17:54:50 +02:00
Ole André Vadla Ravnås 77bb47a543 subprojects: Bump outdated 2026-08-13 16:21:08 +02:00
Ole André Vadla Ravnås ca38e91d1c submodules: Bump releng 2026-08-13 16:21:07 +02:00
Jamie 65e713c762 windows: Tweak ACLs to improve injection success rate 2026-08-13 15:05:23 +02:00
Ole André Vadla Ravnås fb701b8a67 tests: Check that an x86 guest hook fires
Hook a stub that answers 42 with a probe that stamps a marker, then
call it: the answer must survive the detour and the marker must show
the probe ran. Disabling it puts the stub back as it was.

Group the guest-driven tests in their own namespace while here.
2026-08-13 14:46:09 +02:00
Ole André Vadla Ravnås ec27b1872c barebone: Hook x86 guests inline
Lay out a GumCpuContext the way pushax already leaves the registers,
with the vector pointer above it and the instruction pointer below,
and hand its address to the probe. What follows the call is the
displaced code, relocated, and a branch back past the redirect.

Both bitnesses differ only in pointer width, so they share this.
2026-08-13 14:46:09 +02:00
Ole André Vadla Ravnås 5339e16d66 tests: Spell QEMU in test paths 2026-08-13 14:45:51 +02:00
Ole André Vadla Ravnås 0fc8ab428f tests: Exercise guest calls and x86-64 mapping
Write a stub that sums its arguments into a mapped page and call it,
which puts the whole register and stack handoff under test, and run
the mapping check on x86-64 as well.

Pin the guest's memory size so the stub lands on a page the kernel
reports as usable rather than in the tables reserved above it.
2026-08-13 14:45:51 +02:00
Ole André Vadla Ravnås ca04379c29 barebone: Call into x86 guests
Hijack a halted vCPU: point it at the callee with the return address
and arguments in place, then breakpoint the instruction it came from
and let it run until it lands back there. i386 passes both on the
stack and has no red zone to step over, x86-64 splits them.

Map pages on x86-64 too, the walk behind it already being shared.
2026-08-13 14:45:51 +02:00
Ole André Vadla Ravnås b0c073d983 gdb: Size register writes from the target
A register's width came from the Variant holding it, so every integer
went out 64 bits wide. On a 32-bit target that doubles each field of
the G packet and shifts the rest, landing ESP's value in EIP. Take the
width from the target's own description instead, and stop skipping the
narrower registers on stubs without bulk transfer.
2026-08-13 14:45:51 +02:00
Ole André Vadla Ravnås 6136bdc869 tests: Check x86 page mapping against QEMU
Map two pages into a booted guest and let QEMU's own view of the tables
confirm where they landed, then check the run is gone once released.
2026-08-13 13:54:43 +02:00
Ole André Vadla Ravnås f6e2605e3f barebone: Map pages into x86 guests
Reuse a hole in the guest's own page tables rather than allocating new
ones: descend the levels that already permit RWX and claim a run of
absent leaf entries. Restoring the entries the run displaced undoes it.

Skip a run starting at VA 0, so nothing lands where a null dereference
would find it.
2026-08-13 13:54:43 +02:00
Ole André Vadla Ravnås 1c04ae8143 barebone: Keep bindgen away from max_align_t
bindgen parses the headers with x87's 80-bit long double while the C
half is compiled -mlong-double-64, so its layout assertion for a type
nothing here uses fails on 32-bit x86.
2026-08-13 13:38:41 +02:00
Ole André Vadla Ravnås e3e1480a69 barebone: Port the agent crate to 32-bit x86
GLib's lock structures are pointer-aligned, so the overlays standing in
for them cannot hardcode 8-byte alignment: on 32-bit that both
over-aligns them and rounds their size past GLib's. Take the alignment
from the target, and give the recursive mutex's owner the pointer-sized
slot it already documents.

Narrow the thread ID where Gum and the overlay want a word, and hand
g_memdup2 a gsize rather than a u64.
2026-08-13 13:38:41 +02:00
Ole André Vadla Ravnås 73bb8d5a04 barebone: Key agent build on word size too
The kernel calls both x86 bitnesses x86, so ARCH alone cannot say
which one to build. Derive BITS alongside it and select the Rust
target, the SDK flavour and the C flags on the pair.

A target given as a spec file reaches cargo behind its own unstable
flag, so carry the whole --target argument in one variable.
2026-08-13 13:38:41 +02:00
Ole André Vadla Ravnås 0bf656dbbd barebone: Add bare-metal i686 Rust target
Rust ships no freestanding 32-bit x86 target, so spell one out. Its
rustc-abi is softfloat, which returns doubles in EAX:EDX and keeps
x87 out of the generated code, matching what clang emits for the C
half with +soft-float.

Widen the page size and cache flush shims to cover both x86
bitnesses.
2026-08-13 13:38:41 +02:00
Ole André Vadla Ravnås 96d2bfcc34 subprojects: Bump outdated 2026-08-13 13:34:47 +02:00
Ole André Vadla Ravnås ca77bf66c2 submodules: Bump releng 2026-08-13 13:34:46 +02:00
Ole André Vadla Ravnås 0e377306c4 subprojects: Bump outdated 2026-08-13 13:01:49 +02:00
Ole André Vadla Ravnås 65639716c5 submodules: Bump releng 2026-08-13 13:01:49 +02:00
Ole André Vadla Ravnås 713d87ddea tests: Bridge the kmod guest to a host socket
Given a socket, QEMU grows a virtio-serial port the guest can be reached
through, and the init copies between it and the device node the module
exposes. Both ends take a single opener, so each gets one descriptor
covering both directions, and a port with nobody attached on the host
side reads EOF, hence the restarts.

The kernel cannot work the port itself: kernel_read wants read_iter,
which virtio-console does not have.
2026-08-13 11:54:36 +02:00
Ole André Vadla Ravnås d9ebcc3622 barebone: Reach a resident agent over a socket
A guest kernel holding a virtio-serial port open puts the other end of
the link on the host as a socket, so let the resident-agent path connect
to one as readily as it opens a device node.
2026-08-13 11:54:33 +02:00
Ole André Vadla Ravnås b1c3e7cb14 subprojects: Bump outdated 2026-08-13 00:18:54 +02:00
Ole André Vadla Ravnås 317cb41703 submodules: Bump releng 2026-08-13 00:18:53 +02:00
Ole André Vadla Ravnås 1ee29ddfb7 tests: Stress the kmod guest's hooked paths
Read /proc from several tasks at once across several CPUs, which is what
puts the hooks under contention on the paths where the caller holds
locks that forbid sleeping. Fail the run when the kernel reports a
voluntary context switch inside an RCU read-side critical section, that
being what such a mistake looks like from the outside.
2026-08-13 00:09:36 +02:00
Ole André Vadla Ravnås 0610db53ee tests: Mount devtmpfs in the kmod guest
Without /dev every redirect to /dev/null in the init failed, so the
reads meant to put the module's hooks through their paces were doing
nothing at all.
2026-08-13 00:09:36 +02:00
Ole André Vadla Ravnås a8ae43d771 barebone: Spin rather than sleep in hook context
A hook runs on the stack of whoever called the function it replaced, and
that caller may hold an RCU read lock or a spinlock. Sleeping there is
something the kernel complains about at best and deadlocks on at worst,
so wait by spinning when the context forbids it. An RCU read-side
critical section is still preemptible, which is what lets whoever we are
waiting on make progress.
2026-08-13 00:09:36 +02:00
Ole André Vadla Ravnås c4e01ffbfd barebone: Stop waiving declaration-after-statement
The file has none, so all the waiver did was keep quiet whichever
compilers check for it.
2026-08-13 00:09:36 +02:00
Ole André Vadla Ravnås 8a298204fe barebone: Compile the agent without jump tables
An IBT kernel leaves NOTRACK_EN clear, so the NOTRACK jump a compiler
emits for a jump table faults like any other indirect branch landing
somewhere unmarked. The Rust half already avoided them; this is the C
half, and objtool's "notrack prefix found" is what says so.

What survives is libffi's hand-written asm, which nothing on the load
path reaches.
2026-08-13 00:09:36 +02:00
Ole André Vadla Ravnås de7e633df6 barebone: Unseal the code allocator too
It arrived after the rest and was left resolving through the raw lookup,
which meant the first slab allocation faulted rather than the bootstrap.
2026-08-13 00:09:36 +02:00
Ole André Vadla Ravnås 05f64d14d6 barebone: Unseal the landing pads we call through
Every function whose address the kernel itself never takes has had its
landing pad poisoned at boot, which is the kernel saying nothing should
reach it through a pointer. Most of what the shim resolves is such a
function, and a kprobe reports the address after the pad either way, so
rewind to the entry and put the pad back.

MODULE_NAME comes along because a module that dies in its init path
stays wedged under its own name until the next boot, and bring-up wants
more attempts than that.
2026-08-13 00:09:36 +02:00
Ole André Vadla Ravnås fcfd2f21a1 barebone: Resolve symbols past the kprobe's ENDBR
A kprobe may not be planted on the ENDBR64 opening a function, so the
address it reports is the one after it, and calling that is what a CPU
with IBT enabled exists to refuse.
2026-08-13 00:09:36 +02:00
Ole André Vadla Ravnås 369f26f296 barebone: Allocate code slabs near kernel text
A trampoline reaches the function it hooks with a direct branch only
from within range of the text, and vmalloc space is nowhere near it. Out
of range x86 goes through memory instead, landing mid-function where no
ENDBR64 marks the way, so a kernel with IBT enabled faults rather than
arrives. The module region is what execmem hands out.
2026-08-13 00:09:36 +02:00
Ole André Vadla Ravnås 0830507c41 tests: Boot a guest that loads the kmod
The GDB stub guests have no userspace, so nothing there can insmod. This
one boots the kernel the module was built against — by default the one
running, vermagic tying a module to exactly one — loads it over a
busybox initramfs, reads /proc to put the cloak's hooks through their
paces, and leaves a shell on the console.
2026-08-13 00:09:36 +02:00
Ole André Vadla Ravnås 3250dd952c barebone: Keep the agent's relocations loadable
x86_64-unknown-none is a PIC target that routes calls to external
symbols through the GOT, and the module loader takes neither: it rejects
R_X86_64_GOTPCREL outright.
2026-08-13 00:09:36 +02:00
Ole André Vadla Ravnås 1d2eacff10 barebone: Document the x86_64 kmod flavour
Building for the running kernel is short enough to lead with; the arm64
walkthrough keeps the device-flavoured detour.
2026-08-13 00:09:36 +02:00
Ole André Vadla Ravnås 90e3021646 barebone: Port the Linux kmod to x86_64
Makefile covers both architectures now, defaulting to the one it is
built on, and the ABI the kernel imposes differs: ENDBR64 on every
address-taken function for IBT, and switches compiled as comparisons,
objtool being unable to follow a jump table. The blob and the module
object it is linked into both have to be excused from objtool
regardless.

task_struct lost thread_group and fs_struct lost its lock along the way,
and neither is announced by anything the source can test for, so
Makefile asks the headers it is compiled against.
2026-08-13 00:09:36 +02:00
Ole André Vadla Ravnås 3ec763cb4e barebone: Port the Linux agent to x86_64
The page size is a constant where arm64 reads it out of tcr_el1, and x86
keeps its caches coherent itself, so there is nothing to clear. Both the
CC variable and bindgen's triple follow from the Rust target.

gchar is signed here, unlike on arm64, which the three call sites
passing Rust bytes straight to GLib had relied on.
2026-08-13 00:09:36 +02:00
Ole André Vadla Ravnås 3b35e1bf6e barebone: Fix trimming of the agent's libc
sort and join disagreed on collation, so the members the agent
supersedes were only partly dropped: picolibc's malloc, free, write and
gettimeofday all survived into the prelink alongside the agent's own.
2026-08-13 00:09:36 +02:00
Ole André Vadla Ravnås ac7b7e6744 barebone: Assign in the body, not in declarations
A declaration block should declare and nothing else, so the two
initialisers that called out to do their work now assign where the work
belongs.
2026-08-13 00:09:36 +02:00
Ole André Vadla Ravnås 49ceb4a9fc barebone: Test x86-64 walk against a QEMU guest
Parameterize the guest by architecture, and boot a 64-bit
kernel through the same path. Its direct map is made of
1 GiB pages and its text is the only thing left executable,
so both are checked against what the guest itself reports.
2026-08-12 16:00:19 +02:00
Ole André Vadla Ravnås ec7aed65fa barebone: Apply x86-64 relocations
Handle enough of R_X86_64_* to load an agent linked at zero,
mirroring what the IA32 machine does.
2026-08-12 16:00:19 +02:00
Ole André Vadla Ravnås e534e22bfb barebone: Change x86-64 page protection
Clear NX on the way to granting execute, the guest kernels we
attach to having it enabled throughout.
2026-08-12 16:00:19 +02:00
Ole André Vadla Ravnås 6ee4919615 barebone: Walk x86-64 page tables
Implement enumerate_ranges() and translate_address() on top
of the shared walk, including 1 GiB and 2 MiB pages. Teach
the fake stub to pose as a 64-bit target, so that the kernel
half exercises the sign extension.
2026-08-12 16:00:19 +02:00
Ole André Vadla Ravnås 8552c0573e barebone: Rename the x86-64 call frame
It was called Arm64CallFrame, having been copied from there.
2026-08-12 16:00:19 +02:00
Ole André Vadla Ravnås cd344237d0 barebone: Share x86 page table walking
Drive the walk from a table of levels rather than from the
two-level and PAE cases spelled out, and move it out of the
IA32 machine. Long mode is another entry in that table, PAE
being a subset of its format.
2026-08-12 16:00:19 +02:00
Ole André Vadla Ravnås a03b25adfc barebone: Test IA32 walk against a QEMU guest
Boot a stock Linux kernel with no root filesystem, and check
the walk against that guest's own view of its tables. Skip
the test where QEMU is unavailable.

Wait for the boot on the serial console before attaching, as
QEMU drops every GDB packet but Ctrl-C while the guest runs.
2026-08-12 16:00:19 +02:00
Ole André Vadla Ravnås ee39332362 barebone: Apply IA32 relocations
Handle enough of R_386_* to load an agent linked at zero:
shift the absolute ones by the load bias, and leave the
PC-relative ones alone.
2026-08-12 16:00:19 +02:00
Ole André Vadla Ravnås e6fc64cda4 barebone: Change IA32 page protection
Widen the entries above the leaf as needed, access rights
being the AND of every level's. Refuse large pages rather
than split them.
2026-08-12 16:00:19 +02:00
Ole André Vadla Ravnås c258b1d928 barebone: Walk IA32 page tables
Implement enumerate_ranges() and translate_address() for
two-level and PAE paging, including 4 MiB and 2 MiB pages.

Fall back to parsing the control registers out of the
monitor's register dump, as QEMU's x86 stub leaves them out
of its target description.
2026-08-12 16:00:19 +02:00
Ole André Vadla Ravnås 34ebbbbee6 barebone: Share range coalescing
Move it out of the ARM64 machine, as the IA32 machine needs
the same pass over its walk output.
2026-08-12 16:00:19 +02:00
Ole André Vadla Ravnås d3ff6ed321 barebone: Await restore of stub addressing mode
Hold on to any failure across the restore instead of firing
it and forgetting it: the reply is otherwise still in flight
when the next command goes out, that command's predicate
consumes it, and its caller is left with an empty response
and the stub still in physical mode.

Vala rejects yield inside finally, hence the explicit
handling.
2026-08-12 16:00:19 +02:00
Ole André Vadla Ravnås ed0ac90f59 subprojects: Bump outdated 2026-08-12 13:00:06 +02:00
Ole André Vadla Ravnås fba1f24676 submodules: Bump releng 2026-08-12 13:00:05 +02:00
Ole André Vadla Ravnås 903fb22273 barebone: Refresh the Linux kmod README
Document the prctl injection and cloaking channel, and drop the stale
CONFIG_VSOCKETS requirement — the module uses the /dev/frida chardev.
2026-08-12 12:52:39 +02:00
Ole André Vadla Ravnås 6e41df0484 build: Enable the Barebone backend on Android
Android is the primary target for the in-kernel agent, so pick the
Barebone backend up by default there like the other 64-bit hosts.
2026-08-12 12:41:43 +02:00
Ole André Vadla Ravnås f4959b4f5b linux: Add a kernel-assisted injector
Inject through the Frida kernel module when it is loaded, instead of
ptrace: stage the loader in a fresh region, give the bootstrap thread a
private TLS, spawn it into the target and let the agent connect back.
Cloak the loader's region, stack, TLS and thread so it leaves no trace.
Fall back to the existing paths when the module is absent.
2026-08-12 12:41:43 +02:00
Ole André Vadla Ravnås 16ef053845 barebone: Add injection and cloaking to the kmod
Drive the in-kernel agent from userspace over a token-gated magic-prctl
channel: allocate, free, read and write memory in a target pid, and spawn
a real CLONE_THREAD there, adopting its mm, files, creds, namespaces and
fs so the thread is a native member.

Hide cloaked threads, ranges and fds from /proc and keep thread counts
consistent, with a cloaked thread seeing through its own cloak. Enforce
through the in-kernel Gum Interceptor, installed once the worker thread
has brought up Gum.
2026-08-12 12:41:43 +02:00
Ole André Vadla Ravnås 5baa62b03e barebone: Bump clang-sys for libclang 22
Under libclang 22 the pinned clang-sys 1.8.1 silently lowers records to
opaque one-byte structs, so the agent's bindgen output loses every
field and the Rust build fails against the generated bindings. Bump
clang-sys to 1.9.1 (and bindgen to 0.72.1) to build against modern clang.
2026-08-12 12:41:43 +02:00
Ole André Vadla Ravnås 72a5fdf715 subprojects: Bump outdated 2026-08-11 11:41:38 +02:00
Ole André Vadla Ravnås b0c3df8b76 barebone: Note that deps.py needs releng
It is a submodule, so a clone without --recurse-submodules has no
deps.py to sync the SDK with.
2026-08-05 11:49:47 +02:00
Ole André Vadla Ravnås 4cdc54457f barebone: Document the kmod build from a release
Walks from a published GumJS devkit to a loaded module, so nobody has to
build Gum to get one. The Makefile now derives the compiler flags from
the SDK, since they have to agree with it down to the ABI.
2026-08-05 11:37:01 +02:00
Ole André Vadla Ravnås c812943c45 subprojects: Prepare for release 2026-08-05 00:32:48 +02:00
Ole André Vadla Ravnås 2e9521d006 subprojects: Bump outdated 2026-08-05 00:31:57 +02:00
Ole André Vadla Ravnås 65667a1973 submodules: Bump releng 2026-08-05 00:31:57 +02:00
Ole André Vadla Ravnås 2e50475515 barebone: Build the kmod against the SDK
Retires the script that assembled a libc by hand: the soft-float SDK now
ships one, and what the agent replaces is worked out from the archives
rather than listed. Adds KOUT for building against a kernel tree that
was configured elsewhere.
2026-08-04 17:20:21 +02:00
Ole André Vadla Ravnås fca672a9c1 barebone: Let CC carry flags in the agent build
The probe ran the compiler bare, so a bare-metal clang looked for headers
without a sysroot and found none. Split the variable the way cc-rs does
and pass the rest along.
2026-08-04 12:57:50 +02:00
Ole André Vadla Ravnås a8d9cacb1d barebone: Keep the libc lock object alive
Nothing references it, so it was dropped before the linker saw it and the
C library's own lock implementation came in after all, colliding with
ours. Only visible against a libc whose lock stubs live in a member that
gets pulled for other reasons.
2026-08-04 12:55:35 +02:00
Ole André Vadla Ravnås 76a9ac10f2 subprojects: Bump outdated 2026-08-02 18:29:05 +02:00
Ole André Vadla Ravnås 4a9900d165 barebone: Document how to reach the kmod agent
Serve it from a frida-server on the target with --device barebone, so
the device node is opened right next to itself and the host talks to
an ordinary frida-server over adb forward.

Also write down where a matching KDIR comes from. The kernel config
alone is not enough, but nothing needs a kernel build either.
2026-07-31 19:47:10 +02:00
Ole André Vadla Ravnås 4c29710d97 barebone: Fix the Linux agent's ABI mismatches
The prelink whole-archives the Rust staticlib, so Rust's own
compiler_builtins answered the C library's calls to __muldf3 and
friends. It is built for the ordinary AAPCS and takes its double in
d0, while everything else here is -mabi=aapcs-soft and passes it in
x0. Nothing faulted: the arithmetic simply returned garbage, and
GLib, whose hash tables grow on a floating-point threshold, then
never grew one. The first table to fill turned the next miss into an
endless probe loop. Drop those definitions and let the SDK's
compiler-rt answer instead.

That retires the byte-loop memcpy and memmove, which had been put
there to work around the same hang.

Separately, x18 is the kernel's shadow call stack pointer, and
RUSTFLAGS only reaches crates Cargo compiles: core, alloc and
compiler_builtins ship prebuilt and wrote to it in 269 places.
Rebuild them from source with -Zfixed-x18.
2026-07-31 18:40:00 +02:00
Ole André Vadla Ravnås 883e51bb1e submodules: Bump releng 2026-07-31 15:50:18 +02:00
Ole André Vadla Ravnås a8af49e5b3 barebone: Connect to an agent already resident
An agent loaded from inside the target brings its own transport, so
there is nothing to inject and no debugger stub to drive. Open the
device it offers and speak the same protocol over it, with the framing
details it used to take from the stub supplied directly.
2026-07-31 15:50:18 +02:00
Ole André Vadla Ravnås 872fb83da4 barebone: Run the agent as a Linux kernel module
The agent was written for XNU, where it is injected from outside by a
remote stub. Here it starts from the inside instead: a .ko whose init
path spawns the worker, with a character device standing in for the
hostlink.

Everything the kernel side needs lives in a C shim compiled against the
target's own headers, since struct layouts, inline helpers and which of
kallsyms' entry points happen to be exported all vary by release. The
Rust half reaches it through a facade that each flavour fills in.

Scripts are driven through the asynchronous half of the script API: the
thread pumping the JS context cannot also be the one waiting on it.

Neither flavour is the default one, since neither is what you get by
being on a particular host: both are picked deliberately.
2026-07-31 15:50:18 +02:00
Ole André Vadla Ravnås 0e1a53ba7a subprojects: Prepare for release 2026-07-22 00:30:06 +02:00
Ole André Vadla Ravnås bae82ee212 subprojects: Bump outdated 2026-07-22 00:28:46 +02:00
Ole André Vadla Ravnås 5d714719bc subprojects: Prepare for release 2026-07-20 17:55:53 +02:00
Ole André Vadla Ravnås 1d68703132 subprojects: Bump outdated 2026-07-20 17:46:45 +02:00
Ole André Vadla Ravnås afba4699c1 subprojects: Prepare for release 2026-07-19 22:25:17 +02:00
Ole André Vadla Ravnås 8a3afe7c37 subprojects: Bump outdated 2026-07-19 22:24:20 +02:00
Ole André Vadla Ravnås 5ed252adad linux: Stop helper inheriting embedder stdio
For stdio=inherit, pass the host's streams to the helper with each
spawn; with that in place, redirect the helper's own 0/1/2 to
/dev/null at launch so it no longer holds the embedder's pipes.
2026-07-19 22:20:24 +02:00
Ole André Vadla Ravnås 195c6b0618 darwin: Stop helper inheriting embedder stdio
For stdio=inherit, pass the host's streams to the helper with each
spawn; with that in place, redirect the helper's own 0/1/2 to
/dev/null at launch so it no longer holds the embedder's pipes.
2026-07-19 22:20:24 +02:00
Ole André Vadla Ravnås 5934b352b3 subprojects: Prepare for release 2026-07-18 13:30:24 +02:00
Ole André Vadla Ravnås 62f0b50694 subprojects: Bump outdated 2026-07-18 13:29:03 +02:00
Ole André Vadla Ravnås 405fb10d1e submodules: Bump releng 2026-07-18 13:29:02 +02:00
Ole André Vadla Ravnås 07eb059d31 subprojects: Prepare for release 2026-07-17 23:28:11 +02:00
Ole André Vadla Ravnås aca4c8fd14 subprojects: Bump outdated 2026-07-17 23:07:26 +02:00
Ole André Vadla Ravnås 3e6668ef3b subprojects: Bump outdated 2026-07-17 20:48:01 +02:00
Ole André Vadla Ravnås b62cda4965 submodules: Bump releng 2026-07-17 20:48:00 +02:00
Ole André Vadla Ravnås e7d1a48dca barebone: Bump @types/frida-gum to 19.10.0 2026-07-17 18:26:09 +02:00
Ole André Vadla Ravnås 06f685320b compiler: Bump @types/frida-gum to 19.10.0 2026-07-17 18:26:09 +02:00
Ole André Vadla Ravnås b98ae95894 windows: Use gum_memory_allocate for worker code
Follow frida-gum dropping gum_alloc_n_pages()/gum_free_pages().
2026-07-17 15:06:50 +02:00
Ole André Vadla Ravnås a366b1cbf2 compat: Stop forwarding build-machine options
The compat build generates its own machine files, so forwarding the
parent's per-machine build options injected a foreign toolchain. In a
native compat build that produced a compiler-less build machine and
broke every native: true target.
2026-07-17 00:24:49 +02:00
Ole André Vadla Ravnås 206d98a2c5 subprojects: Bump outdated 2026-07-16 17:50:38 +02:00
Ole André Vadla Ravnås e1799ba509 submodules: Bump releng 2026-07-16 17:50:37 +02:00
Ole André Vadla Ravnås 53f3713e86 build: Add glib.wrap to resolve GLib up front
The GLib flavor decides whether payloads are built in-tree, which
frida-gum needs to know before it is configured. Without a wrap of its
own, frida-core could only obtain GLib from the frida-gum subproject, so
the flavor wasn't yet knowable and static cross builds failed to find
it. Shipping glib.wrap lets frida-core resolve GLib itself — the fork
bundles its build deps — so the flavor is known up front for every
build.
2026-07-16 16:43:07 +02:00
Ole André Vadla Ravnås 7b86da31a6 auth: Flush pending reply before closing channel
A rejected token kicked the authentication channel on an idle
source without flushing, so the INVALID_ARGUMENT reply raced
the teardown and clients could see a closed connection instead
of the error.
2026-07-16 15:36:20 +02:00
Ole André Vadla Ravnås 2309a88935 build: Base in-tree payloads on GLib flavor
Whether payloads are built in-tree depends on the GLib flavor, not the
library type: a shared build against Frida's static GLib still wants
them in-tree. Only a shared upstream GLib needs the out-of-tree agent.
2026-07-16 13:52:57 +02:00
Ole André Vadla Ravnås 91011dcdc3 build: Add native languages for cross builds
A cross build still builds the resource compiler for the build
machine, which needs its own C/C++/Vala compilers configured.
2026-07-16 13:52:34 +02:00
Ole André Vadla Ravnås 2f51f2e72c windows: Name helper services from the manager
A spawned helper derived its rendezvous pipe name from its own module
path, which only agrees with the manager when they are colocated (the
embedded layout). In the installed layout each helper lives in its own
arch directory, so the names diverged and they never met. Have the
manager mint one unique name and pass it to every service it spawns —
on the command line for standalone helpers, in the registered binary
path for SCM services — restoring both properties the shared temp dir
used to give: agreement and per-session uniqueness.
2026-07-16 13:52:14 +02:00
Ole André Vadla Ravnås 1355b05293 windows: Find arch helpers in installed layout
The sibling helper path was derived by swapping the filename suffix,
which only fits the embedded layout. Swap the arch token instead, so
it also finds …\<arch>\frida-helper.exe.
2026-07-16 13:24:43 +02:00
Ole André Vadla Ravnås ca736a3268 windows: Expand <arch> in installed helper path
In installed-assets builds the helper path kept the literal <arch>
placeholder, so spawning the cross-arch helper failed with 0x2. Expand
it to the native arch, like the Linux backend already does.
2026-07-16 13:24:21 +02:00
Ole André Vadla Ravnås c71b256bd8 spawn-gating: Fix unhandled-error warning
The droidy and fruity forwards let GLib.Error through raw so the caller can
spot UNKNOWN_METHOD, but declared only Error/IOError. Match the interface's
throws GLib.Error.
2026-07-15 23:58:51 +02:00
Ole André Vadla Ravnås dfc33cd512 spawn-gating: Make it fail-safe and scoped
17.8's eBPF spawn gater stops every execve and leans on the client to
resume each one, so a client that stops resuming wedges process creation
system-wide.

Add a shared SpawnGatingWatchdog that cancels gating and resumes
everything once a caught process sits unresumed too long, wired into all
four suspending gaters (Linux eBPF and RoboLauncher, iOS/tvOS
FruitController, macOS DTrace). Re-send SIGCONT until the task is seen
running, to beat the bpf_send_signal(SIGSTOP) vs SIGCONT race. Report it
via a new spawn_gating_disabled (reason) HostSession signal bubbled to
Device.

Make the system-wide gater opt-in: enable_spawn_gating takes a
SpawnGatingScope (DEFAULT, APPLICATIONS, PROCESSES), arming eBPF only for
PROCESSES. The scope rides a new enable_spawn_gating_with_options; the
plain call stays for wire compatibility and only DEFAULT falls back to
it, so an explicit scope fails loudly against an older Frida.

Also populate ControlService's pending_spawn so the approver model and
resume-on-disable work again.
2026-07-15 23:32:17 +02:00
Håvard Sørbø ca4c90eab1 fruity: Protect segments before applying fixups
The upload injector maps the agent module r-x, streams in its
segments, applies threaded and chained fixups, and only afterwards
restores each segment's protection.

The fixup passes write the rebased and authenticated pointers
directly into the module (e.g. into __DATA_CONST). Because the data
segments are still mapped r-x at that point, on arm64e the first
authenticated-rebase store faults with KERN_PROTECTION_FAILURE and
the injection is aborted with "Invocation of 0x... crashed".

Apply each segment's protection before running the fixup passes, so
the data segments are writable when the pointers land. __TEXT is not
fixed up and stays r-x, so no rwx mapping is created, and the WRITE
command keeps populating the module through its own temporary
writable mapping regardless of ordering.
2026-07-15 19:26:27 +02:00
Ole André Vadla Ravnås 4a1bf993a8 barebone: Bump @types/frida-gum to 19.9.0 2026-07-15 00:20:34 +02:00
Ole André Vadla Ravnås 2ff51da2bc compiler: Bump @types/frida-gum to 19.9.0 2026-07-15 00:20:34 +02:00
Ole André Vadla Ravnås 4372e5f84f subprojects: Prepare for release 2026-07-13 12:55:47 +02:00
Ole André Vadla Ravnås c7661416e5 subprojects: Bump outdated 2026-07-13 12:54:36 +02:00
dezige131 e2bca40e4e android: Tolerate transient USAP patch misses (#1246) 2026-07-13 12:51:34 +02:00
Ole André Vadla Ravnås f31061e846 subprojects: Prepare for release 2026-07-06 19:05:09 +02:00
Ole André Vadla Ravnås f8f9c4386a subprojects: Bump outdated 2026-07-06 19:04:15 +02:00
Ole André Vadla Ravnås a0bd34ebf6 barebone: Bump @types/frida-gum to 19.8.0 2026-07-06 19:03:23 +02:00
Ole André Vadla Ravnås abaebf8a41 compiler: Bump @types/frida-gum to 19.8.0 2026-07-06 19:03:23 +02:00
Ole André Vadla Ravnås e0ec2bee62 subprojects: Prepare for release 2026-06-22 23:31:20 +02:00
Ole André Vadla Ravnås d64a1a934f subprojects: Bump outdated 2026-06-22 23:30:29 +02:00
Ole André Vadla Ravnås 12c052f409 subprojects: Prepare for release 2026-06-22 11:46:17 +02:00
Ole André Vadla Ravnås 6d4443ff26 subprojects: Bump outdated 2026-06-22 11:45:30 +02:00
wave-sky 1b540b96c2 linux: Fix payload base pick on Android 10
Spawning processes on Android 10 fails with a remote connection
error due to Android's XOM mechanism, which restricts
libstagefright.so to execute-only access. This makes the read
permission check in do_prepare_zymbiote_injection fail.

Fix this by:
- Drop the read permission check for target libraries.
- Expand the list of eligible media shared libraries.
- Force read-execute permissions to ensure payload execution.
2026-06-22 11:41:53 +02:00
Ole André Vadla Ravnås 9492205c6a subprojects: Prepare for release 2026-06-20 15:56:50 +02:00
Ole André Vadla Ravnås ff1ecbefd4 subprojects: Bump outdated 2026-06-20 15:55:12 +02:00
Ole André Vadla Ravnås 774db58809 darwin: Fix unused variable warning on non-arm64e 2026-06-19 12:39:22 +02:00
Ole André Vadla Ravnås c1eff69ceb subprojects: Prepare for release 2026-06-19 12:38:27 +02:00
Ole André Vadla Ravnås 99000093ae subprojects: Bump outdated 2026-06-19 11:55:46 +02:00
Ole André Vadla Ravnås 03ba50b50f barebone: Bump @types/frida-gum to 19.7.0 2026-06-18 20:06:01 +02:00
Ole André Vadla Ravnås b3c5a68471 compiler: Bump @types/frida-gum to 19.7.0 2026-06-18 20:05:47 +02:00
Ole André Vadla Ravnås 75802359f5 subprojects: Prepare for release 2026-06-17 12:42:26 +02:00
Ole André Vadla Ravnås a27cc42c15 subprojects: Bump outdated 2026-06-17 12:39:19 +02:00
Ole André Vadla Ravnås c55252ee98 subprojects: Bump outdated 2026-06-17 12:33:00 +02:00
Ole André Vadla Ravnås 12948e78b7 subprojects: Prepare for release 2026-06-16 17:30:17 +02:00
Ole André Vadla Ravnås 8e2e44126f Expose process argv at metadata scope
Make the argv parameter from enumerate_processes() available at
metadata scope, mirroring path, instead of restricting it to full
scope.
2026-06-16 17:30:17 +02:00
Ole André Vadla Ravnås 9ed8e08c26 subprojects: Bump outdated 2026-06-16 16:38:18 +02:00
Ole André Vadla Ravnås eafaf83cdc script: Add interrupt() and terminate()
Expose the new Gum.Script interrupt() and terminate() capabilities
across the agent session API:

  - Script.interrupt() aborts any JavaScript currently executing,
    leaving the script loaded and able to run again.
  - Script.terminate() interrupts execution and unloads the script,
    so one stuck in a long-running or infinite operation can still be
    torn down.

Plumbed through AgentSession, the payload script engine, and the
public Script class. Barebone sessions report these as not supported.
2026-06-16 16:26:56 +02:00
Ole André Vadla Ravnås ac26f8fd84 subprojects: Prepare for release 2026-06-15 19:09:24 +02:00
Ole André Vadla Ravnås 26c180f69c subprojects: Bump outdated 2026-06-15 19:08:33 +02:00
Ole André Vadla Ravnås a461e5d5ab api: Emit filename/line on synthesized docs 2026-06-15 19:00:40 +02:00
Ole André Vadla Ravnås dac42a81d4 Add argv to enumerated process parameters
Populate an "argv" parameter, under full scope, with the target's
command-line arguments. Backed by the PEB on Windows, KERN_PROCARGS2
on macOS, /proc/<pid>/cmdline on Linux, and KERN_PROC_ARGS on
FreeBSD. QNX has no documented source, so it is left out.

Cover it with a local test that spawns a labrat with a known argument
and verifies the round-trip.
2026-06-15 19:00:02 +02:00
Ole André Vadla Ravnås 5c3e027adf subprojects: Prepare for release 2026-06-12 00:42:54 +02:00
Ole André Vadla Ravnås 0bab38e06b compiler: Fix the build on Go 1.24.3
The symbol-replacer module declared go 1.24.4, but CI installs the
toolchain based on the compiler module's go.mod, which says 1.24.3,
and GOTOOLCHAIN=local prevents auto-upgrade. The module has no
dependencies, so lower the requirement to match.
2026-06-12 00:42:54 +02:00
Ole André Vadla Ravnås 5a037879f2 core: Document compiler, packages, and services 2026-06-12 00:12:10 +02:00
Ole André Vadla Ravnås 4ae320f578 core: Document portal, peer, and web types 2026-06-12 00:12:10 +02:00
Ole André Vadla Ravnås bc8f67b45e core: Document base enums and option types 2026-06-12 00:12:10 +02:00
Ole André Vadla Ravnås f6774caab7 core: Document the public API 2026-06-12 00:12:10 +02:00
Ole André Vadla Ravnås 3d33d451a7 api: Inject Vala doc comments into the GIR 2026-06-12 00:12:10 +02:00
Ole André Vadla Ravnås 0d4d5c62f7 ci: Bump actions to latest 2026-06-12 00:12:10 +02:00
Ole André Vadla Ravnås 4d6decfe0a subprojects: Bump outdated 2026-06-11 22:56:26 +02:00
Ole André Vadla Ravnås 5c3da2ba20 submodules: Bump releng 2026-06-11 22:56:26 +02:00
Ole André Vadla Ravnås 522c843b72 subprojects: Bump outdated 2026-06-11 20:25:59 +02:00
Ole André Vadla Ravnås fb6f798400 submodules: Bump releng 2026-06-11 20:25:58 +02:00
Ole André Vadla Ravnås 006563006d gdb: Fix register access on LLDB stubs
Per-register access keyed the ;thread: suffix off the vCont feature,
but the suffix is the QThreadSuffixSupported extension, unrelated to
vCont. The iOS debugserver negotiates the suffix without advertising
vCont, so reads went out bare and came back E03, surfacing as
"Failed to attach: invalid integer value: E03".

Track a thread-suffix capability, set it where the stub negotiates
QThreadSuffixSupported, and gate the suffix on it. Plain gdbserver
now also stops receiving an LLDB-only suffix it never understood.

Add a smoke test driving the client against a debugserver-flavoured
stub that only honours suffixed reads.
2026-06-11 20:24:59 +02:00
Ole André Vadla Ravnås a007e94625 subprojects: Prepare for release 2026-06-10 00:47:55 +02:00
Ole André Vadla Ravnås b814d4a73b subprojects: Bump outdated 2026-06-10 00:46:46 +02:00
Ole André Vadla Ravnås 2d195613a3 ci: Publish GObject introspection data 2026-06-10 00:44:56 +02:00
Ole André Vadla Ravnås 880f7ac7a3 linux: Walk the stack to find a hookable trigger
Once a target is multi-threaded, a thread blocked in a cancellable
syscall parks in the tiny __syscall_cancel_arch wrapper, too small to
host the stub, so sampling its PC alone found nothing to re-hook and
injection timed out from the second time onwards. Also read a few stack
words via the syscall line's stack pointer and let hottest_libc_function
walk outward to the first frame in a large-enough libc routine, trusting
only words a call actually returns to so stray data is never re-hooked.
2026-06-10 00:22:27 +02:00
Ole André Vadla Ravnås f51327fa29 linux: Load agent via /proc/<pid>/fd
An attached debugger reacts to the dynamic linker's library-load event
by reading the freshly mapped object off the path dlopen () was given.
With /proc/self/fd/<n> that read resolves against the debugger's own fd
table rather than ours, so it fails; GDB then leaves the loading thread
parked at its dynamic-linker breakpoint, the agent never finishes coming
up, and injection times out waiting for it to connect back. Spelling out
our PID makes the path resolve identically from inside and outside the
process, so the debugger reads the real object and lets the thread run.
2026-06-09 23:48:10 +02:00
Ole André Vadla Ravnås f0cc3b44ff linux: Restore trigger symmetrically with install
restore_trigger overwrote the patched body while the entry still routed
callers into it, so a multi-byte prologue (e.g. endbr64) could be
re-entered mid-restore and execute a half-written instruction. Re-block
the entry and drain before putting the body back, then restore the first
instruction last, mirroring install_bootstrap.

Also give the agent-connect timeout its own wording, distinct from the
identical stub-trigger timeout, so the two stages can be told apart.
2026-06-09 23:48:10 +02:00
Ole André Vadla Ravnås 21dc508b4a linux: Capture libbpf log on error
Install an accumulating libbpf print callback and fold the captured
output into the errors we throw, so failures carry their diagnostics
instead of spilling onto stderr. The kernel verifier log arrives over
the same callback, making the separate log buffer redundant.
2026-06-09 23:48:10 +02:00
Ole André Vadla Ravnås 33e5879226 linux: Size triggers by the actual stub
The bootstrap is stamped over the trigger function in place, so a
candidate is only usable if it is at least as large as the stub. Build
that stub for the first-injection path and measure it, rather than
gating on a conservative fixed minimum that needlessly rejected small
but frequently-run routines such as glibc's shared syscall-cancellation
trampoline.
2026-06-09 23:48:10 +02:00
Ole André Vadla Ravnås a0a883839c linux: Fall back to /proc when eBPF is denied
When the activity sampler cannot start, typically because perf/eBPF
requires privileges we lack, poll /proc/<pid>/task/<tid>/syscall to find
which libc routines the target is calling and re-hook the hottest one.
2026-06-09 23:48:10 +02:00
Ole André Vadla Ravnås 837b6acaee barebone: Bump @types/frida-gum to 19.6.0 2026-06-09 23:38:46 +02:00
Ole André Vadla Ravnås 63ad485f8a compiler: Bump @types/frida-gum to 19.6.0 2026-06-09 23:38:46 +02:00
Ole André Vadla Ravnås 43fcdf5f0e barebone: Bump @types/frida-gum to 19.5.0 2026-06-05 18:17:04 +02:00
Ole André Vadla Ravnås 2176a7b85c compiler: Bump @types/frida-gum to 19.5.0 2026-06-05 18:17:04 +02:00
Ole André Vadla Ravnås 2c2bd9d159 barebone: Set size on synthesized export details
GumExportDetails gained a size field; the agent builds the struct
literally while enumerating Mach-O exports, which carry no size, so
report -1 to match the unknown convention.
2026-06-05 18:00:11 +02:00
Ole André Vadla Ravnås 3c0f86f393 subprojects: Prepare for release 2026-06-05 17:08:02 +02:00
Ole André Vadla Ravnås 1f0e01f927 activity-sampler: Suppress unused field warnings
SampleEvent must match the layout emitted by the BPF program, so touch
the fields we don't consume yet to keep valac quiet.
2026-06-05 17:06:11 +02:00
Ole André Vadla Ravnås 23455a0e72 subprojects: Bump outdated 2026-06-05 16:59:53 +02:00
Ole André Vadla Ravnås 73ea47f899 activity-sampler: Tweak style 2026-06-05 16:59:25 +02:00
Ole André Vadla Ravnås c0be13b982 linux: Discover a ptrace-free trigger by sampling
An idle ART/Dalvik process gets a SIGUSR1 nudge, but a busy native
target that never calls malloc had no way through. Sample what it
runs and resolve the hottest libc function from each sample's leaf
-- by symbol, then export, then a prologue/ret scan as a last
resort -- then restore malloc and re-hook there.

The leaf is the only reliable frame: the hot mem*/str* routines are
frame-less assembly, so unwinding past them misattributes the call
to whatever the frame pointer happened to hold. Resolution leans on
the symbol table because those routines are ifunc-resolved to
non-exported implementations the export table never names.
2026-06-05 16:59:25 +02:00
Ole André Vadla Ravnås 21f2cba74c linux: Make ActivitySampler collect call stacks
Sample once per millisecond of on-CPU time rather than every
nanosecond, and accumulate each thread's user call stack instead of
printing it. This turns the dormant debug sampler into something a
caller can read to learn what a target is actually running.

Groundwork for picking a ptrace-free trigger when malloc is quiet.
2026-06-05 16:59:25 +02:00
Ole André Vadla Ravnås 3ff34d2c31 linux: Preserve all arg regs in ptrace-free stub
The stub saved only the first argument across re-invoking the
hooked function -- fine for malloc, but not for an arbitrary
trigger. Save and restore the full integer-arg set (rdi-r9 on
x86_64, x0-x8 + lr on arm64) on both the loser path and the
winner's return into the function. x86 needs nothing: its args
ride the stack, which the stub never touches.

Groundwork for hooking something other than malloc when malloc
stays quiet.
2026-06-05 16:59:25 +02:00
Ole André Vadla Ravnås c2ff19e63b compiler: Rename symbols in the archive index too
The symbol index maps names to members and is what the linker uses to
pull a definition. ranlib does not rebuild it for the COFF archive
clang produces on windows-arm64, so the renamed objects no longer
matched their index entries and the link failed.

Rename the names in the index as well. It holds only names and offsets,
so the rewrite stays clear of the runtime data inside the objects.
2026-06-05 12:18:29 +02:00
Ole André Vadla Ravnås 2dad3485c5 barebone: Guard hostlink UNIX socket on Windows 2026-06-05 01:13:21 +02:00
Ole André Vadla Ravnås 196991bbe6 subprojects: Bump outdated 2026-06-05 00:55:55 +02:00
Ole André Vadla Ravnås 3dd3162117 submodules: Bump releng 2026-06-05 00:55:55 +02:00
Ole André Vadla Ravnås fff63df874 linux: Recycle ptrace-free loader region
The region cannot free itself: the worker runs the loader from it,
and afterwards no code of ours is left to call munmap -- ptrace,
which the ptrace path uses for this, is what's unavailable here.

So recycle it instead of leaking one per injection: rediscover the
region by signature -- an anonymous executable mapping still holding
our loader bytes -- and aim the bootstrap at it. Stateless, so it
survives the short-lived helper, and the signature rules out a
reused PID.

Keep nameless anonymous mappings in the maps parser so the scan
finds them.
2026-06-04 22:41:19 +02:00
Ole André Vadla Ravnås 50b9be7a80 linux: Add ptrace-free injection fallback
When a foreign tracer occupies the target's ptrace slot, fall back
to /proc/$pid/mem: patch an in-malloc bootstrap that mmap()s an R-X
region, stage the loader there, and let the elected thread run it.
Losers and re-entrants spin at malloc+0 until it is restored.

Implemented for x86, x86_64 and arm64. On a managed runtime (ART or
Dalvik) an idle process is nudged with SIGUSR1 to force an allocation.
2026-06-04 20:22:40 +02:00
Ole André Vadla Ravnås 5f7295c596 tests: Force sandbox to allow a given PID
Hook sb_evaluate_internal, the chokepoint every per-operation
sandbox check funnels through, and force its result to 0 (allow)
when the subject process matches a target PID. The proc is read
from the evaluator context at +0x10 and resolved via proc_pid.
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås bfd7fd6f88 tests: Hook a kernel function in the VZ guest
Attach to sb_evaluate_internal and confirm the handler runs on a
foreign kernel thread without a panic.
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås 17827a1493 barebone: Query page size from cached MMU params
Reuse the cached MMU parameters instead of reloading them over
the stub, which hangs once the stub has been detached.
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås 8758398a53 barebone: Patch kernel text via physical bridge
Kernel text is locked read-only by CTRR, so guest-side writes
fault. Add a PATCH_CODE command that stages writes in a shadow
buffer and commits them through the host's physical-memory
bridge, which writes the backing store directly.
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås 13a5b23288 barebone: Relocate kext code via kext summaries
An SPTM fileset kernelcache scatters each fileset entry's
segments to independent runtime bases, so kext code living past
the kernel's own __TEXT_EXEC slice cannot be translated through
the kernel's base. The runtime collection header is no help: its
LC_FILESET_ENTRY vmaddrs are clobbered by the loader and the kext
mach-headers are repacked away from their static positions.

Walk gLoadedKextSummaries instead, the array the kernel maintains
for debuggers. It pairs each loaded kext's bundle id with the
runtime address of its mach-header, from which we read that kext's
independently rebased segments and pair them with the static
segment table. The kernel itself is still located by translating
its header through the collection segments.

The summaries pointer is supplied via image.symbols; without it
only the kernel is relocated.
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås 1c8e27f9c4 barebone: List kexts from fileset kernelcaches
Fileset kernelcaches (iOS 16+) carry each kext as an LC_FILESET_ENTRY
load command rather than the __PRELINK_INFO __kmod_info/__kmod_start
tables that compute_module_list knew about, so the module list collapsed
to a single fabricated mach_kernel entry. Parse the fileset entries
instead, expressing each kext relative to com.apple.kernel so the agent
rebases them with its single kernel base (and the per-segment
KernelRelocation scatters them to their real runtime addresses).

Verified against the vphone research kernel: Process.enumerateModules()
now returns all 121 kexts with their bundle ids and runtime bases
instead of just the kernel.
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås e6a334e354 barebone: Detach the debug stub after injection
The vphone research kernel routes every synchronous exception taken
while a debugger is attached straight to a panic. The agent's worker
intermittently faults inside the kernel allocator during gum_init, so
with the stub still attached that fault became a kernel panic that
required rebooting the VM (frequent on a fully-settled kernel, ~25-50%
of injects in the worst case).

Detach the stub once the agent is in place so its exceptions are handled
normally. Post-injection all host reads/writes go through the
physical-memory bridge and the MMU parameters are already cached, so the
stub is no longer needed; gate the detach on the bridge being present
and otherwise keep continuing, since targets without it still drive the
W^X RPC through the stub.

Verified: longwait injects went from ~25-50% panic to ~1/26, and
Interceptor/Stalker still work (their W^X path uses the bridge).
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås 71a299bf25 barebone: Tolerate thread migration during invoke
invoke() and execute() hijack the core that hit thread_block, set the
callee's return address to a landing zone, breakpoint it, and resume.
The stub identifies threads by core, so the loop waited for the same
core to hit the landing zone. But a callee that reschedules (notably the
agent's _start calling kernel_thread_start to spawn the worker) migrates
the hijacked thread to another core; the return then stops on a
different core, the loop rejected it and waited forever, and registers
were restored onto the original core's now-unrelated thread, wedging the
kernel into a cross-core spinlock deadlock (or, on a missed landing,
panicking a bystander thread).

The landing zone is never reached by anything else, so any thread
stopping there is our call returning. Match on the breakpoint alone and
finish on whichever core it landed on. Fixes the intermittent inject
hang/panic that previously required rebooting the VM.
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås dd79d5bbb0 barebone: Make Stalker work in the kernel
Bring up Stalker end-to-end against the in-kernel agent:

- Flush this CPU's TLB after the host rewrites our descriptors. The host
  edits page tables through the physical-memory bridge, so the guest TLB
  keeps the stale translation; Stalker flips a code slab RW then RX in
  place, and without the flush executing the frozen page faults with a
  permission abort.
- Exclude the agent's own range from Stalker. Following the current thread
  into the hostlink transport would deadlock on the locks the RPC it
  depends on is holding. The host injected the image, so it now passes our
  range first in the config and the agent excludes it on each script's
  Stalker, mirroring lib/payload.
- Mask the thread pointer down to a JS-safe width in get_current_thread_id
  so Process.getCurrentThreadId() round-trips through a double, letting
  Stalker.follow recognise the current thread.

Verified: Stalker.follow with a transform over a NativeFunction (traps:
'all') compiles and runs the instrumented blocks and returns correctly
(r: 42 blocks: 30).
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås d8e7075002 barebone: Support W^X remap so Interceptor works
Code-generating gum (Interceptor, Memory.patchCode) allocates pages RX
and writes through a writable alias via gum_memory_try_remap_writable_pages.
Make that path work in-kernel:

- The agent no longer resolves ml_vtophys (absent on this kernel); it sends
  the virtual pages and the host translates them to physical via its own
  page-table walk over the bridge.
- translate_address/read_level3_descriptor reuse the cached MMU registers
  and skip the stub's physical-addressing mode when the bridge is present,
  so they run without halting the agent.
- allocate_pages reads the displaced descriptors through the bridge instead
  of the stub (which would hang on a running target).

Verified: Interceptor.attach on an in-kernel function fires and the original
returns correctly (result: 42 hits: 1).
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås 33d4228871 barebone: Make in-kernel closures executable
NativeCallback is backed by a libffi closure whose trampoline must be
executable. With the static-trampoline backend (FFI_TRAMP_EMBEDDER),
libffi asks the embedder to map the trampoline table; ffi.rs copies the
template, then flips it RX over the hostlink RPC. Supporting changes:

- protect_pages/allocate_pages cache the kernel's MMU registers and skip
  the stub's physical-addressing mode when the bridge is present, so the
  page-table work runs without halting the agent.
- The RPC handlers no longer stop the VM (the bridge writes guest memory
  directly); halting froze the waiting worker.
- Hostlink.process() detaches the frame and resets receive state before
  dispatching, so a synchronous RPC issued while handling a command does
  not re-enter and re-dispatch the same frame.
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås 5158b7bdb8 barebone: Deliver W^X callbacks over vsock-RPC
The mprotect/remap callbacks were patched as BRK breakpoints at the
agent's gum_try_mprotect/gum_barebone_try_remap_writable_pages, but a
BRK is a debug exception that panics this research kernel. Move them to
the hostlink: the agent sends a REMAP_WRITABLE_PAGES/MEMORY_PROTECT
request and blocks pumping the transport until the reply, while the
host does the page-table work via the bridge and replies. This lets
code-generating scripts (NativeCallback, Interceptor, ...) run.
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås ae20324226 barebone: Bring up the vsock hostlink end-to-end
Disable the BRK-based mprotect/remap callbacks (debug exceptions panic
this research kernel; these will move to vsock-RPC) so the agent runs
through to its main loop, and restore the establish_hostlink flow. With
the async-connect and PAC fixes, host<->agent RPC now round-trips: a
script's send() arrives from kernel space over the hostlink.
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås 9d938642d8 barebone: Handle async vsock connect handshake
sock_connect on the in-kernel KPI returns EWOULDBLOCK/EINPROGRESS and
completes in the background once the host accepts; re-calling connect
to poll corrupts the connecting state and yields a hard error. Treat
the in-progress codes (and EISCONN) as success and let the main loop
drive I/O once the link is up.
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås bb3177d185 barebone: PAC-sign the vsock sock_upcall callback
XNU invokes the sock_upcall via an authenticated branch (blraa,
IA key) using the sock_upcall type discriminator, so the pointer
handed to sock_socket must be signed to match. Sign it like the
interrupt handler and thread continuation; otherwise the kernel
takes a PAC failure on the first upcall.
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås a776a40e30 barebone: Bias ABS64 relocs by existing slot value
The agent is a fully-linked --emit-relocs image, so each ABS64 slot
already holds the final base-0 value. Recomputing from
symbol.address + addend is unsafe: some relocs reference the .text
section symbol, whose st_value is a bogus non-zero address rather
than the section base, corrupting the result (e.g. a class_init
pointer landing in .rodata and faulting on execute). Bias the
existing slot by base_va instead.
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås 6910f51acb barebone: Add VZ physical-memory bridge
Map the guest's RAM straight from the Virtualization daemon
(task_for_pid + mach_vm) and route page-table I/O and the agent
upload through it, bypassing the slow kernel GDB stub. Locate
Hv::Vm with Gum.Memory.find_pointers over the daemon heap.

Add a sysconf stub to the barebone agent (gum's find_pointers
pulls it in) and document, in-code, the research-kernel debug-
exception panic that still blocks gum_init in the agent worker.
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås d396365e48 barebone: Fill in vphone kernel symbol map
Resolve the agent's required XNU symbols against the vresearch101
research kernelcache (panic, IOLog, thread_block, assert_wait[_timeout],
thread_wakeup, kalloc_data/kfree_data, the sock_* KPIs,
kernel_thread_start, and the timebase/calendar helpers) and record them
in the example config as offsets from the kernelcache base, leaving the
per-segment relocation to translate them at injection time.
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås b9c0b4dfdc barebone: Resolve kalloc_data/kfree_data allocator
Modern XNU exposes kalloc_data_external(size, flags) and
kfree_data_external(ptr, size) instead of kalloc/kfree. Pass an
allocator-configurable flags argument (invoke leaves x1 undefined) and
record the resolved functions in the vphone example config.
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås 9917f78b59 barebone: Resolve per-segment kernel slides
On a scattered SPTM kernel collection each segment is relocated
independently, so a single KASLR slide cannot map static addresses to
runtime ones. Read the live com.apple.kernel header's rebased
per-segment vmaddrs and pair them with the static table to translate
any address. Compute the relocation once in the host session, apply it
to the allocator functions and to every resolved symbol, and enter
no-ack mode so the header scan has the throughput it needs.
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås 0e770aa846 gdb: Fall back to per-register access without g/G
The VZ kernel stub is lldb-flavoured: it implements neither the bulk
g/G register packets nor qSupported's no-ack advertisement, and stays
halted across reconnects. Add read/write_registers fallbacks that walk
the 64-bit registers via p/P (skipping ones not readable in the current
context), a start_no_ack_mode helper, and make halt() also issue ? so a
stub that is already stopped still replies.
2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås 63d74d7909 barebone: Compute KASLR slide from kernelcache 2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås dd9de99215 tests: Add VZ kernel stub smoke test 2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås 118921b2e5 barebone: Add VZ kernel GDB stub client 2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås e88951d74b gdb: Add connection-preparation hook 2026-06-04 13:21:37 +02:00
Ole André Vadla Ravnås 71c91ece93 gitignore: Ignore clangd index cache 2026-06-04 13:21:37 +02:00
Håvard Sørbø 0cb45826bf gdb: Switch thread before register access 2026-06-04 13:21:37 +02:00
Håvard Sørbø 9e3ac7f1fb gdb: Detect vCont support with fallback 2026-06-04 13:21:37 +02:00
Håvard Sørbø af05c830c8 gdb: Allow qRcmd to return an empty reply 2026-06-04 13:21:37 +02:00
Håvard Sørbø bf3730d28a gdb: Load target properties based on qXfer support 2026-06-04 13:09:47 +02:00
Håvard Sørbø 4d7a1c3fc1 gdb: Always send proper checksum 2026-06-04 13:09:47 +02:00
Ole André Vadla Ravnås 40ec04e48b barebone: Add vsock transport alongside virtio
Give the agent config a transport kind byte; let the host pick
virtio or vsock per target. Add a hostlink_vsock module that
talks AF_VSOCK via XNU's in-kernel socket KPI. Make xnu.rs fall
back across kalloc / kalloc_data and thread_wakeup / wakeup so
one binary covers QEMU's bare XNU and iOS XNU.

Dispatch on transport type in agent-connection.vala and stop
erroring on unresolved .kernel_addrs slots — Option<fn> in the
agent absorbs per-kernel symbol drift.
2026-06-04 13:09:47 +02:00
Ole André Vadla Ravnås 87822f0668 compiler: Rename symbols only in string tables
The symbol-replacer rewrote every matching byte sequence across the
whole archive, mangling machine code, type metadata and GC pointer
maps. That corrupted the embedded Go runtime, causing rare crashes
during garbage collection when compiling agents.

Confine the rewrite to the symbol string tables, locating them in the
ELF, Mach-O and COFF members. Exported symbols are unchanged.
2026-06-04 12:20:11 +02:00
Ole André Vadla Ravnås 64086ff8b5 subprojects: Prepare for release 2026-06-02 02:10:37 +02:00
Ole André Vadla Ravnås d5e018ff2a subprojects: Bump outdated 2026-06-02 02:08:48 +02:00
Ole André Vadla Ravnås e6a05587bb subprojects: Bump outdated 2026-06-02 01:41:03 +02:00
cylentsec 6db41996b8 darwin: Support ElleKit/palera1n in launchd agent
Add findEllekitShouldEnableTweaks() and instrumentEllekitInjector()
to applyJailbreakQuirks(). On palera1n + ElleKit the injector gate
function should_enable_tweaks is exported from libinjector.dylib or
TweakLoader.dylib. Intercept it to return 0 for Frida-managed PIDs,
preventing double-injection for spawned processes.

Also add missing return after instrumentInserter() so ElleKit
detection only runs when no Substitute inserter is present.
2026-06-02 01:33:54 +02:00
cylentsec 8491a90d11 darwin: Fix ElleKit spawn compatibility
Restore selective exception mask in thread_swap_exception_ports:
EXC_MASK_BREAKPOINT | EXC_MASK_CRASH (plus EXC_BAD_ACCESS on
ptrauth/arm64e). This prevents Frida from intercepting non-
breakpoint exceptions raised by ElleKit's TweakLoader/libinjector
during DYLD_INSERT_LIBRARIES initialization on palera1n.

Replace the hard goto-unexpected_exception bailout for non-
EXC_BREAKPOINT exceptions with a forward_to_system path that
sends KERN_FAILURE, letting the kernel pass the exception to the
next handler in the task→host chain. Only EXC_CRASH is still
routed to unexpected_exception for early failure detection.

On ptrauth/arm64e, EXC_BAD_ACCESS at the ret_gadget address is
still allowed through so the PAC fixup already in place can take
effect — previously that fixup was dead code because the hard
bailout ran after it but before the state was committed.

Fixes #1239.
2026-06-02 01:33:54 +02:00
AeonLucid b5d14fda20 android: Fix helper initialization for Nothing OS 2026-06-02 00:43:53 +02:00
Ole André Vadla Ravnås e84c209a00 barebone: Bump @types/frida-gum to 19.4.0 2026-06-01 23:47:03 +02:00
Ole André Vadla Ravnås 1242a236bc compiler: Bump @types/frida-gum to 19.4.0 2026-06-01 23:47:03 +02:00
Ole André Vadla Ravnås b555792d15 subprojects: Prepare for release 2026-05-31 21:03:52 +02:00
Ole André Vadla Ravnås 9d19884fa8 subprojects: Bump outdated 2026-05-31 21:02:53 +02:00
Ole André Vadla Ravnås eba6766e5c submodules: Bump releng 2026-05-31 21:02:53 +02:00
Ole André Vadla Ravnås 3c72076909 barebone: Bump @types/frida-gum to 19.3.0 2026-05-31 15:29:21 +02:00
Ole André Vadla Ravnås 4b4ea3d710 compiler: Bump @types/frida-gum to 19.3.0 2026-05-31 15:29:21 +02:00
Ole André Vadla Ravnås 2e1e35537b subprojects: Bump outdated 2026-05-31 13:13:18 +02:00
Ole André Vadla Ravnås 38121a1815 Expose agent feature toggles and linker offsets
SessionOptions now carries the exceptor mode (full, handler-only, off),
the unwind-broker, exit-monitor, and thread-suspend-monitor toggles, and
a list of linker notifier offsets. These ride the agent parameters and
are applied before the relevant Gum singletons are first obtained.
2026-05-30 23:28:58 +02:00
Ole André Vadla Ravnås 25659c78b6 Adapt to new Interceptor attach/replace API
Move replacement_data and listener_function_data into the
GumReplaceOptions/GumAttachOptions structs, and drop the
removed GUM_ATTACH_FLAGS_NONE argument.
2026-05-30 23:21:36 +02:00
Ole André Vadla Ravnås a58e11d22a payload: Migrate unwind guts to generic Gum API
The dyld interception, libunwind vtable hooking, capstone-based
discovery and PAC resigning now live in Gum behind UnwindBroker and
UnwindSectionsProvider. UnwindSitter is reduced to registering a
provider for the invader's range.

Rework the exception-catcher tests to drive the ObjC runtime
directly, and add an arm64e labrat.
2026-05-30 17:37:34 +02:00
Ole André Vadla Ravnås 4a39801bed base: Fix installed-layout asset resolution
The EXISTS check stat'd a path still containing the literal
<arch> token, so it always failed and the flat fallback won
even when the frida-1.0/<arch>/ tree was present. Probe the
install root instead and return the templated path for
PathTemplate.expand() to resolve.
2026-05-24 09:02:31 +02:00
Ole André Vadla Ravnås 5e8ddd6863 subprojects: Prepare for release 2026-05-23 14:22:26 +02:00
Ole André Vadla Ravnås e1023ccc1d subprojects: Bump outdated 2026-05-23 14:21:15 +02:00
Ole André Vadla Ravnås b3e88c1c40 agent: Enable Exceptor on hardened Darwin tasks
The opt-out was put in place when Gum.Exceptor used Mach exception
ports — those didn't work for /System/, /usr/libexec/ and similar
hardened binaries. The POSIX-signal-based backend now works on
arm64e thanks to recent Gum work, so drop the opt-out.
2026-05-23 14:14:25 +02:00
Ole André Vadla Ravnås ebe45d7d52 subprojects: Bump outdated 2026-05-21 23:19:12 +02:00
Ole André Vadla Ravnås 219ff0ec59 darwin: Launder embedded agent through anon pages
The bytes we previously handed to mach_vm_remap pointed straight
into frida-server's own __DATA segment, which the kernel knows
as a file-backed code-signed mapping. The codesign state may have
been propagated through the cross-task remap on older kernels
too, but newer kernels add hardening that catches a mismatch when
the destination is asked to treat a subset of it (the embedded
dylib slice) as its own mapping.

Copy the agent into a page-aligned anonymous mapping at
host-session construction time so the source pages carry no
codesign state. Mirrors what the installed-asset path does
implicitly by memcpying the file-backed mapping into anon pages
before handing them to the mapper.
2026-05-21 23:15:24 +02:00
Ole André Vadla Ravnås 739e3f626e subprojects: Prepare for release 2026-05-15 14:23:26 +02:00
Ole André Vadla Ravnås 85fd4742c3 subprojects: Bump outdated 2026-05-15 14:21:33 +02:00
pakbaz-dev a41028792c android: Handle android:process in spawn matching
When an activity declares android:process, the zygote-forked
process is named "com.example.app:foo" rather than the bare
package name. The zymbiote's hello carried that suffixed name,
so it never matched the pending spawn_request keyed on the
package name, and the spawn timed out.

Strip the colon suffix before matching, and rename the
zymbiote-side field from package_name to process_name to
reflect what it actually carries.
2026-05-15 14:18:22 +02:00
Ole André Vadla Ravnås 81d8403d34 package-manager: Fix scoped name parsing
Splitting "@scope/name" on '@' left the name empty and
treated "scope/name" as the version range, then fetched
the registry root and failed with a misleading "missing
dist-tags" error. Skip the leading '@' when locating the
name/version separator.
2026-05-15 14:08:46 +02:00
Ole André Vadla Ravnås 8e9e376c7e subprojects: Prepare for release 2026-05-14 20:21:09 +02:00
Ole André Vadla Ravnås 4465abf747 subprojects: Bump outdated 2026-05-14 20:19:50 +02:00
Ole André Vadla Ravnås 3f51017a5d subprojects: Prepare for release 2026-05-11 13:22:42 +02:00
Ole André Vadla Ravnås b88584ec7e subprojects: Bump outdated 2026-05-11 13:21:56 +02:00
Ole André Vadla Ravnås 86c760f2d7 compiler: Bump @types/frida-gum to 19.1.0 2026-05-11 13:21:22 +02:00
Ole André Vadla Ravnås 6c3c58cc6e subprojects: Prepare for release 2026-05-08 10:39:58 +02:00
Ole André Vadla Ravnås b3f2bf1c24 subprojects: Bump outdated 2026-05-08 10:37:29 +02:00
Ole André Vadla Ravnås c80a32c59f compiler: Free backend strings on the cgo side
The backend (Go/cgo) used C.CString to hand bundle, error, and
diagnostic strings to the Vala callback, leaving Vala to g_free
them. On Windows the cgo runtime links against mingw's CRT while
the Vala consumer links against MSVC's ucrt, so the heaps don't
match and the free aborts the process with STATUS_HEAP_CORRUPTION.

Free the C strings on the Go side once the callback returns and
declare the delegates with unowned strings so the consumer dups
into the GLib heap when it needs to retain a copy.
2026-05-05 16:24:10 +02:00
Ole André Vadla Ravnås 6d002ed9ce subprojects: Prepare for release 2026-05-04 15:25:25 +02:00
Ole André Vadla Ravnås 3c66fb1b14 subprojects: Bump outdated 2026-05-04 15:24:25 +02:00
Ole André Vadla Ravnås 0452aae340 darwin: Fix agent path on rootless iOS
Frida.agent_path is now absolute and resolved, so
prepending sysroot or CRYPTEX_MOUNT_PATH duplicated
the rootless prefix and produced a non-existent path.

ControlServiceOptions.sysroot only routed the
detected prefix to TemporaryDirectory.use_sysroot;
have server.vala call the latter directly and drop
the field.

Fixes #1232.
2026-05-04 15:19:36 +02:00
Ole André Vadla Ravnås 36071e39c1 subprojects: Prepare for release 2026-05-02 21:13:25 +02:00
Ole André Vadla Ravnås 787ad2707f subprojects: Bump outdated 2026-05-02 21:12:36 +02:00
Ole André Vadla Ravnås 20e4e0a58d darwin: Reap helper on handshake error
GDBus may surface its EOF before ChildExitMonitor's first tick, leaving
child_exited false and skipping the arm64e-fallback retry in obtain().
2026-05-02 21:10:45 +02:00
Ole André Vadla Ravnås 9629b86ce6 subprojects: Prepare for release 2026-05-02 15:48:34 +02:00
Ole André Vadla Ravnås e8dd7b112a subprojects: Bump outdated 2026-05-02 15:46:13 +02:00
Ole André Vadla Ravnås 28c887a0d1 darwin: Store helper tempfiles in cache dir
~/.Trash was used so MAS sandboxed apps could read it, but
xctest sandboxes deny exec from .Trash. The container's own
Caches dir is exec-allowed and writable everywhere we care
about.
2026-05-02 15:37:32 +02:00
Ole André Vadla Ravnås 8a37c77337 darwin: Use a socketpair for helper handshake
Avoids the 104-byte sun_path limit on macOS, which truncated
the socket file when the home dir was deeply nested (e.g. inside
a sandbox container) and caused EADDRINUSE on subsequent attempts.

Drive the handshake from a while-loop that races the DBus async
constructor against ChildExitMonitor, so a helper that crashes
during dyld surfaces as PROCESS_NOT_FOUND without the exit
callback having to mutate the in-flight stream.
2026-05-02 15:37:32 +02:00
Ole André Vadla Ravnås 6b74017e41 build: Install frida-agent in shared+installed mode
lib/agent was only entered with have_in_tree_payloads, but
that's false for shared builds — so the lipo target that
combines compat's per-arch slices into the universal
frida-agent.dylib never got created, and nothing installed.
Pull lib/agent inclusion out of the in-tree-payloads gate
and move the build-only setup inside the build_agent
branch so the file works without lib/payload.
2026-05-02 01:12:41 +02:00
Ole André Vadla Ravnås 804b67671f tests: Add /HostSession/Darwin/spawn-application 2026-05-02 01:12:22 +02:00
Ole André Vadla Ravnås 38aa3486b2 macos: Spawn applications by bundle identifier
When the program doesn't start with `/`, resolve via
NSWorkspace and pass the bundle's executable URL to the
helper.
2026-05-02 01:12:16 +02:00
Ole André Vadla Ravnås 08675b657f system: Implement applications API on macOS
NSWorkspace for running apps; the private LSApplicationProxy
and LSApplicationWorkspace classes for installed apps and
their metadata. Look the private classes up via
objc_getClass to avoid a linker dependency.
2026-05-02 00:49:56 +02:00
Ole André Vadla Ravnås 7ce4207f57 darwin: Thin helper to arm64 in installed mode too
Read the universal binary from helper.path and write the
arm64 slice to a fresh tempfile, swapping helper to it.
Same fallback in installed mode without shipping two
artifacts.
2026-05-02 00:49:49 +02:00
Ole André Vadla Ravnås 96d4bdd7e8 darwin: Skip stdio dup2 when not redirecting
posix_spawn rejects dup2(fd,fd) with EBADF when the source
fd is a FIFO inherited from a parent like Node. Drop the
no-op.
2026-05-02 00:49:42 +02:00
Ole André Vadla Ravnås 94bafc4d80 darwin: Fix posix_spawn error reporting
Use posix_spawn's return value: it returns the error code
directly and may not touch errno, so failures previously
read "Undefined error: 0".
2026-05-02 00:49:35 +02:00
Ole André Vadla Ravnås 8da44cd3f2 base: Add flat-layout fallback in AssetLocation
Try the install-layout path first; fall back to
`<libdir>/<filename>` when missing. Lets uninstalled
builds resolve assets sitting next to the lib.
2026-05-02 00:31:41 +02:00
Ole André Vadla Ravnås 867b7622b0 tests: Tolerate empty nvram boot-args output
When boot-args is unset, the captured stdout is empty and
splitting on tab yields no tokens; the existing assert
bailed the runner before any test ran.
2026-05-02 00:31:26 +02:00
Ole André Vadla Ravnås 5593160b80 compat: Simplify host-arch agent detection 2026-04-29 22:34:46 +02:00
Ole André Vadla Ravnås d6dee469ef agent: Skip parent build when compat owns it
When compat produces a host-arch agent (use_apple_tls case),
track its path separately as agent_host_compat and skip
the parent shared_library/modulate/post_process pipeline
in lib/agent. The Darwin universal lipo now combines compat's
host-arch and cross-arch outputs directly, fixing the lipo
error caused by mixing the parent's host-arch dylib with
compat's identical-arch one.
2026-04-29 22:30:09 +02:00
Ole André Vadla Ravnås 3c50985950 compat: Build agent with OpenSSL TLS on Apple
Add an apple_tls feature option (auto-detected based on deployment
target) and have compat trigger a host-arch sub-build of frida-agent
with apple_tls=disabled whenever the parent uses gioapple. The injector
cannot pull new libraries into the target, and the Apple TLS module
would drag in Security, Network, and CoreFoundation, so the agent must
keep using gioopenssl.
2026-04-29 22:05:03 +02:00
Ole André Vadla Ravnås e5d3aab083 value: Take ownership of Json.Reader root node
Rename make_json_reader_from_node to make_json_reader_taking_node
and make the root parameter `owned`, so the node we attach as
"frida-json-root" is the very same one passed in.

Previously set_data<Json.Node> would call json_node_copy() to
store its own copy, which is exactly the buggy json-glib operation
the workaround was trying to compensate for: the copy is a thin
shell whose children's parent pointers still reference the
original. By keeping a copy alive instead of the original, the
workaround did nothing — the original could still be freed and
later end_member() walks would touch a freed node.

Update all callers to transfer ownership explicitly. parse_json()
and fetch_node() in package-manager switch from get_root() to
steal_root() so we forward the actual parser-owned node rather
than relying on Vala to silently copy an unowned reference.
2026-04-29 14:56:06 +02:00
Ole André Vadla Ravnås 505087e418 subprojects: Bump outdated 2026-04-29 00:15:41 +02:00
Ole André Vadla Ravnås 059f8725f3 submodules: Bump releng 2026-04-29 00:15:41 +02:00
Ole André Vadla Ravnås 2d18d5cdba subprojects: Prepare for release 2026-04-28 16:54:40 +02:00
Ole André Vadla Ravnås 70e219dab5 base: Fix installed-asset path resolution
When frida-core is statically linked, the module containing
AssetLocation.detect() is the host executable itself. If that
binary lives under bin/ or sbin/, the derived asset path was
anchored at that directory rather than the configured libdir —
e.g. /usr/sbin/frida-1.0/... instead of /usr/lib/frida-1.0/...

Generalize the existing Windows-only bin -> lib redirect so it
also handles sbin, and resolve to the Meson-configured libdir
name (now exposed as Config.FRIDA_LIBDIR).

Also stop including the <arch> subdirectory on Darwin: the
default asset path template doesn't have one there because we
ship fat binaries, so the literal <arch> ended up in the final
path with nothing to substitute it.
2026-04-28 16:35:27 +02:00
Ole André Vadla Ravnås 12d21fb8a9 subprojects: Bump outdated 2026-04-28 12:50:03 +02:00
Ole André Vadla Ravnås 8191442724 submodules: Bump releng 2026-04-28 12:50:02 +02:00
Ole André Vadla Ravnås cee6330e8d subprojects: Prepare for release 2026-04-28 12:23:06 +02:00
Ole André Vadla Ravnås 74b86cb838 compat: Forward parent's apple-min-os
The compat sub-build creates fresh MachineSpecs and lets
env_apple fall back to APPLE_MINIMUM_OS_VERSIONS, which on
macOS is 11.0. With a higher parent setting (e.g. 15.0 to
unlock Apple's TLS backend) frida-core's TLS provider check
silently fails in the sub-build and we end up linking
gioopenssl into the compat slice — pulling libssl/libcrypto
into frida-agent-arm64e.dylib alongside the wrap-built libnice
that already uses CommonCrypto.

Forward host_os_family from compat/meson.build (mirroring how
tools/cgo/detect.py already takes it), parse the parent's
frida-{os}-{arch}.txt machine file for the existing
-target ...-apple-{os}<ver> stamp on Darwin, and feed that
back as apple_min_os to the recursive configure() call so the
sub-build inherits the same deployment floor.
2026-04-26 19:47:40 +02:00
Ole André Vadla Ravnås 07f8d8dd9a deps: Skip OpenSSL on macOS
Now that libnice uses the apple-crypto backend (CommonCrypto +
arc4random_buf) and the fruity sources that called into OpenSSL
(pairing.vala, tls-psk.vala, tunnel.vala) are gated on
host_os != 'macos', nothing on macOS depends on OpenSSL.

Drop openssl_dep / 'openssl' from backend_deps_private and the
fruity backend's --pkg list on macOS so it stops appearing in
frida-core-1.0.pc and getting merged into the devkit.
2026-04-26 17:35:09 +02:00
Ole André Vadla Ravnås 642129c7fc subprojects: Bump outdated 2026-04-26 17:21:09 +02:00
Ole André Vadla Ravnås 44021fc5f2 submodules: Bump releng 2026-04-26 17:19:16 +02:00
Ole André Vadla Ravnås 04952c9f74 fruity: Polish XPC UUID helper
Two small tidy-ups on the make_random_v4_uuid helper added in
the previous commit:

- Drop the redundant `static` qualifier on hex_nibble; at
  namespace scope (rather than inside a class) it's meaningless.
- Use `i != uuid.length` rather than `i < uuid.length` for the
  loop bound, matching the surrounding house style.
2026-04-26 15:34:02 +02:00
Ole André Vadla Ravnås c760728677 fruity: Use GLib.Uuid for XPC random UUID
Drop OpenSSL.Rng.generate from xpc.vala's make_random_v4_uuid in
favour of GLib.Uuid.string_random, parsing the 32 hex chars back
into 16 bytes. Removes the only remaining OpenSSL caller in
xpc.vala, and the v4-and-variant bit fixup goes with it -- the
GLib helper bakes those in already.
2026-04-26 15:28:13 +02:00
Ole André Vadla Ravnås 73031586ab fruity: Skip tls-psk.vala on macOS
TlsPskClientStream is only consumed by TcpTunnelConnection, and
tunnel.vala (along with the rest of the iOS USB tunneling glue)
is already excluded from the macOS build. Move tls-psk.vala into
the same conditional block so its OpenSSL TLS-PSK code stops
getting compiled in -- and pulled into Luma's macOS binary -- on
a host that never reaches that code path.
2026-04-26 15:25:27 +02:00
Ole André Vadla Ravnås fafb45499f package-server-fruity: Use lib/frida-1.0 paths
Catches up with the install-path rename from cd3b35bd. Both
ends move: the input is read from the new bundle layout, and
the output is placed where frida-server resolves the agent
at runtime (via FRIDA_LIBDIR_NAME).
2026-04-26 14:04:52 +02:00
Ole André Vadla Ravnås e600ff51ed compiler: Fix asset paths in c-archive mode
The compiler-backend asset path is only meaningful when the
backend lives as a separate file. In c-archive mode it is
linked into libfrida-core, so FRIDA_COMPILER_BACKEND_NAME is
never defined — narrow the gate to the installed-library and
installed-executable modes that actually consume it.
2026-04-26 13:33:57 +02:00
Ole André Vadla Ravnås cec816b7da subprojects: Bump outdated 2026-04-26 13:17:26 +02:00
Ole André Vadla Ravnås 9d67b97b6e nice: Pick Apple CommonCrypto backend on Darwin
Bump the libnice subproject to 9a3da6e3 (auto-picks
CommonCrypto + arc4random_buf on Darwin) and switch the wrap's
crypto-library default from openssl to auto, matching
releng/deps.toml. On Darwin that gets STUN's RAND/HMAC-SHA1/MD5
from the new Apple primitives instead of OpenSSL; elsewhere it
falls through to gnutls or openssl as before.
2026-04-26 13:12:33 +02:00
Ole André Vadla Ravnås 5cb91d9e25 compiler: Fix backend mode on Apple non-macOS
Use host_os_family to detect Apple platforms instead of
checking Go's GOOS, which is "ios" (not "darwin") for iOS.
The previous check always evaluated false on iOS, causing
configure to pick c-shared — unsupported by Go's toolchain
on any Apple OS other than macOS.
2026-04-26 11:28:23 +02:00
Ole André Vadla Ravnås 9a0880ff5d api: Merge referenced top-level delegates
The .gir merge pulls classes, interfaces, enums and gtype
structs from core and base, but not the top-level <callback>
elements Vala emits for namespace-level delegates. Downstream
consumers like frida-node's codegen then fail to resolve the
delegate type (e.g. WebRequestHeaderFunc).

Scan the merged namespace for Frida./FridaBase. type
references and pull in any source-gir <callback> whose name
is referenced but not yet present. This avoids relying on
beautified C typedef strings and naturally extends to any
future namespace-level delegate.
2026-04-26 11:09:15 +02:00
Ole André Vadla Ravnås 562a74ded7 Revert "api: Propagate top-level delegates"
This reverts commit 4393572b45.

The fix was a no-op: it scanned beautified typedef strings of
the form "(* FridaFoo)" with the regex \(\*(\w+)\), which never
matched because beautify_cprototype rewrites "(*" to "(* " and
the regex requires no space. delegate_c_types stayed empty, so
no callbacks were ever copied into the merged gir. Replaced
in a follow-up commit by a reference-driven merge.
2026-04-26 11:09:15 +02:00
Ole André Vadla Ravnås 3d0f4b0529 subprojects: Bump outdated 2026-04-26 10:28:18 +02:00
Ole André Vadla Ravnås 7c6ad4fdf8 meson: Flatten TLS provider default_options
Splice tls_provider_backend_options into dependency()'s
default_options with `+` instead of nesting it. meson rejects
array[list | str]; default_options must be array[str].
2026-04-26 10:21:04 +02:00
Ole André Vadla Ravnås 751f19e2db subprojects: Bump outdated 2026-04-26 10:16:28 +02:00
Ole André Vadla Ravnås 4059c74936 submodules: Bump releng 2026-04-26 10:16:27 +02:00
Ole André Vadla Ravnås af1b89d02c p2p: Replace OpenSSL cert gen with SecKey
On Darwin, _frida_generate_certificate now uses SecKey for RSA
keygen and signing and hand-rolls the X.509 TBSCertificate DER
instead of going through OpenSSL's X509 + EVP_PKEY + PEM APIs.
The OpenSSL path stays for non-Apple builds.
2026-04-26 09:22:56 +02:00
Ole André Vadla Ravnås f77ae76242 apple: Use gioapple TLS backend on Darwin
Select the Apple-native gioapple backend (Security.framework +
Network.framework) instead of gioopenssl on Darwin.

This removes frida-core's OpenSSL TLS dependency on Apple
platforms, eliminating the non-exempt-crypto footprint App
Store review calls out.
2026-04-26 09:22:56 +02:00
Ole André Vadla Ravnås 0fec62a283 host-session: Add simmy and local provider icons
Give the two remaining providers without artwork a bundled
glyph:

  - local: desktop monitor with a white shell prompt on a
    dark screen — now the default for LocalHostSessionProvider
    on Linux, FreeBSD, and QNX. Windows and Darwin keep
    overriding with their system-extracted icons.
  - simmy: light silver phone with macOS traffic-light dots
    on the screen — reads as a simulated iOS device on macOS.
2026-04-24 01:02:55 +02:00
Ole André Vadla Ravnås bdcae8b809 host-session: Modernize provider icons
Replace the hardcoded base64 RGBA blobs with clean 96x96 PNGs
bundled through the resource compiler (src/icons.resources).
Each provider now calls a shared make_provider_icon helper to
build the icon Variant from the embedded blob.

The new artwork (fruity-network phone, droidy head, socket
globe, barebone chip, portal hub) follows SF Symbols and
libadwaita conventions so it reads well in both SwiftUI and
GTK/Adwaita consumers.

Add tools/generate-provider-icons.py to regenerate the PNGs
from SVG sources.
2026-04-24 00:43:44 +02:00
cl45h ee784bdd18 compat: Fix build with empty prebuild set (#1230)
When FRIDA_ALLOWED_PREBUILDS is set to an empty string (e.g. when
building with --without-prebuilds on platforms without prebuilt
binaries), `"".split(",")` produces `['']` instead of `[]`. This
results in a set containing an empty string `{''}` rather than an
empty set, which is then passed as `allowed` to the build system
causing it to attempt downloading nonexistent prebuilds.

Use `filter(None, ...)` to strip empty strings from the split result,
so an empty env var correctly produces an empty set.

This fixes building from source on platforms like FreeBSD where no
prebuilt toolchain/SDK binaries are available.
2026-04-20 17:19:17 +02:00
Ole André Vadla Ravnås 5adb546db6 subprojects: Bump outdated 2026-04-20 17:16:30 +02:00
Ole André Vadla Ravnås 4393572b45 api: Propagate top-level delegates into merged gir
The .gir merge pulls classes, interfaces, enums and gtype
structs from core and base, but not the top-level <callback>
elements Vala emits for namespace-level delegates. Downstream
consumers like frida-node's codegen then fail to resolve the
delegate type (e.g. WebRequestHeaderFunc). Copy matching
callbacks over using the delegate c-types we already captured
per object type.
2026-04-20 17:00:32 +02:00
Ole André Vadla Ravnås 440a03f0d4 ci: Bypass success() chain for xcframework publish
The needs.xcframework.result alone isn't enough to escape GitHub
Actions' chained success() check; add !cancelled() so the condition
is evaluated even when needed jobs transitively report failure.
2026-04-20 16:34:21 +02:00
Ole André Vadla Ravnås cdcb36b0f3 subprojects: Bump outdated 2026-04-20 16:31:40 +02:00
Ole André Vadla Ravnås 862d4781e0 submodules: Bump releng 2026-04-20 16:31:39 +02:00
Ole André Vadla Ravnås a7be127b63 ci: Gate publish-xcframework on xcframework only
GitHub Actions' default success() check walks the whole needs
graph, so a Linux fluke failing native ended up skipping
publish-xcframework even though xcframework itself succeeded.
Reference needs.xcframework.result explicitly so only that
direct dependency decides whether to publish.
2026-04-20 15:16:04 +02:00
Ole André Vadla Ravnås be055ae47b ci: Add setuptools for Windows distutils shim
glib's gdbus-codegen still imports distutils, which Python 3.12
removed from the stdlib. setuptools ships a distutils-precedence
.pth that puts its own distutils back on sys.path, so installing
it is enough to keep the codegen running.
2026-04-20 15:15:51 +02:00
Ole André Vadla Ravnås 7bbf62c092 subprojects: Bump outdated 2026-04-20 14:52:15 +02:00
Ole André Vadla Ravnås 40bbe0b925 submodules: Bump releng 2026-04-20 14:52:15 +02:00
Ole André Vadla Ravnås 7c61cedeae ci: Drop now-unneeded wrap overrides
With releng isolating pkg-config per machine, the cross-mingw
and emulator jobs no longer leak host packages into their host
machine configuration, so the wraps for glib, pcre2, and openssl
kick in naturally. Keep --force-fallback-for=openssl on native
Windows since msys2's OpenSSL lacks Frida's QUIC patches.
2026-04-20 14:51:51 +02:00
Ole André Vadla Ravnås ec1981aeeb subprojects: Bump outdated 2026-04-20 14:40:58 +02:00
Ole André Vadla Ravnås 8488018e90 submodules: Bump releng 2026-04-20 14:40:58 +02:00
Ole André Vadla Ravnås b386666646 ci: Run xcframework despite unrelated failures
Use !cancelled() on the xcframework job so it still runs when
needed jobs (native, mobile) report failure for unrelated matrix
entries such as a flaky Linux build. The download-artifact steps
will fail if the macOS/iOS devkits are actually missing, so the
publish step still gates on xcframework's real success.
2026-04-20 14:32:26 +02:00
Ole André Vadla Ravnås c9028d73c2 ci: Force glib wrap on emulator and x86-mingw
Both the x-tools-linux-be-target container and the i686 mingw
cross-toolchain expose a target glib through pkg-config, which
the build now prefers over the wrap. That skips wrap-provided
deps such as zlib (needed by libunwind) and pcre2 (needed by
glib's gregex). Force the wrap so Frida's patched glib is used
along with its bundled subprojects.
2026-04-20 14:32:18 +02:00
Ole André Vadla Ravnås 71244206db ci: Force openssl wrap for fruity backend
System OpenSSL on the affected runners lacks the experimental
SSL_QUIC_METHOD API needed by ngtcp2's quictls helper, so the
ngtcp2 subproject builds neither libngtcp2_crypto_quictls nor
libngtcp2_crypto_ossl. Force the openssl wrap, which carries
Frida's QUIC patches, matching the windows-shared workflow.
2026-04-20 14:32:10 +02:00
Ole André Vadla Ravnås 96ebd59ffa core: Use Json.Reader helper to pin root node
Reroute remaining hand-constructed Json.Reader call sites through
make_json_reader_from_node, which attaches the original root node
to the reader as object data.

Json.Reader.set_root() copies the supplied node via json_node_copy().
For object/array nodes that copy is a shallow shell that shares the
JsonObject/JsonArray via refcount, but the members inside the shared
container keep their parent pointers set to the *original* root, not
the shell. Dropping the original leaves json_reader_end_member()'s
walk via json_node_get_parent() touching a freed node, which on
builds with glib checks enabled (e.g. Fedora) trips JSON_NODE_IS_VALID
and aborts.

See GNOME bugzilla #774684.
2026-04-19 16:40:25 +02:00
Ole André Vadla Ravnås 86be996ede compiler: Skip version script on Windows
Windows PE linkers don't honor --version-script. MSYS2 CLANGARM64's
lld rejects the unknown argument outright; the MinGW x86_64 gcc+bfd
toolchain used to silently ignore it. In c-shared mode on Windows,
Go's default export filtering already limits visibility to symbols
tagged with //export, so dropping the version script changes no
observable behavior.
2026-04-19 11:12:10 +02:00
Ole André Vadla Ravnås 2abe10aeec meson: Fall back to system lzfse without .pc file
Detect lzfse.h + -llzfse directly when pkg-config has no entry,
matching how frida-gum handles libdwarf. Lets Fedora's lzfse-devel
satisfy the dependency.
2026-04-18 23:58:30 +02:00
Ole André Vadla Ravnås 3d49a01980 compat: Skip helper/agent without local backend
Without the local backend, frida-helper and frida-agent are not
produced by the main or nested meson builds, so the compat
arch-support target failed trying to compile them for the other
architecture. Treat helper and agent as components gated on
have_local_backend, matching how gadget and server are already
wired, and drop empty output groups before state serialization.
2026-04-18 14:45:54 +02:00
Ole André Vadla Ravnås 7721867fd4 linux: Drop libbpf dep without local backend
The BPF wrappers in lib/base/linux.vala are only consumed by
helper-backend sources that ship as part of the local backend,
and nothing else references libbpf. Gate the class definitions
on HAVE_LOCAL_BACKEND and the libbpf lookup/link on
have_local_backend so socket-only or compiler-only builds on
Linux no longer require libbpf at all.
2026-04-18 14:45:08 +02:00
Ole André Vadla Ravnås b2023ade06 meson: Simplify libbpf dependency lookup
Drop the non-Frida-GLib branch that explicitly instantiated the
libbpf subproject before resolving the dependency. Meson already
falls back to the wrap automatically when the dependency is not
found, so the extra subproject() call was redundant.
2026-04-18 14:29:21 +02:00
Ole André Vadla Ravnås c5e3c37849 compiler: Fix shared-library build on Windows
backend-glue.c unconditionally referenced the cgo init symbol
whenever the compiler backend was enabled, but that symbol only
exists when the Go backend is statically linked (c-archive mode).
Gate it on a new HAVE_COMPILER_BACKEND_LINKED define to mirror
the Vala-side COMPILER_BACKEND_LINKED guard.
2026-04-18 14:18:35 +02:00
Ole André Vadla Ravnås b62bc0e17c compiler: Pass Go config via file, not cmdline
The base64-encoded config embeds the full process environment,
which can easily blow past command-line length limits on some
platforms. Write it to <build_root>/tools/cgo/go-config.json
instead and pass the path to build-backend.py.
2026-04-18 13:00:44 +02:00
Ole André Vadla Ravnås a787ecc4bf socket: Add WebRequestHandler support
Add a WebRequestHandler interface plus WebRequest / WebResponse
data classes. EndpointParameters gains a request_handler property;
ConnectionHandler consults it (via a new handle_http_request flow)
before falling through to asset_root, and returns 404 if neither
is set.

Also extend the API generator to list the new types and scan both
core_header and base_header for delegate typedefs, so types living
in lib/base (including WebRequestHeaderFunc) end up in the public
header.
2026-04-18 01:39:00 +02:00
Ole André Vadla Ravnås 86f76f8630 windows: Install dbghelp / symsrv alongside agent
frida-gum's ext/ subprojects install these helpers, but
releng/meson_make runs `meson install --skip-subprojects` when
default_library=shared to keep other subproject artifacts (e.g.
capstone) out of the prefix. That also drops the dbghelp /
symsrv rules on the floor. Re-install them from frida-core so
installed-mode distributions ship them next to frida-agent.dll,
where the host session's elevated-injection asset bundling
expects them.
2026-04-17 23:51:10 +02:00
Ole André Vadla Ravnås 5e1b97236d portal-service: Allow registering extra endpoints
Add add_cluster_endpoint() and add_control_endpoint() that queue
extra EndpointParameters. start() spins up a WebService per
registered endpoint; stop() tears them all down. Both throw
INVALID_OPERATION once the service has started — dynamic
add-after-start isn't supported.

Motivated by wanting a second control listener (e.g. a plain-HTTP
one behind a Cloudflare Tunnel) alongside the main TLS-pinned one.
2026-04-17 23:50:53 +02:00
Ole André Vadla Ravnås ec8df6a896 windows: Fix agent loading in installed-mode
The host session only populated the AgentDescriptor under
HAVE_EMBEDDED_ASSETS, so any attach with assets=installed
crashed inside Winjector.inject_library_resource() with a null
agent. Mirror the Linux pattern: take the #else branch through
inject_library_file_with_template() using Frida.agent_path (and
the per-arch dbghelp / symsrv neighbors as dependencies for
the elevated-injection asset bundle).

Two supporting fixes:

* AssetLocation.detect() now translates <prefix>\bin to
  <prefix>\lib on Windows, where the frida-core DLL ships in
  bin/ but agent assets live under lib/frida-1.0/.
* Winjector.inject_library_file_with_template() is now public,
  as on Linux, so host sessions can target an installed layout
  without building an AgentDescriptor.
2026-04-17 23:40:49 +02:00
Ole André Vadla Ravnås b92a4562b8 build: Force sqlite subproject to static
libsoup's internal `dependency('sqlite3')` would otherwise trigger the
wrap fallback with the parent's `default_library=shared`.
2026-04-17 23:40:49 +02:00
joe 25d7c2a648 fruity-syscall-trace: Tolerate sig fails (#1226)
In the add-targets handler, the loop that populates target_pids
previously let any failing query_symbolicator_signature call
propagate out and abort the entire request, meaning one unhappy
PID killed the whole trace.

Catch Frida.Error per PID and fall back to a bare Fruity.CsSignature
carrying just the pid. The trace keeps going for the remaining
targets, and the fallback signature is benign for downstream code
that only reads the pid field until a real signature becomes
available.
2026-04-17 20:21:15 +02:00
Megladon 6fef95a6dd syscall-trace: Support include-syscall (#1228) 2026-04-17 20:14:39 +02:00
Francesco Tamagni 1f0c537720 fruity: Detach lldb only if no attached gadgets
before: when multiple gadget entries relied on the same lldb session,
the first to detach also detached lldb
after: only the last detached gadget entry actually detaches lldb
2026-04-17 20:10:52 +02:00
Ole André Vadla Ravnås c41fadd01e core: Expose _-prefixed asset path init helpers
The init/deinit_asset_paths functions are implemented in Vala
and called from frida-glue.c. Declaring them internal made
Vala warn they were never used (it doesn't see the C callers).

Promote to public so Vala stops complaining, and add a leading
underscore to signal they aren't part of the stable public API.
2026-04-14 23:51:21 +02:00
Ole André Vadla Ravnås b0ded01fd3 fruity: Move sleep to device-monitor-portable
The sleep helper only has callers in device-monitor-portable.vala,
which isn't built on macOS — leaving the definition in
device-monitor.vala looked unused there and tripped Vala.
2026-04-14 23:51:21 +02:00
Ole André Vadla Ravnås 98546506e2 fruity: Move OpenSSL keypair helpers to pairing
The make_keypair / get_raw_public_key / get_raw_private_key
wrappers lived in xpc.vala but are only referenced from
pairing.vala and tunnel.vala, which aren't built on macOS.
That orphaned them there and tripped Vala "never used"
warnings on macOS builds.
2026-04-14 23:47:09 +02:00
Ole André Vadla Ravnås a3b336ec15 compiler: Keep backend Module resident
The Module holding the dlopen'd backend was a local in init(),
so it was released the moment init() returned — at which point
g_module_close() is free to unload the image out from under the
build/watch/dispose function pointers we just resolved.

Mark it resident so the module's lifetime matches the symbols.
2026-04-14 23:26:10 +02:00
Ole André Vadla Ravnås 36b092ea66 core: Drop soversion from shared library
The previous soversion tracked the outer Frida toolkit version,
bumping the SONAME on every Frida release regardless of whether
frida-core's ABI actually changed. Drop it for now; the "1.0" in
the library name should probably be replaced by the Frida major
version eventually, at which point any soversion scheme can be
revisited.
2026-04-14 18:43:41 +02:00
Ole André Vadla Ravnås a2a1cce8e9 compiler: Fix macOS c-shared symbol export
The exported_symbols_list patterns use Mach-O symbol names, which
add an underscore prefix to C names. Since CGO //export directives
already start with an underscore, the real Mach-O symbols are
__frida_compiler_backend_*, so the single-underscore pattern matched
nothing and the plugin exported zero symbols.
2026-04-14 18:29:27 +02:00
Ole André Vadla Ravnås b63ff37ad7 build: Always define FRIDA_LIBDIR_NAME
AssetLocation references it unconditionally, so it must
be present in config.h even for embedded-asset builds.
2026-04-13 16:02:12 +02:00
Ole André Vadla Ravnås 9fe9f529b5 subprojects: Bump outdated 2026-04-13 14:36:35 +02:00
Ole André Vadla Ravnås 2bd2816d9a ci: Add Windows shared library build
Test both x86_64 and arm64 using vcpkg for the system
dependencies (GLib, json-glib, libsoup).
2026-04-13 14:28:22 +02:00
Ole André Vadla Ravnås 5d221ea316 api: Bundle GModule-2.0.gir
Gio-2.0.gir depends on GModule, so g-ir-compiler
needs it when compiling the Frida typelib.
2026-04-13 14:28:16 +02:00
Ole André Vadla Ravnås 2c8bb33afb api: Pass girdir to g-ir-compiler
Supply --includedir so g-ir-compiler can find base GIR
files on platforms where they are not in a default search
path.
2026-04-13 14:28:11 +02:00
Ole André Vadla Ravnås 8b5b56990c build: Support shared library on Windows
Add Windows-specific asset directory layout with per-arch
subdirs. Set HAVE_EMBEDDED_ASSETS and HAVE_COMPILER_BACKEND
in config.h for the C code. Link shlwapi on Windows.
2026-04-13 13:52:35 +02:00
Ole André Vadla Ravnås 1875ededf2 windows: Gate embedded assets
Guard resource store and agent blob references with
HAVE_EMBEDDED_ASSETS so non-embedded builds compile.
2026-04-13 13:52:31 +02:00
Ole André Vadla Ravnås 35063b1dea build: Default to static for more fallbacks
Specify default_library=static for json-glib, libsoup,
and libnghttp2, consistent with how other fallback
subprojects are already configured.
2026-04-13 13:52:04 +02:00
Ole André Vadla Ravnås 66c4c0e288 build: Add nghttp2 wrap
Needed for builds without an SDK, where libnghttp2 would
otherwise not be available as a system dependency.
2026-04-13 13:52:04 +02:00
Ole André Vadla Ravnås 7a2127cfd3 build: Add relocatable asset resolution
For non-embedded asset builds, dynamically compute
asset paths at init time using the module's own
location instead of hardcoding absolute paths at
compile time.

Add AssetLocation in lib/base, which uses
Gum.Process.enumerate_modules to find the directory
containing our shared library, then derives asset
paths relative to it.
2026-04-13 13:51:59 +02:00
Ole André Vadla Ravnås d72314b3fa subprojects: Bump outdated 2026-04-10 23:55:08 +02:00
Ole André Vadla Ravnås 58d4b7fa17 submodules: Bump releng 2026-04-10 23:55:07 +02:00
Ole André Vadla Ravnås cfcc8ae9e4 meson: Fix has_function with internal libsoup
Work around a Meson bug where cc.has_function() rejects
internal (subproject) dependencies. Assume the feature is
present when libsoup comes from a subproject, matching
the existing GLib pattern.
2026-04-10 23:54:08 +02:00
Ole André Vadla Ravnås 21cdaee891 meson: Finish adding default_library=static
Cover all remaining dependency() calls that have a corresponding .wrap.
2026-04-10 23:41:56 +02:00
Ole André Vadla Ravnås 2251f7893e fruity: Fix non-macOS build after code split
Add OpenSSL using-directives to pairing.vala, and fully
qualify the few OpenSSL type references in tunnel.vala and
xpc.vala instead of pulling in using-directives for just a
handful of call sites. Also drop the now-unnecessary ones
from xpc.vala.
2026-04-10 20:07:47 +02:00
Ole André Vadla Ravnås 7dc83ad22a fruity: Split out portable tunnel and pairing code
Move UsbmuxTunnel and the PortableCoreDevice* classes from
device-monitor.vala into device-monitor-portable.vala.

Move TunnelConnection, TunnelParameters, TcpTunnelConnection,
QuicTunnelConnection, and TunnelKey from xpc.vala into
tunnel.vala.

Move PairingService, PairingTransport, XpcPairingTransport,
PlainPairingTransport, PairingStore, PairingIdentity,
PairingPeer, PairingServiceMetadata, DeviceOptions,
DeviceInfo, and supporting types from xpc.vala into
pairing.vala.

Also move network-stack.vala into the same conditional
group now that all its users are in excluded files.

These are all excluded at the meson.build level on macOS,
replacing the #if !MACOS blocks with file-level exclusion.
2026-04-10 14:28:38 +02:00
Ole André Vadla Ravnås 15a9fef3a5 subprojects: Bump outdated 2026-04-10 13:21:01 +02:00
Ole André Vadla Ravnås f6d274f49e macos: Fix shared library build
Drop libngtcp2, libusb-1.0, and lwip deps on macOS, where
Apple's CoreDevice tunnel is used instead. Guard the
portable USB/NCM/tunnel code with #if !MACOS in the Vala
sources, and exclude usb.vala/ncm.vala at the meson.build
level.

Fix gee-0.8 vapi discovery for builds using system libgee
by querying its pkg-config prefix to locate the vapidir.

Fix compat sub-builds to use installed assets and filter
conflicting options, avoiding the embed-agent chicken-and-
egg problem on macOS.

Also fix OpenSSL vapi headers, Capstone compat with system
packages, compiler backend linker flags on macOS, and add
missing CoreFoundation framework linkage.
2026-04-10 13:13:22 +02:00
Ole André Vadla Ravnås 7300274bcf subprojects: Bump outdated 2026-04-09 13:41:29 +02:00
Ole André Vadla Ravnås 3b04d85adf submodules: Bump releng 2026-04-09 13:41:29 +02:00
Ole André Vadla Ravnås ba97684104 subprojects: Bump outdated 2026-04-09 12:14:30 +02:00
Ole André Vadla Ravnås e90015f917 submodules: Bump releng 2026-04-09 12:14:29 +02:00
Ole André Vadla Ravnås 57b378387b subprojects: Bump outdated 2026-04-09 11:50:28 +02:00
Ole André Vadla Ravnås 627152da6a submodules: Bump releng 2026-04-09 11:50:27 +02:00
Ole André Vadla Ravnås f7511ea32d fruity: Drive PSK TLS via OpenSSL directly
The TCP tunnel handshake to a paired Apple device speaks TLS-PSK,
which we previously set up by getting at the underlying SSL handle
of a GTlsClientConnection through glib-networking's OpenSSL backend
(g_tls_connection_openssl_get_ssl). This only works on systems where
glib-networking ships an OpenSSL backend, which excludes most distros
that build it against GnuTLS — including Fedora — leaving the Fruity
backend with "GIO OpenSSL TLS backend not available" the moment a
shared build is run there.

Replace the glib-networking detour with a small TlsPskClientStream
that drives OpenSSL on the raw IOStream returned by the netstack:

  - The class is an IOStream wrapping the raw stream's pollable
    input/output. The handshake runs in init_async, alternating
    SSL_do_handshake with non-blocking BIO drains and reads from the
    underlying pollable stream.
  - Steady-state read/write go through SSL_read/SSL_write against
    a pair of memory BIOs. The non-blocking input path pulls fresh
    ciphertext via base.read_nonblocking when SSL signals WANT_READ;
    the non-blocking output path produces ciphertext into wbio and
    drains it via base.write_nonblocking, leaving any leftover in
    wbio so a subsequent call (woken by the chained pollable source)
    can finish flushing it. Back-pressure is structural — there are
    no buffers we own, no pump tasks, no watermarks.
  - The pollable sources chain directly to the base stream's source
    via PollableSource.full, so the consumer's poll loop wakes
    correctly and SSL_pending() is folded into is_readable() to
    avoid stalling on already-decrypted plaintext.

Drop the HAVE_GIOOPENSSL helpers and the runtime dlsym fallback in
xpc.vala — TcpTunnelConnection.open now wraps the raw stream with
TlsPskClientStream and TLS no longer touches GIO at all. Other parts
of frida-core that use the gioopenssl backend (portal, gadget, agent)
are unaffected since they still go through tls_provider_dep.

Extend vapi/openssl.vapi with the bits the new code needs:
SSL_set_bio, SSL_do_handshake, SSL_read/write, SSL_get_error,
SSL_pending, the SSL_ERROR_* enum, and BIO_new/free/read/write/
ctrl_pending/get_mem_data wrappers using void* so we can hand the
BIO ownership to the SSL via SSL_set_bio. Also add the missing
cheader_filename to the OpenSSL.Error namespace.
2026-04-09 11:04:43 +02:00
Ole André Vadla Ravnås 4a7ebebff5 base: Add make_json_reader() lifetime helper
Json.Reader.set_root() copies the supplied node via json_node_copy().
For object/array nodes that copy is a thin shell that shares the
original JsonObject/JsonArray via refcount, but the members inside
the shared container have their parent pointers set to the *original*
root, not the shell. If the original is then dropped — as happens any
time we call `new Json.Reader (Json.from_string (...))` — the parent
walk inside json_reader_end_member() touches a freed node. On builds
with GLib checks enabled (e.g. Fedora) this trips JSON_NODE_IS_VALID
and aborts; on our toolchain builds it silently corrupts.

This is GNOME bugzilla #774684 from 2016. The fix lives in our
JSON-GLib fork but was never upstreamed.

Add a tiny helper that constructs the Json.Reader and attaches the
parsed root to it as object data, keeping the original alive for the
reader's lifetime so the parent pointers inside the shared container
remain valid. Migrate every existing call site that piped from_string
straight into a new Json.Reader.
2026-04-09 11:04:20 +02:00
Ole André Vadla Ravnås 30ef70353e fruity: Fix plist hash/eq for Bytes values
hash_value() and compare_values_eq() in the binary plist serializer
lumped Bytes in with PlistDict and PlistArray and routed all three
through g_value_get_object(). Bytes is a G_TYPE_BOXED, not a GObject,
so the call asserts under GLib builds with checks enabled (e.g. on
Fedora). Our toolchain builds disable checks, so the misuse went
unnoticed and returned the boxed pointer reinterpreted as a GObject*.

Use get_boxed() for Bytes and hash/compare by content, which is also
what the deduplication map presumably wanted in the first place: two
equal-content Bytes will now actually share an offset table entry
instead of staying distinct because their pointers differ.
2026-04-09 11:04:10 +02:00
Ole André Vadla Ravnås 89686d07bb build: Fix installed pkg-config for shared builds
Two issues with the installed frida-core-1.0.pc:

- vapidir was being overwritten by a build-tree path further down in
  meson.build, leaking a local source directory into the installed
  variables. Rename the local one to local_vapidir.

- Passing the core library object to pkg.generate caused Meson to
  auto-derive Requires.private from its declared dependencies, which
  pulled in a bunch of internal subprojects (frida-gum-1.0, libbpf,
  usrsctp, etc.) that aren't installed. In shared mode, pass the
  library as raw -L/-l strings instead so the shared library's
  DT_NEEDED stays the source of truth for runtime dependencies.
2026-04-08 16:43:57 +02:00
Ole André Vadla Ravnås 943cc1e261 compat: Rename upstream-glib payloads on install
When building with upstream GLib, the native-arch agent and gadget come
out of the compat subsystem alongside the cross-arch ones. They share
the same custom_target output list, so the native ones get an arch
suffix in the build directory to avoid colliding with the cross ones.

Install them under the canonical frida-agent.so / frida-gadget.so names
via a small install script that copies them into the correct per-bits
subdirectory of libdir/frida-1.0. Also strip frida-gum:gumjs and
frida-gum:inspector from the forwarded options so the nested compat
build picks them based on its own default_library=static rather than
inheriting the parent's shared-mode choices.
2026-04-08 16:43:57 +02:00
Ole André Vadla Ravnås cd3b35bdde build: Polish shared library output
A grab-bag of related improvements aimed at producing a clean shared
libfrida-core-1.0.so suitable for distro packaging:

- Use frida-1.0/ for the per-API-version asset directory (matching
  libfrida-core-1.0.so, frida-core-1.0.pc, etc.) so future API
  versions can install in parallel.
- Default the assets option to installed for shared builds, embedded
  for static.
- Install the typelib via gobject-introspection's typelibdir variable,
  so it lands next to the system's other typelibs.
- Force frida-gum, quickjs, and libtcc to static linking, and skip
  in-tree agent/gadget/payload + their pkg-config files when building
  shared (the agent and gadget come from compat in that case).
- Disable GumJS in shared builds and rely on the new inspector-only
  option in frida-gum to keep Script.enable_debugger() working.
- Build the compiler backend as an installable plugin under
  frida-1.0/plugins/ when assets=installed, with new
  COMPILER_BACKEND_INSTALLED_LIBRARY and
  COMPILER_BACKEND_INSTALLED_EXECUTABLE modes. Rename the existing
  modes to LINKED, EMBEDDED_LIBRARY, and EMBEDDED_EXECUTABLE for
  symmetry. The plugin is loaded by path at runtime, so distros can
  ship it as an optional separate package and frida-core gracefully
  reports a context-rich error when it's missing.
2026-04-08 16:43:57 +02:00
Ole André Vadla Ravnås 751f43145b subprojects: Bump outdated 2026-04-08 16:22:24 +02:00
Ole André Vadla Ravnås 7f3553d9fe submodules: Bump releng 2026-04-08 16:22:23 +02:00
Ole André Vadla Ravnås 420279280d subprojects: Bump outdated 2026-04-08 01:15:43 +02:00
Ole André Vadla Ravnås 96d4202ff6 submodules: Bump releng 2026-04-08 01:15:42 +02:00
Ole André Vadla Ravnås 8a72775477 compat: Strip subproject default_library opts too
When forcing default_library=static for upstream-glib builds, also
drop per-subproject overrides forwarded from the parent (e.g.
-Dfrida-gum:default_library=shared) so they don't escape the static
linking requirement.
2026-04-08 01:00:03 +02:00
Ole André Vadla Ravnås 287d211bb8 init: Only assign runtime inside once-init guard
Assigning runtime unconditionally meant a later frida_init() call
would clobber the runtime chosen by an earlier
frida_init_with_runtime(). E.g. an app can now select a runtime
before a language binding calls frida_init().
2026-04-07 12:26:03 +02:00
Ole André Vadla Ravnås a6bb4c536f subprojects: Bump outdated 2026-04-06 15:36:53 +02:00
Ole André Vadla Ravnås e7562fcf69 submodules: Bump releng 2026-04-06 15:36:52 +02:00
Ole André Vadla Ravnås 873f5cae3d docs: Add distro packaging instructions
Document the shared library build workflow and list
prerequisites for Fedora and Ubuntu.
2026-04-06 15:31:11 +02:00
Ole André Vadla Ravnås d841b5ea8d claude-md: Add code style section 2026-04-06 15:31:11 +02:00
Ole André Vadla Ravnås 3d37dfe966 Support upstream GLib and system dependencies
Guard Frida GLib fork APIs (glib_init, gio_init,
g_thread_set_garbage_handler, etc.) with HAVE_FRIDA_GLIB.
Guard libsoup back-pressure APIs with a feature check.

Support ngtcp2_crypto_ossl as alternative to the quictls
variant, with the correct API (configure_client_session
on SSL instead of configure_client_context on SSLContext).

Dynamically resolve gioopenssl symbols when not
statically linked, throwing NOT_SUPPORTED if the GIO
OpenSSL backend is unavailable.
2026-04-06 15:31:11 +02:00
Ole André Vadla Ravnås c178aee655 compat: Build agent via SDK with upstream GLib
When glib_flavor is "upstream", add a native-arch output
group for agent and gadget, force default_library=static,
and allow all prebuilds so the SDK is downloaded even if
the top-level build excluded it.
2026-04-06 15:31:11 +02:00
Ole André Vadla Ravnås 46d1b6be32 api: Generate symbol maps and refactor args
Add symbol-maps output mode that generates a version
script and symbols file exporting frida_* and _frida_*
while hiding all other symbols. Refactor argument
parsing so that output_dir is the first positional arg
and the remaining args are mode-specific.
2026-04-06 15:31:11 +02:00
Ole André Vadla Ravnås 6927214f6e build: Add distro packaging support
Detect shared vs static GLib, skip gioopenssl when
using a shared GLib (system GLib already has GIO TLS
support via glib-networking), produce a versioned
shared library with soversion when building shared,
generate typelib via g-ir-compiler when available, and
adjust pkg-config and dependency declarations for the
shared case.
2026-04-06 15:31:11 +02:00
Ole André Vadla Ravnås 94a9aec68d api: Include class records in GIR
The generated GIR was missing record elements for GType class structs
(e.g. DeviceManagerClass), which caused g-ir-compiler to fail with
"type reference not found" errors when compiling the typelib.
2026-04-06 15:31:11 +02:00
Ole André Vadla Ravnås 4d82b7e98b Add CLAUDE.md 2026-04-05 23:08:27 +02:00
Ole André Vadla Ravnås a62376a3d6 subprojects: Prepare for release 2026-03-27 11:36:34 +01:00
Ole André Vadla Ravnås f5803e76ca subprojects: Bump outdated 2026-03-27 11:34:53 +01:00
Ole André Vadla Ravnås 3b052905d7 package-manager: Fix semver pre-release overflow
Numeric pre-release identifiers like "202508252028" would
fail validation because parse_uint() rejects values beyond
UINT32_MAX. The same overflow affected version comparison,
which also used uint.

Fix validation to only check the semver-mandated rule that
numeric identifiers must not have leading zeros, and fix
comparison to use length-prefixed string comparison, which
handles arbitrarily large numeric identifiers correctly.
2026-03-27 11:32:50 +01:00
Ole André Vadla Ravnås c3864a15a1 subprojects: Prepare for release 2026-03-26 20:43:41 +01:00
Ole André Vadla Ravnås b49aac0765 subprojects: Bump outdated 2026-03-26 20:42:39 +01:00
Ole André Vadla Ravnås 1f77154b83 fruity: Add control-endpoint backend option
Default is `tcp:27042`. Only TCP endpoints are supported.
2026-03-26 20:26:02 +01:00
Ole André Vadla Ravnås d2f2c400dc droidy: Add control-endpoint backend option
Default is `tcp:27042`, but may be set to any endpoint supported by ADB,
such as `localabstract:/my-frida-server`.
2026-03-26 20:26:02 +01:00
Ole André Vadla Ravnås 3df058764b device: Add override_option()
Allow callers to override backend-specific options when creating
a host session.

Overrides take effect when establishing a host session. If a host
session already exists, updated values are applied on the next
connection.

Inspired-by: @giantpune
2026-03-26 20:25:33 +01:00
Ole André Vadla Ravnås f962931617 host-session-options: Add copy() 2026-03-26 20:06:47 +01:00
Ole André Vadla Ravnås be23998210 android: Skip 32-bit helper on 64-bit only systems
Do not waste time trying to spawn it.
2026-03-26 15:24:53 +01:00
Ole André Vadla Ravnås 0bee9cba2b linux: Support injection into group-stopped PIDs 2026-03-26 15:24:53 +01:00
NSEchoandOle André Vadla Ravnås 465698a907 linux: Add eBPF-based spawn gater implementation
Co-authored-by: Ole André Vadla Ravnås <[email protected]>
2026-03-26 15:24:53 +01:00
Ole André Vadla Ravnås 4e5a60a576 subprojects: Prepare for release 2026-03-24 15:40:37 +01:00
Ole André Vadla Ravnås aaa66bbafd subprojects: Bump outdated 2026-03-24 15:39:15 +01:00
Soumik Sarkhel 1dc4435f96 android: Modify RTLD path if dlopen(libart) fails 2026-03-24 15:38:14 +01:00
Ole André Vadla Ravnås ac9f8cac5f fruity: Fix TcpTunnelConnection stalls
The remotepairingdeviced service may defer CDTunnel TLS records
larger than 8192 bytes until a smaller record arrives, which can
stall sustained tunnel output.

When a large write drains the pending output queue, send a minimal
dummy IPv6 datagram to trigger delivery of deferred data and
improve reliability.

Kudos to @tux-mind for identifying this behavior and helping track
it down.

Fixes #1223.
2026-03-24 14:54:04 +01:00
Francesco Tamagni 0ed112fac1 fruity: Leak injector pages on deallocate fail
On iOS 26.4 `lldb.deallocate()` fails all the time for sandbox issues,
probably it's an iOS bug or a new "security" feature from Apple. This is
a sub-optimal workaround that will change in the future.
2026-03-23 14:08:08 +01:00
Francesco Tamagni 266032d035 fruity: Search restartWithDyldInCache by substring 2026-03-23 14:08:08 +01:00
Ole André Vadla Ravnås 8995f83898 fruity: Handle tunnel lost super-early
E.g. because remotepairingdeviced crashes.
2026-03-22 15:24:16 +01:00
Ole André Vadla Ravnås 680159ef78 fruity: Fix TreeSet comparator dropping transports
Gee.TreeSet treats elements as equal when the comparator returns 0.
Our comparator returned 0 for transports with the same score, causing
later transports to be dropped from the set.

This happens on macOS when a CoreDevice transport appears after usbmux
transports with the same score. Because the comparator reported
equality, the CoreDevice transport was not inserted.

As a result Frida would prefer usbmuxd with lwIP instead of the already
available CoreDevice tunnel.

Fix this by adding a deterministic tie-breaker based on pointer identity
so the comparator only returns 0 for the same instance.

Fixes #1220.
2026-03-16 19:34:40 +01:00
Ole André Vadla Ravnås 71e50bfb9e subprojects: Prepare for release 2026-03-14 11:52:00 +01:00
Ole André Vadla Ravnås 0416b95375 subprojects: Bump outdated 2026-03-14 11:51:19 +01:00
Ole André Vadla Ravnås 6c2f1e6ae3 android: Detect setArgV0() in anonymous memory too
Fixes #1219.
2026-03-14 11:50:01 +01:00
Ole André Vadla Ravnås 04aef7f0c4 subprojects: Prepare for release 2026-03-13 13:22:03 +01:00
Ole André Vadla Ravnås eefce5d860 Revert "linux: Rebuild native helpers for arm and arm64"
This reverts commit 5d015c5e1c.
2026-03-13 13:20:53 +01:00
Ole André Vadla Ravnås beb2c221ec ci: Temporarily skip helpers and labrats checks
Will revisit them post-release.
2026-03-13 13:20:53 +01:00
Ole André Vadla Ravnås 5444842b22 subprojects: Bump outdated 2026-03-13 13:12:01 +01:00
Ole André Vadla Ravnås 5d015c5e1c linux: Rebuild native helpers for arm and arm64
To satisfy CI checks after the Android NDK upgrade.
2026-03-13 13:09:21 +01:00
Ole André Vadla Ravnås f1eaccaecd ci: Fix linux-helpers and labrats checks 2026-03-13 13:06:33 +01:00
Ole André Vadla Ravnås 1be2ed39ba linux: Fix helpers A32 build with Android NDK r29 2026-03-13 12:58:37 +01:00
Ole André Vadla Ravnås e51bf43bc3 android: Handle Chrome's zygote process
So it's invisible to our spawn gating, but its children are gated.
2026-03-13 12:33:38 +01:00
Ole André Vadla Ravnås e2fef93e03 linux: Add missing newline to the nolibc patch 2026-03-13 11:47:54 +01:00
Ole André Vadla Ravnås 9414cfe281 ci: Bump Android NDK to r29 2026-03-13 11:44:58 +01:00
Ole André Vadla Ravnås 74cbc8646f linux: Fix helpers build with Android NDK r29 2026-03-13 11:38:52 +01:00
Ole André Vadla Ravnås 5925eda6b2 libc-shim: Fix stdio buffer allocation order
The stdio shim initialized rcap/wcap after allocating rbuf/wbuf, so
buffered streams ended up with zero-sized allocations.

This caused buffered reads like fdopen(..., "r") + fread() to pass an
invalid buffer to read(), which could fail with EFAULT ("Bad address").
2026-03-13 10:11:48 +01:00
Ole André Vadla Ravnås 686cfcfda8 subprojects: Bump outdated 2026-03-13 00:01:18 +01:00
Ole André Vadla Ravnås 98a4f4f3e6 submodules: Bump releng 2026-03-13 00:01:17 +01:00
Ole André Vadla Ravnås 5b1e77a04c android: Only load in namespace if ART ns exists 2026-03-12 23:59:45 +01:00
Ole André Vadla Ravnås 9a93437b68 meson: Fix linking on modern LLVM toolchains
Modern LLVM/LLD linkers are stricter about undefined symbols in version
scripts. Some of our executable version scripts referenced
FreeBSD-specific symbols (__progname and environ), which older linkers
silently ignored but newer ones reject.

Fix this by providing a FreeBSD-specific version script and selecting it
in Meson when targeting FreeBSD.

Also centralize the linker scripts used for executables that export no
symbols into tools/, and reuse them across targets instead of
duplicating identical files.
2026-03-12 23:58:44 +01:00
Ole André Vadla Ravnås 685526c52c compiler: Add musl-compatible backend mode
The Go toolchain emits TLS relocations that musl only supports for
objects present at program startup. This rules out the integration
strategies based on c-archive and c-shared that we use elsewhere for
the compiler backend.

Introduce a third backend mode where the Go code is built as a PIE
executable and embedded as a resource. At runtime we extract it to a
temporary file and spawn it as a long-lived helper process. Requests
and events are exchanged over a simple length-prefixed JSON protocol.

This keeps the existing in-process integration on platforms where
c-archive or c-shared works, while providing a musl-compatible backend
strategy.

Implementation details:

  - Add a new executable backend mode selected automatically when the
    target libc is musl.
  - Extend the Meson detection logic to propagate libc and choose
    between archive, shared, or executable backends.
  - Split the Go frontend into two implementations selected by build
    tags: one exposing the C API and one implementing the stdio
    protocol used by the executable mode.
  - Introduce a BackendProcess implementation on the Vala side that
    manages a persistent backend subprocess and multiplexes build and
    watch requests.

This makes it possible to support the compiler backend on musl without
depending on Go integration modes that conflict with musl's TLS model,
which would preclude loading frida-core as a plugin or after the
initial exec.
2026-03-12 14:39:30 +01:00
Ole André Vadla Ravnås 326dd071e7 Revert "compiler: Fix crash on startup on musl"
This only gets us partway there. The Go toolchain emits initial-exec TLS
relocations (e.g. for runtime.tls_g), which musl only supports for objects
present at program startup.

frida-core may also be loaded via dlopen(), and musl rejects such objects
when they contain initial-exec TLS relocations:

```
initial-exec TLS resolves to dynamic definition
```

This is a separate issue from the constructor crash fixed by the
reverted commit.

This reverts commit e096c363e0.
2026-03-11 19:40:38 +01:00
Ole André Vadla Ravnås e096c363e0 compiler: Fix crash on startup on musl
The Go runtime assumes that constructor functions are called with argc
and argv. This is the case with glibc's RTLD, but not musl's. So to work
around this we modify go.o to remove its initializer from .init_array,
and instead call it manually, so we can pass it argc and argv.
2026-03-10 12:32:18 +00:00
Ole André Vadla Ravnås b588219d80 linux: Improve the process enumeration UID logic
- Handle sysconf(_SC_GETPW_R_SIZE_MAX) failing or returning
  "indeterminate".
- Retry with a larger buffer size if getpwuid_r() returns ERANGE.
- Do not fall back to stringifying the UID as the `user` parameter.
- Add a `uid` parameter that is always present.
2026-03-10 10:57:43 +00:00
Ole André Vadla Ravnås 9b567080e9 libc-shim: Fix setbuffer signature on glibc + musl 2026-03-10 09:36:02 +00:00
Ole André Vadla Ravnås e2580ce294 libc-shim: Plug some more holes in the stdio API
Add implementations for a few remaining BSD stdio entry-points:

- setbuf() and __srget() (currently referenced on Darwin)
- setlinebuf() and setbuffer()
2026-03-10 10:16:51 +01:00
Ole André Vadla Ravnås 1bdf9a2fd3 android: Implement sigchain logic when needed 2026-03-09 11:41:15 +01:00
Ole André Vadla Ravnås d5d500be3a subprojects: Prepare for release 2026-03-08 20:35:38 +01:00
Ole André Vadla Ravnås 6a1aa42685 subprojects: Bump outdated 2026-03-08 19:44:01 +01:00
Ole André Vadla Ravnås 2f75c98c0c syscall-tracer: Make eBPF more verifier-friendly 2026-03-08 18:50:15 +01:00
Ole André Vadla Ravnås c50c6cc6dc linux: Bump BpfObject log buffer size to 128 KiB 2026-03-08 18:49:33 +01:00
Ole André Vadla Ravnås 34742d09c3 android: Use in-process helper everywhere
Even as far back as API level 19.
2026-03-08 17:24:05 +01:00
Ole André Vadla Ravnås b08453341d atomics: Fix -Watomic-alignment warnings
Clang may warn that 64-bit atomic operations are misaligned when guint64
only has 4-byte alignment on the target ABI.

Use an explicitly 8-byte aligned typedef for the atomic helpers and
update the Vala bindings to use typed pointers instead of void *.
2026-03-08 17:05:51 +01:00
Ole André Vadla Ravnås cd9c2183ce android: Support in-process helper on Nougat
And potentially even older OSes.

This brings us another step closer to using in-process everywhere.
2026-03-08 12:27:48 +01:00
Ole André Vadla Ravnås 35bb528992 android: Fix in-process helper on older ARTs
Where ART expects the main executable to export certain symbols. We work
around this by replacing the crashing stubs with a function that does
nothing.
2026-03-08 11:21:16 +01:00
Ole André Vadla Ravnås e3a4a031f8 tests: Fix the icon width/height checks 2026-03-08 00:13:06 +01:00
Ole André Vadla Ravnås e9083013a8 subprojects: Bump outdated 2026-03-07 23:51:36 +01:00
Ole André Vadla Ravnås 1105d0d45d droidy: Port to the new AndroidHelperClient API 2026-03-07 23:45:37 +01:00
Ole André Vadla Ravnås 0f5153a5d0 compiler: Bump @types/frida-gum to 19.0.2 2026-03-07 23:37:23 +01:00
Ole André Vadla Ravnås 23f288c5e5 android: Run android-helper in-process if possible
We only try to do this on modern systems, as older versions of ART
require the main executable to export certain symbols. That precludes
usage in scenarios where frida-core is embedded, e.g. like in r2frida.
2026-03-07 22:36:48 +01:00
Ole André Vadla Ravnås e25b4debac base: Add missing G_{BEGIN,END}_DECLS to headers 2026-03-07 21:54:55 +01:00
Ole André Vadla Ravnås e5e3033415 libc-shim: Omit fseeko64 and ftello64 on QNX
They're implicitly defined.
2026-03-03 21:50:37 +01:00
Ole André Vadla Ravnås 719c0c1161 meson: Ensure libc is defined for non-Linux too 2026-03-03 21:36:48 +01:00
Dep c899e04a9c android-helper: Fix crash on Transsion ROMs
Fix a NullPointerException when the android-helper initializes on
Infinix and other Transsion OS devices.

Their modified ActivityThread expects a fully initialized main
looper. Using Looper.prepare() causes a crash in android.os.Handler
before initialization completes.

Use Looper.prepareMainLooper() instead so the expected main looper
is available.

Verified on a rooted Infinix device by compiling the .dex and
running it via app_process.
2026-03-03 21:33:33 +01:00
Alexander Hude 575161cad7 darwin: Fix early instrumentation on iOS 26.4
In iOS 26.4, Apple changed the signature of restartWithDyldInCache,
which broke the existing detection logic.

Generalize the logic instead of relying on another specific symbol.
2026-03-03 21:26:43 +01:00
Ole André Vadla Ravnås a05633f683 linux: Fix the helpers host_machine auto-detection 2026-03-03 21:23:07 +01:00
Ole André Vadla Ravnås 6053f4eb84 linux: Update helpers/rebuild.sh to match Makefile 2026-03-03 21:22:28 +01:00
Ole André Vadla Ravnås 0753bcf4b8 gadget: Only use versioned stdio symbols on glibc 2026-03-03 21:14:18 +01:00
Ole André Vadla Ravnås a66dff2d71 agent: Only use versioned stdio symbols on glibc 2026-03-03 21:13:43 +01:00
Ole André Vadla Ravnås f0fd7aef7b libc-shim: Plug some more holes in the stdio API
- fseeko, fseeko64
- ftello, ftello64
2026-03-03 19:36:42 +01:00
Ole André Vadla Ravnås 41e02684f3 libc-shim: Version fopen/fopen64 for libstdc++
Static libstdc++ introduces references to fopen@GLIBC_2.2.5 and
fopen64@GLIBC_2.2.5.

Provide matching versioned symbols from the libc shim and define a
GLIBC_2.2.5 node in the embedding version scripts so these bind to the
shim instead of glibc in both agent and gadget.
2026-03-03 19:11:27 +01:00
Ole André Vadla Ravnås 3f251baaa2 libc-shim: Plug some more holes in the stdio API
- getwc, fgetwc, ungetwc
- putc, putwc, fputwc
2026-03-03 15:39:26 +01:00
Ole André Vadla Ravnås 066f2dd718 libc-shim: Skip deinit unless explicitly requested
When the process exits and runs destructor functions, there is no
need to clean up these resources.

Previously this could cause undefined behavior because higher-level
subsystems had not been torn down first, e.g. threads still running
when GThread was torn down. Skipping deinit in this case avoids
teardown-time undefined behavior.
2026-03-03 14:48:40 +01:00
Ole André Vadla Ravnås 1fa296e285 linux: Move to latest syscall-trace protocol
- Use a uint64 for the thread ID. Move it before PID for better packing.
- Support including a module UUID and version string. Not yet wired up
  in the Linux backend.
- Support excluding specific syscalls.

This is to align with the tweaks made for the Fruity implementation.
2026-03-02 15:12:32 +01:00
Ole André Vadla Ravnås 9a4b6ac14d linux: Add uint64 key helpers to BpfMap 2026-03-02 15:12:31 +01:00
Ole André Vadla RavnåsandHåvard Sørbø c23ab3e2c9 fruity: Implement syscall-trace service
Wire up a syscall tracing service on top of CoreProfile ktrace/kdebug.

Configure the ktrace tap to emit syscall entry and exit events and
decode them using the XNU syscall signatures.

Detect syscalls with filesystem path arguments and correlate them with
VFS activity to reconstruct the contents of userspace string pointers
passed to the kernel.

Stitch in callstack events and provide symbolication by leveraging
query_symbolicator_signature().

Stream decoded events to consumer and manage lifecycle, including start,
stop, and cleanup on termination.

Co-authored-by: Håvard Sørbø <[email protected]>
2026-03-02 15:12:30 +01:00
Ole André Vadla Ravnås a6701b2fc9 base: Add XNU syscall enums and signatures
Introduce enums and signature metadata for XNU syscalls.

Define syscall numbers, names, and argument layouts to enable decoding
of ktrace/kdebug events.

Provide a foundation for higher-level tracing and analysis.
2026-03-02 15:02:57 +01:00
Ole André Vadla RavnåsandHåvard Sørbø f810946569 dtx: Introduce CoreProfileService
Provide access to CoreProfile via DTX with initial support for
ktrace/kdebug collection through a TapConfig abstraction.

Add KtraceConfig as the first tap type, modelling a subset of available
flags and filters.

Allow additional tap types and broader configuration to be introduced
later.

Co-authored-by: Håvard Sørbø <[email protected]>
2026-03-02 15:02:57 +01:00
Ole André Vadla Ravnås 5cac7c0c6e dtx: Add per-PID symbolicator signature query
Extend DeviceInfoService with support for retrieving the symbolicator
signature for a specific process.

The returned data describes the process' loaded images, including UUIDs,
segment layouts, load addresses, and versions from each image's
Info.plist. Symbols themselves are not included.

Parse the response into structured types to simplify matching against
crash logs and trace data.
2026-03-02 15:02:57 +01:00
Ole André Vadla RavnåsandHåvard Sørbø 65c694a66d fruity: Support spawning non-debuggable apps
As these cannot be launched suspended through LLDB, fall back to
ProcessControlService and send SIGSTOP immediately after launch.

Ensure the app is not already running before spawning, as relying on
ExistingInstancePolicy.KILL may leave it in an inconsistent state.

Track launched PIDs and resume them by sending SIGCONT when requested.
Clean up tracking when the process terminates.

Co-authored-by: Håvard Sørbø <[email protected]>
2026-03-02 15:02:57 +01:00
Ole André Vadla RavnåsandHåvard Sørbø 13bfcaf1a4 dtx: Extend ProcessControlService process control
Extend ProcessControlService with additional capabilities exposed by
Instruments' processcontrol endpoint.

This adds support for:

- Resolving PID from bundle identifier
- Checking whether a PID is debuggable
- Launching apps with arguments and environment
- Sending signals to a process
- Observing process termination events

Introduce LaunchOptions to control launch behavior such as suspended
start and handling of existing instances.

Expose termination notifications via a new process_terminated signal.

Co-authored-by: Håvard Sørbø <[email protected]>
2026-03-02 15:02:57 +01:00
Ole André Vadla RavnåsandHåvard Sørbø 8f83d5c269 dtx: Lift buffering restrictions
Turned out to be too conservative. And it's hard to decide on
appropriate limits when dealing with bulk data use-cases.

Co-authored-by: Håvard Sørbø <[email protected]>
2026-03-02 15:02:57 +01:00
Ole André Vadla Ravnås b12c30078a dtx: Handle incoming nil arguments 2026-03-02 14:14:22 +01:00
Ole André Vadla Ravnås c7669b9701 numeric: Add bytes_hash() and bytes_equal() 2026-02-24 10:43:40 +01:00
Ole André Vadla Ravnås 5096dbfac4 linux: Fix a stylistic inconsistency 2026-02-23 20:24:45 +01:00
Ole André Vadla Ravnås 9c16fa16fb marshal: Avoid null dereference in enum_to_nick()
Return a formatted fallback string when the enum value is unknown.
2026-02-22 20:20:58 +01:00
Ole André Vadla Ravnås 966f609906 buffer: Make recyclable; add peek + align
Allow Buffer to be reset from either Bytes or an unowned uint8[] without
allocating intermediate Bytes objects.

Extend BufferReader with peeking and alignment helpers and make both
classes reusable through reset() semantics.

This enables zero-allocation parsing in hot paths such as kcdata
stackshot processing.
2026-02-22 15:30:25 +01:00
Ole André Vadla Ravnås 889576e1d5 linux: Simplify build_signatures_variant()
The null-check was a leftover from the older API.
2026-02-21 15:29:38 +01:00
Ole André Vadla Ravnås bb9d9bc6a8 linux: Use int32 for syscall numbers
For consistency with Darwin.
2026-02-21 15:29:34 +01:00
Ole André Vadla Ravnås fc2e14a7cf linux: Simplify the syscall event variant logic 2026-02-21 11:31:00 +01:00
Ole André Vadla Ravnås 868166c23a buffer: Add BufferReader.seek() 2026-02-20 22:19:09 +01:00
Ole André Vadla Ravnås 4d7a0464fb dns: Avoid allocating Bytes object needlessly 2026-02-20 12:42:43 +01:00
Ole André Vadla Ravnås 150e4425ed buffer: Add read_data() to Buffer and BufferReader
To access the raw data without having to allocate a new Bytes object.
2026-02-20 12:39:45 +01:00
Ole André Vadla Ravnås 6bbef7880f buffer: Make read_bytes() zero-copy 2026-02-20 12:39:45 +01:00
Ole André Vadla Ravnås 963a8de514 buffer: Optimize read_fixed_string()
We can't avoid copying, but at least don't waste time on UTF-8
validation. This should be done at the higher level.
2026-02-20 12:39:45 +01:00
Ole André Vadla Ravnås fdb0eebb5e buffer: Optimize read_string()
- Do not copy. The caller should make a copy if the string needs to
  outlive the Buffer/BufferReader.
- Do not validate. This should be done at the higher level.
2026-02-20 12:39:44 +01:00
Ole André Vadla Ravnås ea26ae84b7 subprojects: Bump outdated 2026-02-18 22:53:56 +01:00
Ole André Vadla Ravnås 69016b9d5b submodules: Bump releng 2026-02-18 22:53:55 +01:00
03afdb072a websocket: Pause libsoup input to avoid starvation
Co-authored-by: Håvard Sørbø <[email protected]>
Co-authored-by: Ole André Vadla Ravnås <[email protected]>
2026-02-18 22:52:39 +01:00
bf0830c1a2 network-stack: Handle incoming datagrams async
Incoming datagram delivery previously blocked while waiting for lwIP
thread work to complete.

Switch handle_incoming_datagrams() to an async API and schedule the
processing on the lwIP thread without joining immediately.

Use this to pace callers so they don't monopolize the GLib.MainContext
when floods of datagrams are coming in.

Co-authored-by: Håvard Sørbø <[email protected]>
Co-authored-by: Ole André Vadla Ravnås <[email protected]>
2026-02-18 22:52:39 +01:00
Kerbiter 4aac1ae324 compat: Include x86_64 in Windows x86 builds 2026-02-18 22:28:31 +01:00
Ole André Vadla Ravnås e74d51dd13 keyed-archive: Make to_string() prettier 2026-02-18 01:07:43 +01:00
Ole André Vadla RavnåsandHåvard Sørbø 984413c057 keyed-archive: Improve to_string() support
Intended for debugging during development.

Co-authored-by: Håvard Sørbø <[email protected]>
2026-02-17 23:55:02 +01:00
Ole André Vadla RavnåsandHåvard Sørbø ed78ab19d7 keyed-archive: Support NSSet
Only encoding for now.

Co-authored-by: Håvard Sørbø <[email protected]>
2026-02-17 23:54:57 +01:00
Ole André Vadla RavnåsandHåvard Sørbø 5b877cbbff dtx: Upgrade protocol handling
- Rename DTXMessageType.INVOKE to DISPATCH.
- Rename DTXMessageType.RESULT to OBJECT.
- Fix channel code and is_notification logic.
- Add missing message types.
- Support data messages.
- Do not advertise compression support. We don't support it yet.

Co-authored-by: Håvard Sørbø <[email protected]>
2026-02-17 23:54:54 +01:00
Ole André Vadla Ravnås 3f3457b249 subprojects: Prepare for release 2026-02-16 22:45:39 +01:00
Ole André Vadla Ravnås f1c0e87871 subprojects: Bump outdated 2026-02-16 22:08:06 +01:00
Ole André Vadla Ravnås 6096046b05 syscall-tracer: Decode timespec values
Recognize timespec pointer arguments and decode them into structured
values rather than emitting opaque byte arrays.

Supports both __kernel_timespec and old_timespec32 layouts.
2026-02-16 21:27:26 +01:00
Ole André Vadla Ravnås c0815c4d1a syscall-tracer: Fix a stylistic inconsistency 2026-02-16 21:24:05 +01:00
Ole André Vadla Ravnås ab42fcb1ed syscall-tracer: Remove a redundant check 2026-02-16 21:23:52 +01:00
Ole André Vadla Ravnås cd0a327a3c syscall-tracer: Fix raw attachment lifetime
The GVariant was previously created from data freed before return,
leaving it with a dangling pointer.
2026-02-16 21:01:17 +01:00
Ole André Vadla Ravnås 51ff27a08b syscall-tracer: Fix multi-attachment parsing
Fix parsing of multiple attachments by encoding how much space is
reserved for each attachment.
2026-02-16 20:56:54 +01:00
510 changed files with 117740 additions and 31491 deletions

No files matched your search

@@ -0,0 +1,4 @@
set(VCPKG_TARGET_ARCHITECTURE arm64)
set(VCPKG_CRT_LINKAGE dynamic)
set(VCPKG_LIBRARY_LINKAGE dynamic)
set(VCPKG_BUILD_TYPE release)
@@ -0,0 +1,4 @@
set(VCPKG_TARGET_ARCHITECTURE x64)
set(VCPKG_CRT_LINKAGE dynamic)
set(VCPKG_LIBRARY_LINKAGE dynamic)
set(VCPKG_BUILD_TYPE release)
+157 -33
View File
@@ -20,8 +20,8 @@ jobs:
strategy:
matrix:
include:
- { id: windows-arm64, runner: '"windows-11-arm"', sys: CLANGARM64, pkg: 'mingw-w64-clang-aarch64-clang' }
- { id: windows-x86_64, runner: '"windows-latest"', sys: MINGW64, pkg: 'mingw-w64-x86_64-gcc' }
- { id: windows-arm64, runner: '"windows-11-arm"', sys: CLANGARM64, pkg: 'mingw-w64-clang-aarch64-clang', opts: "'-Dfrida-gum:v8=disabled'" }
- { id: windows-x86_64, runner: '"windows-latest"', sys: UCRT64, pkg: 'mingw-w64-ucrt-x86_64-gcc' }
- { id: windows-x86, runner: '"windows-latest"', sys: MINGW32, pkg: 'mingw-w64-i686-gcc' }
- { id: macos-x86_64, runner: '"macos-latest"' }
- { id: macos-arm64, runner: '"macos-latest"' }
@@ -31,7 +31,7 @@ jobs:
runs-on: ${{ fromJSON(matrix.runner) }}
steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Install MinGW toolchain for cgo support
@@ -42,7 +42,7 @@ jobs:
msystem: ${{ matrix.sys }}
install: ${{ matrix.pkg }}
- name: Install Go toolchain
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version-file: 'src/compiler/go.mod'
cache-dependency-path: 'src/compiler/go.mod'
@@ -61,13 +61,17 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install gcc-multilib lib32stdc++-13-dev
- name: Install setuptools for distutils shim
if: ${{ startsWith(matrix.id, 'windows-') }}
run: pip install setuptools
- name: Build
if: ${{ startsWith(matrix.id, 'windows-') }}
run: |
.\configure `
--host=${{ matrix.id }} `
--enable-compiler-backend `
${{ env.FRIDA_CORE_OPTIONS }}
${{ env.FRIDA_CORE_OPTIONS }} `
-- --force-fallback-for=openssl ${{ matrix.opts }}
.\make
env:
MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }}
@@ -91,14 +95,40 @@ jobs:
make
- name: Upload devkit
if: ${{ !startsWith(matrix.id, 'linux-') }}
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: core-devkit-${{ matrix.id }}
path: build/src/devkit/
- name: Upload GObject introspection data
if: ${{ matrix.id == 'linux-x86_64' }}
uses: actions/upload-artifact@v7
with:
name: core-gir
path: build/src/api/Frida-1.0.gir
- name: Test
if: ${{ matrix.id != 'windows-arm64' && !startsWith(matrix.id, 'macos-') }}
run: make test
publish-gir:
if: ${{ !cancelled() && github.ref_type == 'tag' && needs.native.result == 'success' }}
needs:
- native
runs-on: ubuntu-latest
steps:
- name: Download GObject introspection data
uses: actions/download-artifact@v8
with:
name: core-gir
path: gir
- name: Attach to GitHub release
uses: softprops/action-gh-release@v3
with:
files: |
gir/Frida-1.0.gir
prerelease: ${{ contains(github.ref_name, 'snapshot') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
cross:
strategy:
matrix:
@@ -113,7 +143,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Install toolchain
@@ -121,7 +151,7 @@ jobs:
sudo apt-get update
sudo apt-get install ${{ matrix.pkg }}
- name: Install Go toolchain
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version-file: 'src/compiler/go.mod'
cache-dependency-path: 'src/compiler/go.mod'
@@ -134,7 +164,7 @@ jobs:
make
- name: Upload devkit
if: ${{ !startsWith(matrix.id, 'linux-') }}
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: core-devkit-${{ matrix.id }}
path: build/src/devkit/
@@ -148,11 +178,11 @@ jobs:
container: ghcr.io/frida/x-tools-linux-${{ matrix.arch }}:latest
steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Install Go toolchain
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version-file: 'src/compiler/go.mod'
cache-dependency-path: 'src/compiler/go.mod'
@@ -164,7 +194,7 @@ jobs:
${{ env.FRIDA_CORE_OPTIONS }}
make
- name: Upload devkit
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: core-devkit-linux-${{ matrix.arch }}
path: build/src/devkit/
@@ -181,7 +211,7 @@ jobs:
container: ghcr.io/frida/x-tools-linux-${{ matrix.arch }}:latest
steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Build
@@ -312,11 +342,11 @@ jobs:
runs-on: ${{ startsWith(matrix.id, 'ios-') && 'macos-latest' || 'ubuntu-latest' }}
steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Install Go toolchain
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version-file: 'src/compiler/go.mod'
cache-dependency-path: 'src/compiler/go.mod'
@@ -331,7 +361,7 @@ jobs:
if: startsWith(matrix.id, 'android-')
id: setup-ndk
with:
ndk-version: r25c
ndk-version: r29
- name: Add NDK to env
if: startsWith(matrix.id, 'android-')
run: echo ANDROID_NDK_ROOT=${{ steps.setup-ndk.outputs.ndk-path }} >> $GITHUB_ENV
@@ -343,7 +373,7 @@ jobs:
${{ env.FRIDA_CORE_OPTIONS }}
make
- name: Upload devkit
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: core-devkit-${{ matrix.id }}
path: build/src/devkit/
@@ -402,7 +432,7 @@ jobs:
container: ghcr.io/frida/qnx-tools:latest
steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Build
@@ -411,7 +441,7 @@ jobs:
./configure --host=arm-unknown-nto-qnx6.5.0eabi ${{ env.FRIDA_CORE_OPTIONS }}
make
- name: Upload devkit
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: core-devkit-qnx-armeabi
path: build/src/devkit/
@@ -421,13 +451,17 @@ jobs:
cd build
cp -a tests/frida-tests tests/labrats lib/agent/frida-agent.so /tmp/pkg/
tar -C /tmp/pkg -cf /tmp/runner.tar .
/opt/sabrelite/run.sh /tmp/runner.tar /opt/frida/frida-tests
/opt/sabrelite/run.sh /tmp/runner.tar \
"cp /opt/frida/frida-tests /opt/frida/frida-agent.so /tmp/ && \
ln -sP /opt/frida/labrats /tmp/labrats && \
/tmp/frida-tests"
# This job is used to check that the Linux helpers are built correctly for all
# supported architectures. We do this because the binary artifacts are checked
# in, and we want to ensure that they are up-to-date with respect to the source
# for all architectures.
check-linux-helpers:
if: false
strategy:
matrix:
arch: [x86, x86_64, arm, armbe8, arm64, arm64be, arm64beilp32, mips, mipsel, mips64, mips64el]
@@ -436,7 +470,7 @@ jobs:
container: ghcr.io/frida/core-linux-helpers-${{ matrix.arch }}:latest
steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Build
@@ -445,7 +479,7 @@ jobs:
- name: Check for unexpected changes
run: |
git config --global --add safe.directory "$(realpath .)"
status_output=$(git status --porcelain tests/labrats/{resident-agent,simple-agent}-linux-${{ matrix.arch }}.so tests/labrats/{forker,sleeper,spawner}-linux-${{ matrix.arch }})
status_output=$(git status --porcelain src/linux/helpers/artifacts)
if [ -n "$status_output" ]; then
echo "Unexpected changes detected:"
echo "$status_output"
@@ -461,6 +495,7 @@ jobs:
# in, and we want to ensure that they are up-to-date with respect to the source
# for all architectures.
check-labrats:
if: false
strategy:
matrix:
arch: [x86, x86_64, arm, armbe8, arm64, arm64be, arm64beilp32, mips, mipsel, mips64, mips64el]
@@ -469,7 +504,7 @@ jobs:
container: ghcr.io/frida/core-linux-helpers-${{ matrix.arch }}:latest
steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Build
@@ -478,7 +513,7 @@ jobs:
- name: Check for unexpected changes
run: |
git config --global --add safe.directory "$(realpath .)"
status_output=$(git status --porcelain src/linux/helpers/{bootstrapper,loader}-${{ matrix.arch }}.bin)
status_output=$(git status --porcelain tests/labrats/{resident-agent,simple-agent}-linux-${{ matrix.arch }}.so tests/labrats/{forker,sleeper,spawner}-linux-${{ matrix.arch }})
if [ -n "$status_output" ]; then
echo "Unexpected changes detected:"
echo "$status_output"
@@ -491,30 +526,31 @@ jobs:
xcframework:
name: Build FridaCore.xcframework
if: ${{ !cancelled() }}
runs-on: macos-latest
needs:
- native
- mobile
steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Download macOS/x86_64 devkit
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: core-devkit-macos-x86_64
path: devkits/macos-x86_64
- name: Download macOS/arm64 devkit
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: core-devkit-macos-arm64
path: devkits/macos-arm64
- name: Download iOS/arm64 devkit
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: core-devkit-ios-arm64
path: devkits/ios-arm64
- name: Download iOS/arm64-simulator devkit
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: core-devkit-ios-arm64-simulator
path: devkits/ios-arm64-simulator
@@ -577,19 +613,19 @@ jobs:
-headers xcframework_headers/ios-arm64-simulator \
-output FridaCore.xcframework
- name: Upload FridaCore.xcframework
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: FridaCore.xcframework
path: FridaCore.xcframework
publish-xcframework:
if: github.ref_type == 'tag'
if: ${{ !cancelled() && github.ref_type == 'tag' && needs.xcframework.result == 'success' }}
runs-on: macos-latest
needs:
- xcframework
steps:
- name: Download FridaCore.xcframework artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: FridaCore.xcframework
path: dist
@@ -612,10 +648,98 @@ jobs:
cd dist
zip -r FridaCore.xcframework.zip FridaCore.xcframework
- name: Create GitHub release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
with:
files: |
dist/FridaCore.xcframework.zip
prerelease: ${{ contains(github.ref_name, 'snapshot') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
windows-shared:
strategy:
matrix:
include:
- { arch: x86_64, runner: windows-latest, msvc-arch: amd64, vcpkg-triplet: x64-windows-release }
- { arch: arm64, runner: windows-11-arm, msvc-arch: arm64, vcpkg-triplet: arm64-windows-release, opts: "'-Dfrida-gum:v8=disabled'" }
fail-fast: false
runs-on: ${{ matrix.runner }}
timeout-minutes: 120
steps:
- name: Check out repo
uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up MSVC environment
run: |
$vs = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -products * -latest -property installationPath
$before = @{}
Get-ChildItem env: | ForEach-Object { $before[$_.Name] = $_.Value }
cmd /c "`"$vs\VC\Auxiliary\Build\vcvarsall.bat`" ${{ matrix.msvc-arch }} >nul && set" | ForEach-Object {
$name, $value = $_ -split '=', 2
if ($before[$name] -ne $value) {
"$name=$value" >> $env:GITHUB_ENV
}
}
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install build tools
run: |
pip install meson ninja setuptools
choco install pkgconfiglite -y
- name: Restore vcpkg cache
id: vcpkg-cache
uses: actions/cache/restore@v5
with:
path: C:\vcpkg\installed
key: vcpkg-${{ matrix.vcpkg-triplet }}-v5
- name: Install dependencies via vcpkg
if: steps.vcpkg-cache.outputs.cache-hit != 'true'
run: >-
vcpkg install
--overlay-triplets=${{ github.workspace }}/.github/vcpkg-triplets
--triplet ${{ matrix.vcpkg-triplet }}
glib json-glib libsoup
- name: Save vcpkg cache
if: steps.vcpkg-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: C:\vcpkg\installed
key: vcpkg-${{ matrix.vcpkg-triplet }}-v5
- name: Set up vcpkg environment
shell: bash
run: |
TRIPLET="${{ matrix.vcpkg-triplet }}"
VCPKG_PREFIX="C:/vcpkg/installed/$TRIPLET"
echo "$VCPKG_PREFIX/tools/pkgconf" >> $GITHUB_PATH
echo "$VCPKG_PREFIX/bin" >> $GITHUB_PATH
echo "$VCPKG_PREFIX/tools" >> $GITHUB_PATH
echo "PKG_CONFIG_PATH=$VCPKG_PREFIX/lib/pkgconfig" >> $GITHUB_ENV
- name: Configure
run: |
python -c "import sys; sys.path.insert(0, '.'); from releng.meson_configure import main; main()" `
. `
--host=windows-${{ matrix.arch }} `
--enable-shared `
--without-prebuilds=sdk `
"--prefix=$env:RUNNER_TEMP\frida-prefix" `
-- --force-fallback-for=openssl,libnghttp2 -Dcompiler_backend=disabled -Dtests=disabled ${{ matrix.opts }}
- name: Compile
run: python releng/meson/meson.py compile -C build
- name: Install
run: python releng/meson/meson.py install -C build --no-rebuild --skip-subprojects
- name: List installed files
shell: bash
run: find "$RUNNER_TEMP/frida-prefix" -type f | sort
+10 -1
View File
@@ -1,7 +1,16 @@
/.cache/
/build/
/deps/
/src/android-helper/build/
/src/barebone/agent/target/
/src/barebone/agent/linux/*.ko
/src/barebone/agent/linux/*.mod
/src/barebone/agent/linux/*.mod.c
/src/barebone/agent/linux/*.o
/src/barebone/agent/linux/*.o.bin
/src/barebone/agent/linux/.*.cmd
/src/barebone/agent/linux/Module.symvers
/src/barebone/agent/linux/modules.order
/src/barebone/agent/target*/
/src/barebone/agent/xpacks/
/src/barebone/helpers/memory-scanner/target/
/src/barebone/helpers/target/
+153
View File
@@ -0,0 +1,153 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code)
when working with code in this repository.
## What This Is
frida-core is the core library of the Frida dynamic
instrumentation toolkit. It packages GumJS into a shared
library, injects JavaScript instrumentation into target
processes, provides bidirectional RPC communication with
injected scripts, and enumerates apps/processes/devices.
Written primarily in Vala with OS-specific glue in
C/Objective-C/asm.
## Build Commands
```bash
# First-time setup (fetches git submodules, configures build):
./configure \
--enable-compiler-backend \
--with-devkits=core \
--enable-tests
# Build:
make
# Run tests:
make test
# Rebuild after code changes
make
```
The `configure` script and `Makefile` are thin wrappers
around `releng/meson_configure.py` and
`releng/meson_make.py`. The actual build system is Meson
(>=1.1.0).
Build artifacts go into `build/`. The triplet is
platform-dependent (e.g. `linux-x86_64`).
## Build Requirements
- **Vala compiler**: Must be the Frida-optimized fork from
https://github.com/frida-forks/vala (checks for `-frida` suffix
in version)
- **C/C++ standards**: gnu99/c99 for C, c++17 for C++
- **Go >= 1.24**: Required for compiler backend
- **Python 3**: Build scripts
## Architecture
### Backend System
The core abstraction is a pluggable backend system. Each
backend implements `HostSessionBackend` and provides a
`HostSessionProvider` to create sessions with target
processes:
- **local** — Injects into processes on the local system
(Linux, macOS, Windows, FreeBSD, QNX)
- **fruity** — iOS/tvOS/xrOS devices via USB/network
(QUIC, lockdown protocols)
- **droidy** — Android devices via ADB/JDWP
- **socket** — Remote devices via TCP (connects to
frida-server, frida-gadget, frida-portal)
- **barebone** — Minimal backend using QuickJS
- **simmy** — macOS Simulator
- **compiler** — TypeScript/ESBuild compilation backend
(Go + TypeScript in `src/compiler/`)
Backends are selected at build time via meson options
(e.g. `-Dlocal_backend=enabled`).
### Key Source Layout
- **`src/frida.vala`** — Public API: `DeviceManager`,
`Device`, `Session`, `Script`
- **`src/host-session-service.vala`** — Backend
orchestration, provider registration
- **`src/control-service.vala`** — the guts of frida-server
- **`src/portal-service.vala`** — the guts of frida-portal,
which looks like a frida-server that surfaces any joined
processes (think aggregator/reverse proxy)
- **`lib/base/`** — Core primitives: Session, RPC, streams,
sockets, P2P, buffers
- **`lib/agent/`** — In-process JavaScript agent runtime
- **`lib/payload/`** — Payload compilation and embedding
- **`lib/gadget/`** — Gadget (preloaded library)
implementation
- **`src/<platform>/`** — Platform-specific injectors and
helpers (`linux/`, `darwin/`, `windows/`, `freebsd/`,
`qnx/`)
- **`src/fruity/`** — iOS device protocol stack (USB, QUIC,
plist, XPC, LLDB)
- **`src/droidy/`** — Android device protocol stack
- **`src/compiler/`** — Go/TypeScript compiler backend
### Injection Flow
Each platform has its own injector (e.g. `Linjector` on
Linux, `Fruitjector` on Darwin, `Winjector` on Windows). A
helper process (`frida-helper`) performs privileged injection
operations out-of-process. Helpers and agents can be embedded
into the library or installed as separate files (controlled
by the `assets` meson option).
### Deliverables
- **frida-server** (`server/`) — Standalone daemon exposing
the full API over the network
- **frida-portal** (`portal/`) — Cluster node for
distributed instrumentation
- **frida-inject** (`inject/`) — CLI tool for one-shot
script injection
- **frida-gadget** (`lib/gadget/`) — Shared library for
preload-based instrumentation
### Resource Embedding
Build artifacts (frida-helper, frida-agent) are compiled
into resource blobs via `tools/resource-compiler.vala` and
embedding scripts (`src/embed-helper.py`,
`src/embed-agent.py`). On non-MSVC toolchains these become
`.S` assembly files; on MSVC they become `.obj` files.
## Tests
Tests are in `tests/` as Vala files using a custom async
test harness (`tests/async-harness.vala`). Test binary is
`tests/frida-tests`. Platform-specific runner scripts exist
(e.g. `tests/run-linux-x86_64.sh`).
Test modules: `test-system`, `test-injector`, `test-agent`,
`test-host-session`, `test-gadget`, `test-compiler`. Test
helper programs ("labrats") are in `tests/labrats/`.
## Code Style
### Function Order
Use newspaper/stepdown order: high-level entry points
first, low-level helpers and constants last. In C,
`main()` is always first. When ordering siblings at the
same level, use the chronological order of first use
within their caller.
## Commit Style
- Subject line: max 50 characters
- Body lines: wrap at 72 characters (use the full width, or
slightly less if it avoids making the next line awkward)
+2 -2
View File
@@ -1,4 +1,4 @@
PYTHON ?= $(shell which python3 >/dev/null && echo python3 || echo python)
PYTHON ?= $(shell command -v python3 >/dev/null && echo python3 || echo python)
all $(MAKECMDGOALS):
@$(PYTHON) \
@@ -8,7 +8,7 @@ all $(MAKECMDGOALS):
$(MAKECMDGOALS)
git-submodules:
@if [ ! -f releng/meson/meson.py ]; then \
@if [ ! -f releng/meson/meson.py ] || git submodule status 2>/dev/null | grep -q '^-'; then \
git submodule update --init --recursive --depth 1; \
fi
-include git-submodules
+49 -11
View File
@@ -1,15 +1,18 @@
# frida-core
Frida core library intended for static linking into bindings.
Frida core library.
- Lets you inject your own JavaScript instrumentation code into other processes,
optionally with your own [C code][] for performance-sensitive bits.
- Acts as a logistics layer that packages up [GumJS][] into a shared library.
- Provides a two-way communication channel for talking to your scripts,
if needed, and later unload them.
- Also lets you enumerate installed apps, running processes, and connected
devices.
- Written in [Vala][], with OS-specific glue code in C/Objective-C/asm.
- Lets you inject your own JavaScript instrumentation code
into other processes, optionally with your own [C code][]
for performance-sensitive bits.
- Acts as a logistics layer that packages up [GumJS][] into
a shared library.
- Provides a two-way communication channel for talking to
your scripts, if needed, and later unload them.
- Also lets you enumerate installed apps, running processes,
and connected devices.
- Written in [Vala][], with OS-specific glue code in
C/Objective-C/asm.
## Binaries
@@ -29,8 +32,43 @@ $ pip install frida # Python bindings
$ npm install frida # Node.js bindings
```
Or, for static linking into your own project written in a C-compatible language,
download a devkit from the Frida [releases][] page.
Or, for static linking into your own project written in a
C-compatible language, download a devkit from the Frida
[releases][] page.
## Distro Packaging
For building a shared library suitable for distro packaging:
```console
$ ./configure --enable-shared --without-prebuilds=sdk && make
```
This builds `libfrida-core-1.0.so` against system dependencies,
while `frida-agent.so` is built using Frida's SDK (with its GLib
fork, needed for the agent's inject/unload lifecycle).
Prerequisites on Fedora:
```console
$ sudo dnf install libgee-devel json-glib-devel \
libsoup3-devel libunwind-devel libdwarf-devel \
libnice-devel ngtcp2-crypto-ossl-devel
```
Prerequisites on Ubuntu:
```console
$ sudo apt install libgee-0.8-dev libjson-glib-dev \
libsoup-3.0-dev libunwind-dev libdwarf-dev \
libnice-dev libngtcp2-crypto-ossl-dev
```
Prerequisites on macOS:
```console
$ brew install libgee json-glib libsoup libnice capstone
```
## Internals
+250 -74
View File
@@ -9,6 +9,7 @@ import os
from pathlib import Path
import pickle
import platform
import re
import shlex
import shutil
import subprocess
@@ -18,7 +19,7 @@ import traceback
from typing import Any, Literal, Mapping, Optional, Sequence, Tuple
REPO_ROOT = Path(__file__).resolve().parent.parent
REPO_ROOT = Path(__file__).absolute().parent.parent
NINJA = os.environ.get("NINJA", "ninja")
@@ -35,6 +36,7 @@ def main(argv):
command.add_argument("top_builddir", help="top build directory", type=Path)
command.add_argument("frida_version", help="the Frida version")
command.add_argument("host_os", help="operating system binaries are being built for")
command.add_argument("host_os_family", help="operating system family, e.g. darwin")
command.add_argument("host_arch", help="architecture binaries are being built for")
command.add_argument("host_config", help="configuration binaries are being built for")
command.add_argument("compat", help="support for targets with a different architecture",
@@ -42,17 +44,23 @@ def main(argv):
command.add_argument("assets", help="whether assets are embedded vs installed and loaded at runtime")
command.add_argument("components", help="which components will be built",
type=parse_array_option_value)
command.add_argument("glib_flavor", help="upstream or frida", choices=["upstream", "frida"])
command.add_argument("apple_tls", help="whether the parent build is using the Apple TLS backend",
choices=["enabled", "disabled"])
command.add_argument("compilers", help="compiler command arrays", nargs="+")
command.set_defaults(func=lambda args: setup(args.role,
args.builddir,
args.top_builddir,
args.frida_version,
args.host_os,
args.host_os_family,
args.host_arch,
args.host_config if args.host_config else None,
args.compat,
args.assets,
args.components,
args.glib_flavor,
args.apple_tls,
parse_compilers(args.compilers)))
command = subparsers.add_parser("compile", help="compile compatibility assets")
@@ -113,11 +121,14 @@ def setup(role: Role,
top_builddir: Path,
frida_version: str,
host_os: str,
host_os_family: str,
host_arch: str,
host_config: Optional[str],
compat: set[str],
assets: str,
components: set[str],
glib_flavor: str,
apple_tls: str,
compilers: Compilers):
try:
outputs: Mapping[str, Sequence[Output]] = OrderedDict()
@@ -149,9 +160,20 @@ def setup(role: Role,
other_triplet: Optional[str] = None
extra_environ: dict[str, str] = {}
if host_os == "windows" and host_arch == "x86_64" and host_config == "mingw":
other_label = "x86"
have_toolchain, other_triplet = detect_mingw_toolchain_for("x86")
if host_os == "windows" and host_arch in {"x86_64", "x86"}:
if host_arch == "x86_64":
other_label = "x86"
other_kind = "legacy"
else:
other_label = "x86_64"
other_kind = "modern"
if host_config == "mingw":
have_toolchain, other_triplet = detect_mingw_toolchain_for(other_label)
else:
have_toolchain = True
other_triplet = None
elif host_os == "linux" and host_arch == "x86_64" and host_config is None:
other_label = "x86"
other_triplet = "i686-linux-gnu"
@@ -179,21 +201,26 @@ def setup(role: Role,
raise ToolchainNotFoundError(f"unable to locate toolchain for {other_triplet}")
missing.append(MissingFeature(other_label, other_triplet))
if host_os == "windows" and host_arch == "x86_64" and have_toolchain:
group = OutputGroup("x86", other_triplet, extra_environ)
outputs[group] = [
Output(identifier="helper_legacy",
name=HELPER_FILE_WINDOWS.name,
file=HELPER_FILE_WINDOWS,
target=HELPER_TARGET),
Output(identifier="agent_legacy",
name=AGENT_FILE_WINDOWS.name,
file=AGENT_FILE_WINDOWS,
target=AGENT_TARGET),
]
if host_os == "windows" and host_arch in {"x86_64", "x86"} and have_toolchain:
group = OutputGroup(other_label, other_triplet, extra_environ)
outputs[group] = []
if "helper" in components:
outputs[group] += [
Output(identifier=f"helper_{other_kind}",
name=HELPER_FILE_WINDOWS.name,
file=HELPER_FILE_WINDOWS,
target=HELPER_TARGET),
]
if "agent" in components:
outputs[group] += [
Output(identifier=f"agent_{other_kind}",
name=AGENT_FILE_WINDOWS.name,
file=AGENT_FILE_WINDOWS,
target=AGENT_TARGET),
]
if "gadget" in components:
outputs[group] += [
Output(identifier="gadget_legacy",
Output(identifier=f"gadget_{other_kind}",
name=GADGET_FILE_WINDOWS.name,
file=GADGET_FILE_WINDOWS,
target=GADGET_TARGET),
@@ -207,16 +234,21 @@ def setup(role: Role,
other_arch = "arm64e"
kind = "modern"
group = OutputGroup(other_arch)
outputs[group] = [
Output(identifier=f"helper_{kind}",
name=f"frida-helper-{other_arch}",
file=HELPER_FILE_UNIX,
target=HELPER_TARGET),
Output(identifier=f"agent_{kind}",
name=f"frida-agent-{other_arch}.dylib",
file=AGENT_FILE_DARWIN,
target=AGENT_TARGET),
]
outputs[group] = []
if "helper" in components:
outputs[group] += [
Output(identifier=f"helper_{kind}",
name=f"frida-helper-{other_arch}",
file=HELPER_FILE_UNIX,
target=HELPER_TARGET),
]
if "agent" in components:
outputs[group] += [
Output(identifier=f"agent_{kind}",
name=f"frida-agent-{other_arch}.dylib",
file=AGENT_FILE_DARWIN,
target=AGENT_TARGET),
]
if "gadget" in components:
outputs[group] += [
Output(identifier=f"gadget_{kind}",
@@ -234,16 +266,21 @@ def setup(role: Role,
if host_os == "linux" and host_arch == "x86_64" and have_toolchain:
group = OutputGroup("x86", other_triplet, extra_environ)
outputs[group] = [
Output(identifier="helper_legacy",
name=HELPER_FILE_UNIX.name,
file=HELPER_FILE_UNIX,
target=HELPER_TARGET),
Output(identifier="agent_legacy",
name=AGENT_FILE_ELF.name,
file=AGENT_FILE_ELF,
target=AGENT_TARGET),
]
outputs[group] = []
if "helper" in components:
outputs[group] += [
Output(identifier="helper_legacy",
name=HELPER_FILE_UNIX.name,
file=HELPER_FILE_UNIX,
target=HELPER_TARGET),
]
if "agent" in components:
outputs[group] += [
Output(identifier="agent_legacy",
name=AGENT_FILE_ELF.name,
file=AGENT_FILE_ELF,
target=AGENT_TARGET),
]
if "gadget" in components:
outputs[group] += [
Output(identifier="gadget_legacy",
@@ -254,16 +291,21 @@ def setup(role: Role,
if host_os == "android" and host_arch in {"arm64", "x86_64"}:
group = OutputGroup("arm" if host_arch == "arm64" else "x86")
outputs[group] = [
Output(identifier="helper_legacy",
name=HELPER_FILE_UNIX.name,
file=HELPER_FILE_UNIX,
target=HELPER_TARGET),
Output(identifier="agent_legacy",
name=AGENT_FILE_ELF.name,
file=AGENT_FILE_ELF,
target=AGENT_TARGET),
]
outputs[group] = []
if "helper" in components:
outputs[group] += [
Output(identifier="helper_legacy",
name=HELPER_FILE_UNIX.name,
file=HELPER_FILE_UNIX,
target=HELPER_TARGET),
]
if "agent" in components:
outputs[group] += [
Output(identifier="agent_legacy",
name=AGENT_FILE_ELF.name,
file=AGENT_FILE_ELF,
target=AGENT_TARGET),
]
if "gadget" in components:
outputs[group] += [
Output(identifier="gadget_legacy",
@@ -284,18 +326,24 @@ def setup(role: Role,
continue
else:
emulated_triplet = None
outputs[OutputGroup(emulated_arch, emulated_triplet, extra_environ)] = [
Output(identifier=f"helper_emulated_{kind}",
name=HELPER_FILE_WINDOWS.name.replace(".exe", f"-{emulated_arch}.exe"),
file=HELPER_FILE_WINDOWS,
target=HELPER_TARGET),
Output(identifier=f"agent_emulated_{kind}",
name=AGENT_FILE_WINDOWS.name.replace(".dll", f"-{emulated_arch}.dll"),
file=AGENT_FILE_WINDOWS,
target=AGENT_TARGET),
]
group = OutputGroup(emulated_arch, emulated_triplet, extra_environ)
outputs[group] = []
if "helper" in components:
outputs[group] += [
Output(identifier=f"helper_emulated_{kind}",
name=HELPER_FILE_WINDOWS.name.replace(".exe", f"-{emulated_arch}.exe"),
file=HELPER_FILE_WINDOWS,
target=HELPER_TARGET),
]
if "agent" in components:
outputs[group] += [
Output(identifier=f"agent_emulated_{kind}",
name=AGENT_FILE_WINDOWS.name.replace(".dll", f"-{emulated_arch}.dll"),
file=AGENT_FILE_WINDOWS,
target=AGENT_TARGET),
]
if host_os == "android" and host_arch in {"x86_64", "x86"}:
if host_os == "android" and host_arch in {"x86_64", "x86"} and "agent" in components:
outputs[OutputGroup("arm")] = [
Output(identifier="agent_emulated_legacy",
name="frida-agent-arm.so",
@@ -310,10 +358,46 @@ def setup(role: Role,
target=AGENT_TARGET),
]
raw_allowed_prebuilds = os.environ.get("FRIDA_ALLOWED_PREBUILDS")
allowed_prebuilds = set(raw_allowed_prebuilds.split(",")) if raw_allowed_prebuilds is not None else None
if glib_flavor == "upstream":
kind = "modern" if arch_is_modern(host_os, host_arch) else "legacy"
_, agent_file, gadget_file = native_file_paths(host_os)
group = OutputGroup(host_arch)
outputs[group] = []
if "agent" in components:
outputs[group] += [
Output(identifier=f"agent_{kind}",
name=arch_suffixed_name(agent_file.name, host_arch),
file=agent_file,
target=AGENT_TARGET),
]
if "gadget" in components:
outputs[group] += [
Output(identifier=f"gadget_{kind}",
name=arch_suffixed_name(gadget_file.name, host_arch),
file=gadget_file,
target=GADGET_TARGET),
]
elif apple_tls == "enabled" and "agent" in components:
kind = "modern" if arch_is_modern(host_os, host_arch) else "legacy"
_, agent_file, _ = native_file_paths(host_os)
group = OutputGroup(host_arch)
outputs.setdefault(group, [])
outputs[group] += [
Output(identifier=f"agent_{kind}",
name=arch_suffixed_name(agent_file.name, host_arch),
file=agent_file,
target=AGENT_TARGET),
]
state = State(role, builddir, top_builddir, frida_version, host_os, host_config, allowed_prebuilds, outputs)
outputs = OrderedDict((g, items) for g, items in outputs.items() if items)
raw_allowed_prebuilds = os.environ.get("FRIDA_ALLOWED_PREBUILDS")
if raw_allowed_prebuilds is not None:
allowed_prebuilds = set(filter(None, raw_allowed_prebuilds.split(",")))
else:
allowed_prebuilds = None
state = State(role, builddir, top_builddir, frida_version, host_os, host_os_family, host_config, glib_flavor, apple_tls, allowed_prebuilds, outputs)
serialized_state = base64.b64encode(pickle.dumps(state)).decode('ascii')
if missing:
@@ -349,7 +433,10 @@ class State:
top_builddir: Path
frida_version: str
host_os: str
host_os_family: str
host_config: Optional[str]
glib_flavor: str
apple_tls: str
allowed_prebuilds: Optional[set[str]]
outputs: Mapping[OutputGroup, Sequence[Output]]
@@ -429,20 +516,35 @@ def compile(privdir: Path, state: State):
host_machine = MachineSpec(state.host_os, group.arch, state.host_config, group.triplet)
if state.glib_flavor == "upstream":
allowed = None
else:
allowed = state.allowed_prebuilds
if state.glib_flavor == "upstream":
forwarded_options = [o for o in options if not is_stripped_compat_option(o)]
forwarded_options += ["-Ddefault_library=static"]
else:
forwarded_options = [o for o in options if not is_overridden_compat_option(o)]
if state.apple_tls == "enabled":
forwarded_options += ["-Dapple_tls=disabled"]
configure(sourcedir=REPO_ROOT,
builddir=workdir,
host_machine=host_machine,
environ={**build_env, **group.extra_environ},
allowed_prebuilds=state.allowed_prebuilds,
allowed_prebuilds=allowed,
apple_min_os=detect_parent_apple_min_os(top_builddir, state.host_os, state.host_os_family),
extra_meson_options=[
"-Dcompiler_backend=disabled",
"-Dassets=installed",
"-Dhelper_modern=",
"-Dhelper_legacy=",
"-Dagent_modern=",
"-Dagent_legacy=",
"-Dagent_emulated_modern=",
"-Dagent_emulated_legacy=",
*options,
*forwarded_options,
],
call_meson=call_internal_meson,
on_progress=lambda progress: None)
@@ -473,13 +575,27 @@ def compile(privdir: Path, state: State):
(state.builddir / DEPFILE_FILENAME).write_text("\n".join(depfile_lines), encoding="utf-8")
def is_stripped_compat_option(o: str) -> bool:
if is_overridden_compat_option(o):
return True
name = o.split("=", 1)[0].removeprefix("-D")
return name in STRIPPED_COMPAT_OPTIONS or name.endswith(":default_library")
def is_overridden_compat_option(o: str) -> bool:
name = o.split("=", 1)[0].removeprefix("-D")
return name in OVERRIDDEN_COMPAT_OPTIONS
def load_meson_options(top_builddir: Path,
role: Role,
subprojects: set[str]) -> Sequence[str]:
from mesonbuild import coredata
return [f"-D{adapt_key(k, role)}={v.value}" for k, v in coredata.load(top_builddir).options.items()
if option_should_be_forwarded(k, v, role, subprojects)]
optstore = coredata.load(top_builddir).optstore
return [f"-D{adapt_key(k, role)}={v.value}" for k, v in optstore.options.items()
if option_should_be_forwarded(k, v, optstore, role, subprojects)]
def adapt_key(k: "OptionKey", role: Role) -> "OptionKey":
@@ -489,40 +605,46 @@ def adapt_key(k: "OptionKey", role: Role) -> "OptionKey":
def option_should_be_forwarded(k: "OptionKey",
v: "coredata.UserOption[Any]",
v: "options.UserOption[Any]",
optstore: "options.OptionStore",
role: Role,
subprojects: set[str]) -> bool:
from mesonbuild import coredata
from mesonbuild import options
from mesonbuild.mesonlib import MachineChoice
our_project_id = "frida-core" if role == "subproject" else ""
is_for_us = k.subproject == our_project_id
is_for_child = k.subproject in subprojects
if k.is_project():
if optstore.is_project_option(k):
if is_for_us:
if k.name == "compiler_backend":
return False
tokens = k.name.split("_")
if tokens[0] in {"helper", "agent"} and tokens[-1] in {"modern", "legacy"}:
return False
if k.subproject and k.machine is not coredata.MachineChoice.HOST:
if k.subproject and k.machine is not MachineChoice.HOST:
return False
return is_for_us or is_for_child
if coredata.CoreData.is_per_machine_option(k):
return k.machine is coredata.MachineChoice.BUILD
if optstore.is_per_machine_option(k):
# The compat build generates its own machine files, so forwarding the
# parent's build-machine options would inject a foreign toolchain. In a
# native compat build that conjures a compiler-less build machine and
# breaks any native: true target.
return False
if k.is_builtin():
if optstore.is_builtin_option(k):
if k.name in {"buildtype", "genvslite"}:
return False
if not str(v.value):
return False
if not is_for_child and role == "subproject" and k.subproject in {"", our_project_id}:
if not coredata.BUILTIN_OPTIONS[k.as_root()].yielding:
if not options.BUILTIN_OPTIONS[k.evolve(subproject=None, machine=MachineChoice.HOST)].yielding:
return k.subproject == our_project_id
return True
if k.module == "python":
if k.get_module_prefix() == "python":
if k.name == "install_env" and v.value == "prefix":
return False
if not str(v.value):
@@ -582,6 +704,19 @@ def ensure_submodules_checked_out(releng_location: Path):
check=True)
def detect_parent_apple_min_os(top_builddir: Path,
host_os: str,
host_os_family: str) -> Optional[Mapping[str, str]]:
if host_os_family != "darwin":
return None
pattern = re.compile(r"-target['\"],\s*['\"][^-]+-apple-" + re.escape(host_os) + r"([0-9.]+)")
for native_file in top_builddir.glob(f"frida-{host_os}-*.txt"):
match = pattern.search(native_file.read_text(encoding="utf-8"))
if match is not None:
return {host_os: match.group(1)}
return None
def detect_relevant_subprojects(releng_location: Path) -> dict[str, Path]:
subprojects = detect_relevant_subprojects_in(REPO_ROOT, releng_location)
gum_location = subprojects.get("frida-gum")
@@ -632,9 +767,48 @@ def detect_mingw_toolchain_for(arch: str) -> Tuple[bool, Optional[str]]:
return (found, triplet)
def arch_is_modern(host_os: str, host_arch: str) -> bool:
if host_os in {"macos", "ios", "tvos"} and host_arch == "arm64":
return host_arch == "arm64e"
return host_arch in MODERN_ARCHS
def native_file_paths(host_os: str) -> Tuple[Path, Path, Path]:
if host_os == "windows":
return (HELPER_FILE_WINDOWS, AGENT_FILE_WINDOWS, GADGET_FILE_WINDOWS)
elif host_os in {"macos", "ios", "tvos"}:
return (HELPER_FILE_UNIX, AGENT_FILE_DARWIN, GADGET_FILE_DARWIN)
else:
return (HELPER_FILE_UNIX, AGENT_FILE_ELF, GADGET_FILE_ELF)
def arch_suffixed_name(name: str, arch: str) -> str:
stem, _, ext = name.partition(".")
if ext:
return f"{stem}-{arch}.{ext}"
return f"{stem}-{arch}"
STATE_FILENAME = "state.dat"
DEPFILE_FILENAME = "compat.deps"
STRIPPED_COMPAT_OPTIONS = {
"default_library",
"frida-gum:gumjs",
"frida-gum:inspector",
}
OVERRIDDEN_COMPAT_OPTIONS = {
"compiler_backend",
"assets",
"helper_modern",
"helper_legacy",
"agent_modern",
"agent_legacy",
"agent_emulated_modern",
"agent_emulated_legacy",
}
HELPER_TARGET = "frida-helper"
HELPER_FILE_WINDOWS = Path("src") / "frida-helper.exe"
HELPER_FILE_UNIX = Path("src") / "frida-helper"
@@ -659,6 +833,8 @@ MSVS_ENVVARS = {
"LIB",
}
MODERN_ARCHS = {"x86_64", "arm64", "arm64e", "mips64", "riscv64"}
MINGW_ARCHS = {
"x86": "i686",
"arm64": "aarch64",
+15
View File
@@ -0,0 +1,15 @@
import os
import shutil
import sys
src = sys.argv[1]
install_subdir = sys.argv[2]
install_name = sys.argv[3]
destdir_prefix = os.environ["MESON_INSTALL_DESTDIR_PREFIX"]
dst_dir = os.path.join(destdir_prefix, install_subdir)
dst = os.path.join(dst_dir, install_name)
os.makedirs(dst_dir, exist_ok=True)
shutil.copy2(src, dst)
print(f"Installing {os.path.basename(src)} to {dst_dir}/{install_name}")
+47 -3
View File
@@ -8,6 +8,7 @@ helper_depends = []
agent_modern = get_option('agent_modern')
agent_legacy = get_option('agent_legacy')
agent_compat = ''
agent_host_compat = ''
agent_emulated_modern = get_option('agent_emulated_modern')
agent_emulated_legacy = get_option('agent_emulated_legacy')
agent_depends = []
@@ -28,6 +29,9 @@ if no_overridden_compat_bits
build_py = [python, files('build.py')]
components = []
if have_local_backend
components += ['helper', 'agent']
endif
if build_gadget
components += 'gadget'
endif
@@ -42,11 +46,14 @@ if no_overridden_compat_bits
meson.global_build_root(),
frida_version,
host_os,
host_os_family,
host_abi,
host_config,
','.join(get_option('compat')),
get_option('assets'),
assets_mode,
','.join(components),
have_shared_glib ? 'upstream' : 'frida',
use_apple_tls ? 'enabled' : 'disabled',
'--',
'>>>', cc.cmd_array(), '<<<',
'>>>', cpp.cmd_array(), '<<<',
@@ -64,18 +71,27 @@ if no_overridden_compat_bits
depfile = lines[4]
state = lines[5]
native_arch_suffix = '-' + host_abi
if host_os_family == 'darwin'
install = false
install_dir = asset_dir
else
install = true
install_dir = []
i = 0
foreach varname : varnames
if varname != 'arch_support_bundle' and (get_option('assets') == 'installed' or varname.startswith('gadget_'))
output_name = outputs[i]
needs_rename = output_name.contains(native_arch_suffix) or varname.contains('_emulated_')
should_install = varname != 'arch_support_bundle' \
and not needs_rename \
and (assets_mode == 'installed' or varname.startswith('gadget_'))
if should_install
install_dir += varname.endswith('_modern') ? asset_dir_modern : asset_dir_legacy
else
install_dir += false
endif
i += 1
endforeach
endif
@@ -96,6 +112,7 @@ if no_overridden_compat_bits
foreach output : arch_support.to_list()
outpath = output.full_path()
varname = varnames[i]
output_name = outputs[i]
set_variable(varname, outpath)
@@ -103,7 +120,11 @@ if no_overridden_compat_bits
helper_compat = outpath
helper_depends += arch_support
elif varname.startswith('agent_')
agent_compat = outpath
if output_name.contains('-' + host_abi + '.')
agent_host_compat = outpath
else
agent_compat = outpath
endif
agent_depends += arch_support
elif varname.startswith('gadget_')
gadget_compat = outpath
@@ -115,6 +136,29 @@ if no_overridden_compat_bits
assert(false, f'unexpected variable name: @varname@')
endif
if host_os_family != 'darwin'
rename_target_dir = ''
if output_name.contains(native_arch_suffix)
rename_target_dir = asset_dir
elif varname.contains('_emulated_')
emulated_arch = output_name.split('-')[-1].split('.')[0]
rename_target_dir = root_asset_dir / emulated_arch
endif
if rename_target_dir != ''
install_name = output_name.replace(native_arch_suffix, '')
foreach arch_suffix : ['-x86_64', '-x86', '-arm64', '-arm']
install_name = install_name.replace(arch_suffix, '')
endforeach
if assets_mode == 'installed' or varname.startswith('gadget_')
meson.add_install_script(python, files('install-renamed.py'),
outpath,
rename_target_dir,
install_name,
)
endif
endif
endif
i += 1
endforeach
else
Vendored
+2 -2
View File
@@ -1,12 +1,12 @@
#!/bin/sh
[ -z "$PYTHON" ] && PYTHON=$(which python3 >/dev/null && echo python3 || echo python)
[ -z "$PYTHON" ] && PYTHON=$(command -v python3 >/dev/null && echo python3 || echo python)
cd $(dirname $0)
srcroot=$(pwd)
if [ ! -f releng/meson/meson.py ]; then
if [ ! -f releng/meson/meson.py ] || git submodule status 2>/dev/null | grep -q '^-'; then
git submodule update --init --recursive --depth 1 || exit $?
fi
+2 -15
View File
@@ -4,27 +4,14 @@ inject_sources = [
]
system_vala_args = []
if host_os_family != 'windows'
system_vala_args += ['--pkg=posix']
endif
extra_link_args = []
extra_link_depends = []
if host_os_family == 'darwin'
symlist = 'frida-inject.symbols'
extra_link_args += ['-Wl,-exported_symbols_list,' + meson.current_source_dir() / symlist]
extra_link_depends += [symlist]
elif host_os_family != 'windows'
symscript = 'frida-inject.version'
extra_link_args += ['-Wl,--version-script,' + meson.current_source_dir() / symscript]
extra_link_depends += [symscript]
endif
raw_inject = executable('frida-inject-raw', inject_sources,
vala_args: [core_vala_args, system_vala_args],
link_args: extra_link_args,
link_depends: extra_link_depends,
link_args: executable_no_exports_link_args,
link_depends: executable_no_exports_link_depends,
dependencies: [json_glib_dep, core_dep],
)
+46 -2
View File
@@ -9,8 +9,48 @@
# include <signal.h>
# endif
#endif
#ifdef HAVE_GIOOPENSSL
#if defined (HAVE_GIOAPPLE)
# include <gioapple.h>
#elif defined (HAVE_GIOOPENSSL)
# include <gioopenssl.h>
#endif
#ifdef HAVE_PROSPERO
# include "frida-prospero.h"
# include <unistd.h>
#endif
#ifdef HAVE_PROSPERO
FridaProsperoAgentArgs frida_prospero_agent_args = { .fifo_fd = -1 };
int
main (int argc, char * argv[], char * envp[])
{
FridaProsperoAgentArgs * args = &frida_prospero_agent_args;
FridaProsperoInjectorState injector_state;
guint8 hello_byte = FRIDA_PROSPERO_HELLO_BYTE;
FridaUnloadPolicy unload_policy = FRIDA_UNLOAD_POLICY_IMMEDIATE;
guint8 policy_byte;
guint32 worker_id;
injector_state.fifo_fd = args->fifo_fd;
injector_state.agent_ctrlfd = args->agent_ctrlfd;
injector_state.mapped_range = &args->mapped_range;
write (injector_state.fifo_fd, &hello_byte, sizeof (hello_byte));
frida_agent_main (GSIZE_TO_POINTER (args->agent_parameters), &unload_policy, &injector_state);
policy_byte = unload_policy;
worker_id = gum_process_get_current_thread_id ();
write (injector_state.fifo_fd, &policy_byte, sizeof (policy_byte));
write (injector_state.fifo_fd, &worker_id, sizeof (worker_id));
close (injector_state.fifo_fd);
return 0;
}
#endif
void
@@ -31,7 +71,9 @@ _frida_agent_environment_init (void)
g_thread_set_garbage_handler (_frida_agent_on_pending_thread_garbage, NULL);
#ifdef HAVE_GIOOPENSSL
#if defined (HAVE_GIOAPPLE)
g_io_module_apple_register ();
#elif defined (HAVE_GIOOPENSSL)
g_io_module_openssl_register ();
#endif
@@ -51,6 +93,8 @@ void
_frida_agent_environment_deinit (void)
{
#ifndef HAVE_MUSL
frida_libc_shim_prepare_to_deinit ();
gum_shutdown ();
gio_shutdown ();
glib_shutdown ();
+443 -45
View File
@@ -1,3 +1,5 @@
using Efni;
namespace Frida.Agent {
public void main (string agent_parameters, ref Frida.UnloadPolicy unload_policy, void * injector_state) {
if (Runner.shared_instance == null)
@@ -78,6 +80,7 @@ namespace Frida.Agent {
private ExitMonitor? exit_monitor;
private Gum.Interceptor interceptor;
private Gum.Exceptor? exceptor;
private bool code_memory_delegated = false;
private uint child_gating_subscriber_count = 0;
private ForkMonitor? fork_monitor;
@@ -100,6 +103,9 @@ namespace Frida.Agent {
private SpawnMonitor? spawn_monitor;
private ThreadSuspendMonitor? thread_suspend_monitor;
private UnwindSitter? unwind_sitter;
#if ANDROID && (ARM || ARM64)
private NativeCloakMirror? native_cloak_mirror;
#endif
private delegate void CompletionNotify ();
@@ -117,15 +123,48 @@ namespace Frida.Agent {
void * opaque_injector_state) {
Environment._init ();
apply_linker_notifier_offsets (agent_parameters);
{
Gum.MemoryRange? mapped_range = null;
#if WINDOWS
var windows_state = (WindowsInjectorState *) opaque_injector_state;
if (windows_state != null) {
if (windows_state.mapped_range.size != 0)
mapped_range = windows_state.mapped_range;
Gum.Cloak.add_range (windows_state.worker_range);
if (windows_state.dbghelp_range.size != 0) {
Gum.Cloak.add_range (windows_state.dbghelp_range);
Gum.Cloak.add_range (windows_state.symsrv_range);
Gum.Windows.use_dbghelp_modules ((void *) windows_state.dbghelp_range.base_address,
(void *) windows_state.symsrv_range.base_address);
}
}
#endif
#if DARWIN
var injector_state = (DarwinInjectorState *) opaque_injector_state;
if (injector_state != null)
mapped_range = injector_state.mapped_range;
#endif
#if LINUX
var linux_state = (LinuxInjectorState *) opaque_injector_state;
if (linux_state != null && linux_state.mapped_size != 0) {
mapped_range = Gum.MemoryRange () {
base_address = linux_state.mapped_base,
size = linux_state.mapped_size,
};
}
#endif
#if PROSPERO
var prospero_state = (ProsperoInjectorState *) opaque_injector_state;
if (prospero_state != null)
mapped_range = prospero_state.mapped_range;
#endif
if (cached_agent_path == null) {
cached_agent_range = detect_own_range_and_path (mapped_range, out cached_agent_path);
Gum.Cloak.add_range (cached_agent_range);
@@ -155,6 +194,19 @@ namespace Frida.Agent {
}
#endif
#if PROSPERO
string? agent_parameters_with_transport_uri = null;
if (prospero_state != null) {
int agent_ctrlfd = prospero_state.agent_ctrlfd;
prospero_state.agent_ctrlfd = -1;
fdt_padder.move_descriptor_if_needed (ref agent_ctrlfd);
agent_parameters_with_transport_uri = "socket:%d%s".printf (agent_ctrlfd, agent_parameters);
agent_parameters = agent_parameters_with_transport_uri;
}
#endif
var ignore_scope = new ThreadIgnoreScope (FRIDA_THREAD);
shared_instance = new Runner (agent_parameters, cached_agent_path, cached_agent_range);
@@ -178,6 +230,15 @@ namespace Frida.Agent {
return;
} else {
release_shared_instance ();
#if WINDOWS
if (windows_state != null) {
Gum.Cloak.remove_range (windows_state.worker_range);
if (windows_state.dbghelp_range.size != 0) {
Gum.Cloak.remove_range (windows_state.dbghelp_range);
Gum.Cloak.remove_range (windows_state.symsrv_range);
}
}
#endif
}
ignore_scope = null;
@@ -186,6 +247,25 @@ namespace Frida.Agent {
Environment._deinit ();
}
private static void apply_linker_notifier_offsets (string agent_parameters) {
foreach (unowned string token in agent_parameters.split ("|")) {
if (!token.has_prefix ("linker-notifier-offsets:"))
continue;
uint[] offsets = {};
foreach (unowned string entry in token[24:].split (",")) {
uint offset;
if (uint.try_parse (entry, out offset))
offsets += offset;
}
if (offsets.length != 0)
Gum.ModuleRegistry.set_rtld_notifier_offsets (offsets);
return;
}
}
public static void resume_after_transition (ref Frida.UnloadPolicy unload_policy, void * opaque_injector_state) {
{
#if LINUX || FREEBSD
@@ -245,25 +325,12 @@ namespace Frida.Agent {
}
~Runner () {
var interceptor = this.interceptor;
interceptor.begin_transaction ();
remove_instrumentation ();
disable_child_gating ();
exceptor = null;
exit_monitor = null;
interceptor.end_transaction ();
interceptor.begin_transaction ();
thread_suspend_monitor = null;
unwind_sitter = null;
if (code_memory_delegated)
Gum.Memory.set_hooks (null);
invalidate_dbus_context ();
interceptor.end_transaction ();
}
private void run (owned FileDescriptorTablePadder padder) throws Error {
@@ -282,58 +349,69 @@ namespace Frida.Agent {
private async void start (owned FileDescriptorTablePadder padder) {
string[] tokens = agent_parameters.split ("|");
unowned string transport_uri = tokens[0];
bool enable_exceptor = true;
#if DARWIN
enable_exceptor = !Gum.Darwin.query_hardened ();
#endif
var exceptor_mode = Gum.ExceptorMode.FULL;
bool enable_unwind_broker = true;
bool enable_exit_monitor = true;
bool enable_thread_suspend_monitor = true;
bool enable_unwind_sitter = true;
#if ANDROID && (ARM || ARM64)
bool in_emulated_realm = false;
#endif
foreach (unowned string option in tokens[1:]) {
if (option == "eternal")
ensure_eternalized ();
else if (option == "sticky")
stop_thread_on_unload = false;
else if (option == "exceptor:off")
enable_exceptor = false;
exceptor_mode = OFF;
else if (option == "exceptor:handler-only")
exceptor_mode = HANDLER_ONLY;
else if (option == "unwind-broker:off")
enable_unwind_broker = false;
else if (option == "exit-monitor:off")
enable_exit_monitor = false;
else if (option == "thread-suspend-monitor:off")
enable_thread_suspend_monitor = false;
else if (option == "unwind-sitter:off")
enable_unwind_sitter = false;
#if ANDROID && (ARM || ARM64)
else if (option == "realm:emulated")
in_emulated_realm = true;
#endif
}
if (!enable_exceptor)
Gum.Exceptor.disable ();
if (exceptor_mode != FULL)
Gum.Exceptor.set_mode (exceptor_mode);
{
var interceptor = Gum.Interceptor.obtain ();
interceptor.begin_transaction ();
if (enable_exit_monitor)
exit_monitor = new ExitMonitor (this, main_context);
if (enable_thread_suspend_monitor)
thread_suspend_monitor = new ThreadSuspendMonitor (this);
if (enable_unwind_sitter)
unwind_sitter = new UnwindSitter (this);
this.interceptor = interceptor;
this.exceptor = Gum.Exceptor.obtain ();
interceptor.end_transaction ();
}
interceptor = Gum.Interceptor.obtain ();
try {
yield setup_connection_with_transport_uri (transport_uri);
#if ANDROID && (ARM || ARM64)
if (in_emulated_realm)
yield connect_to_native_realm ();
#endif
} catch (Error e) {
start_error = e;
main_loop.quit ();
return;
}
if (!can_allocate_code ())
delegate_code_memory_to_controller ();
interceptor.begin_transaction ();
if (enable_exit_monitor)
exit_monitor = new ExitMonitor (this, main_context);
if (enable_thread_suspend_monitor)
thread_suspend_monitor = new ThreadSuspendMonitor (this);
if (enable_unwind_broker)
unwind_sitter = new UnwindSitter (this);
exceptor = Gum.Exceptor.obtain ();
interceptor.end_transaction ();
Gum.ScriptBackend.get_scheduler ().push_job_on_js_thread (Priority.DEFAULT, () => {
schedule_idle (start.callback);
});
@@ -342,6 +420,77 @@ namespace Frida.Agent {
padder = null;
}
#if ANDROID && (ARM || ARM64)
private async void connect_to_native_realm () throws Error {
try {
NativeCloak cloak = yield connection.get_proxy (null, ObjectPath.NATIVE_CLOAK, DO_NOT_LOAD_PROPERTIES,
null);
native_cloak_mirror = new NativeCloakMirror (cloak);
native_cloak_mirror.activate ();
NativeThreadRegistry registry = yield connection.get_proxy (null, ObjectPath.NATIVE_THREAD_REGISTRY,
DO_NOT_LOAD_PROPERTIES, null);
Gum.ThreadRegistry.set_provider (new NativeThreadProvider (registry));
} catch (GLib.Error e) {
throw new Error.TRANSPORT ("%s", e.message);
}
}
#endif
private static bool can_allocate_code () {
size_t page_size = Gum.query_page_size ();
void * page = Gum.Memory.allocate (null, page_size, page_size, READ | EXECUTE);
if (page == null)
return false;
Gum.Memory.free (page, page_size);
return true;
}
private void delegate_code_memory_to_controller () {
var hooks = Gum.MemoryHooks () {
allocate = allocate_via_controller,
protect = protect_via_controller,
user_data = this,
};
Gum.Memory.set_hooks (hooks);
code_memory_delegated = true;
}
private static void * allocate_via_controller (Gum.AddressSpec? spec, size_t size, size_t alignment,
Gum.PageProtection prot, void * user_data) {
unowned Runner self = (Runner) user_data;
uint64 near_address = 0;
uint64 max_distance = 0;
if (spec != null) {
near_address = (uint64) spec.near_address;
max_distance = spec.max_distance;
}
try {
return (void *) self.controller.allocate_memory (get_process_id (), near_address, max_distance, size,
alignment, prot, null);
} catch (GLib.Error e) {
return null;
}
}
private static bool protect_via_controller (void * address, size_t size, Gum.PageProtection prot,
void * user_data) {
unowned Runner self = (Runner) user_data;
try {
self.controller.protect_memory (get_process_id (), (uint64) address, size, prot, null);
return true;
} catch (GLib.Error e) {
return false;
}
}
private void keep_running_eternalized () {
agent_gthread = new Thread<bool> ("frida-eternal-agent", () => {
var ignore_scope = new ThreadIgnoreScope (FRIDA_THREAD);
@@ -1003,6 +1152,10 @@ namespace Frida.Agent {
if (unloading)
throw new Error.INVALID_OPERATION ("Agent is already unloading");
unloading = true;
if (code_memory_delegated && !is_eternal)
yield remove_instrumentation_while_delegated ();
perform_unload.begin ();
}
@@ -1048,6 +1201,34 @@ namespace Frida.Agent {
}
}
private async void remove_instrumentation_while_delegated () {
yield close_all_sessions ();
remove_instrumentation ();
}
private void remove_instrumentation () {
var interceptor = this.interceptor;
interceptor.begin_transaction ();
disable_child_gating ();
exceptor = null;
exit_monitor = null;
interceptor.end_transaction ();
interceptor.begin_transaction ();
thread_suspend_monitor = null;
unwind_sitter = null;
interceptor.end_transaction ();
Gum.shutdown ();
}
private void ensure_eternalized () {
if (!_is_eternal) {
_is_eternal = true;
@@ -1256,7 +1437,7 @@ namespace Frida.Agent {
private void on_connection_closed (DBusConnection connection, bool remote_peer_vanished, GLib.Error? error) {
bool closed_by_us = !remote_peer_vanished && error == null;
if (!closed_by_us)
if (!closed_by_us && !code_memory_delegated)
unload.begin (null);
Promise<bool> operation = null;
@@ -1328,6 +1509,8 @@ namespace Frida.Agent {
private Socket? emulated_socket;
private BridgeState? emulated_bridge_state;
private Thread<void>? emulated_worker;
private NativeCloakService? native_cloak;
private NativeThreadRegistryService? native_thread_registry;
private async AgentSessionProvider? try_get_emulated_provider (Cancellable? cancellable) throws IOError {
if (get_emulated_request == null)
@@ -1367,6 +1550,8 @@ namespace Frida.Agent {
emulated_agent = nb_api.load_library_ext (emulated_agent_path, RTLD_LAZY, classloader_namespace);
} else {
emulated_agent = nb_api.load_library (emulated_agent_path, RTLD_LAZY);
if (emulated_agent == null && nb_api.load_library_ext != null)
emulated_agent = nb_api.load_library_ext (emulated_agent_path, RTLD_LAZY, null);
}
if (emulated_agent == null)
throw new Error.NOT_SUPPORTED ("Process is not using emulation");
@@ -1406,6 +1591,7 @@ namespace Frida.Agent {
* code being discarded, which seems like an edge-case the translator doesn't handle.
*/
parameters.append ("|exceptor:off|exit-monitor:off");
parameters.append ("|realm:emulated");
emulated_bridge_state = new BridgeState (parameters.str);
@@ -1414,6 +1600,13 @@ namespace Frida.Agent {
var connection = yield new DBusConnection (stream, ServerGuid.HOST_SESSION_SERVICE,
AUTHENTICATION_SERVER | AUTHENTICATION_ALLOW_ANONYMOUS, null, cancellable);
native_cloak = new NativeCloakService ();
connection.register_object (ObjectPath.NATIVE_CLOAK, (NativeCloak) native_cloak);
native_thread_registry = new NativeThreadRegistryService ();
connection.register_object (ObjectPath.NATIVE_THREAD_REGISTRY,
(NativeThreadRegistry) native_thread_registry);
AgentSessionProvider provider = yield connection.get_proxy (null, ObjectPath.AGENT_SESSION_PROVIDER,
DO_NOT_LOAD_PROPERTIES, cancellable);
@@ -1461,6 +1654,16 @@ namespace Frida.Agent {
emulated_worker = null;
}
if (native_thread_registry != null) {
native_thread_registry.close ();
native_thread_registry = null;
}
if (native_cloak != null) {
native_cloak.close ();
native_cloak = null;
}
emulated_socket = null;
if (emulated_agent != null) {
@@ -1621,6 +1824,201 @@ namespace Frida.Agent {
}
#endif
#if ANDROID && (X86 || X86_64)
private sealed class NativeCloakService : Object, NativeCloak, Gum.Cloak.Observer {
private MainContext main_context = MainContext.ref_thread_default ();
private bool observing = false;
public uint[] activate (Cancellable? cancellable) {
Gum.Cloak.add_observer (this);
observing = true;
uint[] threads = {};
Gum.Cloak.enumerate_threads (id => {
threads += (uint) id;
return true;
});
return threads;
}
public void close () {
if (!observing)
return;
Gum.Cloak.remove_observer (this);
observing = false;
}
public void thread_added (Gum.ThreadId id) {
schedule (() => {
thread_cloaked ((uint) id);
return Source.REMOVE;
});
}
public void thread_removed (Gum.ThreadId id) {
schedule (() => {
thread_uncloaked ((uint) id);
return Source.REMOVE;
});
}
private void schedule (owned SourceFunc function) {
var source = new IdleSource ();
source.set_callback ((owned) function);
source.attach (main_context);
}
}
private sealed class NativeThreadRegistryService : Object, NativeThreadRegistry {
private unowned Gum.ThreadRegistry? registry;
private ulong added_handler = 0;
private ulong removed_handler = 0;
private ulong renamed_handler = 0;
public NativeThreadInfo[] activate (Cancellable? cancellable) {
registry = Gum.ThreadRegistry.obtain ();
NativeThreadInfo[] threads = {};
registry.lock ();
added_handler = registry.thread_added.connect (on_thread_added);
removed_handler = registry.thread_removed.connect (on_thread_removed);
renamed_handler = registry.thread_renamed.connect (on_thread_renamed);
registry.enumerate_threads (thread => {
threads += NativeThreadInfo ((uint) thread.id, thread.name ?? "");
return true;
});
registry.unlock ();
return threads;
}
public void deactivate (Cancellable? cancellable) {
close ();
}
public void close () {
if (registry == null)
return;
registry.disconnect (added_handler);
registry.disconnect (removed_handler);
registry.disconnect (renamed_handler);
registry = null;
}
private void on_thread_added (Gum.ThreadDetails thread) {
thread_added (NativeThreadInfo ((uint) thread.id, thread.name ?? ""));
}
private void on_thread_removed (Gum.ThreadDetails thread) {
thread_removed ((uint) thread.id);
}
private void on_thread_renamed (Gum.ThreadDetails thread, string? previous_name) {
thread_renamed ((uint) thread.id, thread.name ?? "");
}
}
#endif
#if ANDROID && (ARM || ARM64)
private sealed class NativeCloakMirror : Object {
public NativeCloak native_cloak {
get;
construct;
}
public NativeCloakMirror (NativeCloak native_cloak) {
Object (native_cloak: native_cloak);
}
public void activate () throws GLib.Error {
native_cloak.thread_cloaked.connect (on_thread_cloaked);
native_cloak.thread_uncloaked.connect (on_thread_uncloaked);
foreach (uint id in native_cloak.activate (null))
on_thread_cloaked (id);
}
private void on_thread_cloaked (uint id) {
if (!Gum.Cloak.has_thread (id))
Gum.Cloak.add_thread (id);
}
private void on_thread_uncloaked (uint id) {
Gum.Cloak.remove_thread (id);
}
}
private sealed class NativeThreadProvider : Object, Gum.ThreadProvider {
public NativeThreadRegistry native_registry {
get;
construct;
}
private unowned Gum.ThreadRegistry registry;
private ulong added_handler = 0;
private ulong removed_handler = 0;
private ulong renamed_handler = 0;
public NativeThreadProvider (NativeThreadRegistry native_registry) {
Object (native_registry: native_registry);
}
public void activate (Gum.ThreadRegistry registry) {
this.registry = registry;
added_handler = native_registry.thread_added.connect (add_thread);
removed_handler = native_registry.thread_removed.connect (on_thread_removed);
renamed_handler = native_registry.thread_renamed.connect (on_thread_renamed);
NativeThreadInfo[] threads;
try {
threads = native_registry.activate (null);
} catch (GLib.Error e) {
return;
}
foreach (var thread in threads)
add_thread (thread);
}
public void deactivate () {
native_registry.disconnect (added_handler);
native_registry.disconnect (removed_handler);
native_registry.disconnect (renamed_handler);
try {
native_registry.deactivate (null);
} catch (GLib.Error e) {
}
}
private void add_thread (NativeThreadInfo thread) {
var details = Gum.ThreadDetails ();
details.id = thread.id;
if (thread.name != "") {
details.name = thread.name;
details.flags = NAME;
}
registry.register (details);
}
private void on_thread_removed (uint id) {
registry.unregister (id);
}
private void on_thread_renamed (uint id, string name) {
registry.rename (id, (name != "") ? name : null);
}
}
#endif
private sealed class LiveAgentSession : BaseAgentSession {
public uint registration_id {
get;
+14
View File
@@ -0,0 +1,14 @@
FRIDA_1.0 {
global:
frida_agent_main;
local:
*;
};
GLIBC_2.2.5 {
local:
fopen*;
fseeko*;
ftello*;
} FRIDA_1.0;
+8
View File
@@ -0,0 +1,8 @@
{
global:
frida_agent_main;
frida_prospero_agent_args;
local:
*;
};
+124 -90
View File
@@ -1,98 +1,132 @@
agent_sources = [
'agent.vala',
'agent-glue.c',
]
force_cpp_linking = have_v8
if force_cpp_linking
agent_sources += ['dummy.cpp']
endif
extra_vala_args = []
extra_link_args = []
extra_link_depends = []
if host_os_family != 'windows'
extra_vala_args += '--pkg=posix'
endif
if host_os_family == 'darwin'
extra_vala_args += '--pkg=frida-gum-darwin-1.0'
endif
if host_os_family == 'windows'
if host_toolchain != 'microsoft'
symfile = (host_arch == 'x86') ? 'frida-agent-x86.symbols' : 'frida-agent.symbols'
extra_link_args += '-Wl,--retain-symbols-file,' + meson.current_source_dir() / symfile
extra_link_depends += symfile
endif
elif host_os_family == 'darwin'
extra_link_args += '-Wl,-exported_symbol,_frida_agent_main'
else
symscript = (host_os == 'android') ? 'frida-agent-android.version' : 'frida-agent.version'
extra_link_args += '-Wl,--version-script,' + meson.current_source_dir() / symscript
extra_link_depends += symscript
endif
if host_os_family in ['linux', 'qnx']
# For our libc shim to be effective.
extra_link_args += '-Wl,-Bsymbolic'
endif
raw_agent = shared_library('frida-agent-raw', agent_sources,
name_prefix: 'lib',
c_args: frida_component_cflags,
vala_header: 'frida-agent.h',
vala_args: [base_vala_args, payload_vala_args, extra_vala_args],
link_args: extra_link_args,
link_depends: extra_link_depends,
vs_module_defs: 'frida-agent.def',
override_options: ['b_asneeded=true'],
dependencies: [gio_dep, gee_dep, gum_dep, gumjs_dep, base_dep, pipe_dep, tls_provider_dep, payload_dep],
)
operations = ['--move', 'constructor', 'frida_libc_shim_init', 'first']
if host_os_family != 'darwin'
operations += ['--move', 'destructor', 'frida_libc_shim_deinit', 'first']
endif
modulated_agent = custom_target('frida-agent-modulated',
input: raw_agent,
output: 'libfrida-agent-modulated' + shlib_suffix,
command: modulate + operations,
)
need_universal_agent = host_os_family == 'darwin' \
and get_option('assets') == 'installed' \
and assets_mode == 'installed' \
and agent_compat != ''
if host_os_family == 'darwin'
identity = 'FridaAgent'
else
identity = agent_name
endif
agent = custom_target('frida-agent',
input: modulated_agent,
output: need_universal_agent ? f'@agent_name@-@host_abi@' : agent_name,
command: post_process + ['shared-library', identity],
build_by_default: true,
install: get_option('assets') == 'installed' and not need_universal_agent,
install_dir: asset_dir,
)
# When compat provides the host-arch agent (e.g. when use_apple_tls is on, so
# the agent has to be built separately to avoid pulling in Apple frameworks),
# skip building it here.
build_agent = agent_host_compat == ''
agent_dep = declare_dependency(link_with: agent)
if build_agent
agent_sources = [
'agent.vala',
'agent-glue.c',
]
if need_universal_agent
custom_target('frida-agent-universal',
input: agent,
output: agent_name,
command: [
lipo,
'@INPUT@',
agent_compat,
'-create',
'-output', meson.current_build_dir() / agent_name,
],
depends: agent_depends,
install: true,
force_cpp_linking = have_v8
if force_cpp_linking
agent_sources += ['dummy.cpp']
endif
extra_vala_args = []
extra_link_args = []
extra_link_depends = []
if host_os_family != 'windows'
extra_vala_args += '--pkg=posix'
endif
if host_os_family == 'windows'
extra_vala_args += '--pkg=frida-gum-windows-1.0'
endif
if host_os_family == 'darwin'
extra_vala_args += '--pkg=frida-gum-darwin-1.0'
endif
if host_os_family == 'windows'
if host_toolchain != 'microsoft'
symfile = (host_arch == 'x86') ? 'frida-agent-x86.symbols' : 'frida-agent.symbols'
extra_link_args += '-Wl,--retain-symbols-file,' + meson.current_source_dir() / symfile
extra_link_depends += symfile
endif
elif host_os_family == 'darwin'
extra_link_args += '-Wl,-exported_symbol,_frida_agent_main'
else
if libc == 'glibc'
symscript = 'frida-agent-glibc.version'
elif host_os == 'android'
symscript = 'frida-agent-android.version'
elif host_is_prospero
symscript = 'frida-agent-prospero.version'
else
symscript = 'frida-agent.version'
endif
extra_link_args += '-Wl,--version-script,' + meson.current_source_dir() / symscript
extra_link_depends += symscript
endif
if host_os_family in ['linux', 'qnx']
# For our libc shim to be effective.
extra_link_args += '-Wl,-Bsymbolic'
endif
extra_link_args += prospero_crt_link_args
raw_agent = shared_library('frida-agent-raw', agent_sources,
name_prefix: 'lib',
c_args: frida_component_cflags,
vala_header: 'frida-agent.h',
vala_args: [base_vala_args, payload_vala_args, extra_vala_args],
link_args: extra_link_args,
link_depends: extra_link_depends,
vs_module_defs: 'frida-agent.def',
override_options: ['b_asneeded=true'],
dependencies: [gio_dep, gee_dep, gum_dep, gumjs_dep, base_dep, pipe_dep, tls_provider_dep, payload_dep],
)
operations = ['--move', 'constructor', 'frida_libc_shim_init', 'first']
if host_os_family != 'darwin'
operations += ['--move', 'destructor', 'frida_libc_shim_deinit', 'first']
endif
modulated_agent = custom_target('frida-agent-modulated',
input: raw_agent,
output: 'libfrida-agent-modulated' + shlib_suffix,
command: modulate + operations,
)
if host_os_family == 'darwin'
identity = 'FridaAgent'
else
identity = agent_name
endif
agent = custom_target('frida-agent',
input: modulated_agent,
output: need_universal_agent ? f'@agent_name@-@host_abi@' : agent_name,
command: post_process + ['shared-library', identity],
build_by_default: true,
install: assets_mode == 'installed' and not need_universal_agent,
install_dir: asset_dir,
)
endif
if need_universal_agent
if build_agent
custom_target('frida-agent-universal',
input: agent,
output: agent_name,
command: [
lipo,
'@INPUT@',
agent_compat,
'-create',
'-output', meson.current_build_dir() / agent_name,
],
depends: agent_depends,
install: true,
install_dir: asset_dir,
)
else
custom_target('frida-agent-universal',
output: agent_name,
command: [
lipo,
agent_host_compat,
agent_compat,
'-create',
'-output', meson.current_build_dir() / agent_name,
],
depends: agent_depends,
install: true,
install_dir: asset_dir,
)
endif
endif
+53
View File
@@ -0,0 +1,53 @@
namespace Frida {
public class AssetLocation : Object {
public string libdir {
get;
construct;
}
public static AssetLocation detect () {
string? module_path = null;
Gum.Address our_address = Gum.Address.from_pointer (Gum.strip_code_pointer ((void *) detect));
Gum.Process.enumerate_modules ((details) => {
var range = details.range;
if (our_address >= range.base_address && our_address < range.base_address + range.size) {
module_path = details.path;
return false;
}
return true;
});
assert (module_path != null);
string libdir = Path.get_dirname (module_path);
string basename = Path.get_basename (libdir).down ();
if (basename == "bin" || basename == "sbin") {
string parent = Path.get_dirname (libdir);
string candidate = Path.build_filename (parent, Config.FRIDA_LIBDIR);
if (FileUtils.test (candidate, IS_DIR))
libdir = candidate;
}
return new AssetLocation (libdir);
}
private AssetLocation (string libdir) {
Object (libdir: libdir);
}
public string derive_asset_path (string arch, string filename) {
string root = Path.build_filename (libdir, Config.FRIDA_LIBDIR_NAME);
if (FileUtils.test (root, IS_DIR))
#if DARWIN
return Path.build_filename (root, filename);
#else
return Path.build_filename (root, arch, filename);
#endif
return Path.build_filename (libdir, filename);
}
public string derive_plugin_path (string filename) {
return Path.build_filename (libdir, Config.FRIDA_LIBDIR_NAME, "plugins", filename);
}
}
}
-12
View File
@@ -1,12 +0,0 @@
#include "frida-base.h"
GBytes *
frida_make_bytes_with_owner (GType t_type,
GBoxedCopyFunc t_dup_func,
GDestroyNotify t_destroy_func,
void * data,
gsize size,
gpointer owner)
{
return g_bytes_new_with_free_func (data, size, t_destroy_func, owner);
}
-662
View File
@@ -1,662 +0,0 @@
namespace Frida {
public sealed class BufferBuilder : Object {
public ByteOrder byte_order {
get;
construct;
}
public uint pointer_size {
get;
construct;
}
public size_t offset {
get {
return cursor;
}
}
private ByteArray buffer = new ByteArray ();
private size_t cursor = 0;
private uint64 base_address = 0;
private Gee.List<LabelRef>? label_refs;
private Gee.Map<string, uint>? label_defs;
public BufferBuilder (ByteOrder byte_order = HOST, uint pointer_size = (uint) sizeof (size_t)) {
Object (
byte_order: byte_order,
pointer_size: pointer_size
);
}
public unowned BufferBuilder seek (size_t offset) {
if (buffer.len < offset) {
size_t n = offset - buffer.len;
Memory.set (get_pointer (offset - n, n), 0, n);
}
cursor = offset;
return this;
}
public unowned BufferBuilder skip (size_t n) {
seek (cursor + n);
return this;
}
public unowned BufferBuilder align (size_t n) {
size_t remainder = cursor % n;
if (remainder != 0)
skip (n - remainder);
return this;
}
public unowned BufferBuilder append_pointer (uint64 val) {
write_pointer (cursor, val);
cursor += pointer_size;
return this;
}
public unowned BufferBuilder append_pointer_to_label (string name) {
if (label_refs == null)
label_refs = new Gee.ArrayList<LabelRef> ();
label_refs.add (new LabelRef (name, cursor));
return skip (pointer_size);
}
public unowned BufferBuilder append_pointer_to_label_if (bool present, string name) {
if (present)
append_pointer_to_label (name);
else
append_pointer (0);
return this;
}
public unowned BufferBuilder append_label (string name) throws Error {
if (label_defs == null)
label_defs = new Gee.HashMap<string, uint> ();
if (label_defs.has_key (name))
throw new Error.INVALID_ARGUMENT ("Label '%s' already exists", name);
label_defs[name] = (uint) cursor;
return this;
}
public unowned BufferBuilder append_size (uint64 val) {
return append_pointer (val);
}
public unowned BufferBuilder append_int8 (int8 val) {
write_int8 (cursor, val);
cursor += (uint) sizeof (int8);
return this;
}
public unowned BufferBuilder append_uint8 (uint8 val) {
write_uint8 (cursor, val);
cursor += (uint) sizeof (uint8);
return this;
}
public unowned BufferBuilder append_int16 (int16 val) {
write_int16 (cursor, val);
cursor += (uint) sizeof (int16);
return this;
}
public unowned BufferBuilder append_uint16 (uint16 val) {
write_uint16 (cursor, val);
cursor += (uint) sizeof (uint16);
return this;
}
public unowned BufferBuilder append_int32 (int32 val) {
write_int32 (cursor, val);
cursor += (uint) sizeof (int32);
return this;
}
public unowned BufferBuilder append_uint32 (uint32 val) {
write_uint32 (cursor, val);
cursor += (uint) sizeof (uint32);
return this;
}
public unowned BufferBuilder append_int64 (int64 val) {
write_int64 (cursor, val);
cursor += (uint) sizeof (int64);
return this;
}
public unowned BufferBuilder append_uint64 (uint64 val) {
write_uint64 (cursor, val);
cursor += (uint) sizeof (uint64);
return this;
}
public unowned BufferBuilder append_float (float val) {
write_float (cursor, val);
cursor += (uint) sizeof (float);
return this;
}
public unowned BufferBuilder append_double (double val) {
write_double (cursor, val);
cursor += (uint) sizeof (double);
return this;
}
public unowned BufferBuilder append_string (string val, StringTerminator terminator = NUL) {
uint size = val.length;
if (terminator == NUL)
size++;
Memory.copy (get_pointer (cursor, size), val, size);
cursor += size;
return this;
}
public unowned BufferBuilder append_bytes (Bytes bytes) {
return append_data (bytes.get_data ());
}
public unowned BufferBuilder append_data (uint8[] data) {
write_data (cursor, data);
cursor += data.length;
return this;
}
public unowned BufferBuilder write_pointer (size_t offset, uint64 val) {
if (pointer_size == 4)
write_uint32 (offset, (uint32) val);
else
write_uint64 (offset, val);
return this;
}
public unowned BufferBuilder write_size (size_t offset, uint64 val) {
return write_pointer (offset, val);
}
public unowned BufferBuilder write_int8 (size_t offset, int8 val) {
*((int8 *) get_pointer (offset, sizeof (int8))) = val;
return this;
}
public unowned BufferBuilder write_uint8 (size_t offset, uint8 val) {
*get_pointer (offset, sizeof (uint8)) = val;
return this;
}
public unowned BufferBuilder write_int16 (size_t offset, int16 val) {
int16 target_val = (byte_order == BIG_ENDIAN)
? val.to_big_endian ()
: val.to_little_endian ();
*((int16 *) get_pointer (offset, sizeof (int16))) = target_val;
return this;
}
public unowned BufferBuilder write_uint16 (size_t offset, uint16 val) {
uint16 target_val = (byte_order == BIG_ENDIAN)
? val.to_big_endian ()
: val.to_little_endian ();
*((uint16 *) get_pointer (offset, sizeof (uint16))) = target_val;
return this;
}
public unowned BufferBuilder write_int32 (size_t offset, int32 val) {
int32 target_val = (byte_order == BIG_ENDIAN)
? val.to_big_endian ()
: val.to_little_endian ();
*((int32 *) get_pointer (offset, sizeof (int32))) = target_val;
return this;
}
public unowned BufferBuilder write_uint32 (size_t offset, uint32 val) {
uint32 target_val = (byte_order == BIG_ENDIAN)
? val.to_big_endian ()
: val.to_little_endian ();
*((uint32 *) get_pointer (offset, sizeof (uint32))) = target_val;
return this;
}
public unowned BufferBuilder write_int64 (size_t offset, int64 val) {
int64 target_val = (byte_order == BIG_ENDIAN)
? val.to_big_endian ()
: val.to_little_endian ();
*((int64 *) get_pointer (offset, sizeof (int64))) = target_val;
return this;
}
public unowned BufferBuilder write_uint64 (size_t offset, uint64 val) {
uint64 target_val = (byte_order == BIG_ENDIAN)
? val.to_big_endian ()
: val.to_little_endian ();
*((uint64 *) get_pointer (offset, sizeof (uint64))) = target_val;
return this;
}
public unowned BufferBuilder write_float (size_t offset, float val) {
return write_uint32 (offset, *((uint32 *) &val));
}
public unowned BufferBuilder write_double (size_t offset, double val) {
return write_uint64 (offset, *((uint64 *) &val));
}
public unowned BufferBuilder write_string (size_t offset, string val) {
uint size = val.length + 1;
Memory.copy (get_pointer (offset, size), val, size);
return this;
}
public unowned BufferBuilder write_bytes (size_t offset, Bytes bytes) {
return write_data (offset, bytes.get_data ());
}
public unowned BufferBuilder write_data (size_t offset, uint8[] data) {
Memory.copy (get_pointer (offset, data.length), data, data.length);
return this;
}
private uint8 * get_pointer (size_t offset, size_t n) {
size_t minimum_size = offset + n;
if (buffer.len < minimum_size)
buffer.set_size ((uint) minimum_size);
return (uint8 *) buffer.data + offset;
}
public Bytes try_build (uint64 base_address = 0) throws Error {
this.base_address = base_address;
if (label_refs != null) {
foreach (LabelRef r in label_refs)
write_pointer (r.offset, address_of (r.name));
}
return ByteArray.free_to_bytes ((owned) buffer);
}
public Bytes build (uint64 base_address = 0) {
try {
return try_build (base_address);
} catch (Error e) {
assert_not_reached ();
}
}
public uint64 address_of (string label) throws Error {
if (label_defs == null || !label_defs.has_key (label))
throw new Error.INVALID_OPERATION ("Label '%s' not defined", label);
size_t offset = label_defs[label];
return base_address + offset;
}
private class LabelRef {
public string name;
public size_t offset;
public LabelRef (string name, size_t offset) {
this.name = name;
this.offset = offset;
}
}
}
public enum StringTerminator {
NONE,
NUL
}
public sealed class Buffer : Object {
public Bytes bytes {
get;
construct;
}
public ByteOrder byte_order {
get;
construct;
}
public uint pointer_size {
get;
construct;
}
private unowned uint8 * data;
private size_t size;
public Buffer (Bytes bytes, ByteOrder byte_order = HOST, uint pointer_size = (uint) sizeof (size_t)) {
Object (
bytes: bytes,
byte_order: byte_order,
pointer_size: pointer_size
);
}
construct {
data = bytes.get_data ();
size = bytes.get_size ();
}
public uint64 read_pointer (size_t offset) {
return (pointer_size == 4)
? read_uint32 (offset)
: read_uint64 (offset);
}
public void write_pointer (size_t offset, uint64 val) {
if (pointer_size == 4)
write_uint32 (offset, (uint32) val);
else
write_uint64 (offset, val);
}
public int8 read_int8 (size_t offset) {
return *((int8 *) get_pointer (offset, sizeof (int8)));
}
public unowned Buffer write_int8 (size_t offset, int8 val) {
*((int8 *) get_pointer (offset, sizeof (int8))) = val;
return this;
}
public uint8 read_uint8 (size_t offset) {
return *get_pointer (offset, sizeof (uint8));
}
public unowned Buffer write_uint8 (size_t offset, uint8 val) {
*((uint8 *) get_pointer (offset, sizeof (uint8))) = val;
return this;
}
public int16 read_int16 (size_t offset) {
int16 val = *((int16 *) get_pointer (offset, sizeof (int16)));
return (byte_order == BIG_ENDIAN)
? int16.from_big_endian (val)
: int16.from_little_endian (val);
}
public unowned Buffer write_int16 (size_t offset, int16 val) {
int16 target_val = (byte_order == BIG_ENDIAN)
? val.to_big_endian ()
: val.to_little_endian ();
*((int16 *) get_pointer (offset, sizeof (int16))) = target_val;
return this;
}
public uint16 read_uint16 (size_t offset) {
uint16 val = *((uint16 *) get_pointer (offset, sizeof (uint16)));
return (byte_order == BIG_ENDIAN)
? uint16.from_big_endian (val)
: uint16.from_little_endian (val);
}
public unowned Buffer write_uint16 (size_t offset, uint16 val) {
uint16 target_val = (byte_order == BIG_ENDIAN)
? val.to_big_endian ()
: val.to_little_endian ();
*((uint16 *) get_pointer (offset, sizeof (uint16))) = target_val;
return this;
}
public int32 read_int32 (size_t offset) {
int32 val = *((int32 *) get_pointer (offset, sizeof (int32)));
return (byte_order == BIG_ENDIAN)
? int32.from_big_endian (val)
: int32.from_little_endian (val);
}
public unowned Buffer write_int32 (size_t offset, int32 val) {
int32 target_val = (byte_order == BIG_ENDIAN)
? val.to_big_endian ()
: val.to_little_endian ();
*((int32 *) get_pointer (offset, sizeof (int32))) = target_val;
return this;
}
public uint32 read_uint32 (size_t offset) {
uint32 val = *((uint32 *) get_pointer (offset, sizeof (uint32)));
return (byte_order == BIG_ENDIAN)
? uint32.from_big_endian (val)
: uint32.from_little_endian (val);
}
public unowned Buffer write_uint32 (size_t offset, uint32 val) {
uint32 target_val = (byte_order == BIG_ENDIAN)
? val.to_big_endian ()
: val.to_little_endian ();
*((uint32 *) get_pointer (offset, sizeof (uint32))) = target_val;
return this;
}
public int64 read_int64 (size_t offset) {
int64 val = *((int64 *) get_pointer (offset, sizeof (int64)));
return (byte_order == BIG_ENDIAN)
? int64.from_big_endian (val)
: int64.from_little_endian (val);
}
public unowned Buffer write_int64 (size_t offset, int64 val) {
int64 target_val = (byte_order == BIG_ENDIAN)
? val.to_big_endian ()
: val.to_little_endian ();
*((int64 *) get_pointer (offset, sizeof (int64))) = target_val;
return this;
}
public uint64 read_uint64 (size_t offset) {
uint64 val = *((uint64 *) get_pointer (offset, sizeof (uint64)));
return (byte_order == BIG_ENDIAN)
? uint64.from_big_endian (val)
: uint64.from_little_endian (val);
}
public unowned Buffer write_uint64 (size_t offset, uint64 val) {
uint64 target_val = (byte_order == BIG_ENDIAN)
? val.to_big_endian ()
: val.to_little_endian ();
*((uint64 *) get_pointer (offset, sizeof (uint64))) = target_val;
return this;
}
public float read_float (size_t offset) {
uint32 bits = read_uint32 (offset);
return *((float *) &bits);
}
public double read_double (size_t offset) {
uint64 bits = read_uint64 (offset);
return *((double *) &bits);
}
public string read_string (size_t offset) throws Error {
string * start = (string *) get_pointer (offset, sizeof (char));
size_t max_length = size - offset;
string * end = memchr (start, 0, max_length);
if (end == null)
throw new Error.PROTOCOL ("Missing null character");
size_t size = end - start;
string val = start->substring (0, (long) size);
if (!val.validate ())
throw new Error.PROTOCOL ("Invalid UTF-8 string");
return val;
}
[CCode (cname = "memchr", cheader_filename = "string.h")]
private extern static string * memchr (string * s, int c, size_t n);
public string read_fixed_string (size_t offset, size_t size) throws Error {
string * start = (string *) get_pointer (offset, size);
size_t max_length = size_t.min (size, this.size - offset);
string * end = memchr (start, 0, max_length);
size_t n;
if (end != null)
n = end - start;
else
n = size;
string val = start->substring (0, (long) n);
if (!val.validate ())
throw new Error.PROTOCOL ("Invalid UTF-8 string");
return val;
}
public unowned Buffer write_string (size_t offset, string val) {
uint size = val.length + 1;
Memory.copy (get_pointer (offset, size), val, size);
return this;
}
public Bytes read_bytes (size_t offset, size_t size) {
var data = new uint8[size + 1];
Memory.copy (data, get_pointer (offset, size), size);
return new Bytes.take ((owned) data);
}
public unowned Buffer write_bytes (size_t offset, Bytes bytes) {
size_t size = bytes.get_size ();
Memory.copy (get_pointer (offset, size), bytes.get_data (), size);
return this;
}
private uint8 * get_pointer (size_t offset, size_t n) {
size_t minimum_size = offset + n;
assert (size >= minimum_size);
return data + offset;
}
}
public sealed class BufferReader {
public size_t offset {
get {
return _offset;
}
}
public size_t available {
get {
return buffer.bytes.get_size () - _offset;
}
}
private Buffer buffer;
private size_t _offset = 0;
public BufferReader (Buffer buf) {
buffer = buf;
}
public uint64 read_pointer () throws Error {
var pointer_size = buffer.pointer_size;
check_available (pointer_size);
var ptr = buffer.read_pointer (_offset);
_offset += pointer_size;
return ptr;
}
public int8 read_int8 () throws Error {
check_available (sizeof (int8));
var val = buffer.read_int8 (_offset);
_offset += sizeof (int8);
return val;
}
public uint8 read_uint8 () throws Error {
check_available (sizeof (uint8));
var val = buffer.read_uint8 (_offset);
_offset += sizeof (uint8);
return val;
}
public int16 read_int16 () throws Error {
check_available (sizeof (int16));
var val = buffer.read_int16 (_offset);
_offset += sizeof (int16);
return val;
}
public uint16 read_uint16 () throws Error {
check_available (sizeof (uint16));
var val = buffer.read_uint16 (_offset);
_offset += sizeof (uint16);
return val;
}
public int32 read_int32 () throws Error {
check_available (sizeof (int32));
var val = buffer.read_int32 (_offset);
_offset += sizeof (int32);
return val;
}
public uint32 read_uint32 () throws Error {
check_available (sizeof (uint32));
var val = buffer.read_uint32 (_offset);
_offset += sizeof (uint32);
return val;
}
public int64 read_int64 () throws Error {
check_available (sizeof (int64));
var val = buffer.read_int64 (_offset);
_offset += sizeof (int64);
return val;
}
public uint64 read_uint64 () throws Error {
check_available (sizeof (uint64));
var val = buffer.read_uint64 (_offset);
_offset += sizeof (uint64);
return val;
}
public float read_float () throws Error {
check_available (sizeof (float));
var val = buffer.read_float (_offset);
_offset += sizeof (float);
return val;
}
public double read_double () throws Error {
check_available (sizeof (double));
var val = buffer.read_double (_offset);
_offset += sizeof (double);
return val;
}
public string read_string () throws Error {
check_available (1);
var val = buffer.read_string (_offset);
_offset += val.length + 1;
return val;
}
public string read_fixed_string (size_t size) throws Error {
check_available (size);
var val = buffer.read_fixed_string (_offset, size);
_offset += size;
return val;
}
public Bytes read_bytes (size_t size) throws Error {
check_available (size);
var val = buffer.bytes[_offset:_offset + size];
_offset += size;
return val;
}
public unowned BufferReader skip (size_t n) throws Error {
check_available (n);
_offset += n;
return this;
}
private void check_available (size_t n) throws Error {
if (available < n)
throw new Error.PROTOCOL ("Malformed buffer: truncated");
}
}
public extern Bytes make_bytes_with_owner<T> (void * data, size_t size, owned T? owner = null);
}
+14 -2
View File
@@ -1,9 +1,13 @@
using Efni;
namespace Frida {
public sealed class ChannelRegistry : Object {
public signal void channel_closed (ChannelId id);
private Gee.Map<ChannelId?, weak IOStream> channels =
new Gee.HashMap<ChannelId?, weak IOStream> (ChannelId.hash, ChannelId.equal);
private Gee.Map<ChannelId?, IOStream> unlinked_channels =
new Gee.HashMap<ChannelId?, IOStream> (ChannelId.hash, ChannelId.equal);
~ChannelRegistry () {
clear ();
@@ -16,25 +20,33 @@ namespace Frida {
channel_stream.abandon ();
}
channels.clear ();
unlinked_channels.clear ();
}
public void register (ChannelId id, IOStream stream) {
stream.set_data ("channel-registry-entry", new Entry (this, id));
lock (channels)
lock (channels) {
channels[id] = stream;
unlinked_channels[id] = stream;
}
}
public IOStream link (ChannelId id) throws Error {
IOStream? stream;
lock (channels)
IOStream? unlinked_stream = null;
lock (channels) {
stream = channels[id];
unlinked_channels.unset (id, out unlinked_stream);
}
if (stream == null)
throw new Error.INVALID_ARGUMENT ("Invalid channel ID");
return stream;
}
public void unlink (ChannelId id) {
IOStream? unlinked_stream = null;
lock (channels) {
unlinked_channels.unset (id, out unlinked_stream);
if (!channels.unset (id))
return;
}
+2
View File
@@ -1,3 +1,5 @@
using Efni;
namespace Frida {
// We need to tease out GDBus' private MainContext as libnice needs to know the MainContext up front :(
public async MainContext get_dbus_context () {
+12 -2
View File
@@ -1,18 +1,26 @@
#ifndef __FRIDA_ATOMICS_H__
#define __FRIDA_ATOMICS_H__
#include <glib.h>
G_BEGIN_DECLS
#ifdef _MSC_VER
/* TODO: Add once needed. */
#else
static inline guint64 frida_atomics_load_u64_acquire (volatile guint64 * p)
typedef guint64 FridaAtomicU64 __attribute__ ((aligned (8)));
static inline guint64
frida_atomics_load_u64_acquire (volatile FridaAtomicU64 * p)
{
return __atomic_load_n (p, __ATOMIC_ACQUIRE);
}
static inline void frida_atomics_store_u64_release (volatile guint64 * p, guint64 v)
static inline void
frida_atomics_store_u64_release (volatile FridaAtomicU64 * p, guint64 v)
{
__atomic_store_n (p, v, __ATOMIC_RELEASE);
}
@@ -24,4 +32,6 @@ static inline guint32 frida_atomics_load_u32_acquire (volatile guint32 * p)
#endif
G_END_DECLS
#endif
+3 -3
View File
@@ -1,6 +1,6 @@
[CCode (cheader_filename = "frida-atomics.h")]
namespace Frida.Atomics {
public static uint64 load_u64_acquire (void * p);
public static void store_u64_release (void * p, uint64 v);
public static uint32 load_u32_acquire (void * p);
public static uint64 load_u64_acquire (uint64 * p);
public static void store_u64_release (uint64 * p, uint64 v);
public static uint32 load_u32_acquire (uint32 * p);
}
-51
View File
@@ -1,51 +0,0 @@
#include "frida-darwin.h"
gpointer
_frida_dispatch_retain (gpointer object)
{
dispatch_retain (object);
return object;
}
void
_frida_xpc_connection_set_event_handler (xpc_connection_t connection, FridaXpcHandler handler, gpointer user_data)
{
xpc_connection_set_event_handler (connection, ^(xpc_object_t object)
{
handler (object, user_data);
});
}
void
_frida_xpc_connection_send_message_with_reply (xpc_connection_t connection, xpc_object_t message, dispatch_queue_t replyq,
FridaXpcHandler handler, gpointer user_data, GDestroyNotify notify)
{
xpc_connection_send_message_with_reply (connection, message, replyq, ^(xpc_object_t object)
{
handler (object, user_data);
if (notify != NULL)
notify (user_data);
});
}
gchar *
_frida_xpc_object_to_string (xpc_object_t object)
{
gchar * result;
char * str;
str = xpc_copy_description (object);
result = g_strdup (str);
free (str);
return result;
}
gboolean
_frida_xpc_dictionary_apply (xpc_object_t dict, FridaXpcDictionaryApplier applier, gpointer user_data)
{
return xpc_dictionary_apply (dict, ^bool (const char * key, xpc_object_t val)
{
return applier (key, val, user_data);
});
}
-22
View File
@@ -1,22 +0,0 @@
#ifndef __FRIDA_DARWIN_H__
#define __FRIDA_DARWIN_H__
#ifdef HAVE_MACOS
#include <glib.h>
#include <xpc/xpc.h>
typedef void (* FridaXpcHandler) (xpc_object_t object, gpointer user_data);
typedef gboolean (* FridaXpcDictionaryApplier) (const gchar * key, xpc_object_t val, gpointer user_data);
gpointer _frida_dispatch_retain (gpointer object);
void _frida_xpc_connection_set_event_handler (xpc_connection_t connection, FridaXpcHandler handler, gpointer user_data);
void _frida_xpc_connection_send_message_with_reply (xpc_connection_t connection, xpc_object_t message, dispatch_queue_t replyq,
FridaXpcHandler handler, gpointer user_data, GDestroyNotify notify);
gchar * _frida_xpc_object_to_string (xpc_object_t object);
gboolean _frida_xpc_dictionary_apply (xpc_object_t dict, FridaXpcDictionaryApplier applier, gpointer user_data);
#endif
#endif
+41
View File
@@ -0,0 +1,41 @@
#ifndef __FRIDA_JNI_H__
#define __FRIDA_JNI_H__
#include <jni.h>
G_BEGIN_DECLS
typedef jint (* JNIDestroyJavaVMFunc) (JavaVM *);
typedef jint (* JNIAttachCurrentThreadFunc) (JavaVM *, JNIEnv **, void *);
typedef jint (* JNIDetachCurrentThreadFunc) (JavaVM *);
typedef jint (* JNIGetEnvFunc) (JavaVM *, JNIEnv **, jint);
typedef jclass (* JNIFindClassFunc) (JNIEnv *, const char *);
typedef jthrowable (* JNIExceptionOccurredFunc) (JNIEnv *);
typedef void (* JNIExceptionDescribeFunc) (JNIEnv *);
typedef void (* JNIExceptionClearFunc) (JNIEnv *);
typedef jint (* JNIPushLocalFrameFunc) (JNIEnv *, jint);
typedef jobject (* JNIPopLocalFrameFunc) (JNIEnv *, jobject);
typedef jobject (* JNINewGlobalRefFunc) (JNIEnv *, jobject);
typedef void (* JNIDeleteGlobalRefFunc) (JNIEnv *, jobject);
typedef void (* JNIDeleteLocalRefFunc) (JNIEnv *, jobject);
typedef jobject (* JNINewObjectFunc) (JNIEnv *, jclass, jmethodID, ...);
typedef jclass (* JNIGetObjectClassFunc) (JNIEnv *, jobject);
typedef jmethodID (* JNIGetMethodIDFunc) (JNIEnv *, jclass, const char *, const char *);
typedef jobject (* JNICallObjectMethodFunc) (JNIEnv *, jobject, jmethodID, ...);
typedef jstring (* JNINewStringUTFFunc) (JNIEnv *, const char *);
typedef const char * (* JNIGetStringUTFCharsFunc) (JNIEnv *, jstring, jboolean *);
typedef void (* JNIReleaseStringUTFCharsFunc) (JNIEnv *, jstring, const char *);
typedef jboolean (* JNIExceptionCheckFunc) (JNIEnv *);
G_END_DECLS
#endif
+42
View File
@@ -0,0 +1,42 @@
#ifndef __FRIDA_PROSPERO_H__
#define __FRIDA_PROSPERO_H__
#ifdef HAVE_PROSPERO
#include <gum/gum.h>
G_BEGIN_DECLS
#define FRIDA_PROSPERO_AGENT_ARGS_SYMBOL "frida_prospero_agent_args"
#define FRIDA_PROSPERO_HELLO_BYTE 0xff
typedef struct _FridaProsperoAgentArgs FridaProsperoAgentArgs;
struct _FridaProsperoAgentArgs
{
GumAddress agent_parameters;
gint fifo_fd;
gint agent_ctrlfd;
GumMemoryRange mapped_range;
};
typedef struct _FridaProsperoAppLaunchContext FridaProsperoAppLaunchContext;
struct _FridaProsperoAppLaunchContext
{
guint32 size;
guint32 user_id;
guint32 app_opt;
guint64 crash_report;
guint32 check_flag;
};
extern int sceUserServiceInitialize (void * params);
extern int sceUserServiceGetForegroundUser (guint32 * user_id);
extern int sceSystemServiceLaunchApp (const gchar * title_id, gchar ** argv, FridaProsperoAppLaunchContext * ctx);
G_END_DECLS
#endif
#endif
-114
View File
@@ -1,114 +0,0 @@
namespace Frida.FS {
public void mkdirp (File dir, Cancellable? cancellable = null) throws Error {
try {
dir.make_directory_with_parents (cancellable);
} catch (GLib.Error e) {
if (!(e is IOError.EXISTS))
throw new Error.PERMISSION_DENIED ("%s", e.message);
}
}
public void rmtree (File dir, Cancellable? cancellable = null) throws Error {
rmtree_worker (dir, 0, cancellable);
}
private void rmtree_worker (File dir, uint depth, Cancellable? cancellable = null) throws Error {
FileEnumerator enumerator;
try {
enumerator = dir.enumerate_children (FileAttribute.STANDARD_NAME, NOFOLLOW_SYMLINKS, cancellable);
} catch (GLib.Error e) {
if (depth == 0 && e is IOError.NOT_FOUND)
return;
throw new Error.PERMISSION_DENIED ("%s", e.message);
}
try {
FileInfo? info;
File? child;
while (enumerator.iterate (out info, out child, cancellable) && info != null) {
if (info == null)
continue;
if (info.get_file_type () == DIRECTORY)
rmtree (child, cancellable);
else
child.delete (cancellable);
}
dir.delete (cancellable);
} catch (GLib.Error e) {
if (!(e is IOError.EXISTS))
throw new Error.PERMISSION_DENIED ("%s", e.message);
}
}
public async void rmtree_async (File dir, Cancellable? cancellable = null) throws Error, IOError {
yield rmtree_async_worker (dir, 0, cancellable);
}
private async void rmtree_async_worker (File dir, uint depth, Cancellable? cancellable = null) throws Error, IOError {
int io_priority = Priority.DEFAULT;
FileEnumerator enumerator;
try {
enumerator = yield dir.enumerate_children_async (FileAttribute.STANDARD_NAME, NOFOLLOW_SYMLINKS, io_priority,
cancellable);
} catch (GLib.Error e) {
if (depth == 0 && e is IOError.NOT_FOUND)
return;
throw new Error.PERMISSION_DENIED ("%s", e.message);
}
try {
while (true) {
var batch = yield enumerator.next_files_async (1000, io_priority, cancellable);
if (batch.is_empty ())
break;
foreach (FileInfo info in batch) {
File child = dir.get_child (info.get_name ());
if (info.get_file_type () == DIRECTORY)
yield rmtree_async_worker (child, depth + 1, cancellable);
else
yield child.delete_async (io_priority, cancellable);
}
}
yield dir.delete_async (io_priority, cancellable);
} catch (GLib.Error e) {
throw new Error.PERMISSION_DENIED ("%s", e.message);
}
}
public async string read_all_text (File f, Cancellable? cancellable) throws Error, IOError {
Bytes b = yield read_all_bytes (f, cancellable);
string text = (string) b.get_data ();
if (!text.validate ())
throw new Error.PROTOCOL ("Unable to read '%s': invalid UTF-8", f.get_parse_name ());
return text;
}
public async void write_all_text (File f, string t, Cancellable? cancellable) throws Error, IOError {
yield write_all_bytes (f, new Bytes (t.data), cancellable);
}
public async Bytes read_all_bytes (File f, Cancellable? cancellable) throws Error, IOError {
try {
return yield f.load_bytes_async (cancellable, null);
} catch (GLib.Error e) {
if (e is IOError.CANCELLED)
throw (IOError) e;
if (e is IOError.NOT_FOUND)
throw new Error.INVALID_ARGUMENT ("%s", e.message);
throw new Error.PERMISSION_DENIED ("%s", e.message);
}
}
public async void write_all_bytes (File f, Bytes b, Cancellable? cancellable) throws Error, IOError {
try {
yield f.replace_contents_bytes_async (b, null, false, FileCreateFlags.NONE, cancellable, null);
} catch (GLib.Error e) {
if (e is IOError.CANCELLED)
throw (IOError) e;
throw new Error.PERMISSION_DENIED ("%s", e.message);
}
}
}
+58 -11
View File
@@ -46,7 +46,7 @@ namespace Frida {
public extern unowned LinuxSyscallSignature[]? get_compat32_syscall_signatures ();
public struct LinuxSyscallSignature {
public uint nr;
public int nr;
public string name;
public uint8 nargs;
public LinuxSyscallArg args[6];
@@ -123,11 +123,21 @@ namespace Frida {
return fd;
}
public static FileDescriptor with_size (string name, size_t size) {
assert (is_supported ());
var fd = new FileDescriptor (memfd_create (name, 0));
int res = Posix.ftruncate (fd.handle, (Posix.off_t) size);
assert (res == 0);
return fd;
}
private int memfd_create (string name, uint flags) {
return Linux.syscall (LinuxSyscall.MEMFD_CREATE, name, flags);
}
}
#if HAVE_LOCAL_BACKEND
public sealed class BpfObject {
public Programs programs {
get;
@@ -138,37 +148,35 @@ namespace Frida {
}
internal Libbpf.Object handle;
private char[] kernel_log_buf;
public static BpfObject open (string name, uint8[] blob) throws Error {
var log_buf = new char[64 * 1024];
var opts = Libbpf.Object.OpenOpts ();
opts.sz = sizeof (Libbpf.Object.OpenOpts);
opts.object_name = name;
opts.kernel_log_buf = log_buf;
var log = new LibbpfLogScope ();
var handle = Libbpf.Object.open_mem (blob, opts);
if (handle == null)
throw_libbpf_error (errno);
throw_libbpf_error (errno, log.take ());
return new BpfObject ((owned) handle, (owned) log_buf);
return new BpfObject ((owned) handle);
}
private BpfObject (owned Libbpf.Object handle, owned char[] kernel_log_buf) {
private BpfObject (owned Libbpf.Object handle) {
this.handle = (owned) handle;
this.kernel_log_buf = (owned) kernel_log_buf;
_programs = new Programs (this);
_maps = new Maps (this);
}
public void prepare () throws Error {
check_libbpf_result (handle.prepare ());
var log = new LibbpfLogScope ();
check_libbpf_result (handle.prepare (), log.take ());
}
public void load () throws Error {
check_libbpf_result (handle.load (), (string) kernel_log_buf);
var log = new LibbpfLogScope ();
check_libbpf_result (handle.load (), log.take ());
}
public sealed class Programs {
@@ -348,6 +356,18 @@ namespace Frida {
remove_raw ((uint8[]) &key);
}
public void update_u64_u8 (uint64 key, uint8 val) throws Error {
update_raw ((uint8[]) &key, (uint8[]) &val);
}
public void update_u64_u32 (uint64 key, uint32 val) throws Error {
update_raw ((uint8[]) &key, (uint8[]) &val);
}
public void remove_u64 (uint64 key) throws Error {
remove_raw ((uint8[]) &key);
}
public void foreach_percpu_value<T> (uint8[] key, PercpuValueFunc<T> func) throws Error {
var ncpus = check_libbpf_result (Libbpf.num_possible_cpus ());
var percpu_stride = round_up_8 (value_size);
@@ -384,6 +404,30 @@ namespace Frida {
}
}
private sealed class LibbpfLogScope {
private static StringBuilder buffer;
public LibbpfLogScope () {
if (buffer == null)
buffer = new StringBuilder ();
buffer.truncate ();
Libbpf.set_print (on_message);
}
~LibbpfLogScope () {
Libbpf.set_print (null);
}
public string take () {
return buffer.str.chomp ();
}
private static int on_message (Libbpf.PrintLevel level, string format, va_list args) {
buffer.append_vprintf (format, args);
return 0;
}
}
private int check_libbpf_result (int result, string details = "") throws Error {
if (result < 0)
throw_libbpf_error (result, details);
@@ -510,6 +554,7 @@ namespace Frida {
STOP,
}
}
#endif
namespace PerfEvent {
public FileDescriptor open (PerfEventAttr * attr, int pid, int cpu, int group_fd, uint flags) throws Error {
@@ -524,7 +569,9 @@ namespace Frida {
throw new Error.NOT_SUPPORTED ("%s (errno=%d: %s)".printf (message, Posix.errno, Posix.strerror (Posix.errno)));
}
#if HAVE_LOCAL_BACKEND
private size_t round_up_8 (size_t x) {
return (x + 7) & ~((size_t) 7);
}
#endif
}
+21 -28
View File
@@ -1,38 +1,29 @@
base_sources = [
'asset-location.vala',
'promise.vala',
'session.vala',
'session-glue.c',
'channel.vala',
'service.vala',
'stream.vala',
'rpc.vala',
'p2p.vala',
'p2p-glue.c',
'dbus.vala',
'promise.vala',
'fs.vala',
'socket.vala',
'socket-glue.c',
'buffer.vala',
'buffer-glue.c',
'value.vala',
'xnu-syscalls' / 'xnu-syscalls.vala',
'xnu-syscalls' / 'xnu-syscalls-glue.c',
]
extra_vala_args = []
if host_os == 'windows'
base_sources += 'windows.vala'
base_sources += ['windows.vala', 'windows-glue.c']
else
base_sources += 'posix.vala'
extra_vala_args += '--pkg=posix'
endif
if host_os == 'macos'
base_sources += [
'xpc.vala',
'frida-darwin.c',
]
endif
if host_os_family == 'darwin'
base_sources += 'corefoundation.vala'
extra_vala_args += '--pkg=darwin-xnu'
@@ -45,8 +36,10 @@ if host_os_family == 'linux'
extra_vala_args += [
'--pkg=linux',
'--pkg=frida-atomics',
'--pkg=libbpf',
]
if have_local_backend
extra_vala_args += '--pkg=libbpf'
endif
syscall_arch_from_abi = {
'armbe': 'arm',
@@ -73,7 +66,7 @@ extra_deps = []
if host_os_family != 'windows'
extra_deps += gio_unix_dep
endif
if host_os_family == 'linux'
if host_os_family == 'linux' and have_local_backend
extra_deps += libbpf_dep
endif
@@ -89,8 +82,6 @@ endif
if host_os_family == 'darwin'
base_vala_args += [
'--pkg=corefoundation',
'--pkg=darwin-gcd',
'--pkg=darwin-xpc',
]
endif
if host_os_family == 'linux'
@@ -109,23 +100,25 @@ base = static_library('frida-base-' + api_version, base_sources,
vala_header: 'frida-base.h',
vala_gir: base_gir_name,
vala_vapi: f'frida-base-@[email protected]',
dependencies: [glib_dep, gobject_dep, gio_dep, libsoup_dep, nice_dep, usrsctp_dep, gee_dep, json_glib_dep, gum_dep, openssl_dep] + extra_deps,
install: true,
dependencies: [glib_dep, gobject_dep, gio_dep, libsoup_dep, nice_dep, usrsctp_dep, gee_dep, json_glib_dep, gum_dep, openssl_dep, efni_dep] + extra_deps,
install: have_in_tree_payloads,
install_dir: [true, header_install_dir, true, false],
)
base_dep = declare_dependency(
include_directories: include_directories('.'),
link_with: base,
dependencies: [nice_dep, json_glib_dep, gum_dep] + extra_deps,
dependencies: [nice_dep, json_glib_dep, gum_dep, efni_dep] + extra_deps,
)
base_build_dir = meson.current_build_dir()
pkg = import('pkgconfig')
pkg.generate(base,
filebase: f'frida-base-@api_version@',
name: 'Frida Base Library',
version: frida_version,
description: 'Basic building blocks',
)
if have_in_tree_payloads
pkg = import('pkgconfig')
pkg.generate(base,
filebase: f'frida-base-@api_version@',
name: 'Frida Base Library',
version: frida_version,
description: 'Basic building blocks',
)
endif
+417 -9
View File
@@ -1,26 +1,432 @@
#ifdef HAVE_NICE
#define OPENSSL_SUPPRESS_DEPRECATED
#include "frida-base.h"
#include <errno.h>
#include <openssl/asn1.h>
#include <openssl/bio.h>
#include <openssl/bn.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <usrsctp.h>
#ifdef HAVE_GIOAPPLE
# include <CoreFoundation/CoreFoundation.h>
# include <Security/Security.h>
#else
# define OPENSSL_SUPPRESS_DEPRECATED
# include <openssl/asn1.h>
# include <openssl/bio.h>
# include <openssl/bn.h>
# include <openssl/evp.h>
# include <openssl/pem.h>
# include <openssl/rsa.h>
# include <openssl/x509.h>
#endif
#ifdef HAVE_GIOAPPLE
static SecKeyRef frida_rsa_keypair_generate (void);
static void frida_rsa_export_public_key (SecKeyRef private_key, GByteArray * out);
static void frida_rsa_export_private_key (SecKeyRef private_key, GByteArray * out);
static void frida_rsa_sign_sha256 (SecKeyRef private_key, const guint8 * message, gsize message_length, GByteArray * out);
static void frida_build_tbs_certificate (GByteArray * out, const GByteArray * subject_public_key);
static void frida_build_name (GByteArray * out);
static void frida_build_rdn (GByteArray * out, const guint8 * oid, gsize oid_length, guint8 value_tag, const gchar * value);
static void frida_build_validity (GByteArray * out);
static void frida_build_subject_public_key_info (GByteArray * out, const GByteArray * rsa_public_key);
static void frida_build_signed_certificate (GByteArray * out, const GByteArray * tbs, const GByteArray * signature);
static void frida_build_algorithm_identifier (GByteArray * out, const guint8 * oid, gsize oid_length);
static void frida_build_pkcs8_private_key_info (GByteArray * out, const GByteArray * rsa_private_key);
static gchar * frida_pem_encode (const gchar * type, const guint8 * der, gsize der_length);
static void frida_der_append_tlv (GByteArray * out, guint8 tag, const guint8 * value, gsize length);
static void frida_der_append_length (GByteArray * out, gsize length);
static void frida_der_append_integer_uint (GByteArray * out, guint64 value);
static void frida_der_append_null (GByteArray * out);
static void frida_der_append_oid (GByteArray * out, const guint8 * oid, gsize length);
static void frida_der_append_utctime (GByteArray * out, time_t t);
static void frida_der_append_bit_string (GByteArray * out, const guint8 * bytes, gsize length);
static const guint8 frida_oid_country[] = { 0x55, 0x04, 0x06 };
static const guint8 frida_oid_organization[] = { 0x55, 0x04, 0x0a };
static const guint8 frida_oid_common_name[] = { 0x55, 0x04, 0x03 };
static const guint8 frida_oid_rsa_encryption[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 };
static const guint8 frida_oid_sha256_with_rsa[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b };
#else
static gchar * frida_steal_bio_to_string (BIO ** bio);
#endif
static int frida_on_connection_output (void * addr, void * buffer, size_t length, uint8_t tos, uint8_t set_df);
static void frida_on_debug_printf (const char * format, ...);
static void frida_on_upcall (struct socket * sock, void * user_data, int flags);
#ifdef HAVE_GIOAPPLE
void
_frida_generate_certificate (guint8 ** cert_der, gint * cert_der_length, gchar ** cert_pem, gchar ** key_pem)
{
SecKeyRef private_key;
GByteArray * rsa_public_key, * rsa_private_key, * subject_public_key, * tbs, * signature, * certificate, * pkcs8;
*cert_der = NULL;
*cert_der_length = 0;
*cert_pem = NULL;
*key_pem = NULL;
private_key = frida_rsa_keypair_generate ();
rsa_public_key = g_byte_array_new ();
frida_rsa_export_public_key (private_key, rsa_public_key);
rsa_private_key = g_byte_array_new ();
frida_rsa_export_private_key (private_key, rsa_private_key);
subject_public_key = g_byte_array_new ();
frida_build_subject_public_key_info (subject_public_key, rsa_public_key);
tbs = g_byte_array_new ();
frida_build_tbs_certificate (tbs, subject_public_key);
signature = g_byte_array_new ();
frida_rsa_sign_sha256 (private_key, tbs->data, tbs->len, signature);
certificate = g_byte_array_new ();
frida_build_signed_certificate (certificate, tbs, signature);
pkcs8 = g_byte_array_new ();
frida_build_pkcs8_private_key_info (pkcs8, rsa_private_key);
*cert_der = g_memdup2 (certificate->data, certificate->len);
*cert_der_length = certificate->len;
*cert_pem = frida_pem_encode ("CERTIFICATE", certificate->data, certificate->len);
*key_pem = frida_pem_encode ("PRIVATE KEY", pkcs8->data, pkcs8->len);
g_byte_array_unref (pkcs8);
g_byte_array_unref (certificate);
g_byte_array_unref (signature);
g_byte_array_unref (tbs);
g_byte_array_unref (subject_public_key);
g_byte_array_unref (rsa_private_key);
g_byte_array_unref (rsa_public_key);
CFRelease (private_key);
}
static SecKeyRef
frida_rsa_keypair_generate (void)
{
SecKeyRef private_key;
CFMutableDictionaryRef attrs;
const int key_size_bits = 2048;
CFNumberRef key_size;
attrs = CFDictionaryCreateMutable (kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
key_size = CFNumberCreate (kCFAllocatorDefault, kCFNumberIntType, &key_size_bits);
CFDictionarySetValue (attrs, kSecAttrKeyType, kSecAttrKeyTypeRSA);
CFDictionarySetValue (attrs, kSecAttrKeySizeInBits, key_size);
CFRelease (key_size);
private_key = SecKeyCreateRandomKey (attrs, NULL);
g_assert (private_key != NULL);
CFRelease (attrs);
return private_key;
}
static void
frida_rsa_export_public_key (SecKeyRef private_key, GByteArray * out)
{
SecKeyRef public_key;
CFDataRef data;
public_key = SecKeyCopyPublicKey (private_key);
g_assert (public_key != NULL);
data = SecKeyCopyExternalRepresentation (public_key, NULL);
g_assert (data != NULL);
g_byte_array_append (out, CFDataGetBytePtr (data), CFDataGetLength (data));
CFRelease (data);
CFRelease (public_key);
}
static void
frida_rsa_export_private_key (SecKeyRef private_key, GByteArray * out)
{
CFDataRef data;
data = SecKeyCopyExternalRepresentation (private_key, NULL);
g_assert (data != NULL);
g_byte_array_append (out, CFDataGetBytePtr (data), (guint) CFDataGetLength (data));
CFRelease (data);
}
static void
frida_rsa_sign_sha256 (SecKeyRef private_key, const guint8 * message, gsize message_length, GByteArray * out)
{
CFDataRef to_sign, signature;
to_sign = CFDataCreate (kCFAllocatorDefault, message, (CFIndex) message_length);
signature = SecKeyCreateSignature (private_key, kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA256, to_sign, NULL);
g_assert (signature != NULL);
g_byte_array_append (out, CFDataGetBytePtr (signature), (guint) CFDataGetLength (signature));
CFRelease (signature);
CFRelease (to_sign);
}
static void
frida_build_tbs_certificate (GByteArray * out, const GByteArray * subject_public_key)
{
GByteArray * inner = g_byte_array_new ();
frida_der_append_integer_uint (inner, 1);
frida_build_algorithm_identifier (inner, frida_oid_sha256_with_rsa, sizeof (frida_oid_sha256_with_rsa));
frida_build_name (inner);
frida_build_validity (inner);
frida_build_name (inner);
g_byte_array_append (inner, subject_public_key->data, subject_public_key->len);
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
g_byte_array_unref (inner);
}
static void
frida_build_name (GByteArray * out)
{
GByteArray * inner = g_byte_array_new ();
frida_build_rdn (inner, frida_oid_country, sizeof (frida_oid_country), 0x13, "CA");
frida_build_rdn (inner, frida_oid_organization, sizeof (frida_oid_organization), 0x0c, "Frida");
frida_build_rdn (inner, frida_oid_common_name, sizeof (frida_oid_common_name), 0x0c, "lolcathost");
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
g_byte_array_unref (inner);
}
static void
frida_build_rdn (GByteArray * out, const guint8 * oid, gsize oid_length, guint8 value_tag, const gchar * value)
{
GByteArray * atv, * rdn;
atv = g_byte_array_new ();
rdn = g_byte_array_new ();
frida_der_append_oid (atv, oid, oid_length);
frida_der_append_tlv (atv, value_tag, (const guint8 *) value, strlen (value));
frida_der_append_tlv (rdn, 0x30, atv->data, atv->len);
frida_der_append_tlv (out, 0x31, rdn->data, rdn->len);
g_byte_array_unref (rdn);
g_byte_array_unref (atv);
}
static void
frida_build_validity (GByteArray * out)
{
const time_t lifetime_seconds = 15780000;
GByteArray * inner;
time_t not_before, not_after;
inner = g_byte_array_new ();
not_before = time (NULL);
not_after = not_before + lifetime_seconds;
frida_der_append_utctime (inner, not_before);
frida_der_append_utctime (inner, not_after);
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
g_byte_array_unref (inner);
}
static void
frida_build_subject_public_key_info (GByteArray * out, const GByteArray * rsa_public_key)
{
GByteArray * inner = g_byte_array_new ();
frida_build_algorithm_identifier (inner, frida_oid_rsa_encryption, sizeof (frida_oid_rsa_encryption));
frida_der_append_bit_string (inner, rsa_public_key->data, rsa_public_key->len);
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
g_byte_array_unref (inner);
}
static void
frida_build_signed_certificate (GByteArray * out, const GByteArray * tbs, const GByteArray * signature)
{
GByteArray * inner = g_byte_array_new ();
g_byte_array_append (inner, tbs->data, tbs->len);
frida_build_algorithm_identifier (inner, frida_oid_sha256_with_rsa, sizeof (frida_oid_sha256_with_rsa));
frida_der_append_bit_string (inner, signature->data, signature->len);
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
g_byte_array_unref (inner);
}
static void
frida_build_algorithm_identifier (GByteArray * out, const guint8 * oid, gsize oid_length)
{
GByteArray * inner = g_byte_array_new ();
frida_der_append_oid (inner, oid, oid_length);
frida_der_append_null (inner);
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
g_byte_array_unref (inner);
}
static void
frida_build_pkcs8_private_key_info (GByteArray * out, const GByteArray * rsa_private_key)
{
GByteArray * inner = g_byte_array_new ();
frida_der_append_integer_uint (inner, 0);
frida_build_algorithm_identifier (inner, frida_oid_rsa_encryption, sizeof (frida_oid_rsa_encryption));
frida_der_append_tlv (inner, 0x04, rsa_private_key->data, rsa_private_key->len);
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
g_byte_array_unref (inner);
}
static gchar *
frida_pem_encode (const gchar * type, const guint8 * der, gsize der_length)
{
GString * pem;
gchar * base64;
gsize length, i;
base64 = g_base64_encode (der, der_length);
length = strlen (base64);
pem = g_string_sized_new (length + 128);
g_string_append_printf (pem, "-----BEGIN %s-----\n", type);
for (i = 0; i < length; i += 64)
{
gsize chunk = MIN (64, length - i);
g_string_append_len (pem, base64 + i, chunk);
g_string_append_c (pem, '\n');
}
g_string_append_printf (pem, "-----END %s-----\n", type);
g_free (base64);
return g_string_free (pem, FALSE);
}
static void
frida_der_append_tlv (GByteArray * out, guint8 tag, const guint8 * value, gsize length)
{
g_byte_array_append (out, &tag, 1);
frida_der_append_length (out, length);
if (length > 0)
g_byte_array_append (out, value, length);
}
static void
frida_der_append_length (GByteArray * out, gsize length)
{
gint i;
guint8 buf[9], header;
if (length < 0x80)
{
guint8 b = (guint8) length;
g_byte_array_append (out, &b, 1);
return;
}
i = 0;
while (length > 0)
{
buf[++i] = (guint8) (length & 0xff);
length >>= 8;
}
header = (guint8) (0x80 | i);
g_byte_array_append (out, &header, 1);
while (i > 0)
{
g_byte_array_append (out, &buf[i], 1);
i--;
}
}
static void
frida_der_append_integer_uint (GByteArray * out, guint64 value)
{
gint i;
guint8 buf[9];
gint start;
for (i = 7; i >= 0; i--)
{
buf[i + 1] = (guint8) (value & 0xff);
value >>= 8;
}
buf[0] = 0;
start = 0;
while (start < 8 && buf[start] == 0 && (buf[start + 1] & 0x80) == 0)
start++;
frida_der_append_tlv (out, 0x02, buf + start, (gsize) (9 - start));
}
static void
frida_der_append_null (GByteArray * out)
{
frida_der_append_tlv (out, 0x05, NULL, 0);
}
static void
frida_der_append_oid (GByteArray * out, const guint8 * oid, gsize length)
{
frida_der_append_tlv (out, 0x06, oid, length);
}
static void
frida_der_append_utctime (GByteArray * out, time_t t)
{
struct tm tm;
gchar buf[16];
gmtime_r (&t, &tm);
g_snprintf (buf, sizeof (buf), "%02d%02d%02d%02d%02d%02dZ",
tm.tm_year % 100, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);
frida_der_append_tlv (out, 0x17, (const guint8 *) buf, strlen (buf));
}
static void
frida_der_append_bit_string (GByteArray * out, const guint8 * bytes, gsize length)
{
guint8 tag = 0x03;
guint8 unused_bits = 0;
g_byte_array_append (out, &tag, 1);
frida_der_append_length (out, length + 1);
g_byte_array_append (out, &unused_bits, 1);
if (length > 0)
g_byte_array_append (out, bytes, length);
}
#else
void
_frida_generate_certificate (guint8 ** cert_der, gint * cert_der_length, gchar ** cert_pem, gchar ** key_pem)
{
@@ -90,6 +496,8 @@ frida_steal_bio_to_string (BIO ** bio)
return result;
}
#endif
void
_frida_sctp_connection_initialize_sctp_backend (void)
{
+2
View File
@@ -1,3 +1,5 @@
using Efni;
#if HAVE_NICE
namespace Frida {
namespace PeerConnection {
+124
View File
@@ -220,4 +220,128 @@ namespace Frida {
Posix.tcsetattr (fd, 0, tios);
}
#endif
namespace ChildProcess {
public async WaitResult wait_for_next_stop (uint pid, Cancellable? cancellable) throws Error, IOError {
var main_context = MainContext.get_thread_default ();
bool timed_out = false;
var timeout_source = new TimeoutSource.seconds (5);
timeout_source.set_callback (() => {
timed_out = true;
return Source.REMOVE;
});
timeout_source.attach (main_context);
int status = 0;
uint[] delays = { 0, 1, 2, 5, 10, 20, 50, 250 };
try {
for (uint i = 0; !timed_out && !cancellable.set_error_if_cancelled (); i++) {
int res = Posix.waitpid ((Posix.pid_t) pid, out status, Posix.WNOHANG);
if (res == -1)
throw new Error.NOT_SUPPORTED ("Unable to wait for next stop: %s", strerror (errno));
if (res != 0)
break;
uint delay_ms = (i < delays.length) ? delays[i] : delays[delays.length - 1];
var delay_source = new TimeoutSource (delay_ms);
delay_source.set_callback (wait_for_next_stop.callback);
delay_source.attach (main_context);
var cancel_source = new CancellableSource (cancellable);
cancel_source.set_callback (wait_for_next_stop.callback);
cancel_source.attach (main_context);
yield;
cancel_source.destroy ();
delay_source.destroy ();
}
} finally {
timeout_source.destroy ();
}
if (timed_out)
throw new Error.TIMED_OUT ("Unexpectedly timed out while waiting for stop from process with PID %u", pid);
return WaitResult (pid, status);
}
}
public struct WaitResult {
public uint pid;
public int status;
public WaitKind kind;
public uint exit_status;
public int term_signal;
public int stop_signal;
public WaitResult (uint pid, int status) {
this.pid = pid;
this.status = status;
kind = WaitKind.OTHER;
exit_status = 0;
term_signal = 0;
stop_signal = 0;
if (PosixStatus.is_exit (status)) {
kind = WaitKind.EXITED;
exit_status = PosixStatus.parse_exit_status (status);
} else if (PosixStatus.is_signaled (status)) {
kind = WaitKind.SIGNALED;
term_signal = PosixStatus.parse_termination_signal (status);
} else if (PosixStatus.is_stopped (status)) {
kind = WaitKind.STOPPED;
stop_signal = PosixStatus.parse_stop_signal (status);
}
}
public void check_stopped () throws Error {
switch (kind) {
case WaitKind.EXITED:
throw new Error.NOT_SUPPORTED ("Target exited with status %u", exit_status);
case WaitKind.SIGNALED:
throw new Error.NOT_SUPPORTED ("Target terminated with signal %u", term_signal);
case WaitKind.STOPPED:
return;
default:
throw new Error.NOT_SUPPORTED ("Unexpected status: 0x%08x", status);
}
}
}
public enum WaitKind {
EXITED,
SIGNALED,
STOPPED,
OTHER
}
namespace PosixStatus {
[CCode (cname = "WIFEXITED", cheader_filename = "sys/wait.h")]
private extern bool is_exit (int status);
[CCode (cname = "WIFSIGNALED", cheader_filename = "sys/wait.h")]
private extern bool is_signaled (int status);
[CCode (cname = "WIFSTOPPED", cheader_filename = "sys/wait.h")]
private extern bool is_stopped (int status);
[CCode (cname = "WEXITSTATUS", cheader_filename = "sys/wait.h")]
private extern uint parse_exit_status (int status);
[CCode (cname = "WTERMSIG", cheader_filename = "sys/wait.h")]
private extern Posix.Signal parse_termination_signal (int status);
[CCode (cname = "WSTOPSIG", cheader_filename = "sys/wait.h")]
private extern Posix.Signal parse_stop_signal (int status);
}
}
+58 -124
View File
@@ -1,19 +1,18 @@
namespace Frida {
public sealed class Promise<T> {
private Impl<T> impl;
private Efni.Promise<T> impl;
private Future<T>? _future;
public Future<T> future {
get {
return impl;
if (_future == null)
_future = new FutureImpl<T> (impl.future);
return _future;
}
}
public Promise () {
impl = new Impl<T> ();
}
~Promise () {
impl.abandon ();
impl = new Efni.Promise<T> ();
}
public void resolve (T result) {
@@ -24,149 +23,84 @@ namespace Frida {
impl.reject (error);
}
private class Impl<T> : Object, Future<T> {
private class FutureImpl<T> : Object, Future<T> {
private Efni.Future<T> inner;
public bool ready {
get {
return _ready;
return inner.ready;
}
}
private bool _ready = false;
public T? value {
get {
return _value;
return inner.value;
}
}
private T? _value;
public GLib.Error? error {
get {
return _error;
return inner.error;
}
}
private GLib.Error? _error;
private Gee.ArrayQueue<CompletionFuncEntry>? on_complete;
private Gee.ArrayQueue<ThenHandlerEntry<T>>? on_then;
public FutureImpl (Efni.Future<T> inner) {
this.inner = inner;
}
public async T wait_async (Cancellable? cancellable) throws Frida.Error, IOError {
if (_ready)
return get_result ();
public async T wait_async (Cancellable? cancellable) throws Error, IOError {
if (!inner.ready) {
bool resumed = false;
var entry = new CompletionFuncEntry (wait_async.callback);
if (on_complete == null)
on_complete = new Gee.ArrayQueue<CompletionFuncEntry> ();
on_complete.offer (entry);
var cancel_source = new CancellableSource (cancellable);
cancel_source.set_callback (() => {
if (!resumed) {
resumed = true;
wait_async.callback ();
}
return Source.REMOVE;
});
cancel_source.attach (MainContext.get_thread_default ());
var cancel_source = new CancellableSource (cancellable);
cancel_source.set_callback (() => {
on_complete.remove (entry);
wait_async.callback ();
return false;
});
cancel_source.attach (MainContext.get_thread_default ());
inner.then (f => {
if (!resumed) {
resumed = true;
wait_async.callback ();
}
});
yield;
yield;
cancel_source.destroy ();
cancel_source.destroy ();
cancellable.set_error_if_cancelled ();
cancellable.set_error_if_cancelled ();
}
return get_result ();
}
public void then (owned FutureCompletionHandler<T> handler) {
if (_ready) {
schedule (() => {
handler (this);
return Source.REMOVE;
});
return;
inner.then (f => {
handler (this);
});
}
public T get_result () throws Error, IOError {
GLib.Error? e = inner.error;
if (e != null) {
if (e is Error)
throw (Error) e;
if (e is Efni.Error)
throw error_from_efni ((Efni.Error) e);
if (e is IOError.CANCELLED)
throw (IOError) e;
throw new Error.TRANSPORT ("%s", e.message);
}
if (on_then == null)
on_then = new Gee.ArrayQueue<ThenHandlerEntry<T>> ();
on_then.offer (new ThenHandlerEntry<T> ((owned) handler));
}
public T get_result () throws Frida.Error, IOError {
if (error != null) {
if (error is Frida.Error)
throw (Frida.Error) error;
if (error is IOError.CANCELLED)
throw (IOError) error;
throw new Frida.Error.TRANSPORT ("%s", error.message);
}
return _value;
}
internal void resolve (T value) {
assert (!_ready);
_value = value;
transition_to_ready ();
}
internal void reject (GLib.Error error) {
assert (!_ready);
_error = error;
transition_to_ready ();
}
internal void abandon () {
if (!_ready) {
reject (new Frida.Error.INVALID_OPERATION ("Promise abandoned"));
}
}
internal void transition_to_ready () {
_ready = true;
if (on_complete != null || on_then != null) {
schedule (() => {
if (on_complete != null) {
CompletionFuncEntry? entry;
while ((entry = on_complete.poll ()) != null)
entry.func ();
on_complete = null;
}
if (on_then != null) {
ThenHandlerEntry? entry;
while ((entry = on_then.poll ()) != null)
entry.handler (this);
on_then = null;
}
return Source.REMOVE;
});
}
}
private static void schedule (owned SourceFunc function) {
var source = new IdleSource ();
source.set_callback ((owned) function);
source.attach (MainContext.get_thread_default ());
}
}
private class CompletionFuncEntry {
public SourceFunc func;
public CompletionFuncEntry (owned SourceFunc func) {
this.func = (owned) func;
}
}
private class ThenHandlerEntry<T> {
public FutureCompletionHandler<T> handler;
public ThenHandlerEntry (owned FutureCompletionHandler<T> handler) {
this.handler = (owned) handler;
return inner.value;
}
}
}
@@ -175,9 +109,9 @@ namespace Frida {
public abstract bool ready { get; }
public abstract T? value { get; }
public abstract GLib.Error? error { get; }
public abstract async T wait_async (Cancellable? cancellable) throws Frida.Error, IOError;
public abstract async T wait_async (Cancellable? cancellable) throws Error, IOError;
public abstract void then (owned FutureCompletionHandler<T> handler);
public abstract T get_result () throws Frida.Error, IOError;
public abstract T get_result () throws Error, IOError;
}
public delegate void FutureCompletionHandler<T> (Future<T> future);
+36
View File
@@ -1,5 +1,11 @@
namespace Frida {
/**
* Issues RPC calls over a {@link RpcPeer}, matching responses to requests.
*/
public sealed class RpcClient : Object {
/**
* The peer used to send and receive RPC messages.
*/
public weak RpcPeer peer {
get;
construct;
@@ -7,10 +13,23 @@ namespace Frida {
private Gee.HashMap<string, PendingResponse> pending_responses = new Gee.HashMap<string, PendingResponse> ();
/**
* Creates an RPC client that talks over @peer.
*
* @param peer the peer to send and receive messages through
*/
public RpcClient (RpcPeer peer) {
Object (peer: peer);
}
/**
* Calls a remote method and waits for its result.
*
* @param method the method name to invoke
* @param args the JSON arguments
* @param data binary data to accompany the call, if any
* @return the method's JSON result
*/
public async Json.Node call (string method, Json.Node[] args, Bytes? data, Cancellable? cancellable) throws Error, IOError {
string request_id = Uuid.string_random ();
@@ -69,6 +88,13 @@ namespace Frida {
return pending.result;
}
/**
* Feeds an incoming message to the client, matching it against pending
* calls if it is an RPC response.
*
* @param json the incoming message as a JSON string
* @return true if the message was an RPC message and was handled
*/
public bool try_handle_message (string json) {
if (json.index_of ("\"frida:rpc\"") == -1)
return false;
@@ -157,7 +183,17 @@ namespace Frida {
}
}
/**
* A transport over which an {@link RpcClient} sends and receives RPC
* messages.
*/
public interface RpcPeer : Object {
/**
* Posts an RPC message to the peer.
*
* @param json the message as a JSON string
* @param data binary data to accompany the message, if any
*/
public abstract async void post_rpc_message (string json, Bytes? data, Cancellable? cancellable) throws Error, IOError;
}
}
+702 -40
View File
File diff suppressed because it is too large. Load diff
-25
View File
@@ -1,30 +1,5 @@
#include "frida-base.h"
#ifdef HAVE_WINDOWS
# include <winsock2.h>
#else
# include <netinet/in.h>
# include <netinet/tcp.h>
#endif
void
frida_unix_socket_tune_buffer_sizes (gint fd)
{
#ifndef HAVE_WINDOWS
/* The defaults are typically as low as 4K. */
const int buffer_size = 256 * 1024;
setsockopt (fd, SOL_SOCKET, SO_RCVBUF, &buffer_size, sizeof (buffer_size));
setsockopt (fd, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof (buffer_size));
#endif
}
void
frida_tcp_enable_nodelay (GSocket * socket)
{
g_socket_set_option (socket, IPPROTO_TCP, TCP_NODELAY, TRUE, NULL);
}
const gchar *
_frida_version_string (void)
{
+270 -96
View File
@@ -1,133 +1,96 @@
using Efni;
namespace Frida {
public const uint16 DEFAULT_CONTROL_PORT = 27042;
public const uint16 DEFAULT_CLUSTER_PORT = 27052;
public SocketConnectable parse_control_address (string? address, uint16 port = 0) throws Error {
return parse_socket_address (address, port, "127.0.0.1", DEFAULT_CONTROL_PORT);
return parse_frida_address (address, port, DEFAULT_CONTROL_PORT);
}
public SocketConnectable parse_cluster_address (string? address, uint16 port = 0) throws Error {
return parse_socket_address (address, port, "127.0.0.1", DEFAULT_CLUSTER_PORT);
return parse_frida_address (address, port, DEFAULT_CLUSTER_PORT);
}
public SocketConnectable parse_socket_address (string? address, uint16 port, string default_address,
uint16 default_port) throws Error {
if (address == null)
address = default_address;
if (port == 0)
port = default_port;
#if !WINDOWS
if (address.has_prefix ("unix:")) {
string path = address.substring (5);
UnixSocketAddressType type = UnixSocketAddress.abstract_names_supported ()
? UnixSocketAddressType.ABSTRACT
: UnixSocketAddressType.PATH;
return new UnixSocketAddress.with_type (path, -1, type);
}
#endif
private SocketConnectable parse_frida_address (string? address, uint16 port, uint16 default_port) throws Error {
try {
return NetworkAddress.parse (address, port);
} catch (GLib.Error e) {
throw new Error.INVALID_ARGUMENT ("%s", e.message);
}
}
namespace UnixSocket {
public extern void tune_buffer_sizes (int fd);
}
namespace Tcp {
public extern void enable_nodelay (Socket socket);
}
namespace Udp {
public size_t recv (uint8[] data, DatagramBased source, Cancellable? cancellable, out InetSocketAddress remote_address)
throws Error, IOError {
var v = InputVector ();
v.buffer = data;
v.size = data.length;
InputVector[] vectors = { v };
var m = InputMessage ();
remote_address = null;
m.address = &remote_address;
m.vectors = vectors;
m.num_vectors = vectors.length;
InputMessage[] messages = { m };
try {
source.receive_messages (messages, 0, 0, cancellable);
} catch (GLib.Error e) {
throw new Error.TRANSPORT ("%s", e.message);
}
return messages[0].bytes_received;
}
public void send (uint8[] data, DatagramBased sink, Cancellable? cancellable) throws Error, IOError {
send_to (data, null, sink, cancellable);
}
public void send_to (uint8[] data, InetSocketAddress? dest_addr, DatagramBased sink, Cancellable? cancellable)
throws Error, IOError {
var v = OutputVector ();
v.buffer = data;
v.size = data.length;
OutputVector[] vectors = { v };
var m = OutputMessage ();
m.address = dest_addr;
m.vectors = vectors;
m.num_vectors = vectors.length;
OutputMessage[] messages = { m };
try {
sink.send_messages (messages, 0, 0, cancellable);
} catch (GLib.Error e) {
throw new Error.TRANSPORT ("%s", e.message);
}
return Efni.parse_socket_address (address, port, "127.0.0.1", default_port);
} catch (Efni.Error e) {
throw error_from_efni (e);
}
}
/**
* Configures the network endpoint a {@link PortalService} or
* {@link ControlService} listens on.
*/
public sealed class EndpointParameters : Object {
/**
* The address to bind to, or null for the default.
*/
public string? address {
get;
construct;
}
/**
* The port to listen on, or 0 for the default.
*/
public uint16 port {
get;
construct;
}
/**
* TLS certificate to serve, enabling TLS when set.
*/
public TlsCertificate? certificate {
get;
construct;
}
/**
* Origin to require from connecting clients, if any.
*/
public string? origin {
get;
construct;
}
/**
* Service used to authenticate connecting clients, if any.
*/
public AuthenticationService? auth_service {
get;
construct;
}
/**
* Directory whose files are served as static web assets, if any.
*/
public File? asset_root {
get;
set;
}
/**
* Handler invoked for web requests, if any.
*/
public WebRequestHandler? request_handler {
get;
set;
}
/**
* Creates endpoint parameters.
*
* @param address the address to bind to, or null for the default
* @param port the port to listen on, or 0 for the default
* @param certificate TLS certificate to serve, or null for plaintext
* @param origin origin to require from clients, or null
* @param auth_service service used to authenticate clients, or null
* @param asset_root directory of static assets to serve, or null
*/
public EndpointParameters (string? address = null, uint16 port = 0, TlsCertificate? certificate = null,
string? origin = null, AuthenticationService? auth_service = null, File? asset_root = null) {
Object (
@@ -141,6 +104,132 @@ namespace Frida {
}
}
/**
* Handles web requests made to an endpoint's served assets.
*/
public interface WebRequestHandler : Object {
/**
* Handles a single web request.
*
* @param request the incoming request
* @return the response to send, or null to fall through to the default
* handling
*/
public abstract async WebResponse? handle_request (WebRequest request, Cancellable? cancellable) throws Error, IOError;
}
/**
* An incoming web request passed to a {@link WebRequestHandler}.
*/
public sealed class WebRequest : Object {
/**
* The HTTP method, such as `GET` or `POST`.
*/
public string method {
get;
construct;
}
/**
* The request path.
*/
public string path {
get;
construct;
}
/**
* The raw query string, if any.
*/
public string? query_string {
get;
construct;
}
/**
* The request body, if any.
*/
public Bytes? body {
get;
construct;
}
private Soup.MessageHeaders raw_headers;
internal WebRequest (string method, string path, string? query_string, Soup.MessageHeaders headers, Bytes? body) {
Object (method: method, path: path, query_string: query_string, body: body);
raw_headers = headers;
}
/**
* Invokes @func for each request header.
*
* @param func function called with each header's name and value
*/
public void foreach_header (WebRequestHeaderFunc func) {
raw_headers.foreach ((name, val) => {
func (name, val);
});
}
}
/**
* Callback invoked for each header while iterating a {@link WebRequest}.
*
* @param name the header name
* @param val the header value
*/
public delegate void WebRequestHeaderFunc (string name, string val);
/**
* A response returned from a {@link WebRequestHandler}.
*/
public sealed class WebResponse : Object {
/**
* The HTTP status code.
*/
public uint status {
get;
construct;
}
/**
* The response body.
*/
public Bytes body {
get;
construct;
}
private Soup.MessageHeaders raw_headers = new Soup.MessageHeaders (RESPONSE);
/**
* Creates a response.
*
* @param status the HTTP status code
* @param body the response body
*/
public WebResponse (uint status, Bytes body) {
Object (status: status, body: body);
}
/**
* Adds a header to the response.
*
* @param name the header name
* @param val the header value
*/
public void add_header (string name, string val) {
raw_headers.append (name, val);
}
internal void write_headers_to (Soup.MessageHeaders target) {
raw_headers.foreach ((name, val) => {
target.replace (name, val);
});
}
}
public async IOStream negotiate_connection (IOStream stream, WebServiceTransport transport, string host, string? origin,
Cancellable? cancellable) throws Error, IOError {
var input = (DataInputStream) Object.new (typeof (DataInputStream),
@@ -468,14 +557,14 @@ namespace Frida {
server.add_websocket_handler ("/ws", endpoint_params.origin, null, on_websocket_opened);
if (endpoint_params.asset_root != null)
server.add_handler (null, on_asset_request);
if (endpoint_params.request_handler != null || endpoint_params.asset_root != null)
server.add_handler (null, on_http_request);
}
public void close () {
io_cancellable.cancel ();
if (endpoint_params.asset_root != null)
if (endpoint_params.request_handler != null || endpoint_params.asset_root != null)
server.remove_handler ("/");
server.remove_handler ("/ws");
@@ -560,7 +649,8 @@ namespace Frida {
try {
remote_address = sc.get_remote_address ();
} catch (GLib.Error e) {
assert_not_reached ();
remove_connection (peer);
return;
}
incoming (peer, remote_address);
@@ -575,20 +665,77 @@ namespace Frida {
connections.remove (connection);
}
private void on_asset_request (Soup.Server server, Soup.ServerMessage msg, string path,
private void on_http_request (Soup.Server server, Soup.ServerMessage msg, string path,
HashTable<string, string>? query) {
msg.get_response_headers ().replace ("Server", "Frida/" + _version_string ());
unowned string method = msg.get_method ();
if (method != "GET" && method != "HEAD") {
msg.set_status (Soup.Status.METHOD_NOT_ALLOWED, null);
msg.pause ();
handle_http_request.begin (msg, path);
}
private async void handle_http_request (Soup.ServerMessage msg, string path) {
WebRequestHandler? handler = endpoint_params.request_handler;
if (handler != null) {
if (yield try_request_handler (handler, msg, path, io_cancellable))
return;
}
File? asset_root = endpoint_params.asset_root;
if (asset_root != null) {
unowned string method = msg.get_method ();
if (method != "GET" && method != "HEAD") {
msg.set_status (Soup.Status.METHOD_NOT_ALLOWED, null);
msg.unpause ();
return;
}
File location = asset_root.resolve_relative_path (path.next_char ());
yield handle_asset_request (path, location, msg);
return;
}
File location = endpoint_params.asset_root.resolve_relative_path (path.next_char ());
msg.set_status (Soup.Status.NOT_FOUND, null);
msg.unpause ();
}
msg.pause ();
handle_asset_request.begin (path, location, msg);
private static async bool try_request_handler (WebRequestHandler handler, Soup.ServerMessage msg, string path,
Cancellable? cancellable) {
var body = msg.get_request_body ();
Bytes? request_body = (body.length != 0) ? body.flatten () : null;
unowned Uri? uri = msg.get_uri ();
string? query_string = (uri != null) ? uri.get_query () : null;
var request = new WebRequest (msg.get_method (), path, query_string, msg.get_request_headers (),
request_body);
WebResponse? response;
try {
response = yield handler.handle_request (request, cancellable);
} catch (Error e) {
msg.set_status (Soup.Status.INTERNAL_SERVER_ERROR, null);
msg.set_response ("text/plain; charset=utf-8", Soup.MemoryUse.COPY, e.message.data);
msg.unpause ();
return true;
} catch (IOError e) {
msg.unpause ();
return true;
}
if (response == null)
return false;
apply_response (msg, response);
msg.unpause ();
return true;
}
private static void apply_response (Soup.ServerMessage msg, WebResponse response) {
msg.set_status (response.status, null);
response.write_headers_to (msg.get_response_headers ());
Bytes body = response.body;
if (body.length > 0)
msg.get_response_body ().append_bytes (body);
}
private async void handle_asset_request (string path, File file, Soup.ServerMessage msg) {
@@ -893,6 +1040,9 @@ namespace Frida {
construct {
websocket.max_incoming_payload_size = (256 * 1024) + 1; // XXX: There's an off-by-one error in libsoup
if (websocket.get_class ().find_property ("max-total-message-size") != null)
websocket.set ("max-total-message-size", websocket.max_incoming_payload_size);
websocket.closed.connect (on_closed);
websocket.message.connect (on_message);
}
@@ -940,6 +1090,12 @@ namespace Frida {
Memory.copy (buffer, recv_queue.data, n);
recv_queue.remove_range (0, (uint) n);
if (recv_queue.len == 0) {
#if HAVE_SOUP_WEBSOCKET_BACKPRESSURE
resume_websocket_input ();
#endif
}
update_pending_io ();
} else {
if (state == OPEN)
@@ -953,6 +1109,21 @@ namespace Frida {
return n;
}
#if HAVE_SOUP_WEBSOCKET_BACKPRESSURE
private void resume_websocket_input () {
if (main_context.is_owner ()) {
websocket.resume_input ();
} else {
var source = new IdleSource ();
source.set_callback (() => {
websocket.resume_input ();
return Source.REMOVE;
});
source.attach (main_context);
}
}
#endif
public override ssize_t write (uint8[] buffer) {
with_state_lock (() => {
send_queue.append (buffer);
@@ -1006,6 +1177,9 @@ namespace Frida {
private void on_message (int type, Bytes message) {
with_state_lock (() => {
recv_queue.append (message.get_data ());
#if HAVE_SOUP_WEBSOCKET_BACKPRESSURE
websocket.pause_input ();
#endif
update_pending_io ();
});
}
-336
View File
@@ -1,336 +0,0 @@
namespace Frida {
public abstract class VirtualStream : IOStream {
public override InputStream input_stream {
get {
return _input_stream;
}
}
public override OutputStream output_stream {
get {
return _output_stream;
}
}
public IOCondition pending_io {
get {
lock (state)
return _pending_io;
}
}
protected State state = OPEN;
private VirtualInputStream _input_stream;
private VirtualOutputStream _output_stream;
private IOCondition _pending_io;
private Gee.Map<unowned Source, IOCondition> sources = new Gee.HashMap<unowned Source, IOCondition> ();
protected Cancellable io_cancellable = new Cancellable ();
protected MainContext main_context;
protected enum State {
CREATED,
OPENING,
OPEN,
CLOSED
}
protected delegate void WorkFunc ();
construct {
_input_stream = new VirtualInputStream (this);
_output_stream = new VirtualOutputStream (this);
main_context = MainContext.ref_thread_default ();
state = query_initial_state ();
_pending_io = query_events ();
}
public override void dispose () {
_output_stream.detach ();
_input_stream.detach ();
io_cancellable.cancel ();
base.dispose ();
}
protected virtual State query_initial_state () {
return OPEN;
}
protected abstract IOCondition query_events ();
public override bool close (GLib.Cancellable? cancellable) throws IOError {
_close ();
return true;
}
public override async bool close_async (int io_priority, GLib.Cancellable? cancellable) throws IOError {
_close ();
return true;
}
private void _close () {
if (main_context.is_owner ()) {
do_close ();
} else {
var source = new IdleSource ();
source.set_callback (() => {
do_close ();
return Source.REMOVE;
});
source.attach (main_context);
}
}
private void do_close () {
if (state == CLOSED)
return;
handle_close ();
}
protected abstract void handle_close ();
public virtual void shutdown_read () throws IOError {
}
public virtual void shutdown_write () throws IOError {
}
public abstract ssize_t read (uint8[] buffer) throws IOError;
public abstract ssize_t write (uint8[] buffer) throws IOError;
protected void with_state_lock (WorkFunc func) {
lock (state)
func ();
}
public void register_source (Source source, IOCondition condition) {
lock (state)
sources[source] = condition | IOCondition.ERR | IOCondition.HUP;
}
public void unregister_source (Source source) {
lock (state)
sources.unset (source);
}
protected virtual void update_pending_io () {
lock (state) {
_pending_io = query_events ();
foreach (var entry in sources.entries) {
unowned Source source = entry.key;
IOCondition c = entry.value;
if ((_pending_io & c) != 0)
source.set_ready_time (0);
}
}
notify_property ("pending-io");
}
}
private sealed class VirtualInputStream : InputStream, PollableInputStream {
private weak VirtualStream stream;
public VirtualInputStream (VirtualStream stream) {
Object ();
this.stream = stream;
}
internal void detach () {
stream = null;
}
public override bool close (Cancellable? cancellable) throws IOError {
if (stream != null)
stream.shutdown_read ();
return true;
}
public override async bool close_async (int io_priority, Cancellable? cancellable) throws GLib.IOError {
return close (cancellable);
}
public override ssize_t read (uint8[] buffer, Cancellable? cancellable) throws IOError {
if (stream == null)
return 0;
if (!is_readable ()) {
bool done = false;
var mutex = Mutex ();
var cond = Cond ();
ulong io_handler = stream.notify["pending-io"].connect ((obj, pspec) => {
if (is_readable ()) {
mutex.lock ();
done = true;
cond.signal ();
mutex.unlock ();
}
});
ulong cancellation_handler = 0;
if (cancellable != null) {
cancellation_handler = cancellable.connect (() => {
mutex.lock ();
done = true;
cond.signal ();
mutex.unlock ();
});
}
if (!is_readable ()) {
mutex.lock ();
while (!done)
cond.wait (mutex);
mutex.unlock ();
}
if (cancellation_handler != 0)
cancellable.disconnect (cancellation_handler);
stream.disconnect (io_handler);
cancellable.set_error_if_cancelled ();
}
return stream.read (buffer);
}
public bool can_poll () {
return true;
}
public bool is_readable () {
if (stream == null)
return true;
return (stream.pending_io & IOCondition.IN) != 0;
}
public PollableSource create_source (Cancellable? cancellable) {
return new PollableSource.full (this, new VirtualIOSource (stream, IOCondition.IN), cancellable);
}
public ssize_t read_nonblocking_fn (uint8[] buffer) throws GLib.Error {
if (stream == null)
return 0;
return stream.read (buffer);
}
}
private sealed class VirtualOutputStream : OutputStream, PollableOutputStream {
private weak VirtualStream? stream;
public VirtualOutputStream (VirtualStream stream) {
Object ();
this.stream = stream;
}
internal void detach () {
stream = null;
}
public override bool close (Cancellable? cancellable) throws IOError {
if (stream != null)
stream.shutdown_write ();
return true;
}
public override async bool close_async (int io_priority, Cancellable? cancellable) throws GLib.IOError {
return close (cancellable);
}
public override bool flush (GLib.Cancellable? cancellable) throws GLib.Error {
return true;
}
public override async bool flush_async (int io_priority, GLib.Cancellable? cancellable) throws GLib.Error {
return true;
}
public override ssize_t write (uint8[] buffer, Cancellable? cancellable) throws IOError {
return stream.write (buffer);
}
public bool can_poll () {
return true;
}
public bool is_writable () {
if (stream == null)
return false;
return (stream.pending_io & IOCondition.OUT) != 0;
}
public PollableSource create_source (Cancellable? cancellable) {
return new PollableSource.full (this, new VirtualIOSource (stream, IOCondition.OUT), cancellable);
}
public ssize_t write_nonblocking_fn (uint8[]? buffer) throws GLib.Error {
if (stream == null)
throw new IOError.CLOSED ("Virtual is closed");
return stream.write (buffer);
}
public PollableReturn writev_nonblocking_fn (OutputVector[] vectors, out size_t bytes_written) throws GLib.Error {
assert_not_reached ();
}
}
private class VirtualIOSource : Source {
public VirtualStream? stream;
public IOCondition condition;
public VirtualIOSource (VirtualStream stream, IOCondition condition) {
this.stream = stream;
this.condition = condition;
if (stream != null)
stream.register_source (this, condition);
}
~VirtualIOSource () {
if (stream != null)
stream.unregister_source (this);
}
protected override bool prepare (out int timeout) {
timeout = -1;
return is_ready ();
}
protected override bool check () {
return is_ready ();
}
private bool is_ready () {
IOCondition pending_io = (stream != null) ? stream.pending_io : IOCondition.IN;
return (pending_io & condition) != 0;
}
protected override bool dispatch (SourceFunc? callback) {
set_ready_time (-1);
if (callback == null)
return Source.REMOVE;
return callback ();
}
protected static bool closure_callback (Closure closure) {
var return_value = Value (typeof (bool));
closure.invoke (ref return_value, {});
return return_value.get_boolean ();
}
}
}
-407
View File
@@ -1,407 +0,0 @@
namespace Frida {
public interface ObjectBuilder : Object {
public abstract unowned ObjectBuilder begin_dictionary ();
public abstract unowned ObjectBuilder set_member_name (string name);
public abstract unowned ObjectBuilder end_dictionary ();
public abstract unowned ObjectBuilder begin_array ();
public abstract unowned ObjectBuilder end_array ();
public abstract unowned ObjectBuilder add_null_value ();
public abstract unowned ObjectBuilder add_bool_value (bool val);
public abstract unowned ObjectBuilder add_int64_value (int64 val);
public abstract unowned ObjectBuilder add_uint64_value (uint64 val);
public abstract unowned ObjectBuilder add_data_value (Bytes val);
public abstract unowned ObjectBuilder add_string_value (string val);
public abstract unowned ObjectBuilder add_uuid_value (uint8[] val);
public abstract unowned ObjectBuilder add_raw_value (Bytes val);
public abstract Bytes build ();
}
public interface ObjectReader : Object {
public abstract bool has_member (string name) throws Error;
public abstract unowned ObjectReader read_member (string name) throws Error;
public abstract unowned ObjectReader end_member ();
public abstract uint count_elements () throws Error;
public abstract unowned ObjectReader read_element (uint index) throws Error;
public abstract unowned ObjectReader end_element () throws Error;
public abstract bool get_bool_value () throws Error;
public abstract uint8 get_uint8_value () throws Error;
public abstract uint16 get_uint16_value () throws Error;
public abstract int64 get_int64_value () throws Error;
public abstract uint64 get_uint64_value () throws Error;
public abstract Bytes get_data_value () throws Error;
public abstract unowned string get_string_value () throws Error;
public abstract unowned string get_uuid_value () throws Error;
}
public sealed class VariantReader : Object, ObjectReader {
public Variant root_object {
get {
return scopes.peek_head ().val;
}
}
public Variant current_object {
get {
return scopes.peek_tail ().val;
}
}
private Gee.Deque<Scope> scopes = new Gee.ArrayQueue<Scope> ();
public VariantReader (Variant v) {
push_scope (v);
}
public string[] list_members () throws Error {
var scope = peek_scope ();
if (scope.dict == null)
throw new Error.PROTOCOL ("Dictionary expected, but at %s", scope.val.print (true));
var members = new Gee.ArrayList<string> ();
foreach (var item in scope.val) {
string k;
Variant v;
item.get ("{sv}", out k, out v);
members.add (k);
}
return members.to_array ();
}
public bool has_member (string name) throws Error {
var scope = peek_scope ();
if (scope.dict == null)
throw new Error.PROTOCOL ("Dictionary expected, but at %s", scope.val.print (true));
return scope.dict.contains (name);
}
public unowned ObjectReader read_member (string name) throws Error {
var scope = peek_scope ();
if (scope.dict == null)
throw new Error.PROTOCOL ("Dictionary expected, but at %s", scope.val.print (true));
Variant? v = scope.dict.lookup_value (name, null);
if (v == null)
throw new Error.PROTOCOL ("Key '%s' not found in dictionary: %s", name, scope.val.print (true));
push_scope (v);
return this;
}
public unowned ObjectReader end_member () {
pop_scope ();
return this;
}
public uint count_elements () throws Error {
var scope = peek_scope ();
scope.check_array ();
return (uint) scope.val.n_children ();
}
public unowned ObjectReader read_element (uint index) throws Error {
var scope = peek_scope ();
scope.check_array ();
push_scope (scope.val.get_child_value (index).get_variant ());
return this;
}
public unowned ObjectReader end_element () throws Error {
pop_scope ();
return this;
}
public bool get_bool_value () throws Error {
return peek_scope ().get_value (VariantType.BOOLEAN).get_boolean ();
}
public uint8 get_uint8_value () throws Error {
return peek_scope ().get_value (VariantType.BYTE).get_byte ();
}
public uint16 get_uint16_value () throws Error {
return peek_scope ().get_value (VariantType.UINT16).get_uint16 ();
}
public int64 get_int64_value () throws Error {
return peek_scope ().get_value (VariantType.INT64).get_int64 ();
}
public uint64 get_uint64_value () throws Error {
return peek_scope ().get_value (VariantType.UINT64).get_uint64 ();
}
public Bytes get_data_value () throws Error {
return peek_scope ().get_value (new VariantType.array (VariantType.BYTE)).get_data_as_bytes ();
}
public unowned string get_string_value () throws Error {
return peek_scope ().get_value (VariantType.STRING).get_string ();
}
public unowned string get_uuid_value () throws Error {
return peek_scope ().get_value (VariantType.STRING).get_string (); // TODO: Use a tuple to avoid ambiguity.
}
private void push_scope (Variant v) {
scopes.offer_tail (new Scope (v));
}
private Scope peek_scope () {
return scopes.peek_tail ();
}
private Scope pop_scope () {
return scopes.poll_tail ();
}
private class Scope {
public Variant val;
public VariantDict? dict;
public bool is_array = false;
public Scope (Variant v) {
val = v;
VariantType t = v.get_type ();
if (t.equal (VariantType.VARDICT))
dict = new VariantDict (v);
else if (t.is_subtype_of (VariantType.ARRAY))
is_array = true;
}
public Variant get_value (VariantType expected_type) throws Error {
if (!val.get_type ().equal (expected_type)) {
throw new Error.PROTOCOL ("Expected type '%s', got '%s'",
(string) expected_type.peek_string (),
(string) val.get_type ().peek_string ());
}
return val;
}
public void check_array () throws Error {
if (!is_array)
throw new Error.PROTOCOL ("Array expected, but at %s", val.print (true));
}
}
}
public sealed class JsonObjectBuilder : Object, ObjectBuilder {
private Json.Builder builder = new Json.Builder ();
private Gee.Map<string, Bytes> raw_values = new Gee.HashMap<string, Bytes> ();
public unowned ObjectBuilder begin_dictionary () {
builder.begin_object ();
return this;
}
public unowned ObjectBuilder set_member_name (string name) {
builder.set_member_name (name);
return this;
}
public unowned ObjectBuilder end_dictionary () {
builder.end_object ();
return this;
}
public unowned ObjectBuilder begin_array () {
builder.begin_array ();
return this;
}
public unowned ObjectBuilder end_array () {
builder.end_array ();
return this;
}
public unowned ObjectBuilder add_null_value () {
builder.add_null_value ();
return this;
}
public unowned ObjectBuilder add_bool_value (bool val) {
builder.add_boolean_value (val);
return this;
}
public unowned ObjectBuilder add_int64_value (int64 val) {
builder.add_int_value (val);
return this;
}
public unowned ObjectBuilder add_uint64_value (uint64 val) {
builder.add_int_value ((int64) val);
return this;
}
public unowned ObjectBuilder add_data_value (Bytes val) {
builder.add_string_value (Base64.encode (val.get_data ()));
return this;
}
public unowned ObjectBuilder add_string_value (string val) {
builder.add_string_value (val);
return this;
}
public unowned ObjectBuilder add_uuid_value (uint8[] val) {
assert_not_reached ();
}
public unowned ObjectBuilder add_raw_value (Bytes val) {
string uuid = Uuid.string_random ();
builder.add_string_value (uuid);
raw_values[uuid] = val;
return this;
}
public Bytes build () {
string json = Json.to_string (builder.get_root (), false);
foreach (var e in raw_values.entries) {
unowned string uuid = e.key;
Bytes val = e.value;
unowned string raw_str = (string) val.get_data ();
string str = raw_str[:(long) val.get_size ()];
json = json.replace ("\"" + uuid + "\"", str);
}
return new Bytes (json.data);
}
}
public sealed class JsonObjectReader : Object, ObjectReader {
private Json.Reader reader;
public JsonObjectReader (string json) throws Error {
try {
reader = new Json.Reader (Json.from_string (json));
} catch (GLib.Error e) {
throw new Error.INVALID_ARGUMENT ("%s", e.message);
}
}
public bool has_member (string name) throws Error {
bool result = reader.read_member (name);
reader.end_member ();
return result;
}
public unowned ObjectReader read_member (string name) throws Error {
if (!reader.read_member (name))
throw_dict_access_error ();
return this;
}
public unowned ObjectReader end_member () {
reader.end_member ();
return this;
}
[NoReturn]
private void throw_dict_access_error () throws Error {
GLib.Error e = reader.get_error ();
reader.end_member ();
throw new Error.PROTOCOL ("%s", e.message);
}
public uint count_elements () throws Error {
int n = reader.count_elements ();
if (n == -1)
throw_array_access_error ();
return n;
}
public unowned ObjectReader read_element (uint index) throws Error {
if (!reader.read_element (index)) {
GLib.Error e = reader.get_error ();
reader.end_element ();
throw new Error.PROTOCOL ("%s", e.message);
}
return this;
}
public unowned ObjectReader end_element () throws Error {
reader.end_element ();
return this;
}
[NoReturn]
private void throw_array_access_error () throws Error {
GLib.Error e = reader.get_error ();
reader.end_element ();
throw new Error.PROTOCOL ("%s", e.message);
}
public bool get_bool_value () throws Error {
bool v = reader.get_boolean_value ();
if (!v)
maybe_throw_value_access_error ();
return v;
}
public uint8 get_uint8_value () throws Error {
int64 v = get_int64_value ();
if (v < 0 || v > uint8.MAX)
throw new Error.PROTOCOL ("Invalid uint8");
return (uint8) v;
}
public uint16 get_uint16_value () throws Error {
int64 v = get_int64_value ();
if (v < 0 || v > uint16.MAX)
throw new Error.PROTOCOL ("Invalid uint16");
return (uint16) v;
}
public int64 get_int64_value () throws Error {
int64 v = reader.get_int_value ();
if (v == 0)
maybe_throw_value_access_error ();
return v;
}
public uint64 get_uint64_value () throws Error {
int64 v = get_int64_value ();
if (v < 0)
throw new Error.PROTOCOL ("Invalid uint64");
return v;
}
public Bytes get_data_value () throws Error {
return new Bytes (Base64.decode (get_string_value ()));
}
public unowned string get_string_value () throws Error {
unowned string? v = reader.get_string_value ();
if (v == null)
maybe_throw_value_access_error ();
return v;
}
public unowned string get_uuid_value () throws Error {
return get_string_value ();
}
private void maybe_throw_value_access_error () throws Error {
GLib.Error? e = reader.get_error ();
if (e == null)
return;
reader.end_member ();
throw new Error.PROTOCOL ("%s", e.message);
}
}
}
+376
View File
@@ -0,0 +1,376 @@
#include "frida-base.h"
#include <windows.h>
#define FRIDA_TYPE_WINDOWS_HANDLE_STREAM (frida_windows_handle_stream_get_type ())
#define FRIDA_TYPE_WINDOWS_HANDLE_INPUT_STREAM (frida_windows_handle_input_stream_get_type ())
#define FRIDA_TYPE_WINDOWS_HANDLE_OUTPUT_STREAM (frida_windows_handle_output_stream_get_type ())
#define FRIDA_WINDOWS_HANDLE_STREAM(o) ((FridaWindowsHandleStream *) (o))
#define FRIDA_WINDOWS_HANDLE_INPUT_STREAM(o) ((FridaWindowsHandleInputStream *) (o))
#define FRIDA_WINDOWS_HANDLE_OUTPUT_STREAM(o) ((FridaWindowsHandleOutputStream *) (o))
typedef struct _FridaWindowsHandleStream FridaWindowsHandleStream;
typedef struct _FridaWindowsHandleStreamClass FridaWindowsHandleStreamClass;
typedef struct _FridaWindowsHandleInputStream FridaWindowsHandleInputStream;
typedef struct _FridaWindowsHandleInputStreamClass FridaWindowsHandleInputStreamClass;
typedef struct _FridaWindowsHandleOutputStream FridaWindowsHandleOutputStream;
typedef struct _FridaWindowsHandleOutputStreamClass FridaWindowsHandleOutputStreamClass;
struct _FridaWindowsHandleStream
{
GIOStream parent;
HANDLE handle;
HANDLE read_complete;
HANDLE read_cancel;
HANDLE write_complete;
HANDLE write_cancel;
GInputStream * input;
GOutputStream * output;
};
struct _FridaWindowsHandleStreamClass
{
GIOStreamClass parent_class;
};
struct _FridaWindowsHandleInputStream
{
GInputStream parent;
FridaWindowsHandleStream * stream;
};
struct _FridaWindowsHandleInputStreamClass
{
GInputStreamClass parent_class;
};
struct _FridaWindowsHandleOutputStream
{
GOutputStream parent;
FridaWindowsHandleStream * stream;
};
struct _FridaWindowsHandleOutputStreamClass
{
GOutputStreamClass parent_class;
};
static GIOStream * frida_windows_handle_stream_new (HANDLE handle);
static void frida_windows_handle_stream_finalize (GObject * object);
static GInputStream * frida_windows_handle_stream_get_input_stream (GIOStream * stream);
static GOutputStream * frida_windows_handle_stream_get_output_stream (GIOStream * stream);
static gboolean frida_windows_handle_stream_close_fn (GIOStream * stream, GCancellable * cancellable, GError ** error);
static gssize frida_windows_handle_input_stream_read (GInputStream * base, void * buffer, gsize count,
GCancellable * cancellable, GError ** error);
static gboolean frida_windows_handle_input_stream_close (GInputStream * base, GCancellable * cancellable, GError ** error);
static gssize frida_windows_handle_output_stream_write (GOutputStream * base, const void * buffer, gsize count,
GCancellable * cancellable, GError ** error);
static gboolean frida_windows_handle_output_stream_close (GOutputStream * base, GCancellable * cancellable,
GError ** error);
static gboolean frida_windows_handle_stream_await (FridaWindowsHandleStream * self, HANDLE complete, HANDLE cancel,
GCancellable * cancellable, GError ** error);
static void frida_windows_handle_stream_on_cancel (GCancellable * cancellable, gpointer user_data);
G_DEFINE_TYPE (FridaWindowsHandleStream, frida_windows_handle_stream, G_TYPE_IO_STREAM)
G_DEFINE_TYPE (FridaWindowsHandleInputStream, frida_windows_handle_input_stream, G_TYPE_INPUT_STREAM)
G_DEFINE_TYPE (FridaWindowsHandleOutputStream, frida_windows_handle_output_stream, G_TYPE_OUTPUT_STREAM)
GIOStream *
frida_windows_named_pipe_open_client (const gchar * path, GError ** error)
{
HANDLE handle;
WCHAR * path_utf16;
DWORD last_error;
path_utf16 = (WCHAR *) g_utf8_to_utf16 (path, -1, NULL, NULL, NULL);
if (path_utf16 == NULL)
goto invalid_path;
handle = CreateFileW (path_utf16,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
last_error = GetLastError ();
g_free (path_utf16);
if (handle == INVALID_HANDLE_VALUE)
goto cannot_open;
return frida_windows_handle_stream_new (handle);
invalid_path:
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Invalid pipe path");
return NULL;
}
cannot_open:
{
g_set_error (error,
G_IO_ERROR,
g_io_error_from_win32_error (last_error),
"Unable to open %s (CreateFile returned 0x%08lx)",
path, last_error);
return NULL;
}
}
static GIOStream *
frida_windows_handle_stream_new (HANDLE handle)
{
FridaWindowsHandleStream * stream;
FridaWindowsHandleInputStream * input;
FridaWindowsHandleOutputStream * output;
stream = g_object_new (FRIDA_TYPE_WINDOWS_HANDLE_STREAM, NULL);
stream->handle = handle;
stream->read_complete = CreateEvent (NULL, TRUE, FALSE, NULL);
stream->read_cancel = CreateEvent (NULL, TRUE, FALSE, NULL);
stream->write_complete = CreateEvent (NULL, TRUE, FALSE, NULL);
stream->write_cancel = CreateEvent (NULL, TRUE, FALSE, NULL);
input = g_object_new (FRIDA_TYPE_WINDOWS_HANDLE_INPUT_STREAM, NULL);
input->stream = stream;
stream->input = G_INPUT_STREAM (input);
output = g_object_new (FRIDA_TYPE_WINDOWS_HANDLE_OUTPUT_STREAM, NULL);
output->stream = stream;
stream->output = G_OUTPUT_STREAM (output);
return G_IO_STREAM (stream);
}
static void
frida_windows_handle_stream_class_init (FridaWindowsHandleStreamClass * klass)
{
GObjectClass * object_class = G_OBJECT_CLASS (klass);
GIOStreamClass * stream_class = G_IO_STREAM_CLASS (klass);
object_class->finalize = frida_windows_handle_stream_finalize;
stream_class->get_input_stream = frida_windows_handle_stream_get_input_stream;
stream_class->get_output_stream = frida_windows_handle_stream_get_output_stream;
stream_class->close_fn = frida_windows_handle_stream_close_fn;
}
static void
frida_windows_handle_stream_init (FridaWindowsHandleStream * self)
{
self->handle = INVALID_HANDLE_VALUE;
}
static void
frida_windows_handle_stream_finalize (GObject * object)
{
FridaWindowsHandleStream * self = FRIDA_WINDOWS_HANDLE_STREAM (object);
g_clear_object (&self->input);
g_clear_object (&self->output);
CloseHandle (self->read_complete);
CloseHandle (self->read_cancel);
CloseHandle (self->write_complete);
CloseHandle (self->write_cancel);
if (self->handle != INVALID_HANDLE_VALUE)
CloseHandle (self->handle);
G_OBJECT_CLASS (frida_windows_handle_stream_parent_class)->finalize (object);
}
static GInputStream *
frida_windows_handle_stream_get_input_stream (GIOStream * stream)
{
return FRIDA_WINDOWS_HANDLE_STREAM (stream)->input;
}
static GOutputStream *
frida_windows_handle_stream_get_output_stream (GIOStream * stream)
{
return FRIDA_WINDOWS_HANDLE_STREAM (stream)->output;
}
static gboolean
frida_windows_handle_stream_close_fn (GIOStream * stream, GCancellable * cancellable, GError ** error)
{
FridaWindowsHandleStream * self = FRIDA_WINDOWS_HANDLE_STREAM (stream);
SetEvent (self->read_cancel);
SetEvent (self->write_cancel);
if (self->handle != INVALID_HANDLE_VALUE)
{
CancelIo (self->handle);
CloseHandle (self->handle);
self->handle = INVALID_HANDLE_VALUE;
}
return TRUE;
}
static void
frida_windows_handle_input_stream_class_init (FridaWindowsHandleInputStreamClass * klass)
{
GInputStreamClass * stream_class = G_INPUT_STREAM_CLASS (klass);
stream_class->read_fn = frida_windows_handle_input_stream_read;
stream_class->close_fn = frida_windows_handle_input_stream_close;
}
static void
frida_windows_handle_input_stream_init (FridaWindowsHandleInputStream * self)
{
}
static gssize
frida_windows_handle_input_stream_read (GInputStream * base, void * buffer, gsize count, GCancellable * cancellable,
GError ** error)
{
FridaWindowsHandleStream * stream = FRIDA_WINDOWS_HANDLE_INPUT_STREAM (base)->stream;
OVERLAPPED overlapped = { 0, };
DWORD bytes_transferred, last_error;
if (stream->handle == INVALID_HANDLE_VALUE)
return 0;
overlapped.hEvent = stream->read_complete;
if (!ReadFile (stream->handle, buffer, count, NULL, &overlapped) && GetLastError () != ERROR_IO_PENDING)
goto failure;
if (!frida_windows_handle_stream_await (stream, stream->read_complete, stream->read_cancel, cancellable, error))
return -1;
if (!GetOverlappedResult (stream->handle, &overlapped, &bytes_transferred, FALSE))
goto failure;
return bytes_transferred;
failure:
{
last_error = GetLastError ();
if (last_error == ERROR_BROKEN_PIPE || last_error == ERROR_PIPE_NOT_CONNECTED)
return 0;
g_set_error (error,
G_IO_ERROR,
g_io_error_from_win32_error (last_error),
"Error reading from pipe");
return -1;
}
}
static gboolean
frida_windows_handle_input_stream_close (GInputStream * base, GCancellable * cancellable, GError ** error)
{
return TRUE;
}
static void
frida_windows_handle_output_stream_class_init (FridaWindowsHandleOutputStreamClass * klass)
{
GOutputStreamClass * stream_class = G_OUTPUT_STREAM_CLASS (klass);
stream_class->write_fn = frida_windows_handle_output_stream_write;
stream_class->close_fn = frida_windows_handle_output_stream_close;
}
static void
frida_windows_handle_output_stream_init (FridaWindowsHandleOutputStream * self)
{
}
static gssize
frida_windows_handle_output_stream_write (GOutputStream * base, const void * buffer, gsize count,
GCancellable * cancellable, GError ** error)
{
FridaWindowsHandleStream * stream = FRIDA_WINDOWS_HANDLE_OUTPUT_STREAM (base)->stream;
OVERLAPPED overlapped = { 0, };
DWORD bytes_transferred, last_error;
if (stream->handle == INVALID_HANDLE_VALUE)
goto closed;
overlapped.hEvent = stream->write_complete;
if (!WriteFile (stream->handle, buffer, count, NULL, &overlapped) && GetLastError () != ERROR_IO_PENDING)
goto failure;
if (!frida_windows_handle_stream_await (stream, stream->write_complete, stream->write_cancel, cancellable, error))
return -1;
if (!GetOverlappedResult (stream->handle, &overlapped, &bytes_transferred, FALSE))
goto failure;
return bytes_transferred;
closed:
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, "Pipe is closed");
return -1;
}
failure:
{
last_error = GetLastError ();
g_set_error (error,
G_IO_ERROR,
g_io_error_from_win32_error (last_error),
"Error writing to pipe");
return -1;
}
}
static gboolean
frida_windows_handle_output_stream_close (GOutputStream * base, GCancellable * cancellable, GError ** error)
{
return TRUE;
}
static gboolean
frida_windows_handle_stream_await (FridaWindowsHandleStream * self, HANDLE complete, HANDLE cancel,
GCancellable * cancellable, GError ** error)
{
gulong handler_id = 0;
HANDLE events[2];
if (cancellable != NULL)
handler_id = g_cancellable_connect (cancellable, G_CALLBACK (frida_windows_handle_stream_on_cancel), cancel, NULL);
events[0] = complete;
events[1] = cancel;
WaitForMultipleObjects (G_N_ELEMENTS (events), events, FALSE, INFINITE);
if (cancellable != NULL)
{
g_cancellable_disconnect (cancellable, handler_id);
if (g_cancellable_set_error_if_cancelled (cancellable, error))
{
CancelIo (self->handle);
return FALSE;
}
}
if (WaitForSingleObject (cancel, 0) == WAIT_OBJECT_0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, "Pipe is closed");
return FALSE;
}
return TRUE;
}
static void
frida_windows_handle_stream_on_cancel (GCancellable * cancellable, gpointer user_data)
{
HANDLE cancel = (HANDLE) user_data;
SetEvent (cancel);
}
+4
View File
@@ -1,4 +1,8 @@
namespace Frida {
namespace WindowsNamedPipe {
public extern IOStream open_client (string path) throws GLib.Error;
}
public sealed class StdioPipes : Object {
public OutputStream input {
get;
+860
View File
@@ -0,0 +1,860 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import annotations
import argparse
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Optional, Tuple
MAX_ARGS = 9
@dataclass(frozen=True)
class SyscallArg:
type: str
name: str
@dataclass(frozen=True)
class SyscallSignature:
name: str
args: Tuple[SyscallArg, ...]
MACH_TRAP_INDEXED_CALL_RE = re.compile(r"/\*\s*(?P<idx>\d+)\s*\*/\s*MACH_TRAP\s*\(")
MACH_TRAPS_IFNDEF_KERNEL_RE = re.compile(r"^\s*#ifndef\s+KERNEL\b", re.MULTILINE)
MACH_TRAPS_ELSE_KERNEL_RE = re.compile(r"^\s*#else\b.*\bKERNEL\b", re.MULTILINE)
USER_EXTERN_PROTO_RE = re.compile(
r"^\s*extern\s+(?P<ret>.+?)\s+(?P<fn>[A-Za-z_]\w*)\s*\((?P<args>.*?)\)\s*;",
re.MULTILINE | re.DOTALL,
)
KERNEL_STRUCT_RE = re.compile(
r"\bstruct\s+(?P<name>[A-Za-z_]\w*)\s*\{\s*(?P<body>.*?)\s*\}\s*;",
re.DOTALL,
)
KERNEL_TRAP_EXTERN_RE = re.compile(
r"^\s*(?:extern\s+)?(?P<ret>.+?)\s+(?P<fn>[A-Za-z_]\w*)\s*\(\s*struct\s+(?P<args_struct>[A-Za-z_]\w*)\s*\*\s*args\s*\)\s*;",
re.MULTILINE | re.DOTALL,
)
PAD_ARG_RE = re.compile(
r"\bPAD_ARG_\s*\(\s*(?P<ty>[^,]+?)\s*,\s*(?P<name>[^)]+?)\s*\)\s*;",
)
PLAIN_FIELD_RE = re.compile(r"^\s*(?P<ty>.+?)\s+(?P<name>[A-Za-z_]\w*)\s*;\s*$")
BSD_MASTER_ENTRY_RE = re.compile(
r"^\s*(?P<nr>\d+)\s+(?P<audit>\S+)\s+(?P<files>\S+)\s+\{\s*(?P<proto>[^}]+?)\s*\}\s*(?:\{.*\})?\s*$"
)
BSD_MASTER_PROTO_RE = re.compile(
r"^\s*(?P<ret>.+?)\s+(?P<fn>[A-Za-z_]\w*)\s*\((?P<args>.*)\)\s*(?P<attrs>.*)$",
re.DOTALL,
)
LINE_COMMENT_RE = re.compile(r"//.*$")
BLOCK_COMMENT_RE = re.compile(r"/\*[^*]*\*+(?:[^/*][^*]*\*+)*/")
TRAILING_ARRAY_SUFFIX_RE = re.compile(r"\[[^\]]*\]\s*$")
POINTER_SPACING_RE = re.compile(r"\s*\*\s*")
PARAM_NAME_RE = re.compile(r"[A-Za-z_]\w*$")
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--kernel-tree", required=True)
ap.add_argument("--out-vala", required=True)
ap.add_argument("--out-c", required=True)
args = ap.parse_args()
kernel_tree = Path(args.kernel_tree).resolve()
if not kernel_tree.exists():
raise SystemExit(f"--kernel-tree does not exist: {kernel_tree}")
mach_numbers, mach_sigs = load_mach_traps(kernel_tree)
bsd_numbers, bsd_sigs = load_bsd_syscalls_master(kernel_tree)
vala = render_vala(
mach_numbers=mach_numbers,
bsd_numbers=bsd_numbers,
)
c_src = render_c_source(
mach_numbers=mach_numbers,
bsd_numbers=bsd_numbers,
mach_sigs=mach_sigs,
bsd_sigs=bsd_sigs,
)
out_vala = Path(args.out_vala)
out_c = Path(args.out_c)
out_vala.parent.mkdir(parents=True, exist_ok=True)
out_c.parent.mkdir(parents=True, exist_ok=True)
out_vala.write_text(vala, encoding="utf-8")
out_c.write_text(c_src, encoding="utf-8")
return 0
def load_mach_traps(
kernel_tree: Path,
) -> Tuple[Dict[int, str], Dict[str, SyscallSignature]]:
table_path = kernel_tree / "osfmk" / "kern" / "syscall_sw.c"
if not table_path.exists():
raise RuntimeError(f"Missing Mach trap table source: {table_path}")
header_path = kernel_tree / "osfmk" / "mach" / "mach_traps.h"
if not header_path.exists():
raise RuntimeError(f"Missing Mach traps header: {header_path}")
table_text = table_path.read_text(encoding="utf-8", errors="ignore")
trap_routines = parse_mach_trap_table(table_text, table_path)
header_text = header_path.read_text(encoding="utf-8", errors="ignore")
userspace_text, kernel_text = split_mach_traps_header(header_text, header_path)
userspace_protos = parse_userspace_mach_traps_prototypes(
userspace_text, header_path
)
kernel_argmap = parse_kernel_mach_traps_args(kernel_text, header_path)
numbers: Dict[int, str] = {}
sigs: Dict[str, SyscallSignature] = {}
for index, routine in trap_routines:
if routine == "kern_invalid":
continue
nr = -index
public_name = normalize_mach_name(routine)
if public_name == "":
raise RuntimeError(f"{table_path}: empty normalized name from {routine!r}")
if nr in numbers:
raise RuntimeError(f"{table_path}: duplicate mach trap number {nr}")
if public_name in sigs:
raise RuntimeError(
f"{table_path}: normalized Mach name collision for {public_name!r}"
)
args = resolve_mach_trap_signature(
routine=routine,
userspace_protos=userspace_protos,
kernel_argmap=kernel_argmap,
table_path=table_path,
)
if len(args) > MAX_ARGS:
raise RuntimeError(
f"{table_path}: trap {routine!r} has {len(args)} args; max supported is {MAX_ARGS}"
)
numbers[nr] = public_name
sigs[public_name] = SyscallSignature(name=public_name, args=tuple(args))
return numbers, sigs
def parse_mach_trap_table(text: str, path: Path) -> List[Tuple[int, str]]:
anchor = text.find("mach_trap_table")
if anchor == -1:
raise RuntimeError(f"{path}: mach_trap_table not found")
brace = text.find("{", anchor)
if brace == -1:
raise RuntimeError(f"{path}: mach_trap_table '{{' not found")
payload, _ = extract_brace_payload(text, brace, path)
by_index: Dict[int, str] = {}
off = 0
while True:
m = MACH_TRAP_INDEXED_CALL_RE.search(payload, off)
if m is None:
break
idx = int(m.group("idx"), 10)
open_paren = m.end(0) - 1
args_blob, end_off = extract_parentheses_payload(payload, open_paren, path)
parts = [
a.strip() for a in split_top_level_commas(args_blob) if a.strip() != ""
]
if len(parts) < 1:
raise RuntimeError(f"{path}: bad MACH_TRAP() entry: {args_blob!r}")
routine = parts[0]
prev = by_index.get(idx)
if prev is None:
by_index[idx] = routine
else:
if prev == routine:
pass
elif prev == "kern_invalid" and routine != "kern_invalid":
by_index[idx] = routine
elif routine == "kern_invalid" and prev != "kern_invalid":
pass
else:
raise RuntimeError(
f"{path}: conflicting MACH_TRAP entries for index {idx}: {prev!r} vs {routine!r}"
)
off = end_off
return sorted(by_index.items(), key=lambda kv: kv[0])
def split_mach_traps_header(text: str, path: Path) -> Tuple[str, str]:
m_start = MACH_TRAPS_IFNDEF_KERNEL_RE.search(text)
if m_start is None:
raise RuntimeError(f"{path}: missing '#ifndef KERNEL'")
m_else = MACH_TRAPS_ELSE_KERNEL_RE.search(text, m_start.end())
if m_else is None:
raise RuntimeError(f"{path}: missing '#else ... KERNEL ...'")
userspace = text[m_start.end():m_else.start()]
kernel = text[m_else.end():]
return userspace, kernel
def parse_userspace_mach_traps_prototypes(
userspace_text: str, path: Path
) -> Dict[str, Tuple[SyscallArg, ...]]:
protos: Dict[str, Tuple[SyscallArg, ...]] = {}
for m in USER_EXTERN_PROTO_RE.finditer(userspace_text):
fn = m.group("fn").strip()
args_blob = m.group("args").strip()
ret_blob = strip_c_comments(m.group("ret")).strip()
if ret_blob == "":
raise RuntimeError(f"{path}: empty return type for {fn!r}")
line_no = line_number_at_offset(userspace_text, m.start())
args = parse_c_param_list(args_blob, path, line_no)
cur = tuple(args)
prev = protos.get(fn)
if prev is not None and prev != cur:
raise RuntimeError(f"{path}:{line_no}: conflicting prototype for {fn!r}")
protos[fn] = cur
return protos
def parse_kernel_mach_traps_args(
kernel_text: str, path: Path
) -> Dict[str, Tuple[SyscallArg, ...]]:
text = strip_c_comments(kernel_text)
structs: Dict[str, str] = {}
for m in KERNEL_STRUCT_RE.finditer(text):
name = m.group("name").strip()
body = m.group("body")
structs[name] = body
fn_to_struct: Dict[str, str] = {}
for m in KERNEL_TRAP_EXTERN_RE.finditer(text):
fn = m.group("fn").strip()
args_struct = m.group("args_struct").strip()
fn_to_struct[fn] = args_struct
out: Dict[str, Tuple[SyscallArg, ...]] = {}
for fn, args_struct in fn_to_struct.items():
body = structs.get(args_struct)
if body is None:
continue
args = parse_mach_args_struct_body(body, path)
out[fn] = tuple(args)
return out
def parse_mach_args_struct_body(body: str, path: Path) -> List[SyscallArg]:
args: List[SyscallArg] = []
body = strip_c_comments(body)
for raw_ln in body.splitlines():
ln = raw_ln.strip()
if ln == "" or ln.startswith("#"):
continue
if "PAD_ARG_8" in ln:
continue
m = PAD_ARG_RE.search(ln)
if m is not None:
ty = normalize_whitespace(m.group("ty").strip())
name = normalize_whitespace(m.group("name").strip())
if ty == "" or name == "":
raise RuntimeError(f"{path}: bad PAD_ARG_ in args struct: {raw_ln!r}")
args.append(SyscallArg(type=ty, name=name))
continue
m = PLAIN_FIELD_RE.match(ln)
if m is not None:
ty = normalize_whitespace(m.group("ty").strip())
name = m.group("name").strip()
if ty == "" or name == "":
raise RuntimeError(f"{path}: bad field in args struct: {raw_ln!r}")
args.append(SyscallArg(type=ty, name=name))
continue
if len(args) == 1 and args[0].name == "dummy":
return []
return args
def resolve_mach_trap_signature(
*,
routine: str,
userspace_protos: Dict[str, Tuple[SyscallArg, ...]],
kernel_argmap: Dict[str, Tuple[SyscallArg, ...]],
table_path: Path,
) -> List[SyscallArg]:
def get_userspace(name: str) -> Optional[Tuple[SyscallArg, ...]]:
return userspace_protos.get(name)
def get_kernel(name: str) -> Optional[Tuple[SyscallArg, ...]]:
return kernel_argmap.get(name)
proto_args = get_userspace(routine)
if proto_args is None and routine.endswith("_trap"):
proto_args = get_userspace(routine[: -len("_trap")])
if proto_args is not None:
return list(proto_args)
proto_args = get_kernel(routine)
if proto_args is None and routine.endswith("_trap"):
proto_args = get_kernel(routine[: -len("_trap")])
if proto_args is None:
raise RuntimeError(
f"{table_path}: missing userspace prototype for {routine!r}, and no kernel extern(struct *_args *args) mapping found"
)
return list(proto_args)
def normalize_mach_name(routine: str) -> str:
if routine.startswith("_"):
routine = routine[1:]
if routine.startswith("kernelrpc_"):
routine = routine[len("kernelrpc_"):]
if routine.endswith("_trap"):
routine = routine[: -len("_trap")]
return routine
def load_bsd_syscalls_master(
kernel_tree: Path,
) -> Tuple[Dict[int, str], Dict[str, SyscallSignature]]:
path = kernel_tree / "bsd" / "kern" / "syscalls.master"
if not path.exists():
raise RuntimeError(f"Missing BSD master file: {path}")
numbers: Dict[int, str] = {}
sigs: Dict[str, SyscallSignature] = {}
lines = path.read_text(encoding="utf-8", errors="ignore").splitlines()
stack: List[bool] = []
active = True
for line_no, raw in enumerate(lines, start=1):
line = raw.rstrip("\n")
stripped = line.lstrip()
if (
stripped.startswith("#if ")
or stripped.startswith("#ifdef ")
or stripped.startswith("#ifndef ")
):
stack.append(active)
continue
if stripped.startswith("#else"):
if not stack:
raise RuntimeError(f"{path}:{line_no}: #else without #if")
active = False
continue
if stripped.startswith("#endif"):
if not stack:
raise RuntimeError(f"{path}:{line_no}: #endif without #if")
active = stack.pop()
continue
if not active:
continue
m = BSD_MASTER_ENTRY_RE.match(line)
if m is None:
continue
nr = int(m.group("nr"), 10)
proto_blob = strip_c_comments(m.group("proto")).strip()
mp = BSD_MASTER_PROTO_RE.match(proto_blob)
if mp is None:
raise RuntimeError(
f"{path}:{line_no}: cannot parse prototype: {proto_blob!r}"
)
fn = mp.group("fn").strip()
args_blob = mp.group("args").strip()
if fn in {"nosys", "enosys"}:
continue
public_name = normalize_bsd_name(fn)
if public_name == "":
raise RuntimeError(f"{path}:{line_no}: empty normalized name from {fn!r}")
if nr in numbers:
raise RuntimeError(f"{path}:{line_no}: duplicate syscall number {nr}")
if public_name in sigs:
raise RuntimeError(
f"{path}:{line_no}: duplicate syscall name {public_name!r}"
)
args = parse_c_param_list(args_blob, path, line_no)
if len(args) > MAX_ARGS:
raise RuntimeError(
f"{path}:{line_no}: syscall {public_name!r} has {len(args)} args; max supported is {MAX_ARGS}"
)
numbers[nr] = public_name
sigs[public_name] = SyscallSignature(name=public_name, args=tuple(args))
return numbers, sigs
def normalize_bsd_name(fn: str) -> str:
if fn.startswith("sys_"):
return fn[len("sys_"):]
if fn.startswith("__"):
return fn[2:]
return fn
def strip_c_comments(s: str) -> str:
s = LINE_COMMENT_RE.sub("", s)
while True:
new_s = BLOCK_COMMENT_RE.sub("", s)
if new_s == s:
break
s = new_s
return s
def split_top_level_commas(text: str) -> List[str]:
parts: List[str] = []
buf: List[str] = []
depth = 0
in_string = False
string_quote = ""
cursor = 0
while cursor < len(text):
ch = text[cursor]
if in_string:
buf.append(ch)
if ch == "\\" and cursor + 1 < len(text):
buf.append(text[cursor + 1])
cursor += 2
continue
if ch == string_quote:
in_string = False
cursor += 1
continue
if ch in ("'", '"'):
in_string = True
string_quote = ch
buf.append(ch)
cursor += 1
continue
if ch == "(":
depth += 1
elif ch == ")":
depth = max(0, depth - 1)
if ch == "," and depth == 0:
parts.append("".join(buf).strip())
buf = []
cursor += 1
continue
buf.append(ch)
cursor += 1
parts.append("".join(buf).strip())
return parts
def parse_c_param_list(args_blob: str, path: Path, line_no: int) -> List[SyscallArg]:
raw = strip_c_comments(args_blob).strip()
if raw == "" or raw == "void":
return []
raw_params = [p.strip() for p in split_top_level_commas(raw) if p.strip() != ""]
if len(raw_params) == 1 and raw_params[0] == "void":
return []
args: List[SyscallArg] = []
for idx, p in enumerate(raw_params):
ty, name = split_param_decl(p, path, line_no)
if name is None:
name = f"arg{idx}"
if ty == "user_addr_t" and name in {"attrname", "link", "path"}:
ty = "char *"
args.append(SyscallArg(type=ty, name=name))
return args
def split_param_decl(decl: str, path: Path, line_no: int) -> Tuple[str, Optional[str]]:
d = strip_c_comments(decl).strip()
if d == "" or d == "void":
raise RuntimeError(f"{path}:{line_no}: bad parameter decl: {decl!r}")
tokens = _tokenize_decl(d)
ty_tokens, name = _split_type_and_name(tokens)
ty = _format_type(ty_tokens)
if not ty:
raise RuntimeError(
f"{path}:{line_no}: cannot parse parameter type from {decl!r}"
)
return ty, _camel_to_snake(name) if name else None
def _tokenize_decl(s: str) -> list[str]:
out = []
for c in s:
out.append(" * " if c == "*" else c)
return "".join(out).split()
def _split_type_and_name(tokens: list[str]) -> Tuple[list[str], Optional[str]]:
if not tokens:
return [], None
last = tokens[-1]
if last == "*":
return tokens, None
stars, remainder = _split_leading_stars(last)
if stars:
if _is_identifier(remainder):
return tokens[:-1] + ["*"] * stars, remainder
return tokens, None
if _is_identifier(last):
return tokens[:-1], last
return tokens, None
def _format_type(tokens: list[str]) -> str:
formatted: list[str] = []
i = 0
while i < len(tokens):
if tokens[i] != "*":
formatted.append(tokens[i])
i += 1
continue
j = i
while j < len(tokens) and tokens[j] == "*":
j += 1
formatted.append("*" * (j - i))
i = j
return " ".join(formatted).strip()
def _split_leading_stars(token: str) -> Tuple[int, str]:
i = 0
while i < len(token) and token[i] == "*":
i += 1
return i, token[i:]
def _is_identifier(s: str) -> bool:
if not s:
return False
if not (s[0].isalpha() or s[0] == "_"):
return False
return all(c.isalnum() or c == "_" for c in s)
def _camel_to_snake(name: str) -> str:
out = []
for i, c in enumerate(name):
if (
c.isupper()
and i > 0
and (name[i - 1].islower() or (i + 1 < len(name) and name[i + 1].islower()))
):
out.append("_")
out.append(c.lower())
return "".join(out)
def normalize_whitespace(s: str) -> str:
return " ".join(s.split())
def line_number_at_offset(text: str, offset: int) -> int:
return text.count("\n", 0, offset) + 1
def extract_parentheses_payload(
text: str, open_paren_offset: int, path: Path
) -> Tuple[str, int]:
if (
open_paren_offset < 0
or open_paren_offset >= len(text)
or text[open_paren_offset] != "("
):
raise RuntimeError(f"{path}: expected '(' at offset {open_paren_offset}")
depth = 0
cursor = open_paren_offset
start = open_paren_offset + 1
in_string = False
string_quote = ""
while cursor < len(text):
ch = text[cursor]
if in_string:
if ch == "\\":
cursor += 2
continue
if ch == string_quote:
in_string = False
cursor += 1
continue
if ch in ("'", '"'):
in_string = True
string_quote = ch
cursor += 1
continue
if ch == "(":
depth += 1
elif ch == ")":
depth -= 1
if depth == 0:
return text[start:cursor], cursor + 1
cursor += 1
raise RuntimeError(f"{path}: unterminated parentheses payload")
def extract_brace_payload(
text: str, open_brace_offset: int, path: Path
) -> Tuple[str, int]:
if (
open_brace_offset < 0
or open_brace_offset >= len(text)
or text[open_brace_offset] != "{"
):
raise RuntimeError(f"{path}: expected '{{' at offset {open_brace_offset}")
depth = 0
cursor = open_brace_offset
start = open_brace_offset + 1
in_string = False
string_quote = ""
while cursor < len(text):
ch = text[cursor]
if in_string:
if ch == "\\":
cursor += 2
continue
if ch == string_quote:
in_string = False
cursor += 1
continue
if ch in ("'", '"'):
in_string = True
string_quote = ch
cursor += 1
continue
if ch == "{":
depth += 1
elif ch == "}":
depth -= 1
if depth == 0:
return text[start:cursor], cursor + 1
cursor += 1
raise RuntimeError(f"{path}: unterminated brace payload")
def render_vala(*, mach_numbers: Dict[int, str], bsd_numbers: Dict[int, str]) -> str:
out: List[str] = []
out += [
"// AUTO-GENERATED FILE. DO NOT EDIT.\n",
"\n",
"namespace Frida {\n",
"\tpublic extern unowned XnuSyscallSignature[] get_xnu_mach_traps ();\n",
"\tpublic extern unowned XnuSyscallSignature[] get_xnu_bsd_syscalls ();\n",
"\n",
"\tpublic struct XnuSyscallSignature {\n",
"\t\tpublic int nr;\n",
"\t\tpublic string name;\n",
"\t\tpublic uint8 nargs;\n",
f"\t\tpublic XnuSyscallArg args[{MAX_ARGS}];\n",
"\t}\n",
"\n",
"\tpublic struct XnuSyscallArg {\n",
"\t\tpublic string? type;\n",
"\t\tpublic string? name;\n",
"\t}\n",
"\n",
"\tpublic enum XnuMachTrap {\n",
]
for nr, name in sorted(mach_numbers.items(), key=lambda kv: kv[0], reverse=True):
out += [f"\t\t{name.upper()} = {nr},\n"]
out += [
"\t}\n",
"\n",
"\tpublic enum XnuBsdSyscall {\n",
]
for nr, name in sorted(bsd_numbers.items(), key=lambda kv: kv[0]):
out += [f"\t\t{name.upper()} = {nr},\n"]
out += [
"\t}\n",
"}\n",
]
return "".join(out)
def render_c_source(
*,
mach_numbers: Dict[int, str],
bsd_numbers: Dict[int, str],
mach_sigs: Dict[str, SyscallSignature],
bsd_sigs: Dict[str, SyscallSignature],
) -> str:
out: List[str] = []
out += [
"/* AUTO-GENERATED FILE. DO NOT EDIT. */\n",
"\n",
'#include "frida-base.h"\n',
"\n",
]
emit_c_signature_table(
out,
c_var="frida_xnu_mach_traps",
numbers=mach_numbers,
sigs=mach_sigs,
reverse=True,
)
emit_c_signature_table(
out,
c_var="frida_xnu_bsd_syscalls",
numbers=bsd_numbers,
sigs=bsd_sigs,
)
emit_c_table_getter(
out, c_func="frida_get_xnu_mach_traps", c_var="frida_xnu_mach_traps"
)
out += ["\n"]
emit_c_table_getter(
out, c_func="frida_get_xnu_bsd_syscalls", c_var="frida_xnu_bsd_syscalls"
)
return "".join(out)
def emit_c_signature_table(
out: List[str],
*,
c_var: str,
numbers: Dict[int, str],
sigs: Dict[str, SyscallSignature],
reverse: bool = False,
) -> None:
out += [
f"static const FridaXnuSyscallSignature {c_var}[] =\n",
"{\n",
]
for nr, name in sorted(numbers.items(), key=lambda kv: kv[0], reverse=reverse):
sig = sigs[name]
args_parts: List[str] = []
for a in sig.args:
args_parts += [f"{{ {c_escape(a.type)}, {c_escape(a.name)} }}"]
args_blob = ", ".join(args_parts)
if args_blob != "":
out += [
f" {{ {nr}, {c_escape(name)}, {len(sig.args)}, {{ {args_blob} }} }},\n"
]
else:
out += [f" {{ {nr}, {c_escape(name)}, 0, {{}} }},\n"]
out += [
"};\n",
"\n",
]
def emit_c_table_getter(out: List[str], *, c_func: str, c_var: str) -> None:
out += [
"FridaXnuSyscallSignature *\n",
f"{c_func} (int * len)\n",
"{\n",
" if (len != NULL)\n",
f" *len = G_N_ELEMENTS ({c_var});\n",
f" return (FridaXnuSyscallSignature *) {c_var};\n",
"}\n",
]
def c_escape(s: str) -> str:
return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"'
if __name__ == "__main__":
raise SystemExit(main())
+542
View File
@@ -0,0 +1,542 @@
/* AUTO-GENERATED FILE. DO NOT EDIT. */
#include "frida-base.h"
static const FridaXnuSyscallSignature frida_xnu_mach_traps[] =
{
{ -10, "mach_vm_allocate", 4, { { "mach_port_name_t", "target" }, { "mach_vm_offset_t *", "addr" }, { "mach_vm_size_t", "size" }, { "int", "flags" } } },
{ -11, "mach_vm_purgable_control", 4, { { "mach_port_name_t", "target" }, { "mach_vm_offset_t", "address" }, { "vm_purgable_t", "control" }, { "int *", "state" } } },
{ -12, "mach_vm_deallocate", 3, { { "mach_port_name_t", "target" }, { "mach_vm_address_t", "address" }, { "mach_vm_size_t", "size" } } },
{ -13, "task_dyld_process_info_notify_get", 2, { { "mach_port_name_array_t", "names_addr" }, { "natural_t *", "names_count_addr" } } },
{ -14, "mach_vm_protect", 5, { { "mach_port_name_t", "target" }, { "mach_vm_address_t", "address" }, { "mach_vm_size_t", "size" }, { "boolean_t", "set_maximum" }, { "vm_prot_t", "new_protection" } } },
{ -15, "mach_vm_map", 6, { { "mach_port_name_t", "target" }, { "mach_vm_offset_t *", "address" }, { "mach_vm_size_t", "size" }, { "mach_vm_offset_t", "mask" }, { "int", "flags" }, { "vm_prot_t", "cur_protection" } } },
{ -16, "mach_port_allocate", 3, { { "mach_port_name_t", "target" }, { "mach_port_right_t", "right" }, { "mach_port_name_t *", "name" } } },
{ -18, "mach_port_deallocate", 2, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" } } },
{ -19, "mach_port_mod_refs", 4, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "mach_port_right_t", "right" }, { "mach_port_delta_t", "delta" } } },
{ -20, "mach_port_move_member", 3, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "member" }, { "mach_port_name_t", "after" } } },
{ -21, "mach_port_insert_right", 4, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "mach_port_name_t", "poly" }, { "mach_msg_type_name_t", "poly_poly" } } },
{ -22, "mach_port_insert_member", 3, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "mach_port_name_t", "pset" } } },
{ -23, "mach_port_extract_member", 3, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "mach_port_name_t", "pset" } } },
{ -24, "mach_port_construct", 4, { { "mach_port_name_t", "target" }, { "mach_port_options_t *", "options" }, { "uint64_t", "context" }, { "mach_port_name_t *", "name" } } },
{ -25, "mach_port_destruct", 4, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "mach_port_delta_t", "srdelta" }, { "uint64_t", "guard" } } },
{ -26, "mach_reply_port", 0, {} },
{ -27, "thread_self", 0, {} },
{ -28, "task_self", 0, {} },
{ -29, "host_self", 0, {} },
{ -31, "mach_msg", 7, { { "mach_msg_header_t *", "msg" }, { "mach_msg_option_t", "option" }, { "mach_msg_size_t", "send_size" }, { "mach_msg_size_t", "rcv_size" }, { "mach_port_name_t", "rcv_name" }, { "mach_msg_timeout_t", "timeout" }, { "mach_port_name_t", "notify" } } },
{ -32, "mach_msg_overwrite", 9, { { "mach_msg_header_t *", "msg" }, { "mach_msg_option_t", "option" }, { "mach_msg_size_t", "send_size" }, { "mach_msg_size_t", "rcv_size" }, { "mach_port_name_t", "rcv_name" }, { "mach_msg_timeout_t", "timeout" }, { "mach_msg_priority_t", "priority" }, { "mach_msg_header_t *", "rcv_msg" }, { "mach_msg_size_t", "rcv_limit" } } },
{ -33, "semaphore_signal", 1, { { "mach_port_name_t", "signal_name" } } },
{ -34, "semaphore_signal_all", 1, { { "mach_port_name_t", "signal_name" } } },
{ -35, "semaphore_signal_thread", 2, { { "mach_port_name_t", "signal_name" }, { "mach_port_name_t", "thread_name" } } },
{ -36, "semaphore_wait", 1, { { "mach_port_name_t", "wait_name" } } },
{ -37, "semaphore_wait_signal", 2, { { "mach_port_name_t", "wait_name" }, { "mach_port_name_t", "signal_name" } } },
{ -38, "semaphore_timedwait", 3, { { "mach_port_name_t", "wait_name" }, { "unsigned int", "sec" }, { "clock_res_t", "nsec" } } },
{ -39, "semaphore_timedwait_signal", 4, { { "mach_port_name_t", "wait_name" }, { "mach_port_name_t", "signal_name" }, { "unsigned int", "sec" }, { "clock_res_t", "nsec" } } },
{ -40, "mach_port_get_attributes", 5, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "mach_port_flavor_t", "flavor" }, { "mach_port_info_t", "port_info_out" }, { "mach_msg_type_number_t *", "port_info_out_cnt" } } },
{ -41, "mach_port_guard", 4, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "uint64_t", "guard" }, { "boolean_t", "strict" } } },
{ -42, "mach_port_unguard", 3, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "uint64_t", "guard" } } },
{ -43, "mach_generate_activity_id", 3, { { "mach_port_name_t", "target" }, { "int", "count" }, { "uint64_t *", "activity_id" } } },
{ -44, "task_name_for_pid", 3, { { "mach_port_name_t", "target_tport" }, { "int", "pid" }, { "mach_port_name_t *", "tn" } } },
{ -45, "task_for_pid", 3, { { "mach_port_name_t", "target_tport" }, { "int", "pid" }, { "mach_port_name_t *", "t" } } },
{ -46, "pid_for_task", 2, { { "mach_port_name_t", "t" }, { "int *", "x" } } },
{ -47, "mach_msg2", 8, { { "void *", "data" }, { "mach_msg_option64_t", "options" }, { "uint64_t", "msgh_bits_and_send_size" }, { "uint64_t", "msgh_remote_and_local_port" }, { "uint64_t", "msgh_voucher_and_id" }, { "uint64_t", "desc_count_and_rcv_name" }, { "uint64_t", "rcv_size_and_priority" }, { "uint64_t", "timeout" } } },
{ -48, "macx_swapon", 4, { { "uint64_t", "filename" }, { "int", "flags" }, { "int", "size" }, { "int", "priority" } } },
{ -49, "macx_swapoff", 2, { { "uint64_t", "filename" }, { "int", "flags" } } },
{ -50, "thread_get_special_reply_port", 0, {} },
{ -51, "macx_triggers", 4, { { "int", "hi_water" }, { "int", "low_water" }, { "int", "flags" }, { "mach_port_t", "alert_port" } } },
{ -52, "macx_backing_store_suspend", 1, { { "boolean_t", "suspend" } } },
{ -53, "macx_backing_store_recovery", 1, { { "int", "pid" } } },
{ -58, "pfz_exit", 0, {} },
{ -59, "swtch_pri", 1, { { "int", "pri" } } },
{ -60, "swtch", 0, {} },
{ -61, "thread_switch", 3, { { "mach_port_name_t", "thread_name" }, { "int", "option" }, { "mach_msg_timeout_t", "option_time" } } },
{ -62, "clock_sleep", 5, { { "mach_port_name_t", "clock_name" }, { "sleep_type_t", "sleep_type" }, { "int", "sleep_sec" }, { "int", "sleep_nsec" }, { "mach_timespec_t *", "wakeup_time" } } },
{ -63, "mach_vm_reclaim_update_kernel_accounting", 2, { { "mach_port_name_t", "target_tport" }, { "uint64_t *", "bytes_reclaimed" } } },
{ -70, "host_create_mach_voucher", 4, { { "mach_port_name_t", "host" }, { "mach_voucher_attr_raw_recipe_array_t", "recipes" }, { "int", "recipes_size" }, { "mach_port_name_t *", "voucher" } } },
{ -72, "mach_voucher_extract_attr_recipe", 4, { { "mach_port_name_t", "voucher_name" }, { "mach_voucher_attr_key_t", "key" }, { "mach_voucher_attr_raw_recipe_t", "recipe" }, { "mach_msg_type_number_t *", "recipe_size" } } },
{ -76, "mach_port_type", 3, { { "ipc_space_t", "task" }, { "mach_port_name_t", "name" }, { "mach_port_type_t *", "ptype" } } },
{ -77, "mach_port_request_notification", 7, { { "ipc_space_t", "task" }, { "mach_port_name_t", "name" }, { "mach_msg_id_t", "msgid" }, { "mach_port_mscount_t", "sync" }, { "mach_port_name_t", "notify" }, { "mach_msg_type_name_t", "notify_poly" }, { "mach_port_name_t *", "previous" } } },
{ -88, "exclaves_ctl", 8, { { "mach_port_name_t", "name" }, { "uint32_t", "operation_and_flags" }, { "uint64_t", "identifier" }, { "mach_vm_address_t", "buffer" }, { "mach_vm_size_t", "size" }, { "mach_vm_size_t", "size2" }, { "mach_vm_size_t", "offset" }, { "mach_vm_address_t", "status" } } },
{ -89, "mach_timebase_info", 1, { { "user_addr_t", "info" } } },
{ -90, "mach_wait_until", 1, { { "uint64_t", "deadline" } } },
{ -91, "mk_timer_create", 0, {} },
{ -92, "mk_timer_destroy", 1, { { "mach_port_name_t", "name" } } },
{ -93, "mk_timer_arm", 2, { { "mach_port_name_t", "name" }, { "uint64_t", "expire_time" } } },
{ -94, "mk_timer_cancel", 2, { { "mach_port_name_t", "name" }, { "user_addr_t", "result_time" } } },
{ -95, "mk_timer_arm_leeway", 4, { { "mach_port_name_t", "name" }, { "uint64_t", "mk_timer_flags" }, { "uint64_t", "expire_time" }, { "uint64_t", "mk_leeway" } } },
{ -96, "debug_control_port_for_pid", 3, { { "mach_port_name_t", "target_tport" }, { "int", "pid" }, { "mach_port_name_t *", "t" } } },
{ -100, "iokit_user_client", 8, { { "void *", "userClientRef" }, { "uint32_t", "index" }, { "void *", "p1" }, { "void *", "p2" }, { "void *", "p3" }, { "void *", "p4" }, { "void *", "p5" }, { "void *", "p6" } } },
};
static const FridaXnuSyscallSignature frida_xnu_bsd_syscalls[] =
{
{ 1, "exit", 1, { { "int", "rval" } } },
{ 2, "fork", 0, {} },
{ 3, "read", 3, { { "int", "fd" }, { "user_addr_t", "cbuf" }, { "user_size_t", "nbyte" } } },
{ 4, "write", 3, { { "int", "fd" }, { "user_addr_t", "cbuf" }, { "user_size_t", "nbyte" } } },
{ 5, "open", 3, { { "char *", "path" }, { "int", "flags" }, { "int", "mode" } } },
{ 6, "close", 1, { { "int", "fd" } } },
{ 7, "wait4", 4, { { "int", "pid" }, { "user_addr_t", "status" }, { "int", "options" }, { "user_addr_t", "rusage" } } },
{ 9, "link", 2, { { "char *", "path" }, { "char *", "link" } } },
{ 10, "unlink", 1, { { "char *", "path" } } },
{ 12, "chdir", 1, { { "char *", "path" } } },
{ 13, "fchdir", 1, { { "int", "fd" } } },
{ 14, "mknod", 3, { { "char *", "path" }, { "int", "mode" }, { "int", "dev" } } },
{ 15, "chmod", 2, { { "char *", "path" }, { "int", "mode" } } },
{ 16, "chown", 3, { { "char *", "path" }, { "int", "uid" }, { "int", "gid" } } },
{ 18, "getfsstat", 3, { { "user_addr_t", "buf" }, { "int", "bufsize" }, { "int", "flags" } } },
{ 20, "getpid", 0, {} },
{ 23, "setuid", 1, { { "uid_t", "uid" } } },
{ 24, "getuid", 0, {} },
{ 25, "geteuid", 0, {} },
{ 26, "ptrace", 4, { { "int", "req" }, { "pid_t", "pid" }, { "caddr_t", "addr" }, { "int", "data" } } },
{ 27, "recvmsg", 3, { { "int", "s" }, { "struct msghdr *", "msg" }, { "int", "flags" } } },
{ 28, "sendmsg", 3, { { "int", "s" }, { "caddr_t", "msg" }, { "int", "flags" } } },
{ 29, "recvfrom", 6, { { "int", "s" }, { "void *", "buf" }, { "size_t", "len" }, { "int", "flags" }, { "struct sockaddr *", "from" }, { "int *", "fromlenaddr" } } },
{ 30, "accept", 3, { { "int", "s" }, { "caddr_t", "name" }, { "socklen_t *", "anamelen" } } },
{ 31, "getpeername", 3, { { "int", "fdes" }, { "caddr_t", "asa" }, { "socklen_t *", "alen" } } },
{ 32, "getsockname", 3, { { "int", "fdes" }, { "caddr_t", "asa" }, { "socklen_t *", "alen" } } },
{ 33, "access", 2, { { "char *", "path" }, { "int", "flags" } } },
{ 34, "chflags", 2, { { "char *", "path" }, { "int", "flags" } } },
{ 35, "fchflags", 2, { { "int", "fd" }, { "int", "flags" } } },
{ 36, "sync", 0, {} },
{ 37, "kill", 3, { { "int", "pid" }, { "int", "signum" }, { "int", "posix" } } },
{ 38, "crossarch_trap", 1, { { "uint32_t", "name" } } },
{ 39, "getppid", 0, {} },
{ 41, "dup", 1, { { "u_int", "fd" } } },
{ 42, "pipe", 0, {} },
{ 43, "getegid", 0, {} },
{ 46, "sigaction", 3, { { "int", "signum" }, { "struct __sigaction *", "nsa" }, { "struct sigaction *", "osa" } } },
{ 47, "getgid", 0, {} },
{ 48, "sigprocmask", 3, { { "int", "how" }, { "user_addr_t", "mask" }, { "user_addr_t", "omask" } } },
{ 49, "getlogin", 2, { { "char *", "namebuf" }, { "u_int", "namelen" } } },
{ 50, "setlogin", 1, { { "char *", "namebuf" } } },
{ 51, "acct", 1, { { "char *", "path" } } },
{ 52, "sigpending", 1, { { "struct sigvec *", "osv" } } },
{ 53, "sigaltstack", 2, { { "struct sigaltstack *", "nss" }, { "struct sigaltstack *", "oss" } } },
{ 54, "ioctl", 3, { { "int", "fd" }, { "u_long", "com" }, { "caddr_t", "data" } } },
{ 55, "reboot", 2, { { "int", "opt" }, { "char *", "msg" } } },
{ 56, "revoke", 1, { { "char *", "path" } } },
{ 57, "symlink", 2, { { "char *", "path" }, { "char *", "link" } } },
{ 58, "readlink", 3, { { "char *", "path" }, { "char *", "buf" }, { "int", "count" } } },
{ 59, "execve", 3, { { "char *", "fname" }, { "char **", "argp" }, { "char **", "envp" } } },
{ 60, "umask", 1, { { "int", "newmask" } } },
{ 61, "chroot", 1, { { "char *", "path" } } },
{ 65, "msync", 3, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "int", "flags" } } },
{ 66, "vfork", 0, {} },
{ 67, "oslog_coproc_reg", 3, { { "const char *", "uuid" }, { "const char *", "file_path" }, { "size_t", "file_path_len" } } },
{ 68, "oslog_coproc", 7, { { "void *", "buff" }, { "uint64_t", "buff_len" }, { "uint32_t", "type" }, { "const char *", "uuid" }, { "uint64_t", "timestamp" }, { "uint32_t", "offset" }, { "uint32_t", "stream_log" } } },
{ 73, "munmap", 2, { { "caddr_ut", "addr" }, { "size_ut", "len" } } },
{ 74, "mprotect", 3, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "int", "prot" } } },
{ 75, "madvise", 3, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "int", "behav" } } },
{ 78, "mincore", 3, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "user_addr_t", "vec" } } },
{ 79, "getgroups", 2, { { "u_int", "gidsetsize" }, { "gid_t *", "gidset" } } },
{ 80, "setgroups", 2, { { "u_int", "gidsetsize" }, { "gid_t *", "gidset" } } },
{ 81, "getpgrp", 0, {} },
{ 82, "setpgid", 2, { { "int", "pid" }, { "int", "pgid" } } },
{ 83, "setitimer", 3, { { "u_int", "which" }, { "struct itimerval *", "itv" }, { "struct itimerval *", "oitv" } } },
{ 85, "swapon", 0, {} },
{ 86, "getitimer", 2, { { "u_int", "which" }, { "struct itimerval *", "itv" } } },
{ 89, "getdtablesize", 0, {} },
{ 90, "dup2", 2, { { "u_int", "from" }, { "u_int", "to" } } },
{ 92, "fcntl", 3, { { "int", "fd" }, { "int", "cmd" }, { "long", "arg" } } },
{ 93, "select", 5, { { "int", "nd" }, { "u_int32_t *", "in" }, { "u_int32_t *", "ou" }, { "u_int32_t *", "ex" }, { "struct timeval *", "tv" } } },
{ 95, "fsync", 1, { { "int", "fd" } } },
{ 96, "setpriority", 3, { { "int", "which" }, { "id_t", "who" }, { "int", "prio" } } },
{ 97, "socket", 3, { { "int", "domain" }, { "int", "type" }, { "int", "protocol" } } },
{ 98, "connect", 3, { { "int", "s" }, { "caddr_t", "name" }, { "socklen_t", "namelen" } } },
{ 100, "getpriority", 2, { { "int", "which" }, { "id_t", "who" } } },
{ 104, "bind", 3, { { "int", "s" }, { "caddr_t", "name" }, { "socklen_t", "namelen" } } },
{ 105, "setsockopt", 5, { { "int", "s" }, { "int", "level" }, { "int", "name" }, { "caddr_t", "val" }, { "socklen_t", "valsize" } } },
{ 106, "listen", 2, { { "int", "s" }, { "int", "backlog" } } },
{ 111, "sigsuspend", 1, { { "sigset_t", "mask" } } },
{ 116, "gettimeofday", 3, { { "struct timeval *", "tp" }, { "struct timezone *", "tzp" }, { "uint64_t *", "mach_absolute_time" } } },
{ 117, "getrusage", 2, { { "int", "who" }, { "struct rusage *", "rusage" } } },
{ 118, "getsockopt", 5, { { "int", "s" }, { "int", "level" }, { "int", "name" }, { "caddr_t", "val" }, { "socklen_t *", "avalsize" } } },
{ 120, "readv", 3, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "u_int", "iovcnt" } } },
{ 121, "writev", 3, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "u_int", "iovcnt" } } },
{ 122, "settimeofday", 2, { { "struct timeval *", "tv" }, { "struct timezone *", "tzp" } } },
{ 123, "fchown", 3, { { "int", "fd" }, { "int", "uid" }, { "int", "gid" } } },
{ 124, "fchmod", 2, { { "int", "fd" }, { "int", "mode" } } },
{ 126, "setreuid", 2, { { "uid_t", "ruid" }, { "uid_t", "euid" } } },
{ 127, "setregid", 2, { { "gid_t", "rgid" }, { "gid_t", "egid" } } },
{ 128, "rename", 2, { { "char *", "from" }, { "char *", "to" } } },
{ 131, "flock", 2, { { "int", "fd" }, { "int", "how" } } },
{ 132, "mkfifo", 2, { { "char *", "path" }, { "int", "mode" } } },
{ 133, "sendto", 6, { { "int", "s" }, { "caddr_t", "buf" }, { "size_t", "len" }, { "int", "flags" }, { "caddr_t", "to" }, { "socklen_t", "tolen" } } },
{ 134, "shutdown", 2, { { "int", "s" }, { "int", "how" } } },
{ 135, "socketpair", 4, { { "int", "domain" }, { "int", "type" }, { "int", "protocol" }, { "int *", "rsv" } } },
{ 136, "mkdir", 2, { { "char *", "path" }, { "int", "mode" } } },
{ 137, "rmdir", 1, { { "char *", "path" } } },
{ 138, "utimes", 2, { { "char *", "path" }, { "struct timeval *", "tptr" } } },
{ 139, "futimes", 2, { { "int", "fd" }, { "struct timeval *", "tptr" } } },
{ 140, "adjtime", 2, { { "struct timeval *", "delta" }, { "struct timeval *", "olddelta" } } },
{ 142, "gethostuuid", 2, { { "unsigned char *", "uuid_buf" }, { "const struct timespec *", "timeoutp" } } },
{ 147, "setsid", 0, {} },
{ 151, "getpgid", 1, { { "pid_t", "pid" } } },
{ 152, "setprivexec", 1, { { "int", "flag" } } },
{ 153, "pread", 4, { { "int", "fd" }, { "user_addr_t", "buf" }, { "user_size_t", "nbyte" }, { "off_t", "offset" } } },
{ 154, "pwrite", 4, { { "int", "fd" }, { "user_addr_t", "buf" }, { "user_size_t", "nbyte" }, { "off_t", "offset" } } },
{ 155, "nfssvc", 2, { { "int", "flag" }, { "caddr_t", "argp" } } },
{ 157, "statfs", 2, { { "char *", "path" }, { "struct statfs *", "buf" } } },
{ 158, "fstatfs", 2, { { "int", "fd" }, { "struct statfs *", "buf" } } },
{ 159, "unmount", 2, { { "char *", "path" }, { "int", "flags" } } },
{ 161, "getfh", 2, { { "char *", "fname" }, { "fhandle_t *", "fhp" } } },
{ 164, "funmount", 2, { { "int", "fd" }, { "int", "flags" } } },
{ 165, "quotactl", 4, { { "const char *", "path" }, { "int", "cmd" }, { "int", "uid" }, { "caddr_t", "arg" } } },
{ 167, "mount", 4, { { "char *", "type" }, { "char *", "path" }, { "int", "flags" }, { "caddr_t", "data" } } },
{ 169, "csops", 4, { { "pid_t", "pid" }, { "uint32_t", "ops" }, { "user_addr_t", "useraddr" }, { "user_size_t", "usersize" } } },
{ 170, "csops_audittoken", 5, { { "pid_t", "pid" }, { "uint32_t", "ops" }, { "user_addr_t", "useraddr" }, { "user_size_t", "usersize" }, { "user_addr_t", "uaudittoken" } } },
{ 173, "waitid", 4, { { "idtype_t", "idtype" }, { "id_t", "id" }, { "siginfo_t *", "infop" }, { "int", "options" } } },
{ 177, "kdebug_typefilter", 2, { { "void **", "addr" }, { "size_t *", "size" } } },
{ 178, "kdebug_trace_string", 3, { { "uint32_t", "debugid" }, { "uint64_t", "str_id" }, { "const char *", "str" } } },
{ 179, "kdebug_trace64", 5, { { "uint32_t", "code" }, { "uint64_t", "arg1" }, { "uint64_t", "arg2" }, { "uint64_t", "arg3" }, { "uint64_t", "arg4" } } },
{ 180, "kdebug_trace", 5, { { "uint32_t", "code" }, { "u_long", "arg1" }, { "u_long", "arg2" }, { "u_long", "arg3" }, { "u_long", "arg4" } } },
{ 181, "setgid", 1, { { "gid_t", "gid" } } },
{ 182, "setegid", 1, { { "gid_t", "egid" } } },
{ 183, "seteuid", 1, { { "uid_t", "euid" } } },
{ 184, "sigreturn", 3, { { "struct ucontext *", "uctx" }, { "int", "infostyle" }, { "user_addr_t", "token" } } },
{ 185, "panic_with_data", 5, { { "uuid_t", "uuid" }, { "void *", "addr" }, { "uint32_t", "len" }, { "uint32_t", "flags" }, { "const char *", "msg" } } },
{ 186, "thread_selfcounts", 3, { { "uint32_t", "kind" }, { "user_addr_t", "buf" }, { "user_size_t", "size" } } },
{ 187, "fdatasync", 1, { { "int", "fd" } } },
{ 188, "stat", 2, { { "char *", "path" }, { "user_addr_t", "ub" } } },
{ 189, "fstat", 2, { { "int", "fd" }, { "user_addr_t", "ub" } } },
{ 190, "lstat", 2, { { "char *", "path" }, { "user_addr_t", "ub" } } },
{ 191, "pathconf", 2, { { "char *", "path" }, { "int", "name" } } },
{ 192, "fpathconf", 2, { { "int", "fd" }, { "int", "name" } } },
{ 194, "getrlimit", 2, { { "u_int", "which" }, { "struct rlimit *", "rlp" } } },
{ 195, "setrlimit", 2, { { "u_int", "which" }, { "struct rlimit *", "rlp" } } },
{ 196, "getdirentries", 4, { { "int", "fd" }, { "char *", "buf" }, { "u_int", "count" }, { "long *", "basep" } } },
{ 197, "mmap", 6, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "int", "prot" }, { "int", "flags" }, { "int", "fd" }, { "off_t", "pos" } } },
{ 199, "lseek", 3, { { "int", "fd" }, { "off_t", "offset" }, { "int", "whence" } } },
{ 200, "truncate", 2, { { "char *", "path" }, { "off_t", "length" } } },
{ 201, "ftruncate", 2, { { "int", "fd" }, { "off_t", "length" } } },
{ 202, "sysctl", 6, { { "int *", "name" }, { "u_int", "namelen" }, { "void *", "old" }, { "size_t *", "oldlenp" }, { "void *", "new" }, { "size_t", "newlen" } } },
{ 203, "mlock", 2, { { "caddr_ut", "addr" }, { "size_ut", "len" } } },
{ 204, "munlock", 2, { { "caddr_ut", "addr" }, { "size_ut", "len" } } },
{ 205, "undelete", 1, { { "char *", "path" } } },
{ 216, "open_dprotected_np", 5, { { "char *", "path" }, { "int", "flags" }, { "int", "class" }, { "int", "dpflags" }, { "int", "mode" } } },
{ 217, "fsgetpath_ext", 5, { { "user_addr_t", "buf" }, { "size_t", "bufsize" }, { "user_addr_t", "fsid" }, { "uint64_t", "objid" }, { "uint32_t", "options" } } },
{ 218, "openat_dprotected_np", 7, { { "int", "fd" }, { "char *", "path" }, { "int", "flags" }, { "int", "class" }, { "int", "dpflags" }, { "int", "mode" }, { "int", "authfd" } } },
{ 220, "getattrlist", 5, { { "const char *", "path" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "u_long", "options" } } },
{ 221, "setattrlist", 5, { { "const char *", "path" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "u_long", "options" } } },
{ 222, "getdirentriesattr", 8, { { "int", "fd" }, { "struct attrlist *", "alist" }, { "void *", "buffer" }, { "size_t", "buffersize" }, { "u_long *", "count" }, { "u_long *", "basep" }, { "u_long *", "newstate" }, { "u_long", "options" } } },
{ 223, "exchangedata", 3, { { "const char *", "path1" }, { "const char *", "path2" }, { "u_long", "options" } } },
{ 225, "searchfs", 6, { { "const char *", "path" }, { "struct fssearchblock *", "searchblock" }, { "uint32_t *", "nummatches" }, { "uint32_t", "scriptcode" }, { "uint32_t", "options" }, { "struct searchstate *", "state" } } },
{ 226, "delete", 1, { { "char *", "path" } } },
{ 227, "copyfile", 4, { { "char *", "from" }, { "char *", "to" }, { "int", "mode" }, { "int", "flags" } } },
{ 228, "fgetattrlist", 5, { { "int", "fd" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "u_long", "options" } } },
{ 229, "fsetattrlist", 5, { { "int", "fd" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "u_long", "options" } } },
{ 230, "poll", 3, { { "struct pollfd *", "fds" }, { "u_int", "nfds" }, { "int", "timeout" } } },
{ 234, "getxattr", 6, { { "char *", "path" }, { "char *", "attrname" }, { "user_addr_t", "value" }, { "size_t", "size" }, { "uint32_t", "position" }, { "int", "options" } } },
{ 235, "fgetxattr", 6, { { "int", "fd" }, { "char *", "attrname" }, { "user_addr_t", "value" }, { "size_t", "size" }, { "uint32_t", "position" }, { "int", "options" } } },
{ 236, "setxattr", 6, { { "char *", "path" }, { "char *", "attrname" }, { "user_addr_t", "value" }, { "size_t", "size" }, { "uint32_t", "position" }, { "int", "options" } } },
{ 237, "fsetxattr", 6, { { "int", "fd" }, { "char *", "attrname" }, { "user_addr_t", "value" }, { "size_t", "size" }, { "uint32_t", "position" }, { "int", "options" } } },
{ 238, "removexattr", 3, { { "char *", "path" }, { "char *", "attrname" }, { "int", "options" } } },
{ 239, "fremovexattr", 3, { { "int", "fd" }, { "char *", "attrname" }, { "int", "options" } } },
{ 240, "listxattr", 4, { { "char *", "path" }, { "user_addr_t", "namebuf" }, { "size_t", "bufsize" }, { "int", "options" } } },
{ 241, "flistxattr", 4, { { "int", "fd" }, { "user_addr_t", "namebuf" }, { "size_t", "bufsize" }, { "int", "options" } } },
{ 242, "fsctl", 4, { { "const char *", "path" }, { "u_long", "cmd" }, { "caddr_t", "data" }, { "u_int", "options" } } },
{ 243, "initgroups", 3, { { "u_int", "gidsetsize" }, { "gid_t *", "gidset" }, { "int", "gmuid" } } },
{ 244, "posix_spawn", 5, { { "pid_t *", "pid" }, { "const char *", "path" }, { "const struct _posix_spawn_args_desc *", "adesc" }, { "char **", "argv" }, { "char **", "envp" } } },
{ 245, "ffsctl", 4, { { "int", "fd" }, { "u_long", "cmd" }, { "caddr_t", "data" }, { "u_int", "options" } } },
{ 248, "fhopen", 2, { { "const struct fhandle *", "u_fhp" }, { "int", "flags" } } },
{ 250, "minherit", 3, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "int", "inherit" } } },
{ 251, "semsys", 5, { { "u_int", "which" }, { "int", "a2" }, { "int", "a3" }, { "int", "a4" }, { "int", "a5" } } },
{ 252, "msgsys", 5, { { "u_int", "which" }, { "int", "a2" }, { "int", "a3" }, { "int", "a4" }, { "int", "a5" } } },
{ 253, "shmsys", 4, { { "u_int", "which" }, { "int", "a2" }, { "int", "a3" }, { "int", "a4" } } },
{ 254, "semctl", 4, { { "int", "semid" }, { "int", "semnum" }, { "int", "cmd" }, { "semun_t", "arg" } } },
{ 255, "semget", 3, { { "key_t", "key" }, { "int", "nsems" }, { "int", "semflg" } } },
{ 256, "semop", 3, { { "int", "semid" }, { "struct sembuf *", "sops" }, { "int", "nsops" } } },
{ 258, "msgctl", 3, { { "int", "msqid" }, { "int", "cmd" }, { "struct msqid_ds *", "buf" } } },
{ 259, "msgget", 2, { { "key_t", "key" }, { "int", "msgflg" } } },
{ 260, "msgsnd", 4, { { "int", "msqid" }, { "void *", "msgp" }, { "size_t", "msgsz" }, { "int", "msgflg" } } },
{ 261, "msgrcv", 5, { { "int", "msqid" }, { "void *", "msgp" }, { "size_t", "msgsz" }, { "long", "msgtyp" }, { "int", "msgflg" } } },
{ 262, "shmat", 3, { { "int", "shmid" }, { "void *", "shmaddr" }, { "int", "shmflg" } } },
{ 263, "shmctl", 3, { { "int", "shmid" }, { "int", "cmd" }, { "struct shmid_ds *", "buf" } } },
{ 264, "shmdt", 1, { { "void *", "shmaddr" } } },
{ 265, "shmget", 3, { { "key_t", "key" }, { "size_t", "size" }, { "int", "shmflg" } } },
{ 266, "shm_open", 3, { { "const char *", "name" }, { "int", "oflag" }, { "int", "mode" } } },
{ 267, "shm_unlink", 1, { { "const char *", "name" } } },
{ 268, "sem_open", 4, { { "const char *", "name" }, { "int", "oflag" }, { "int", "mode" }, { "int", "value" } } },
{ 269, "sem_close", 1, { { "sem_t *", "sem" } } },
{ 270, "sem_unlink", 1, { { "const char *", "name" } } },
{ 271, "sem_wait", 1, { { "sem_t *", "sem" } } },
{ 272, "sem_trywait", 1, { { "sem_t *", "sem" } } },
{ 273, "sem_post", 1, { { "sem_t *", "sem" } } },
{ 274, "sysctlbyname", 6, { { "const char *", "name" }, { "size_t", "namelen" }, { "void *", "old" }, { "size_t *", "oldlenp" }, { "void *", "new" }, { "size_t", "newlen" } } },
{ 277, "open_extended", 6, { { "char *", "path" }, { "int", "flags" }, { "uid_t", "uid" }, { "gid_t", "gid" }, { "int", "mode" }, { "user_addr_t", "xsecurity" } } },
{ 278, "umask_extended", 2, { { "int", "newmask" }, { "user_addr_t", "xsecurity" } } },
{ 279, "stat_extended", 4, { { "char *", "path" }, { "user_addr_t", "ub" }, { "user_addr_t", "xsecurity" }, { "user_addr_t", "xsecurity_size" } } },
{ 280, "lstat_extended", 4, { { "char *", "path" }, { "user_addr_t", "ub" }, { "user_addr_t", "xsecurity" }, { "user_addr_t", "xsecurity_size" } } },
{ 281, "fstat_extended", 4, { { "int", "fd" }, { "user_addr_t", "ub" }, { "user_addr_t", "xsecurity" }, { "user_addr_t", "xsecurity_size" } } },
{ 282, "chmod_extended", 5, { { "char *", "path" }, { "uid_t", "uid" }, { "gid_t", "gid" }, { "int", "mode" }, { "user_addr_t", "xsecurity" } } },
{ 283, "fchmod_extended", 5, { { "int", "fd" }, { "uid_t", "uid" }, { "gid_t", "gid" }, { "int", "mode" }, { "user_addr_t", "xsecurity" } } },
{ 284, "access_extended", 4, { { "user_addr_t", "entries" }, { "size_t", "size" }, { "user_addr_t", "results" }, { "uid_t", "uid" } } },
{ 285, "settid", 2, { { "uid_t", "uid" }, { "gid_t", "gid" } } },
{ 286, "gettid", 2, { { "uid_t *", "uidp" }, { "gid_t *", "gidp" } } },
{ 287, "setsgroups", 2, { { "int", "setlen" }, { "user_addr_t", "guidset" } } },
{ 288, "getsgroups", 2, { { "user_addr_t", "setlen" }, { "user_addr_t", "guidset" } } },
{ 289, "setwgroups", 2, { { "int", "setlen" }, { "user_addr_t", "guidset" } } },
{ 290, "getwgroups", 2, { { "user_addr_t", "setlen" }, { "user_addr_t", "guidset" } } },
{ 291, "mkfifo_extended", 5, { { "char *", "path" }, { "uid_t", "uid" }, { "gid_t", "gid" }, { "int", "mode" }, { "user_addr_t", "xsecurity" } } },
{ 292, "mkdir_extended", 5, { { "char *", "path" }, { "uid_t", "uid" }, { "gid_t", "gid" }, { "int", "mode" }, { "user_addr_t", "xsecurity" } } },
{ 293, "identitysvc", 2, { { "int", "opcode" }, { "user_addr_t", "message" } } },
{ 294, "shared_region_check_np", 1, { { "uint64_t *", "start_address" } } },
{ 296, "vm_pressure_monitor", 3, { { "int", "wait_for_pressure" }, { "int", "nsecs_monitored" }, { "uint32_t *", "pages_reclaimed" } } },
{ 297, "psynch_rw_longrdlock", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
{ 298, "psynch_rw_yieldwrlock", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
{ 299, "psynch_rw_downgrade", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
{ 300, "psynch_rw_upgrade", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
{ 301, "psynch_mutexwait", 5, { { "user_addr_t", "mutex" }, { "uint32_t", "mgen" }, { "uint32_t", "ugen" }, { "uint64_t", "tid" }, { "uint32_t", "flags" } } },
{ 302, "psynch_mutexdrop", 5, { { "user_addr_t", "mutex" }, { "uint32_t", "mgen" }, { "uint32_t", "ugen" }, { "uint64_t", "tid" }, { "uint32_t", "flags" } } },
{ 303, "psynch_cvbroad", 7, { { "user_addr_t", "cv" }, { "uint64_t", "cvlsgen" }, { "uint64_t", "cvudgen" }, { "uint32_t", "flags" }, { "user_addr_t", "mutex" }, { "uint64_t", "mugen" }, { "uint64_t", "tid" } } },
{ 304, "psynch_cvsignal", 8, { { "user_addr_t", "cv" }, { "uint64_t", "cvlsgen" }, { "uint32_t", "cvugen" }, { "int", "thread_port" }, { "user_addr_t", "mutex" }, { "uint64_t", "mugen" }, { "uint64_t", "tid" }, { "uint32_t", "flags" } } },
{ 305, "psynch_cvwait", 8, { { "user_addr_t", "cv" }, { "uint64_t", "cvlsgen" }, { "uint32_t", "cvugen" }, { "user_addr_t", "mutex" }, { "uint64_t", "mugen" }, { "uint32_t", "flags" }, { "int64_t", "sec" }, { "uint32_t", "nsec" } } },
{ 306, "psynch_rw_rdlock", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
{ 307, "psynch_rw_wrlock", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
{ 308, "psynch_rw_unlock", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
{ 309, "psynch_rw_unlock2", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
{ 310, "getsid", 1, { { "pid_t", "pid" } } },
{ 311, "settid_with_pid", 2, { { "pid_t", "pid" }, { "int", "assume" } } },
{ 312, "psynch_cvclrprepost", 7, { { "user_addr_t", "cv" }, { "uint32_t", "cvgen" }, { "uint32_t", "cvugen" }, { "uint32_t", "cvsgen" }, { "uint32_t", "prepocnt" }, { "uint32_t", "preposeq" }, { "uint32_t", "flags" } } },
{ 313, "aio_fsync", 2, { { "int", "op" }, { "user_addr_t", "aiocbp" } } },
{ 314, "aio_return", 1, { { "user_addr_t", "aiocbp" } } },
{ 315, "aio_suspend", 3, { { "user_addr_t", "aiocblist" }, { "int", "nent" }, { "user_addr_t", "timeoutp" } } },
{ 316, "aio_cancel", 2, { { "int", "fd" }, { "user_addr_t", "aiocbp" } } },
{ 317, "aio_error", 1, { { "user_addr_t", "aiocbp" } } },
{ 318, "aio_read", 1, { { "user_addr_t", "aiocbp" } } },
{ 319, "aio_write", 1, { { "user_addr_t", "aiocbp" } } },
{ 320, "lio_listio", 4, { { "int", "mode" }, { "user_addr_t", "aiocblist" }, { "int", "nent" }, { "user_addr_t", "sigp" } } },
{ 322, "iopolicysys", 2, { { "int", "cmd" }, { "void *", "arg" } } },
{ 323, "process_policy", 7, { { "int", "scope" }, { "int", "action" }, { "int", "policy" }, { "int", "policy_subtype" }, { "user_addr_t", "attrp" }, { "pid_t", "target_pid" }, { "uint64_t", "target_threadid" } } },
{ 324, "mlockall", 1, { { "int", "how" } } },
{ 325, "munlockall", 1, { { "int", "how" } } },
{ 327, "issetugid", 0, {} },
{ 328, "pthread_kill", 2, { { "int", "thread_port" }, { "int", "sig" } } },
{ 329, "pthread_sigmask", 3, { { "int", "how" }, { "user_addr_t", "set" }, { "user_addr_t", "oset" } } },
{ 330, "sigwait", 2, { { "user_addr_t", "set" }, { "user_addr_t", "sig" } } },
{ 331, "disable_threadsignal", 1, { { "int", "value" } } },
{ 332, "pthread_markcancel", 1, { { "int", "thread_port" } } },
{ 333, "pthread_canceled", 1, { { "int", "action" } } },
{ 334, "semwait_signal", 6, { { "int", "cond_sem" }, { "int", "mutex_sem" }, { "int", "timeout" }, { "int", "relative" }, { "int64_t", "tv_sec" }, { "int32_t", "tv_nsec" } } },
{ 336, "proc_info", 6, { { "int32_t", "callnum" }, { "int32_t", "pid" }, { "uint32_t", "flavor" }, { "uint64_t", "arg" }, { "user_addr_t", "buffer" }, { "int32_t", "buffersize" } } },
{ 337, "sendfile", 6, { { "int", "fd" }, { "int", "s" }, { "off_t", "offset" }, { "off_t *", "nbytes" }, { "struct sf_hdtr *", "hdtr" }, { "int", "flags" } } },
{ 338, "stat64", 2, { { "char *", "path" }, { "user_addr_t", "ub" } } },
{ 339, "fstat64", 2, { { "int", "fd" }, { "user_addr_t", "ub" } } },
{ 340, "lstat64", 2, { { "char *", "path" }, { "user_addr_t", "ub" } } },
{ 341, "stat64_extended", 4, { { "char *", "path" }, { "user_addr_t", "ub" }, { "user_addr_t", "xsecurity" }, { "user_addr_t", "xsecurity_size" } } },
{ 342, "lstat64_extended", 4, { { "char *", "path" }, { "user_addr_t", "ub" }, { "user_addr_t", "xsecurity" }, { "user_addr_t", "xsecurity_size" } } },
{ 343, "fstat64_extended", 4, { { "int", "fd" }, { "user_addr_t", "ub" }, { "user_addr_t", "xsecurity" }, { "user_addr_t", "xsecurity_size" } } },
{ 344, "getdirentries64", 4, { { "int", "fd" }, { "void *", "buf" }, { "user_size_t", "bufsize" }, { "off_t *", "position" } } },
{ 345, "statfs64", 2, { { "char *", "path" }, { "struct statfs64 *", "buf" } } },
{ 346, "fstatfs64", 2, { { "int", "fd" }, { "struct statfs64 *", "buf" } } },
{ 347, "getfsstat64", 3, { { "user_addr_t", "buf" }, { "int", "bufsize" }, { "int", "flags" } } },
{ 348, "pthread_chdir", 1, { { "char *", "path" } } },
{ 349, "pthread_fchdir", 1, { { "int", "fd" } } },
{ 350, "audit", 2, { { "void *", "record" }, { "int", "length" } } },
{ 351, "auditon", 3, { { "int", "cmd" }, { "void *", "data" }, { "int", "length" } } },
{ 353, "getauid", 1, { { "au_id_t *", "auid" } } },
{ 354, "setauid", 1, { { "au_id_t *", "auid" } } },
{ 357, "getaudit_addr", 2, { { "struct auditinfo_addr *", "auditinfo_addr" }, { "int", "length" } } },
{ 358, "setaudit_addr", 2, { { "struct auditinfo_addr *", "auditinfo_addr" }, { "int", "length" } } },
{ 359, "auditctl", 1, { { "char *", "path" } } },
{ 360, "bsdthread_create", 5, { { "user_addr_t", "func" }, { "user_addr_t", "func_arg" }, { "user_addr_t", "stack" }, { "user_addr_t", "pthread" }, { "uint32_t", "flags" } } },
{ 361, "bsdthread_terminate", 4, { { "user_addr_t", "stackaddr" }, { "size_t", "freesize" }, { "uint32_t", "port" }, { "user_addr_t", "sema_or_ulock" } } },
{ 362, "kqueue", 0, {} },
{ 363, "kevent", 6, { { "int", "fd" }, { "const struct kevent *", "changelist" }, { "int", "nchanges" }, { "struct kevent *", "eventlist" }, { "int", "nevents" }, { "const struct timespec *", "timeout" } } },
{ 364, "lchown", 3, { { "char *", "path" }, { "uid_t", "owner" }, { "gid_t", "group" } } },
{ 366, "bsdthread_register", 7, { { "user_addr_t", "threadstart" }, { "user_addr_t", "wqthread" }, { "uint32_t", "flags" }, { "user_addr_t", "stack_addr_hint" }, { "user_addr_t", "targetconc_ptr" }, { "uint32_t", "dispatchqueue_offset" }, { "uint32_t", "tsd_offset" } } },
{ 367, "workq_open", 0, {} },
{ 368, "workq_kernreturn", 4, { { "int", "options" }, { "user_addr_t", "item" }, { "int", "affinity" }, { "int", "prio" } } },
{ 369, "kevent64", 7, { { "int", "fd" }, { "const struct kevent64_s *", "changelist" }, { "int", "nchanges" }, { "struct kevent64_s *", "eventlist" }, { "int", "nevents" }, { "unsigned int", "flags" }, { "const struct timespec *", "timeout" } } },
{ 372, "thread_selfid", 0, {} },
{ 373, "ledger", 4, { { "int", "cmd" }, { "caddr_t", "arg1" }, { "caddr_t", "arg2" }, { "caddr_t", "arg3" } } },
{ 374, "kevent_qos", 8, { { "int", "fd" }, { "const struct kevent_qos_s *", "changelist" }, { "int", "nchanges" }, { "struct kevent_qos_s *", "eventlist" }, { "int", "nevents" }, { "void *", "data_out" }, { "size_t *", "data_available" }, { "unsigned int", "flags" } } },
{ 375, "kevent_id", 8, { { "uint64_t", "id" }, { "const struct kevent_qos_s *", "changelist" }, { "int", "nchanges" }, { "struct kevent_qos_s *", "eventlist" }, { "int", "nevents" }, { "void *", "data_out" }, { "size_t *", "data_available" }, { "unsigned int", "flags" } } },
{ 380, "mac_execve", 4, { { "char *", "fname" }, { "char **", "argp" }, { "char **", "envp" }, { "struct mac *", "mac_p" } } },
{ 381, "mac_syscall", 3, { { "char *", "policy" }, { "int", "call" }, { "user_addr_t", "arg" } } },
{ 382, "mac_get_file", 2, { { "char *", "path_p" }, { "struct mac *", "mac_p" } } },
{ 383, "mac_set_file", 2, { { "char *", "path_p" }, { "struct mac *", "mac_p" } } },
{ 384, "mac_get_link", 2, { { "char *", "path_p" }, { "struct mac *", "mac_p" } } },
{ 385, "mac_set_link", 2, { { "char *", "path_p" }, { "struct mac *", "mac_p" } } },
{ 386, "mac_get_proc", 1, { { "struct mac *", "mac_p" } } },
{ 387, "mac_set_proc", 1, { { "struct mac *", "mac_p" } } },
{ 388, "mac_get_fd", 2, { { "int", "fd" }, { "struct mac *", "mac_p" } } },
{ 389, "mac_set_fd", 2, { { "int", "fd" }, { "struct mac *", "mac_p" } } },
{ 390, "mac_get_pid", 2, { { "pid_t", "pid" }, { "struct mac *", "mac_p" } } },
{ 394, "pselect", 6, { { "int", "nd" }, { "u_int32_t *", "in" }, { "u_int32_t *", "ou" }, { "u_int32_t *", "ex" }, { "const struct timespec *", "ts" }, { "const struct sigset_t *", "mask" } } },
{ 395, "pselect_nocancel", 6, { { "int", "nd" }, { "u_int32_t *", "in" }, { "u_int32_t *", "ou" }, { "u_int32_t *", "ex" }, { "const struct timespec *", "ts" }, { "const struct sigset_t *", "mask" } } },
{ 396, "read_nocancel", 3, { { "int", "fd" }, { "user_addr_t", "cbuf" }, { "user_size_t", "nbyte" } } },
{ 397, "write_nocancel", 3, { { "int", "fd" }, { "user_addr_t", "cbuf" }, { "user_size_t", "nbyte" } } },
{ 398, "open_nocancel", 3, { { "char *", "path" }, { "int", "flags" }, { "int", "mode" } } },
{ 399, "close_nocancel", 1, { { "int", "fd" } } },
{ 400, "wait4_nocancel", 4, { { "int", "pid" }, { "user_addr_t", "status" }, { "int", "options" }, { "user_addr_t", "rusage" } } },
{ 401, "recvmsg_nocancel", 3, { { "int", "s" }, { "struct msghdr *", "msg" }, { "int", "flags" } } },
{ 402, "sendmsg_nocancel", 3, { { "int", "s" }, { "caddr_t", "msg" }, { "int", "flags" } } },
{ 403, "recvfrom_nocancel", 6, { { "int", "s" }, { "void *", "buf" }, { "size_t", "len" }, { "int", "flags" }, { "struct sockaddr *", "from" }, { "int *", "fromlenaddr" } } },
{ 404, "accept_nocancel", 3, { { "int", "s" }, { "caddr_t", "name" }, { "socklen_t *", "anamelen" } } },
{ 405, "msync_nocancel", 3, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "int", "flags" } } },
{ 406, "fcntl_nocancel", 3, { { "int", "fd" }, { "int", "cmd" }, { "long", "arg" } } },
{ 407, "select_nocancel", 5, { { "int", "nd" }, { "u_int32_t *", "in" }, { "u_int32_t *", "ou" }, { "u_int32_t *", "ex" }, { "struct timeval *", "tv" } } },
{ 408, "fsync_nocancel", 1, { { "int", "fd" } } },
{ 409, "connect_nocancel", 3, { { "int", "s" }, { "caddr_t", "name" }, { "socklen_t", "namelen" } } },
{ 410, "sigsuspend_nocancel", 1, { { "sigset_t", "mask" } } },
{ 411, "readv_nocancel", 3, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "u_int", "iovcnt" } } },
{ 412, "writev_nocancel", 3, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "u_int", "iovcnt" } } },
{ 413, "sendto_nocancel", 6, { { "int", "s" }, { "caddr_t", "buf" }, { "size_t", "len" }, { "int", "flags" }, { "caddr_t", "to" }, { "socklen_t", "tolen" } } },
{ 414, "pread_nocancel", 4, { { "int", "fd" }, { "user_addr_t", "buf" }, { "user_size_t", "nbyte" }, { "off_t", "offset" } } },
{ 415, "pwrite_nocancel", 4, { { "int", "fd" }, { "user_addr_t", "buf" }, { "user_size_t", "nbyte" }, { "off_t", "offset" } } },
{ 416, "waitid_nocancel", 4, { { "idtype_t", "idtype" }, { "id_t", "id" }, { "siginfo_t *", "infop" }, { "int", "options" } } },
{ 417, "poll_nocancel", 3, { { "struct pollfd *", "fds" }, { "u_int", "nfds" }, { "int", "timeout" } } },
{ 418, "msgsnd_nocancel", 4, { { "int", "msqid" }, { "void *", "msgp" }, { "size_t", "msgsz" }, { "int", "msgflg" } } },
{ 419, "msgrcv_nocancel", 5, { { "int", "msqid" }, { "void *", "msgp" }, { "size_t", "msgsz" }, { "long", "msgtyp" }, { "int", "msgflg" } } },
{ 420, "sem_wait_nocancel", 1, { { "sem_t *", "sem" } } },
{ 421, "aio_suspend_nocancel", 3, { { "user_addr_t", "aiocblist" }, { "int", "nent" }, { "user_addr_t", "timeoutp" } } },
{ 422, "sigwait_nocancel", 2, { { "user_addr_t", "set" }, { "user_addr_t", "sig" } } },
{ 423, "semwait_signal_nocancel", 6, { { "int", "cond_sem" }, { "int", "mutex_sem" }, { "int", "timeout" }, { "int", "relative" }, { "int64_t", "tv_sec" }, { "int32_t", "tv_nsec" } } },
{ 424, "mac_mount", 5, { { "char *", "type" }, { "char *", "path" }, { "int", "flags" }, { "caddr_t", "data" }, { "struct mac *", "mac_p" } } },
{ 425, "mac_get_mount", 2, { { "char *", "path" }, { "struct mac *", "mac_p" } } },
{ 426, "mac_getfsstat", 5, { { "user_addr_t", "buf" }, { "int", "bufsize" }, { "user_addr_t", "mac" }, { "int", "macsize" }, { "int", "flags" } } },
{ 427, "fsgetpath", 4, { { "user_addr_t", "buf" }, { "size_t", "bufsize" }, { "user_addr_t", "fsid" }, { "uint64_t", "objid" } } },
{ 428, "audit_session_self", 0, {} },
{ 429, "audit_session_join", 1, { { "mach_port_name_t", "port" } } },
{ 430, "fileport_makeport", 2, { { "int", "fd" }, { "user_addr_t", "portnamep" } } },
{ 431, "fileport_makefd", 1, { { "mach_port_name_t", "port" } } },
{ 432, "audit_session_port", 2, { { "au_asid_t", "asid" }, { "user_addr_t", "portnamep" } } },
{ 433, "pid_suspend", 1, { { "int", "pid" } } },
{ 434, "pid_resume", 1, { { "int", "pid" } } },
{ 435, "pid_hibernate", 1, { { "int", "pid" } } },
{ 436, "pid_shutdown_sockets", 2, { { "int", "pid" }, { "int", "level" } } },
{ 439, "kas_info", 3, { { "int", "selector" }, { "void *", "value" }, { "size_t *", "size" } } },
{ 440, "memorystatus_control", 5, { { "uint32_t", "command" }, { "int32_t", "pid" }, { "uint32_t", "flags" }, { "user_addr_t", "buffer" }, { "size_t", "buffersize" } } },
{ 441, "guarded_open_np", 5, { { "char *", "path" }, { "const guardid_t *", "guard" }, { "u_int", "guardflags" }, { "int", "flags" }, { "int", "mode" } } },
{ 442, "guarded_close_np", 2, { { "int", "fd" }, { "const guardid_t *", "guard" } } },
{ 443, "guarded_kqueue_np", 2, { { "const guardid_t *", "guard" }, { "u_int", "guardflags" } } },
{ 444, "change_fdguard_np", 6, { { "int", "fd" }, { "const guardid_t *", "guard" }, { "u_int", "guardflags" }, { "const guardid_t *", "nguard" }, { "u_int", "nguardflags" }, { "int *", "fdflagsp" } } },
{ 445, "usrctl", 1, { { "uint32_t", "flags" } } },
{ 446, "proc_rlimit_control", 3, { { "pid_t", "pid" }, { "int", "flavor" }, { "void *", "arg" } } },
{ 447, "connectx", 8, { { "int", "socket" }, { "const sa_endpoints_t *", "endpoints" }, { "sae_associd_t", "associd" }, { "unsigned int", "flags" }, { "const struct iovec *", "iov" }, { "unsigned int", "iovcnt" }, { "size_t *", "len" }, { "sae_connid_t *", "connid" } } },
{ 448, "disconnectx", 3, { { "int", "s" }, { "sae_associd_t", "aid" }, { "sae_connid_t", "cid" } } },
{ 449, "peeloff", 2, { { "int", "s" }, { "sae_associd_t", "aid" } } },
{ 450, "socket_delegate", 4, { { "int", "domain" }, { "int", "type" }, { "int", "protocol" }, { "pid_t", "epid" } } },
{ 451, "telemetry", 6, { { "uint64_t", "cmd" }, { "uint64_t", "deadline" }, { "uint64_t", "interval" }, { "uint64_t", "leeway" }, { "uint64_t", "arg4" }, { "uint64_t", "arg5" } } },
{ 452, "proc_uuid_policy", 4, { { "uint32_t", "operation" }, { "uuid_t", "uuid" }, { "size_t", "uuidlen" }, { "uint32_t", "flags" } } },
{ 453, "memorystatus_get_level", 1, { { "user_addr_t", "level" } } },
{ 454, "system_override", 2, { { "uint64_t", "timeout" }, { "uint64_t", "flags" } } },
{ 455, "vfs_purge", 0, {} },
{ 456, "sfi_ctl", 4, { { "uint32_t", "operation" }, { "uint32_t", "sfi_class" }, { "uint64_t", "time" }, { "uint64_t *", "out_time" } } },
{ 457, "sfi_pidctl", 4, { { "uint32_t", "operation" }, { "pid_t", "pid" }, { "uint32_t", "sfi_flags" }, { "uint32_t *", "out_sfi_flags" } } },
{ 458, "coalition", 3, { { "uint32_t", "operation" }, { "uint64_t *", "cid" }, { "uint32_t", "flags" } } },
{ 459, "coalition_info", 4, { { "uint32_t", "flavor" }, { "uint64_t *", "cid" }, { "void *", "buffer" }, { "size_t *", "bufsize" } } },
{ 460, "necp_match_policy", 3, { { "uint8_t *", "parameters" }, { "size_t", "parameters_size" }, { "struct necp_aggregate_result *", "returned_result" } } },
{ 461, "getattrlistbulk", 5, { { "int", "dirfd" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "uint64_t", "options" } } },
{ 462, "clonefileat", 5, { { "int", "src_dirfd" }, { "user_addr_t", "src" }, { "int", "dst_dirfd" }, { "user_addr_t", "dst" }, { "uint32_t", "flags" } } },
{ 463, "openat", 4, { { "int", "fd" }, { "char *", "path" }, { "int", "flags" }, { "int", "mode" } } },
{ 464, "openat_nocancel", 4, { { "int", "fd" }, { "char *", "path" }, { "int", "flags" }, { "int", "mode" } } },
{ 465, "renameat", 4, { { "int", "fromfd" }, { "char *", "from" }, { "int", "tofd" }, { "char *", "to" } } },
{ 466, "faccessat", 4, { { "int", "fd" }, { "char *", "path" }, { "int", "amode" }, { "int", "flag" } } },
{ 467, "fchmodat", 4, { { "int", "fd" }, { "char *", "path" }, { "int", "mode" }, { "int", "flag" } } },
{ 468, "fchownat", 5, { { "int", "fd" }, { "char *", "path" }, { "uid_t", "uid" }, { "gid_t", "gid" }, { "int", "flag" } } },
{ 469, "fstatat", 4, { { "int", "fd" }, { "char *", "path" }, { "user_addr_t", "ub" }, { "int", "flag" } } },
{ 470, "fstatat64", 4, { { "int", "fd" }, { "char *", "path" }, { "user_addr_t", "ub" }, { "int", "flag" } } },
{ 471, "linkat", 5, { { "int", "fd1" }, { "char *", "path" }, { "int", "fd2" }, { "char *", "link" }, { "int", "flag" } } },
{ 472, "unlinkat", 3, { { "int", "fd" }, { "char *", "path" }, { "int", "flag" } } },
{ 473, "readlinkat", 4, { { "int", "fd" }, { "char *", "path" }, { "user_addr_t", "buf" }, { "size_t", "bufsize" } } },
{ 474, "symlinkat", 3, { { "user_addr_t *", "path1" }, { "int", "fd" }, { "user_addr_t", "path2" } } },
{ 475, "mkdirat", 3, { { "int", "fd" }, { "char *", "path" }, { "int", "mode" } } },
{ 476, "getattrlistat", 6, { { "int", "fd" }, { "const char *", "path" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "u_long", "options" } } },
{ 477, "proc_trace_log", 2, { { "pid_t", "pid" }, { "uint64_t", "uniqueid" } } },
{ 478, "bsdthread_ctl", 4, { { "user_addr_t", "cmd" }, { "user_addr_t", "arg1" }, { "user_addr_t", "arg2" }, { "user_addr_t", "arg3" } } },
{ 479, "openbyid_np", 3, { { "user_addr_t", "fsid" }, { "user_addr_t", "objid" }, { "int", "oflags" } } },
{ 480, "recvmsg_x", 4, { { "int", "s" }, { "struct msghdr_x *", "msgp" }, { "u_int", "cnt" }, { "int", "flags" } } },
{ 481, "sendmsg_x", 4, { { "int", "s" }, { "struct msghdr_x *", "msgp" }, { "u_int", "cnt" }, { "int", "flags" } } },
{ 482, "thread_selfusage", 0, {} },
{ 483, "csrctl", 3, { { "uint32_t", "op" }, { "user_addr_t", "useraddr" }, { "user_addr_t", "usersize" } } },
{ 484, "guarded_open_dprotected_np", 7, { { "char *", "path" }, { "const guardid_t *", "guard" }, { "u_int", "guardflags" }, { "int", "flags" }, { "int", "dpclass" }, { "int", "dpflags" }, { "int", "mode" } } },
{ 485, "guarded_write_np", 4, { { "int", "fd" }, { "const guardid_t *", "guard" }, { "user_addr_t", "cbuf" }, { "user_size_t", "nbyte" } } },
{ 486, "guarded_pwrite_np", 5, { { "int", "fd" }, { "const guardid_t *", "guard" }, { "user_addr_t", "buf" }, { "user_size_t", "nbyte" }, { "off_t", "offset" } } },
{ 487, "guarded_writev_np", 4, { { "int", "fd" }, { "const guardid_t *", "guard" }, { "struct iovec *", "iovp" }, { "int", "iovcnt" } } },
{ 488, "renameatx_np", 5, { { "int", "fromfd" }, { "char *", "from" }, { "int", "tofd" }, { "char *", "to" }, { "u_int", "flags" } } },
{ 489, "mremap_encrypted", 5, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "uint32_t", "cryptid" }, { "uint32_t", "cputype" }, { "uint32_t", "cpusubtype" } } },
{ 490, "netagent_trigger", 2, { { "uuid_t", "agent_uuid" }, { "size_t", "agent_uuidlen" } } },
{ 491, "stack_snapshot_with_config", 3, { { "int", "stackshot_config_version" }, { "user_addr_t", "stackshot_config" }, { "size_t", "stackshot_config_size" } } },
{ 492, "microstackshot", 3, { { "user_addr_t", "tracebuf" }, { "uint32_t", "tracebuf_size" }, { "uint32_t", "flags" } } },
{ 493, "grab_pgo_data", 4, { { "user_addr_t", "uuid" }, { "int", "flags" }, { "user_addr_t", "buffer" }, { "user_ssize_t", "size" } } },
{ 494, "persona", 6, { { "uint32_t", "operation" }, { "uint32_t", "flags" }, { "struct kpersona_info *", "info" }, { "uid_t *", "id" }, { "size_t *", "idlen" }, { "char *", "path" } } },
{ 496, "mach_eventlink_signal", 2, { { "mach_port_name_t", "eventlink_port" }, { "uint64_t", "signal_count" } } },
{ 497, "mach_eventlink_wait_until", 5, { { "mach_port_name_t", "eventlink_port" }, { "uint64_t", "wait_count" }, { "uint64_t", "deadline" }, { "uint32_t", "clock_id" }, { "uint32_t", "option" } } },
{ 498, "mach_eventlink_signal_wait_until", 6, { { "mach_port_name_t", "eventlink_port" }, { "uint64_t", "wait_count" }, { "uint64_t", "signal_count" }, { "uint64_t", "deadline" }, { "uint32_t", "clock_id" }, { "uint32_t", "option" } } },
{ 499, "work_interval_ctl", 4, { { "uint32_t", "operation" }, { "uint64_t", "work_interval_id" }, { "void *", "arg" }, { "size_t", "len" } } },
{ 500, "getentropy", 2, { { "void *", "buffer" }, { "size_t", "size" } } },
{ 502, "necp_client_action", 6, { { "int", "necp_fd" }, { "uint32_t", "action" }, { "uuid_t", "client_id" }, { "size_t", "client_id_len" }, { "uint8_t *", "buffer" }, { "size_t", "buffer_size" } } },
{ 503, "nexus_open", 2, { { "struct nxctl_init *", "init" }, { "uint32_t", "init_len" } } },
{ 504, "nexus_register", 5, { { "int", "ctl" }, { "struct nxprov_reg *", "reg" }, { "uint32_t", "reg_len" }, { "uuid_t *", "prov_uuid" }, { "uint32_t", "prov_uuid_len" } } },
{ 505, "nexus_deregister", 3, { { "int", "ctl" }, { "uuid_t", "prov_uuid" }, { "uint32_t", "prov_uuid_len" } } },
{ 506, "nexus_create", 5, { { "int", "ctl" }, { "uuid_t", "prov_uuid" }, { "uint32_t", "prov_uuid_len" }, { "uuid_t *", "nx_uuid" }, { "uint32_t", "nx_uuid_len" } } },
{ 507, "nexus_destroy", 3, { { "int", "ctl" }, { "uuid_t", "nx_uuid" }, { "uint32_t", "nx_uuid_len" } } },
{ 508, "nexus_get_opt", 4, { { "int", "ctl" }, { "uint32_t", "opt" }, { "void *", "aoptval" }, { "uint32_t *", "aoptlen" } } },
{ 509, "nexus_set_opt", 4, { { "int", "ctl" }, { "uint32_t", "opt" }, { "const void *", "aoptval" }, { "uint32_t", "optlen" } } },
{ 510, "channel_open", 2, { { "struct ch_init *", "init" }, { "uint32_t", "init_len" } } },
{ 511, "channel_get_info", 3, { { "int", "c" }, { "struct ch_info *", "cinfo" }, { "uint32_t", "cinfolen" } } },
{ 512, "channel_sync", 3, { { "int", "c" }, { "int", "mode" }, { "int", "flags" } } },
{ 513, "channel_get_opt", 4, { { "int", "c" }, { "uint32_t", "opt" }, { "void *", "aoptval" }, { "uint32_t *", "aoptlen" } } },
{ 514, "channel_set_opt", 4, { { "int", "c" }, { "uint32_t", "opt" }, { "const void *", "aoptval" }, { "uint32_t", "optlen" } } },
{ 515, "ulock_wait", 4, { { "uint32_t", "operation" }, { "void *", "addr" }, { "uint64_t", "value" }, { "uint32_t", "timeout" } } },
{ 516, "ulock_wake", 3, { { "uint32_t", "operation" }, { "void *", "addr" }, { "uint64_t", "wake_value" } } },
{ 517, "fclonefileat", 4, { { "int", "src_fd" }, { "int", "dst_dirfd" }, { "user_addr_t", "dst" }, { "uint32_t", "flags" } } },
{ 518, "fs_snapshot", 6, { { "uint32_t", "op" }, { "int", "dirfd" }, { "user_addr_t", "name1" }, { "user_addr_t", "name2" }, { "user_addr_t", "data" }, { "uint32_t", "flags" } } },
{ 520, "terminate_with_payload", 7, { { "int", "pid" }, { "uint32_t", "reason_namespace" }, { "uint64_t", "reason_code" }, { "void *", "payload" }, { "uint32_t", "payload_size" }, { "const char *", "reason_string" }, { "uint64_t", "reason_flags" } } },
{ 521, "abort_with_payload", 6, { { "uint32_t", "reason_namespace" }, { "uint64_t", "reason_code" }, { "void *", "payload" }, { "uint32_t", "payload_size" }, { "const char *", "reason_string" }, { "uint64_t", "reason_flags" } } },
{ 523, "necp_session_action", 6, { { "int", "necp_fd" }, { "uint32_t", "action" }, { "uint8_t *", "in_buffer" }, { "size_t", "in_buffer_length" }, { "uint8_t *", "out_buffer" }, { "size_t", "out_buffer_length" } } },
{ 524, "setattrlistat", 6, { { "int", "fd" }, { "const char *", "path" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "uint32_t", "options" } } },
{ 525, "net_qos_guideline", 2, { { "struct net_qos_param *", "param" }, { "uint32_t", "param_len" } } },
{ 526, "fmount", 4, { { "const char *", "type" }, { "int", "fd" }, { "int", "flags" }, { "void *", "data" } } },
{ 527, "ntp_adjtime", 1, { { "struct timex *", "tp" } } },
{ 528, "ntp_gettime", 1, { { "struct ntptimeval *", "ntvp" } } },
{ 529, "os_fault_with_payload", 6, { { "uint32_t", "reason_namespace" }, { "uint64_t", "reason_code" }, { "void *", "payload" }, { "uint32_t", "payload_size" }, { "const char *", "reason_string" }, { "uint64_t", "reason_flags" } } },
{ 530, "kqueue_workloop_ctl", 4, { { "user_addr_t", "cmd" }, { "uint64_t", "options" }, { "user_addr_t", "addr" }, { "size_t", "sz" } } },
{ 531, "mach_bridge_remote_time", 1, { { "uint64_t", "local_timestamp" } } },
{ 532, "coalition_ledger", 4, { { "uint32_t", "operation" }, { "uint64_t *", "cid" }, { "void *", "buffer" }, { "size_t *", "bufsize" } } },
{ 533, "log_data", 4, { { "unsigned int", "tag" }, { "unsigned int", "flags" }, { "void *", "buffer" }, { "unsigned int", "size" } } },
{ 534, "memorystatus_available_memory", 0, {} },
{ 535, "objc_bp_assist_cfg_np", 2, { { "uint64_t", "adr" }, { "uint64_t", "ctl" } } },
{ 536, "shared_region_map_and_slide_2_np", 4, { { "uint32_t", "files_count" }, { "const struct shared_file_np *", "files" }, { "uint32_t", "mappings_count" }, { "const shared_file_mapping_slide_np_ut *", "mappings_u" } } },
{ 537, "pivot_root", 2, { { "const char *", "new_rootfs_path_before" }, { "const char *", "old_rootfs_path_after" } } },
{ 538, "task_inspect_for_pid", 3, { { "mach_port_name_t", "target_tport" }, { "int", "pid" }, { "mach_port_name_t *", "t" } } },
{ 539, "task_read_for_pid", 3, { { "mach_port_name_t", "target_tport" }, { "int", "pid" }, { "mach_port_name_t *", "t" } } },
{ 540, "preadv", 4, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "int", "iovcnt" }, { "off_t", "offset" } } },
{ 541, "pwritev", 4, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "int", "iovcnt" }, { "off_t", "offset" } } },
{ 542, "preadv_nocancel", 4, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "int", "iovcnt" }, { "off_t", "offset" } } },
{ 543, "pwritev_nocancel", 4, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "int", "iovcnt" }, { "off_t", "offset" } } },
{ 544, "ulock_wait2", 5, { { "uint32_t", "operation" }, { "void *", "addr" }, { "uint64_t", "value" }, { "uint64_t", "timeout" }, { "uint64_t", "value2" } } },
{ 545, "proc_info_extended_id", 8, { { "int32_t", "callnum" }, { "int32_t", "pid" }, { "uint32_t", "flavor" }, { "uint32_t", "flags" }, { "uint64_t", "ext_id" }, { "uint64_t", "arg" }, { "user_addr_t", "buffer" }, { "int32_t", "buffersize" } } },
{ 546, "tracker_action", 3, { { "int", "action" }, { "char *", "buffer" }, { "size_t", "buffer_size" } } },
{ 547, "debug_syscall_reject", 1, { { "uint64_t", "packed_selectors" } } },
{ 548, "debug_syscall_reject_config", 3, { { "uint64_t", "packed_selectors1" }, { "uint64_t", "packed_selectors2" }, { "uint64_t", "flags" } } },
{ 549, "graftdmg", 4, { { "int", "dmg_fd" }, { "const char *", "mountdir" }, { "uint32_t", "graft_type" }, { "graftdmg_args_un *", "gda" } } },
{ 550, "map_with_linking_np", 4, { { "void *", "regions" }, { "uint32_t", "region_count" }, { "void *", "link_info" }, { "uint32_t", "link_info_size" } } },
{ 551, "freadlink", 3, { { "int", "fd" }, { "user_addr_t", "buf" }, { "user_size_t", "bufsize" } } },
{ 552, "record_system_event", 4, { { "uint32_t", "type" }, { "uint32_t", "subsystem" }, { "const char *", "event" }, { "const char *", "payload" } } },
{ 553, "mkfifoat", 3, { { "int", "fd" }, { "char *", "path" }, { "int", "mode" } } },
{ 554, "mknodat", 4, { { "int", "fd" }, { "char *", "path" }, { "int", "mode" }, { "int", "dev" } } },
{ 555, "ungraftdmg", 2, { { "const char *", "mountdir" }, { "uint64_t", "flags" } } },
{ 556, "coalition_policy_set", 3, { { "uint64_t", "cid" }, { "uint32_t", "flavor" }, { "uint32_t", "value" } } },
{ 557, "coalition_policy_get", 2, { { "uint64_t", "cid" }, { "uint32_t", "flavor" } } },
};
FridaXnuSyscallSignature *
frida_get_xnu_mach_traps (int * len)
{
if (len != NULL)
*len = G_N_ELEMENTS (frida_xnu_mach_traps);
return (FridaXnuSyscallSignature *) frida_xnu_mach_traps;
}
FridaXnuSyscallSignature *
frida_get_xnu_bsd_syscalls (int * len)
{
if (len != NULL)
*len = G_N_ELEMENTS (frida_xnu_bsd_syscalls);
return (FridaXnuSyscallSignature *) frida_xnu_bsd_syscalls;
}
+545
View File
@@ -0,0 +1,545 @@
// AUTO-GENERATED FILE. DO NOT EDIT.
namespace Frida {
public extern unowned XnuSyscallSignature[] get_xnu_mach_traps ();
public extern unowned XnuSyscallSignature[] get_xnu_bsd_syscalls ();
public struct XnuSyscallSignature {
public int nr;
public string name;
public uint8 nargs;
public XnuSyscallArg args[9];
}
public struct XnuSyscallArg {
public string? type;
public string? name;
}
/**
* The XNU Mach trap numbers, as used on Apple platforms.
*/
public enum XnuMachTrap {
MACH_VM_ALLOCATE = -10,
MACH_VM_PURGABLE_CONTROL = -11,
MACH_VM_DEALLOCATE = -12,
TASK_DYLD_PROCESS_INFO_NOTIFY_GET = -13,
MACH_VM_PROTECT = -14,
MACH_VM_MAP = -15,
MACH_PORT_ALLOCATE = -16,
MACH_PORT_DEALLOCATE = -18,
MACH_PORT_MOD_REFS = -19,
MACH_PORT_MOVE_MEMBER = -20,
MACH_PORT_INSERT_RIGHT = -21,
MACH_PORT_INSERT_MEMBER = -22,
MACH_PORT_EXTRACT_MEMBER = -23,
MACH_PORT_CONSTRUCT = -24,
MACH_PORT_DESTRUCT = -25,
MACH_REPLY_PORT = -26,
THREAD_SELF = -27,
TASK_SELF = -28,
HOST_SELF = -29,
MACH_MSG = -31,
MACH_MSG_OVERWRITE = -32,
SEMAPHORE_SIGNAL = -33,
SEMAPHORE_SIGNAL_ALL = -34,
SEMAPHORE_SIGNAL_THREAD = -35,
SEMAPHORE_WAIT = -36,
SEMAPHORE_WAIT_SIGNAL = -37,
SEMAPHORE_TIMEDWAIT = -38,
SEMAPHORE_TIMEDWAIT_SIGNAL = -39,
MACH_PORT_GET_ATTRIBUTES = -40,
MACH_PORT_GUARD = -41,
MACH_PORT_UNGUARD = -42,
MACH_GENERATE_ACTIVITY_ID = -43,
TASK_NAME_FOR_PID = -44,
TASK_FOR_PID = -45,
PID_FOR_TASK = -46,
MACH_MSG2 = -47,
MACX_SWAPON = -48,
MACX_SWAPOFF = -49,
THREAD_GET_SPECIAL_REPLY_PORT = -50,
MACX_TRIGGERS = -51,
MACX_BACKING_STORE_SUSPEND = -52,
MACX_BACKING_STORE_RECOVERY = -53,
PFZ_EXIT = -58,
SWTCH_PRI = -59,
SWTCH = -60,
THREAD_SWITCH = -61,
CLOCK_SLEEP = -62,
MACH_VM_RECLAIM_UPDATE_KERNEL_ACCOUNTING = -63,
HOST_CREATE_MACH_VOUCHER = -70,
MACH_VOUCHER_EXTRACT_ATTR_RECIPE = -72,
MACH_PORT_TYPE = -76,
MACH_PORT_REQUEST_NOTIFICATION = -77,
EXCLAVES_CTL = -88,
MACH_TIMEBASE_INFO = -89,
MACH_WAIT_UNTIL = -90,
MK_TIMER_CREATE = -91,
MK_TIMER_DESTROY = -92,
MK_TIMER_ARM = -93,
MK_TIMER_CANCEL = -94,
MK_TIMER_ARM_LEEWAY = -95,
DEBUG_CONTROL_PORT_FOR_PID = -96,
IOKIT_USER_CLIENT = -100,
}
/**
* The XNU BSD syscall numbers, as used on Apple platforms.
*/
public enum XnuBsdSyscall {
EXIT = 1,
FORK = 2,
READ = 3,
WRITE = 4,
OPEN = 5,
CLOSE = 6,
WAIT4 = 7,
LINK = 9,
UNLINK = 10,
CHDIR = 12,
FCHDIR = 13,
MKNOD = 14,
CHMOD = 15,
CHOWN = 16,
GETFSSTAT = 18,
GETPID = 20,
SETUID = 23,
GETUID = 24,
GETEUID = 25,
PTRACE = 26,
RECVMSG = 27,
SENDMSG = 28,
RECVFROM = 29,
ACCEPT = 30,
GETPEERNAME = 31,
GETSOCKNAME = 32,
ACCESS = 33,
CHFLAGS = 34,
FCHFLAGS = 35,
SYNC = 36,
KILL = 37,
CROSSARCH_TRAP = 38,
GETPPID = 39,
DUP = 41,
PIPE = 42,
GETEGID = 43,
SIGACTION = 46,
GETGID = 47,
SIGPROCMASK = 48,
GETLOGIN = 49,
SETLOGIN = 50,
ACCT = 51,
SIGPENDING = 52,
SIGALTSTACK = 53,
IOCTL = 54,
REBOOT = 55,
REVOKE = 56,
SYMLINK = 57,
READLINK = 58,
EXECVE = 59,
UMASK = 60,
CHROOT = 61,
MSYNC = 65,
VFORK = 66,
OSLOG_COPROC_REG = 67,
OSLOG_COPROC = 68,
MUNMAP = 73,
MPROTECT = 74,
MADVISE = 75,
MINCORE = 78,
GETGROUPS = 79,
SETGROUPS = 80,
GETPGRP = 81,
SETPGID = 82,
SETITIMER = 83,
SWAPON = 85,
GETITIMER = 86,
GETDTABLESIZE = 89,
DUP2 = 90,
FCNTL = 92,
SELECT = 93,
FSYNC = 95,
SETPRIORITY = 96,
SOCKET = 97,
CONNECT = 98,
GETPRIORITY = 100,
BIND = 104,
SETSOCKOPT = 105,
LISTEN = 106,
SIGSUSPEND = 111,
GETTIMEOFDAY = 116,
GETRUSAGE = 117,
GETSOCKOPT = 118,
READV = 120,
WRITEV = 121,
SETTIMEOFDAY = 122,
FCHOWN = 123,
FCHMOD = 124,
SETREUID = 126,
SETREGID = 127,
RENAME = 128,
FLOCK = 131,
MKFIFO = 132,
SENDTO = 133,
SHUTDOWN = 134,
SOCKETPAIR = 135,
MKDIR = 136,
RMDIR = 137,
UTIMES = 138,
FUTIMES = 139,
ADJTIME = 140,
GETHOSTUUID = 142,
SETSID = 147,
GETPGID = 151,
SETPRIVEXEC = 152,
PREAD = 153,
PWRITE = 154,
NFSSVC = 155,
STATFS = 157,
FSTATFS = 158,
UNMOUNT = 159,
GETFH = 161,
FUNMOUNT = 164,
QUOTACTL = 165,
MOUNT = 167,
CSOPS = 169,
CSOPS_AUDITTOKEN = 170,
WAITID = 173,
KDEBUG_TYPEFILTER = 177,
KDEBUG_TRACE_STRING = 178,
KDEBUG_TRACE64 = 179,
KDEBUG_TRACE = 180,
SETGID = 181,
SETEGID = 182,
SETEUID = 183,
SIGRETURN = 184,
PANIC_WITH_DATA = 185,
THREAD_SELFCOUNTS = 186,
FDATASYNC = 187,
STAT = 188,
FSTAT = 189,
LSTAT = 190,
PATHCONF = 191,
FPATHCONF = 192,
GETRLIMIT = 194,
SETRLIMIT = 195,
GETDIRENTRIES = 196,
MMAP = 197,
LSEEK = 199,
TRUNCATE = 200,
FTRUNCATE = 201,
SYSCTL = 202,
MLOCK = 203,
MUNLOCK = 204,
UNDELETE = 205,
OPEN_DPROTECTED_NP = 216,
FSGETPATH_EXT = 217,
OPENAT_DPROTECTED_NP = 218,
GETATTRLIST = 220,
SETATTRLIST = 221,
GETDIRENTRIESATTR = 222,
EXCHANGEDATA = 223,
SEARCHFS = 225,
DELETE = 226,
COPYFILE = 227,
FGETATTRLIST = 228,
FSETATTRLIST = 229,
POLL = 230,
GETXATTR = 234,
FGETXATTR = 235,
SETXATTR = 236,
FSETXATTR = 237,
REMOVEXATTR = 238,
FREMOVEXATTR = 239,
LISTXATTR = 240,
FLISTXATTR = 241,
FSCTL = 242,
INITGROUPS = 243,
POSIX_SPAWN = 244,
FFSCTL = 245,
FHOPEN = 248,
MINHERIT = 250,
SEMSYS = 251,
MSGSYS = 252,
SHMSYS = 253,
SEMCTL = 254,
SEMGET = 255,
SEMOP = 256,
MSGCTL = 258,
MSGGET = 259,
MSGSND = 260,
MSGRCV = 261,
SHMAT = 262,
SHMCTL = 263,
SHMDT = 264,
SHMGET = 265,
SHM_OPEN = 266,
SHM_UNLINK = 267,
SEM_OPEN = 268,
SEM_CLOSE = 269,
SEM_UNLINK = 270,
SEM_WAIT = 271,
SEM_TRYWAIT = 272,
SEM_POST = 273,
SYSCTLBYNAME = 274,
OPEN_EXTENDED = 277,
UMASK_EXTENDED = 278,
STAT_EXTENDED = 279,
LSTAT_EXTENDED = 280,
FSTAT_EXTENDED = 281,
CHMOD_EXTENDED = 282,
FCHMOD_EXTENDED = 283,
ACCESS_EXTENDED = 284,
SETTID = 285,
GETTID = 286,
SETSGROUPS = 287,
GETSGROUPS = 288,
SETWGROUPS = 289,
GETWGROUPS = 290,
MKFIFO_EXTENDED = 291,
MKDIR_EXTENDED = 292,
IDENTITYSVC = 293,
SHARED_REGION_CHECK_NP = 294,
VM_PRESSURE_MONITOR = 296,
PSYNCH_RW_LONGRDLOCK = 297,
PSYNCH_RW_YIELDWRLOCK = 298,
PSYNCH_RW_DOWNGRADE = 299,
PSYNCH_RW_UPGRADE = 300,
PSYNCH_MUTEXWAIT = 301,
PSYNCH_MUTEXDROP = 302,
PSYNCH_CVBROAD = 303,
PSYNCH_CVSIGNAL = 304,
PSYNCH_CVWAIT = 305,
PSYNCH_RW_RDLOCK = 306,
PSYNCH_RW_WRLOCK = 307,
PSYNCH_RW_UNLOCK = 308,
PSYNCH_RW_UNLOCK2 = 309,
GETSID = 310,
SETTID_WITH_PID = 311,
PSYNCH_CVCLRPREPOST = 312,
AIO_FSYNC = 313,
AIO_RETURN = 314,
AIO_SUSPEND = 315,
AIO_CANCEL = 316,
AIO_ERROR = 317,
AIO_READ = 318,
AIO_WRITE = 319,
LIO_LISTIO = 320,
IOPOLICYSYS = 322,
PROCESS_POLICY = 323,
MLOCKALL = 324,
MUNLOCKALL = 325,
ISSETUGID = 327,
PTHREAD_KILL = 328,
PTHREAD_SIGMASK = 329,
SIGWAIT = 330,
DISABLE_THREADSIGNAL = 331,
PTHREAD_MARKCANCEL = 332,
PTHREAD_CANCELED = 333,
SEMWAIT_SIGNAL = 334,
PROC_INFO = 336,
SENDFILE = 337,
STAT64 = 338,
FSTAT64 = 339,
LSTAT64 = 340,
STAT64_EXTENDED = 341,
LSTAT64_EXTENDED = 342,
FSTAT64_EXTENDED = 343,
GETDIRENTRIES64 = 344,
STATFS64 = 345,
FSTATFS64 = 346,
GETFSSTAT64 = 347,
PTHREAD_CHDIR = 348,
PTHREAD_FCHDIR = 349,
AUDIT = 350,
AUDITON = 351,
GETAUID = 353,
SETAUID = 354,
GETAUDIT_ADDR = 357,
SETAUDIT_ADDR = 358,
AUDITCTL = 359,
BSDTHREAD_CREATE = 360,
BSDTHREAD_TERMINATE = 361,
KQUEUE = 362,
KEVENT = 363,
LCHOWN = 364,
BSDTHREAD_REGISTER = 366,
WORKQ_OPEN = 367,
WORKQ_KERNRETURN = 368,
KEVENT64 = 369,
THREAD_SELFID = 372,
LEDGER = 373,
KEVENT_QOS = 374,
KEVENT_ID = 375,
MAC_EXECVE = 380,
MAC_SYSCALL = 381,
MAC_GET_FILE = 382,
MAC_SET_FILE = 383,
MAC_GET_LINK = 384,
MAC_SET_LINK = 385,
MAC_GET_PROC = 386,
MAC_SET_PROC = 387,
MAC_GET_FD = 388,
MAC_SET_FD = 389,
MAC_GET_PID = 390,
PSELECT = 394,
PSELECT_NOCANCEL = 395,
READ_NOCANCEL = 396,
WRITE_NOCANCEL = 397,
OPEN_NOCANCEL = 398,
CLOSE_NOCANCEL = 399,
WAIT4_NOCANCEL = 400,
RECVMSG_NOCANCEL = 401,
SENDMSG_NOCANCEL = 402,
RECVFROM_NOCANCEL = 403,
ACCEPT_NOCANCEL = 404,
MSYNC_NOCANCEL = 405,
FCNTL_NOCANCEL = 406,
SELECT_NOCANCEL = 407,
FSYNC_NOCANCEL = 408,
CONNECT_NOCANCEL = 409,
SIGSUSPEND_NOCANCEL = 410,
READV_NOCANCEL = 411,
WRITEV_NOCANCEL = 412,
SENDTO_NOCANCEL = 413,
PREAD_NOCANCEL = 414,
PWRITE_NOCANCEL = 415,
WAITID_NOCANCEL = 416,
POLL_NOCANCEL = 417,
MSGSND_NOCANCEL = 418,
MSGRCV_NOCANCEL = 419,
SEM_WAIT_NOCANCEL = 420,
AIO_SUSPEND_NOCANCEL = 421,
SIGWAIT_NOCANCEL = 422,
SEMWAIT_SIGNAL_NOCANCEL = 423,
MAC_MOUNT = 424,
MAC_GET_MOUNT = 425,
MAC_GETFSSTAT = 426,
FSGETPATH = 427,
AUDIT_SESSION_SELF = 428,
AUDIT_SESSION_JOIN = 429,
FILEPORT_MAKEPORT = 430,
FILEPORT_MAKEFD = 431,
AUDIT_SESSION_PORT = 432,
PID_SUSPEND = 433,
PID_RESUME = 434,
PID_HIBERNATE = 435,
PID_SHUTDOWN_SOCKETS = 436,
KAS_INFO = 439,
MEMORYSTATUS_CONTROL = 440,
GUARDED_OPEN_NP = 441,
GUARDED_CLOSE_NP = 442,
GUARDED_KQUEUE_NP = 443,
CHANGE_FDGUARD_NP = 444,
USRCTL = 445,
PROC_RLIMIT_CONTROL = 446,
CONNECTX = 447,
DISCONNECTX = 448,
PEELOFF = 449,
SOCKET_DELEGATE = 450,
TELEMETRY = 451,
PROC_UUID_POLICY = 452,
MEMORYSTATUS_GET_LEVEL = 453,
SYSTEM_OVERRIDE = 454,
VFS_PURGE = 455,
SFI_CTL = 456,
SFI_PIDCTL = 457,
COALITION = 458,
COALITION_INFO = 459,
NECP_MATCH_POLICY = 460,
GETATTRLISTBULK = 461,
CLONEFILEAT = 462,
OPENAT = 463,
OPENAT_NOCANCEL = 464,
RENAMEAT = 465,
FACCESSAT = 466,
FCHMODAT = 467,
FCHOWNAT = 468,
FSTATAT = 469,
FSTATAT64 = 470,
LINKAT = 471,
UNLINKAT = 472,
READLINKAT = 473,
SYMLINKAT = 474,
MKDIRAT = 475,
GETATTRLISTAT = 476,
PROC_TRACE_LOG = 477,
BSDTHREAD_CTL = 478,
OPENBYID_NP = 479,
RECVMSG_X = 480,
SENDMSG_X = 481,
THREAD_SELFUSAGE = 482,
CSRCTL = 483,
GUARDED_OPEN_DPROTECTED_NP = 484,
GUARDED_WRITE_NP = 485,
GUARDED_PWRITE_NP = 486,
GUARDED_WRITEV_NP = 487,
RENAMEATX_NP = 488,
MREMAP_ENCRYPTED = 489,
NETAGENT_TRIGGER = 490,
STACK_SNAPSHOT_WITH_CONFIG = 491,
MICROSTACKSHOT = 492,
GRAB_PGO_DATA = 493,
PERSONA = 494,
MACH_EVENTLINK_SIGNAL = 496,
MACH_EVENTLINK_WAIT_UNTIL = 497,
MACH_EVENTLINK_SIGNAL_WAIT_UNTIL = 498,
WORK_INTERVAL_CTL = 499,
GETENTROPY = 500,
NECP_CLIENT_ACTION = 502,
NEXUS_OPEN = 503,
NEXUS_REGISTER = 504,
NEXUS_DEREGISTER = 505,
NEXUS_CREATE = 506,
NEXUS_DESTROY = 507,
NEXUS_GET_OPT = 508,
NEXUS_SET_OPT = 509,
CHANNEL_OPEN = 510,
CHANNEL_GET_INFO = 511,
CHANNEL_SYNC = 512,
CHANNEL_GET_OPT = 513,
CHANNEL_SET_OPT = 514,
ULOCK_WAIT = 515,
ULOCK_WAKE = 516,
FCLONEFILEAT = 517,
FS_SNAPSHOT = 518,
TERMINATE_WITH_PAYLOAD = 520,
ABORT_WITH_PAYLOAD = 521,
NECP_SESSION_ACTION = 523,
SETATTRLISTAT = 524,
NET_QOS_GUIDELINE = 525,
FMOUNT = 526,
NTP_ADJTIME = 527,
NTP_GETTIME = 528,
OS_FAULT_WITH_PAYLOAD = 529,
KQUEUE_WORKLOOP_CTL = 530,
MACH_BRIDGE_REMOTE_TIME = 531,
COALITION_LEDGER = 532,
LOG_DATA = 533,
MEMORYSTATUS_AVAILABLE_MEMORY = 534,
OBJC_BP_ASSIST_CFG_NP = 535,
SHARED_REGION_MAP_AND_SLIDE_2_NP = 536,
PIVOT_ROOT = 537,
TASK_INSPECT_FOR_PID = 538,
TASK_READ_FOR_PID = 539,
PREADV = 540,
PWRITEV = 541,
PREADV_NOCANCEL = 542,
PWRITEV_NOCANCEL = 543,
ULOCK_WAIT2 = 544,
PROC_INFO_EXTENDED_ID = 545,
TRACKER_ACTION = 546,
DEBUG_SYSCALL_REJECT = 547,
DEBUG_SYSCALL_REJECT_CONFIG = 548,
GRAFTDMG = 549,
MAP_WITH_LINKING_NP = 550,
FREADLINK = 551,
RECORD_SYSTEM_EVENT = 552,
MKFIFOAT = 553,
MKNODAT = 554,
UNGRAFTDMG = 555,
COALITION_POLICY_SET = 556,
COALITION_POLICY_GET = 557,
}
}
-291
View File
@@ -1,291 +0,0 @@
namespace Frida {
public sealed class XpcClient : Object {
public signal void message (Darwin.Xpc.Object obj);
public State state {
get {
return _state;
}
}
public Darwin.Xpc.Connection connection {
get;
construct;
}
public Darwin.GCD.DispatchQueue queue {
get;
construct;
}
private State _state;
private string? close_reason;
private MainContext main_context;
public enum State {
OPEN,
CLOSING,
CLOSED,
}
public static XpcClient make_for_mach_service (string name, Darwin.GCD.DispatchQueue queue) {
return new XpcClient (Darwin.Xpc.Connection.create_mach_service (name, queue, 0), queue);
}
public XpcClient (Darwin.Xpc.Connection connection, Darwin.GCD.DispatchQueue queue) {
Object (connection: connection, queue: queue);
}
construct {
main_context = MainContext.ref_thread_default ();
connection.set_event_handler (on_event);
connection.activate ();
}
public override void dispose () {
if (close_reason != null) {
change_state (CLOSED);
} else {
change_state (CLOSING);
this.ref ();
connection.cancel ();
}
base.dispose ();
}
public async Darwin.Xpc.Object request (Darwin.Xpc.Object message, Cancellable? cancellable) throws Error, IOError {
Darwin.Xpc.Object? reply = null;
connection.send_message_with_reply (message, queue, r => {
schedule_on_frida_thread (() => {
reply = r;
request.callback ();
return Source.REMOVE;
});
});
var cancel_source = new CancellableSource (cancellable);
cancel_source.set_callback (() => {
connection.cancel ();
return Source.REMOVE;
});
cancel_source.attach (main_context);
yield;
cancel_source.destroy ();
if (reply.type == Darwin.Xpc.Error.TYPE) {
var e = (Darwin.Xpc.Error) reply;
throw new Error.NOT_SUPPORTED ("%s", e.get_string (Darwin.Xpc.Error.KEY_DESCRIPTION));
}
return reply;
}
public void post (Darwin.Xpc.Object message) throws Error, IOError {
connection.send_message (message);
}
private void change_state (State new_state) {
_state = new_state;
notify_property ("state");
}
private void on_event (Darwin.Xpc.Object obj) {
schedule_on_frida_thread (() => {
if (obj.type == Darwin.Xpc.Error.TYPE) {
var e = (Darwin.Xpc.Error) obj;
close_reason = e.get_string (Darwin.Xpc.Error.KEY_DESCRIPTION);
switch (state) {
case OPEN:
change_state (CLOSED);
break;
case CLOSING:
change_state (CLOSED);
unref ();
break;
case CLOSED:
assert_not_reached ();
}
} else {
message (obj);
}
return Source.REMOVE;
});
}
private void schedule_on_frida_thread (owned SourceFunc function) {
var source = new IdleSource ();
source.set_callback ((owned) function);
source.attach (main_context);
}
}
public sealed class XpcObjectReader {
public Darwin.Xpc.Object root_object {
get {
return scopes.peek_head ().object;
}
}
public Darwin.Xpc.Object current_object {
get {
return scopes.peek_tail ().object;
}
}
public delegate int TranslateErrorFunc (string domain, int code, string description);
private Gee.Deque<Scope> scopes = new Gee.ArrayQueue<Scope> ();
public XpcObjectReader (Darwin.Xpc.Object obj) {
push_scope (obj);
}
public bool has_member (string name) throws Error {
return peek_scope ().get_dictionary ().get_value (name) != null;
}
public bool try_read_member (string name) throws Error {
var scope = peek_scope ();
var dict = scope.get_dictionary ();
var val = dict.get_value (name);
if (val == null)
return false;
push_scope (val);
return true;
}
public unowned XpcObjectReader read_member (string name) throws Error {
var scope = peek_scope ();
var dict = scope.get_dictionary ();
var val = dict.get_value (name);
if (val == null)
throw new Error.PROTOCOL ("Key '%s' not found in dictionary: %s", name, scope.object.to_string ());
push_scope (val);
return this;
}
public unowned XpcObjectReader end_member () {
pop_scope ();
return this;
}
public size_t count_elements () throws Error {
return peek_scope ().get_array ().count;
}
public unowned XpcObjectReader read_element (size_t index) throws Error {
push_scope (peek_scope ().get_array ().get_value (index));
return this;
}
public unowned XpcObjectReader end_element () throws Error {
pop_scope ();
return this;
}
public bool get_bool_value () throws Error {
return peek_scope ().get_object<Darwin.Xpc.Bool> (Darwin.Xpc.Bool.TYPE).get_value ();
}
public int64 get_int64_value () throws Error {
return peek_scope ().get_object<Darwin.Xpc.Int64> (Darwin.Xpc.Int64.TYPE).get_value ();
}
public uint64 get_uint64_value () throws Error {
return peek_scope ().get_object<Darwin.Xpc.UInt64> (Darwin.Xpc.UInt64.TYPE).get_value ();
}
public unowned uint8[] get_data_value () throws Error {
var data = peek_scope ().get_object<Darwin.Xpc.Data> (Darwin.Xpc.Data.TYPE);
unowned uint8[] buf = (uint8[]) data.get_bytes_ptr ();
buf.length = (int) data.get_length ();
return buf;
}
public unowned string get_string_value () throws Error {
return peek_scope ().get_object<Darwin.Xpc.String> (Darwin.Xpc.String.TYPE).get_string_ptr ();
}
public unowned uint8[] get_uuid_value () throws Error {
return peek_scope ().get_object<Darwin.Xpc.Uuid> (Darwin.Xpc.Uuid.TYPE).get_bytes ()[:16];
}
public unowned string get_error_description () throws Error {
var error = peek_scope ().get_object<Darwin.Xpc.Error> (Darwin.Xpc.Error.TYPE);
return error.get_string (Darwin.Xpc.Error.KEY_DESCRIPTION);
}
public void check_nserror (TranslateErrorFunc translate_error) throws Error {
if (!has_member ("error"))
return;
read_member ("error");
unowned string domain = read_member ("domain").get_string_value ();
end_member ();
var code = (int) read_member ("code").get_int64_value ();
end_member ();
string description = read_member ("userInfo").read_member ("NSLocalizedDescription").get_string_value ();
if (description.has_suffix ("."))
description = description[:description.length - 1];
throw (Error) new GLib.Error.literal (
Quark.from_string ("frida-error-quark"),
translate_error (domain, code, description),
description);
}
public unowned Darwin.Xpc.Object get_object_value (Darwin.Xpc.Type expected_type) throws Error {
return peek_scope ().get_object<Darwin.Xpc.Object> (expected_type);
}
private void push_scope (Darwin.Xpc.Object obj) {
scopes.offer_tail (new Scope (obj));
}
private Scope peek_scope () {
return scopes.peek_tail ();
}
private Scope pop_scope () {
return scopes.poll_tail ();
}
private class Scope {
public Darwin.Xpc.Object object;
private unowned Darwin.Xpc.Type type;
public Scope (Darwin.Xpc.Object obj) {
object = obj;
type = obj.type;
}
public unowned T get_object<T> (Darwin.Xpc.Type expected_type) throws Error {
if (type != expected_type)
throw new Error.PROTOCOL ("Expected type '%s', got '%s'", expected_type.name, type.name);
return object;
}
public unowned Darwin.Xpc.Array get_array () throws Error {
return get_object<Darwin.Xpc.Array> (Darwin.Xpc.Array.TYPE);
}
public unowned Darwin.Xpc.Dictionary get_dictionary () throws Error {
return get_object<Darwin.Xpc.Dictionary> (Darwin.Xpc.Dictionary.TYPE);
}
}
}
}
+11
View File
@@ -0,0 +1,11 @@
FRIDA_1.0 {
local:
*;
};
GLIBC_2.2.5 {
local:
fopen*;
fseeko*;
ftello*;
} FRIDA_1.0;
+2 -4
View File
@@ -144,10 +144,8 @@ frida_gadget_environment_ensure_debugger_breakpoints_only (void)
interceptor = gum_interceptor_obtain ();
gum_interceptor_attach (interceptor, exceptions_set, frida_dont_steal_my_breakpoints,
NULL, GUM_ATTACH_FLAGS_NONE);
gum_interceptor_attach (interceptor, exceptions_swap, frida_dont_steal_my_breakpoints,
NULL, GUM_ATTACH_FLAGS_NONE);
gum_interceptor_attach (interceptor, exceptions_set, frida_dont_steal_my_breakpoints, NULL);
gum_interceptor_attach (interceptor, exceptions_swap, frida_dont_steal_my_breakpoints, NULL);
g_object_unref (interceptor);
}
+8 -2
View File
@@ -9,7 +9,9 @@
# include <signal.h>
#endif
#include <gumjs/gumscriptbackend.h>
#ifdef HAVE_GIOOPENSSL
#if defined (HAVE_GIOAPPLE)
# include <gioapple.h>
#elif defined (HAVE_GIOOPENSSL)
# include <gioopenssl.h>
#endif
@@ -91,7 +93,9 @@ frida_gadget_environment_init (void)
g_thread_set_garbage_handler (_frida_gadget_on_pending_thread_garbage, NULL);
#ifdef HAVE_GIOOPENSSL
#if defined (HAVE_GIOAPPLE)
g_io_module_apple_register ();
#elif defined (HAVE_GIOOPENSSL)
g_io_module_openssl_register ();
#endif
@@ -118,6 +122,8 @@ frida_gadget_environment_deinit (void)
g_assert (worker_loop != NULL);
frida_libc_shim_prepare_to_deinit ();
source = g_idle_source_new ();
g_source_set_priority (source, G_PRIORITY_LOW);
g_source_set_callback (source, stop_worker_loop, NULL, NULL);
+7
View File
@@ -1,3 +1,5 @@
using Efni;
namespace Frida.Gadget {
private sealed class Config : Object, Json.Serializable {
public Object interaction {
@@ -1810,6 +1812,11 @@ namespace Frida.Gadget {
throw new Error.NOT_SUPPORTED ("Not possible when embedded");
}
public async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
Cancellable? cancellable) throws Error, IOError {
throw new Error.NOT_SUPPORTED ("Not possible when embedded");
}
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
throw new Error.NOT_SUPPORTED ("Not possible when embedded");
}
+5 -1
View File
@@ -45,7 +45,7 @@ elif host_os_family == 'darwin'
extra_link_args += '-Wl,-exported_symbols_list,' + meson.current_source_dir() / symlist
extra_link_depends += symlist
else
symscript = 'frida-gadget.version'
symscript = (libc == 'glibc') ? 'frida-gadget-glibc.version' : 'frida-gadget.version'
extra_link_args += '-Wl,--version-script,' + meson.current_source_dir() / symscript
extra_link_depends += symscript
endif
@@ -55,6 +55,10 @@ if host_os_family in ['linux', 'qnx']
extra_link_args += ['-Wl,-Bsymbolic']
endif
if host_is_prospero
extra_link_args += [prospero_crt_link_args, '-Wl,--defsym=main=0']
endif
raw_gadget = shared_library('frida-gadget-raw', gadget_sources,
name_prefix: 'lib',
vala_args: [base_vala_args, payload_vala_args, extra_vala_args],
+4 -2
View File
@@ -4,10 +4,12 @@ endif
subdir('base')
subdir('netif')
subdir('pipe')
subdir('payload')
if have_in_tree_payloads
subdir('payload')
endif
if have_local_backend
subdir('agent')
endif
if build_gadget
if have_in_tree_payloads and build_gadget
subdir('gadget')
endif
-1
View File
@@ -8,7 +8,6 @@ extra_link_args = []
if host_os in ['ios', 'tvos']
extra_vala_args += [
'--pkg=corefoundation',
'--pkg=darwin-gcd',
'--pkg=darwin-systemconfiguration',
]
extra_link_args += '-Wl,-framework,SystemConfiguration'
+14
View File
@@ -1,3 +1,5 @@
using Efni;
namespace Frida {
public abstract class BaseAgentSession : Object, AgentSession {
public signal void closed ();
@@ -179,6 +181,18 @@ namespace Frida {
yield script_engine.load_script (script_id);
}
public async void interrupt_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
check_open ();
script_engine.interrupt_script (script_id);
}
public async void terminate_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
check_open ();
yield script_engine.terminate_script (script_id);
}
public async void eternalize_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
check_open ();
+7 -1
View File
@@ -35,15 +35,21 @@ namespace Frida {
interceptor.attach ((void *) Gum.Process.find_module_by_name ("kernel32.dll").find_export_by_name ("ExitProcess"),
listener);
#else
var libc = Gum.Process.get_libc_module ();
const string[] apis = {
"exit",
"_exit",
"abort",
};
#if PROSPERO
foreach (var symbol in apis) {
interceptor.attach ((void *) Gum.Module.find_global_export_by_name (symbol), listener);
}
#else
var libc = Gum.Process.get_libc_module ();
foreach (var symbol in apis) {
interceptor.attach ((void *) libc.find_export_by_name (symbol), listener);
}
#endif
#endif
}
+11 -4
View File
@@ -65,7 +65,9 @@ namespace Frida {
if (runtime != null) {
var set_argv0 = (void *) runtime.find_export_by_name ("_Z27android_os_Process_setArgV0P7_JNIEnvP8_jobjectP8_jstring");
if (set_argv0 != null) {
interceptor.attach (set_argv0, listener, (void *) HookId.SET_ARGV0);
var set_argv0_options = Gum.AttachOptions ();
set_argv0_options.listener_function_data = (void *) HookId.SET_ARGV0;
interceptor.attach (set_argv0, listener, set_argv0_options);
child_recovery_behavior = DEFERRED_UNTIL_SET_ARGV0;
}
}
@@ -73,8 +75,11 @@ namespace Frida {
var selinux = Gum.Process.find_module_by_name ("libselinux.so");
if (selinux != null) {
var setcontext = (void *) selinux.find_export_by_name ("selinux_android_setcontext");
if (setcontext != null)
interceptor.attach (setcontext, listener, (void *) HookId.SET_CTX);
if (setcontext != null) {
var setcontext_options = Gum.AttachOptions ();
setcontext_options.listener_function_data = (void *) HookId.SET_CTX;
interceptor.attach (setcontext, listener, setcontext_options);
}
}
}
} catch (FileError e) {
@@ -82,7 +87,9 @@ namespace Frida {
}
#endif
interceptor.attach (fork_impl, listener, (void *) HookId.FORK);
var fork_options = Gum.AttachOptions ();
fork_options.listener_function_data = (void *) HookId.FORK;
interceptor.attach (fork_impl, listener, fork_options);
interceptor.replace (vfork_impl, fork_impl);
}
+432 -51
View File
@@ -10,6 +10,7 @@
# include <assert.h>
# include <fcntl.h>
# include <unistd.h>
# include <wchar.h>
#endif
#if defined (HAVE_DARWIN)
# include <sys/attr.h>
@@ -23,23 +24,51 @@
# include <xlocale.h>
#endif
#undef __srget
#undef feof
#undef ferror
#undef fgetwc
#undef fileno
#undef fopen
#undef fopen64
#undef fputwc
#undef fseek
#undef fseeko
#undef fseeko64
#undef ftell
#undef ftello
#undef ftello64
#undef getc
#undef getc_unlocked
#undef getwc
#undef memcpy
#undef putc
#undef putchar
#undef putwc
#undef setbuf
#undef setbuffer
#undef setlinebuf
#undef snprintf
#undef sprintf
#undef stderr
#undef stdin
#undef stdout
#undef ungetc
#undef ungetwc
#undef vsnprintf
#ifdef HAVE_MUSL
# define FRIDA_STDIO_OPAQUE_FILE 1
#endif
static gboolean frida_deinit_expected = FALSE;
void
frida_libc_shim_prepare_to_deinit (void)
{
frida_deinit_expected = TRUE;
}
#if defined (HAVE_WINDOWS) || defined (HAVE_ASAN)
void
@@ -70,6 +99,9 @@ __attribute__ ((destructor)) static
void
frida_libc_shim_deinit (void)
{
if (!frida_deinit_expected)
return;
# ifdef HAVE_ASAN
gum_deinit ();
# else
@@ -134,16 +166,18 @@ struct _FridaFile
int err;
guint8 * rbuf;
gboolean owns_rbuf;
size_t rcap;
size_t rpos;
size_t rlen;
guint8 * wbuf;
gboolean owns_wbuf;
size_t wcap;
size_t wlen;
int ungot;
gboolean has_ungot;
guint8 ungot_buf[8];
guint ungot_len;
};
struct _FridaFileHandle
@@ -171,12 +205,17 @@ struct _FridaDir
#endif
static FILE * frida_fopen_impl (const char * pathname, const char * mode);
static int frida_fseek_impl (FILE * stream, off_t offset, int whence);
static off_t frida_ftell_impl (FILE * stream);
static void frida_stdio_register_stream (FILE * stream);
static void frida_stdio_unregister_stream (FILE * stream);
#ifdef HAVE_FRIDA_DIR
static void frida_stdio_register_dir (DIR * dirp);
static void frida_stdio_unregister_dir (DIR * dirp);
#endif
static void frida_stdio_ensure_registries (void);
static void frida_flush_all_streams (int * result);
static FridaFile * frida_file_get_impl (FILE * stream);
@@ -193,6 +232,8 @@ static void frida_parse_fopen_mode (const char * mode, int * oflags);
static int frida_write_formatted_to_fd (int fd, const char * format, va_list args);
static gboolean frida_utf8_expected_len (guint8 first, guint * out_len);
#ifdef HAVE_FRIDA_DIR
static FridaDir * frida_dir_get_impl (DIR * dirp);
static DIR * frida_dir_wrap (FridaDir * impl);
@@ -212,7 +253,13 @@ static ssize_t frida_write_nointr (int fd, const void * buf, size_t count);
static int frida_close_nointr (int fd);
static off_t frida_lseek_nointr (int fd, off_t offset, int whence);
#ifndef FRIDA_STDIO_OPAQUE_FILE
#if defined (HAVE_QNX)
G_GNUC_INTERNAL FILE _Stdin, _Stdout, _Stderr;
G_GNUC_INTERNAL FILE * stdin = &_Stdin;
G_GNUC_INTERNAL FILE * stdout = &_Stdout;
G_GNUC_INTERNAL FILE * stderr = &_Stderr;
#elif !defined (FRIDA_STDIO_OPAQUE_FILE)
G_GNUC_INTERNAL FILE __sF[3];
G_GNUC_INTERNAL FILE * stdin = &__sF[0];
@@ -262,33 +309,18 @@ frida_libc_shim_init (void)
G_LOCK (frida_stdio);
frida_streams = g_hash_table_new (g_direct_hash, g_direct_equal);
#ifdef HAVE_FRIDA_DIR
frida_dirs = g_hash_table_new (g_direct_hash, g_direct_equal);
#endif
frida_stdio_ensure_registries ();
G_UNLOCK (frida_stdio);
f0 = frida_file_new (0, FALSE, _IOFBF);
#ifndef FRIDA_STDIO_OPAQUE_FILE
frida_file_bind_slot (&__sF[0], f0);
#else
frida_file_bind_slot ((FILE *) &frida_stdio[0], f0);
#endif
frida_file_bind_slot (stdin, f0);
f1 = frida_file_new (1, FALSE, _IOLBF);
#ifndef FRIDA_STDIO_OPAQUE_FILE
frida_file_bind_slot (&__sF[1], f1);
#else
frida_file_bind_slot ((FILE *) &frida_stdio[1], f1);
#endif
frida_file_bind_slot (stdout, f1);
f2 = frida_file_new (2, FALSE, _IONBF);
#ifndef FRIDA_STDIO_OPAQUE_FILE
frida_file_bind_slot (&__sF[2], f2);
#else
frida_file_bind_slot ((FILE *) &frida_stdio[2], f2);
#endif
frida_file_bind_slot (stderr, f2);
frida_libc_shim_initialized = TRUE;
}
@@ -317,6 +349,9 @@ frida_libc_shim_deinit (void)
fflush (NULL);
if (!frida_deinit_expected)
return;
G_LOCK (frida_stdio);
g_hash_table_iter_init (&iter, frida_streams);
@@ -596,7 +631,7 @@ __vsnprintf_chk (char * string, size_t size, int flags, size_t len, const char *
return gum_vsnprintf (string, size, format, args);
}
#ifdef HAVE_XLOCALE_H
#if defined (HAVE_XLOCALE_H) && !defined (HAVE_PROSPERO)
G_GNUC_INTERNAL int
sprintf_l (char * string, locale_t loc, const char * format, ...)
@@ -639,8 +674,34 @@ asprintf_l (char ** ret, locale_t loc, const char * format, ...)
#endif
#ifdef HAVE_GLIBC
G_GNUC_INTERNAL FILE *
frida_fopen_glibc_225 (const char * pathname, const char * mode)
{
return frida_fopen_impl (pathname, mode);
}
__asm__ (".symver frida_fopen_glibc_225,fopen@@GLIBC_2.2.5");
G_GNUC_INTERNAL FILE *
frida_fopen64_glibc_225 (const char * pathname, const char * mode)
{
return frida_fopen_impl (pathname, mode);
}
__asm__ (".symver frida_fopen64_glibc_225,fopen64@@GLIBC_2.2.5");
#else
G_GNUC_INTERNAL FILE *
fopen (const char * pathname, const char * mode)
{
return frida_fopen_impl (pathname, mode);
}
#endif
static FILE *
frida_fopen_impl (const char * pathname, const char * mode)
{
FILE * result;
int oflags, fd;
@@ -714,8 +775,17 @@ setvbuf (FILE * stream, char * buf, int mode, size_t size)
if (mode == _IONBF)
{
g_clear_pointer (&f->rbuf, g_free);
g_clear_pointer (&f->wbuf, g_free);
if (f->owns_rbuf)
g_clear_pointer (&f->rbuf, g_free);
else
f->rbuf = NULL;
f->owns_rbuf = FALSE;
if (f->owns_wbuf)
g_clear_pointer (&f->wbuf, g_free);
else
f->wbuf = NULL;
f->owns_wbuf = FALSE;
f->rcap = 0;
f->wcap = 0;
@@ -727,13 +797,40 @@ setvbuf (FILE * stream, char * buf, int mode, size_t size)
}
if (size == 0)
size = FRIDA_STDIO_BUFSIZE;
size = BUFSIZ;
if (!f->owns_rbuf)
{
f->rbuf = NULL;
f->rcap = 0;
}
f->rbuf = g_realloc (f->rbuf, size);
f->wbuf = g_realloc (f->wbuf, size);
f->owns_rbuf = TRUE;
f->rcap = size;
f->wcap = size;
if (buf != NULL)
{
if (f->owns_wbuf)
g_clear_pointer (&f->wbuf, g_free);
f->wbuf = (guint8 *) buf;
f->owns_wbuf = FALSE;
f->wcap = size;
}
else
{
if (!f->owns_wbuf)
{
f->wbuf = NULL;
f->wcap = 0;
}
f->wbuf = g_realloc (f->wbuf, size);
f->owns_wbuf = TRUE;
f->wcap = size;
}
f->rpos = 0;
f->rlen = 0;
f->wlen = 0;
@@ -741,6 +838,39 @@ setvbuf (FILE * stream, char * buf, int mode, size_t size)
return 0;
}
G_GNUC_INTERNAL void
setbuf (FILE * stream, char * buf)
{
if (buf == NULL)
(void) setvbuf (stream, NULL, _IONBF, 0);
else
(void) setvbuf (stream, buf, _IOFBF, BUFSIZ);
}
#if defined (HAVE_DARWIN) || defined (HAVE_ANDROID) || defined (HAVE_FREEBSD)
G_GNUC_INTERNAL int
setlinebuf (FILE * stream)
{
return setvbuf (stream, NULL, _IOLBF, 0);
}
#else
G_GNUC_INTERNAL void
setlinebuf (FILE * stream)
{
setvbuf (stream, NULL, _IOLBF, 0);
}
#endif
G_GNUC_INTERNAL void
#if defined (HAVE_DARWIN) || defined (HAVE_ANDROID) || defined (HAVE_FREEBSD)
setbuffer (FILE * stream, char * buf, int size)
#else
setbuffer (FILE * stream, char * buf, size_t size)
#endif
{
(void) setvbuf (stream, buf, (buf != NULL) ? _IOFBF : _IONBF, size);
}
G_GNUC_INTERNAL int
fflush (FILE * stream)
{
@@ -801,11 +931,8 @@ getc_unlocked (FILE * stream)
f = frida_file_get_impl (stream);
if (f->has_ungot)
{
f->has_ungot = FALSE;
return f->ungot;
}
if (f->ungot_len != 0)
return f->ungot_buf[--f->ungot_len];
if (f->buf_mode == _IONBF)
{
@@ -846,6 +973,64 @@ getc (FILE * stream)
return getc_unlocked (stream);
}
G_GNUC_INTERNAL int
__srget (FILE * stream)
{
return getc_unlocked (stream);
}
G_GNUC_INTERNAL wint_t
getwc (FILE * stream)
{
return fgetwc (stream);
}
G_GNUC_INTERNAL wint_t
fgetwc (FILE * stream)
{
char bytes[4];
guint need, have;
int c;
gunichar ch;
c = getc_unlocked (stream);
if (c == EOF)
return WEOF;
bytes[0] = c;
if (!frida_utf8_expected_len (bytes[0], &need))
{
errno = EILSEQ;
frida_file_get_impl (stream)->err = errno;
return WEOF;
}
have = 1;
while (have != need)
{
c = getc_unlocked (stream);
if (c == EOF)
{
errno = EILSEQ;
frida_file_get_impl (stream)->err = errno;
return WEOF;
}
bytes[have++] = c;
}
ch = g_utf8_get_char_validated (bytes, (gssize) need);
if (ch == (gunichar) -1 || ch == (gunichar) -2)
{
errno = EILSEQ;
frida_file_get_impl (stream)->err = errno;
return WEOF;
}
return (wint_t) ch;
}
G_GNUC_INTERNAL int
ungetc (int c, FILE * stream)
{
@@ -856,16 +1041,46 @@ ungetc (int c, FILE * stream)
if (c == EOF)
return EOF;
if (f->has_ungot)
if (f->ungot_len == G_N_ELEMENTS (f->ungot_buf))
return EOF;
f->ungot = c;
f->has_ungot = TRUE;
f->ungot_buf[f->ungot_len++] = c;
f->eof = FALSE;
return c;
}
G_GNUC_INTERNAL wint_t
ungetwc (wint_t wc, FILE * stream)
{
FridaFile * f;
char tmp[4];
int len;
int i;
if (wc == WEOF)
return WEOF;
f = frida_file_get_impl (stream);
len = g_unichar_to_utf8 ((gunichar) wc, tmp);
if (len <= 0 || len > 4)
{
errno = EILSEQ;
f->err = errno;
return WEOF;
}
if (f->ungot_len + len > G_N_ELEMENTS (f->ungot_buf))
return WEOF;
for (i = 0; i != len; i++)
f->ungot_buf[f->ungot_len++] = tmp[i];
f->eof = FALSE;
return wc;
}
G_GNUC_INTERNAL size_t
fread (void * ptr, size_t size, size_t nmemb, FILE * stream)
{
@@ -884,10 +1099,9 @@ fread (void * ptr, size_t size, size_t nmemb, FILE * stream)
while (got != want)
{
if (f->has_ungot)
if (f->ungot_len != 0)
{
out[got++] = (guint8) f->ungot;
f->has_ungot = FALSE;
out[got++] = f->ungot_buf[--f->ungot_len];
continue;
}
@@ -1006,6 +1220,12 @@ fwrite (const void * ptr, size_t size, size_t nmemb, FILE * stream)
return off / size;
}
G_GNUC_INTERNAL int
putc (int c, FILE * stream)
{
return fputc (c, stream);
}
G_GNUC_INTERNAL int
fputc (int c, FILE * stream)
{
@@ -1019,6 +1239,37 @@ fputc (int c, FILE * stream)
return ch;
}
G_GNUC_INTERNAL wint_t
putwc (wchar_t wc, FILE * stream)
{
return fputwc (wc, stream);
}
G_GNUC_INTERNAL wint_t
fputwc (wchar_t wc, FILE * stream)
{
char tmp[4];
int len;
size_t n;
if (wc == WEOF)
return WEOF;
len = g_unichar_to_utf8 ((gunichar) wc, tmp);
if (len <= 0 || len > 4)
{
errno = EILSEQ;
frida_file_get_impl (stream)->err = errno;
return WEOF;
}
n = fwrite (tmp, 1, len, stream);
if (n != len)
return WEOF;
return (wint_t) wc;
}
G_GNUC_INTERNAL int
fputs (const char * s, FILE * stream)
{
@@ -1062,6 +1313,84 @@ fgets (char * s, int size, FILE * stream)
G_GNUC_INTERNAL int
fseek (FILE * stream, long offset, int whence)
{
return frida_fseek_impl (stream, (off_t) offset, whence);
}
G_GNUC_INTERNAL long
ftell (FILE * stream)
{
off_t pos = frida_ftell_impl (stream);
if (pos == (off_t) -1)
return -1;
return (long) pos;
}
#ifdef HAVE_GLIBC
G_GNUC_INTERNAL int
frida_fseeko_glibc_225 (FILE * stream, off_t offset, int whence)
{
return frida_fseek_impl (stream, offset, whence);
}
__asm__ (".symver frida_fseeko_glibc_225,fseeko@@GLIBC_2.2.5");
G_GNUC_INTERNAL int
frida_fseeko64_glibc_225 (FILE * stream, off_t offset, int whence)
{
return frida_fseek_impl (stream, offset, whence);
}
__asm__ (".symver frida_fseeko64_glibc_225,fseeko64@@GLIBC_2.2.5");
G_GNUC_INTERNAL off_t
frida_ftello_glibc_225 (FILE * stream)
{
return frida_ftell_impl (stream);
}
__asm__ (".symver frida_ftello_glibc_225,ftello@@GLIBC_2.2.5");
G_GNUC_INTERNAL off_t
frida_ftello64_glibc_225 (FILE * stream)
{
return frida_ftell_impl (stream);
}
__asm__ (".symver frida_ftello64_glibc_225,ftello64@@GLIBC_2.2.5");
#else
G_GNUC_INTERNAL int
fseeko (FILE * stream, off_t offset, int whence)
{
return frida_fseek_impl (stream, offset, whence);
}
G_GNUC_INTERNAL off_t
ftello (FILE * stream)
{
return frida_ftell_impl (stream);
}
# ifndef HAVE_QNX
G_GNUC_INTERNAL int
fseeko64 (FILE * stream, off_t offset, int whence)
{
return frida_fseek_impl (stream, offset, whence);
}
G_GNUC_INTERNAL off_t
ftello64 (FILE * stream)
{
return frida_ftell_impl (stream);
}
# endif
#endif
static int
frida_fseek_impl (FILE * stream, off_t offset, int whence)
{
FridaFile * f;
off_t r;
@@ -1073,10 +1402,10 @@ fseek (FILE * stream, long offset, int whence)
f->rpos = 0;
f->rlen = 0;
f->has_ungot = FALSE;
f->ungot_len = 0;
f->eof = FALSE;
r = frida_lseek_nointr (f->fd, (off_t) offset, whence);
r = frida_lseek_nointr (f->fd, offset, whence);
if (r == (off_t) -1)
{
f->err = errno;
@@ -1086,8 +1415,8 @@ fseek (FILE * stream, long offset, int whence)
return 0;
}
G_GNUC_INTERNAL long
ftell (FILE * stream)
static off_t
frida_ftell_impl (FILE * stream)
{
FridaFile * f;
off_t pos;
@@ -1104,10 +1433,9 @@ ftell (FILE * stream)
if (f->buf_mode != _IONBF)
pos -= (off_t) (f->rlen - f->rpos);
if (f->has_ungot)
pos -= 1;
pos -= f->ungot_len;
return (long) pos;
return pos;
}
G_GNUC_INTERNAL void
@@ -1569,6 +1897,7 @@ frida_stdio_register_stream (FILE * stream)
{
G_LOCK (frida_stdio);
frida_stdio_ensure_registries ();
g_hash_table_add (frida_streams, stream);
G_UNLOCK (frida_stdio);
@@ -1591,6 +1920,7 @@ frida_stdio_register_dir (DIR * dirp)
{
G_LOCK (frida_stdio);
frida_stdio_ensure_registries ();
g_hash_table_add (frida_dirs, dirp);
G_UNLOCK (frida_stdio);
@@ -1608,6 +1938,18 @@ frida_stdio_unregister_dir (DIR * dirp)
#endif
static void
frida_stdio_ensure_registries (void)
{
if (frida_streams != NULL)
return;
frida_streams = g_hash_table_new (g_direct_hash, g_direct_equal);
#ifdef HAVE_FRIDA_DIR
frida_dirs = g_hash_table_new (g_direct_hash, g_direct_equal);
#endif
}
static void
frida_flush_all_streams (int * result)
{
@@ -1718,10 +2060,12 @@ frida_file_new (int fd, gboolean close_fd, int buf_mode)
if (buf_mode != _IONBF)
{
f->rcap = FRIDA_STDIO_BUFSIZE;
f->wcap = FRIDA_STDIO_BUFSIZE;
f->rbuf = g_malloc (f->rcap);
f->owns_rbuf = TRUE;
f->wcap = FRIDA_STDIO_BUFSIZE;
f->wbuf = g_malloc (f->wcap);
f->owns_wbuf = TRUE;
}
return f;
@@ -1733,8 +2077,15 @@ frida_file_free (FridaFile * f)
if (f == NULL)
return;
g_clear_pointer (&f->rbuf, g_free);
g_clear_pointer (&f->wbuf, g_free);
if (f->owns_rbuf)
g_clear_pointer (&f->rbuf, g_free);
else
f->rbuf = NULL;
if (f->owns_wbuf)
g_clear_pointer (&f->wbuf, g_free);
else
f->wbuf = NULL;
g_free (f);
}
@@ -1866,6 +2217,36 @@ beach:
}
}
static gboolean
frida_utf8_expected_len (guint8 first, guint * out_len)
{
if (first < 0x80)
{
*out_len = 1;
return TRUE;
}
if ((first & 0xe0) == 0xc0)
{
*out_len = 2;
return TRUE;
}
if ((first & 0xf0) == 0xe0)
{
*out_len = 3;
return TRUE;
}
if ((first & 0xf8) == 0xf0)
{
*out_len = 4;
return TRUE;
}
return FALSE;
}
#ifdef HAVE_FRIDA_DIR
static FridaDir *
+2
View File
@@ -1,3 +1,5 @@
using Efni;
namespace Frida {
public sealed class PortalClient : Object, AgentSessionProvider {
public signal void resume ();
+3
View File
@@ -1,4 +1,7 @@
using Efni;
namespace Frida {
public extern void libc_shim_prepare_to_deinit ();
#if DARWIN
public extern void libc_shim_deinit ();
#endif
+20
View File
@@ -1,3 +1,5 @@
using Efni;
namespace Frida {
public sealed class ScriptEngine : Object {
public signal void message_from_script (AgentScriptId script_id, string json, Bytes? data);
@@ -166,6 +168,14 @@ namespace Frida {
yield get_instance (script_id).load ();
}
public void interrupt_script (AgentScriptId script_id) throws Error {
get_instance (script_id).interrupt ();
}
public async void terminate_script (AgentScriptId script_id) throws Error {
yield get_instance (script_id).terminate ();
}
public Gum.Script eternalize_script (AgentScriptId script_id) throws Error {
var instance = get_instance (script_id);
@@ -354,6 +364,16 @@ namespace Frida {
load_request.resolve (true);
}
public void interrupt () {
script.interrupt ();
}
public async void terminate () {
script.interrupt ();
yield close ();
}
private Promise<bool> unload () {
var request = new Promise<bool> ();
+3 -1
View File
@@ -59,7 +59,9 @@ namespace Frida {
interceptor.attach ((void *) posix_spawn, this);
interceptor.replace (execve, (void *) replacement_execve, this);
var replace_options = Gum.ReplaceOptions ();
replace_options.replacement_data = this;
interceptor.replace (execve, (void *) replacement_execve, null, replace_options);
#else
Gum.Address execve = 0;
#if ANDROID
+5 -3
View File
@@ -31,9 +31,11 @@ namespace Frida {
thread_suspend = (ThreadSuspendFunc) kernel.find_export_by_name ("thread_suspend");
thread_resume = (ThreadResumeFunc) kernel.find_export_by_name ("thread_resume");
interceptor.replace ((void *) task_threads, (void *) replacement_task_threads, this);
interceptor.replace ((void *) thread_suspend, (void *) replacement_thread_suspend, this);
interceptor.replace ((void *) thread_resume, (void *) replacement_thread_resume, this);
var replace_options = Gum.ReplaceOptions ();
replace_options.replacement_data = this;
interceptor.replace ((void *) task_threads, (void *) replacement_task_threads, null, replace_options);
interceptor.replace ((void *) thread_suspend, (void *) replacement_thread_suspend, null, replace_options);
interceptor.replace ((void *) thread_resume, (void *) replacement_thread_resume, null, replace_options);
}
public override void dispose () {
+6 -409
View File
@@ -2,29 +2,14 @@
#ifdef HAVE_DARWIN
#include <capstone.h>
#include <gum/gumdarwin.h>
#include <gum/gummemory.h>
#include <ptrauth.h>
#define FRIDA_MH_MAGIC_64 0xfeedfacf
#define FRIDA_LIBUNWIND_PATH "/usr/lib/system/libunwind.dylib"
#define FRIDA_UNWIND_CURSOR_VTABLE_OFFSET_SET_INFO 0x68
#define FRIDA_UNWIND_CURSOR_VTABLE_OFFSET_GET_REG 0x18
#define FRIDA_FP_TO_SP(fp) (fp + 0x10)
#ifdef HAVE_ARM64
# define FRIDA_UNWIND_CURSOR_unwindInfoMissing 0x268
# define FRIDA_UNWAARCH64_X29 29
# define FRIDA_STRIP_MASK 0x0000007fffffffffULL
#else
# define FRIDA_UNWIND_CURSOR_unwindInfoMissing 0x100
# define FRIDA_UNWX86_64_RBP 6
#endif
typedef struct _FridaFillInfoContext FridaFillInfoContext;
typedef struct _FridaDyldUnwindSections FridaDyldUnwindSections;
typedef struct _FridaCreateArgs FridaCreateArgs;
typedef struct _FridaUnwindHookState FridaUnwindHookState;
struct _FridaFillInfoContext
{
@@ -47,37 +32,9 @@ struct _FridaCreateArgs
GumAddress range_end;
};
struct _FridaUnwindHookState
{
gpointer vtable;
gssize shift;
gpointer * set_info_slot;
gpointer set_info_original;
void (* set_info) (gpointer cursor, gint is_return_address);
gpointer (* get_reg) (gpointer cursor, gint reg);
GumInterceptor * interceptor;
};
#if __has_feature (ptrauth_calls)
# define FRIDA_RESIGN_PTR(x) GSIZE_TO_POINTER (gum_sign_code_address (gum_strip_code_address (GUM_ADDRESS (x))))
#else
# define FRIDA_RESIGN_PTR(x) (x)
#endif
static FridaDyldUnwindSections * frida_get_cached_sections (GumAddress range_start, GumAddress range_end);
static FridaDyldUnwindSections * frida_create_cached_sections (FridaCreateArgs * args);
static gboolean frida_fill_info (const GumDarwinSectionDetails * details, FridaFillInfoContext * ctx);
static void frida_unwind_cursor_set_info_replacement (gpointer cursor, gint is_return_address);
static gpointer frida_find_vtable (void);
static gboolean frida_compute_vtable_shift (gpointer vtable, gssize * shift);
#ifdef HAVE_ARM64
static gboolean frida_find_bss_range (const GumSectionDetails * details, GumMemoryRange * range);
#else
static gboolean frida_is_empty_function (GumAddress address);
static gboolean frida_has_first_match (GumAddress address, gsize size, gboolean * matches);
#endif
static FridaUnwindHookState * state = NULL;
static gboolean frida_fill_info (const GumMachoSectionDetails * details, FridaFillInfoContext * ctx);
void
_frida_unwind_sitter_fill_unwind_sections (GumAddress invader_start, GumAddress invader_end, void * info)
@@ -92,63 +49,6 @@ _frida_unwind_sitter_fill_unwind_sections (GumAddress invader_start, GumAddress
memcpy (unwind_sections, cached, sizeof (FridaDyldUnwindSections));
}
void
_frida_unwind_sitter_hook_libunwind (void)
{
#if GLIB_SIZEOF_VOID_P == 8
gpointer * set_info_slot;
gpointer get_reg_impl;
if (state != NULL)
return;
state = g_slice_new0 (FridaUnwindHookState);
if (state == NULL)
return;
state->vtable = frida_find_vtable ();
if (state->vtable == NULL)
goto unsupported_version;
if (!frida_compute_vtable_shift (state->vtable, &state->shift))
goto unsupported_version;
set_info_slot = (gpointer *) (GUM_ADDRESS (state->vtable) + FRIDA_UNWIND_CURSOR_VTABLE_OFFSET_SET_INFO + state->shift);
get_reg_impl = *(gpointer *) (GUM_ADDRESS (state->vtable) + FRIDA_UNWIND_CURSOR_VTABLE_OFFSET_GET_REG + state->shift);
state->set_info_slot = set_info_slot;
state->set_info_original = *set_info_slot;
state->set_info = FRIDA_RESIGN_PTR (state->set_info_original);
state->get_reg = FRIDA_RESIGN_PTR (get_reg_impl);
state->interceptor = gum_interceptor_obtain ();
if (gum_interceptor_replace (state->interceptor, state->set_info_original, frida_unwind_cursor_set_info_replacement, NULL, NULL)
!= GUM_REPLACE_OK)
goto unsupported_version;
return;
unsupported_version:
g_slice_free (FridaUnwindHookState, state);
state = NULL;
#endif
}
void
_frida_unwind_sitter_unhook_libunwind (void)
{
if (state == NULL)
return;
gum_interceptor_revert (state->interceptor, state->set_info_original);
g_object_unref (state->interceptor);
g_slice_free (FridaUnwindHookState, state);
state = NULL;
}
static FridaDyldUnwindSections *
frida_get_cached_sections (GumAddress range_start, GumAddress range_end)
{
@@ -170,7 +70,7 @@ frida_create_cached_sections (FridaCreateArgs * args)
gsize page_size;
gpointer header;
GumPageProtection prot;
GumDarwinModule * module;
GumMachoModule * module;
FridaFillInfoContext ctx;
page_size = gum_query_page_size ();
@@ -187,13 +87,14 @@ frida_create_cached_sections (FridaCreateArgs * args)
cached_sections = g_slice_new0 (FridaDyldUnwindSections);
cached_sections->mh = header;
module = gum_darwin_module_new_from_memory ("Frida", mach_task_self (), GPOINTER_TO_SIZE (header), GUM_DARWIN_MODULE_FLAGS_NONE, NULL);
module = gum_macho_module_new_from_memory ("Frida", mach_task_self (), GPOINTER_TO_SIZE (header),
GUM_MACHO_MODULE_FLAGS_NONE, NULL);
if (module == NULL)
return cached_sections;
ctx.info = cached_sections;
ctx.missing_info = 2;
gum_darwin_module_enumerate_sections (module, (GumFoundDarwinSectionFunc) frida_fill_info, &ctx);
gum_macho_module_enumerate_sections (module, (GumFoundMachoSectionFunc) frida_fill_info, &ctx);
g_object_unref (module);
@@ -201,7 +102,7 @@ frida_create_cached_sections (FridaCreateArgs * args)
}
static gboolean
frida_fill_info (const GumDarwinSectionDetails * details, FridaFillInfoContext * ctx)
frida_fill_info (const GumMachoSectionDetails * details, FridaFillInfoContext * ctx)
{
if (strcmp ("__TEXT", details->segment_name) != 0)
return TRUE;
@@ -222,308 +123,4 @@ frida_fill_info (const GumDarwinSectionDetails * details, FridaFillInfoContext *
return ctx->missing_info > 0;
}
static void
frida_unwind_cursor_set_info_replacement (gpointer self, gint is_return_address)
{
gboolean missing_info;
GumAddress fp, stored_pc;
gpointer * stored_pc_slot;
#if defined (HAVE_ARM64) && !__has_feature (ptrauth_calls)
gboolean was_signed = FALSE;
#endif
if (state == NULL)
return;
state->set_info (self, is_return_address);
#ifdef HAVE_ARM64
fp = GUM_ADDRESS (state->get_reg (self, FRIDA_UNWAARCH64_X29));
#else
fp = GUM_ADDRESS (state->get_reg (self, FRIDA_UNWX86_64_RBP));
#endif
if (fp == 0 || fp == -1)
return;
missing_info = *((guint8 *) self + FRIDA_UNWIND_CURSOR_unwindInfoMissing);
stored_pc_slot = GSIZE_TO_POINTER (fp + GLIB_SIZEOF_VOID_P);
stored_pc = GUM_ADDRESS (*stored_pc_slot);
#if __has_feature (ptrauth_calls)
stored_pc = gum_strip_code_address (stored_pc);
#elif defined (HAVE_ARM64)
was_signed = (stored_pc & ~FRIDA_STRIP_MASK) != 0ULL;
if (was_signed)
stored_pc &= FRIDA_STRIP_MASK;
#endif
if (!missing_info)
{
GumAddress translated;
translated = GUM_ADDRESS (gum_invocation_stack_translate (gum_interceptor_get_current_stack (), GSIZE_TO_POINTER (stored_pc)));
if (translated != stored_pc)
{
#if __has_feature (ptrauth_calls)
*stored_pc_slot = ptrauth_sign_unauthenticated (
ptrauth_strip (GSIZE_TO_POINTER (translated), ptrauth_key_asia), ptrauth_key_asib, FRIDA_FP_TO_SP (fp));
#elif defined (HAVE_ARM64)
if (was_signed)
{
GumAddress resigned;
asm volatile (
"mov x17, %1\n\t"
"mov x16, %2\n\t"
".byte 0x5f, 0x21, 0x03, 0xd5\n\t" /* pacib1716 */
"mov %0, x17\n\t"
: "=r" (resigned)
: "r" (translated & FRIDA_STRIP_MASK),
"r" (FRIDA_FP_TO_SP (fp))
: "x16", "x17"
);
*stored_pc_slot = GSIZE_TO_POINTER (resigned);
}
else
{
*stored_pc_slot = GSIZE_TO_POINTER (translated);
}
#else
*stored_pc_slot = GSIZE_TO_POINTER (translated);
#endif
}
}
}
static gpointer
frida_find_vtable (void)
{
GumAddress result = 0;
GumModule * libunwind;
GumAddress export;
uint64_t address;
G_GNUC_UNUSED cs_err err;
csh capstone;
cs_insn * insn = NULL;
const uint8_t * code;
size_t size;
const size_t max_size = 2048;
libunwind = gum_process_find_module_by_name (FRIDA_LIBUNWIND_PATH);
if (libunwind == NULL)
goto beach;
export = gum_module_find_export_by_name (libunwind, "unw_init_local");
if (export == 0)
export = gum_module_find_export_by_name (libunwind, "_Unwind_RaiseException");
if (export == 0)
goto beach;
export = gum_strip_code_address (export);
address = export;
#ifdef HAVE_ARM64
cs_arch_register_arm64 ();
err = cs_open (CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN, &capstone);
#else
cs_arch_register_x86 ();
err = cs_open (CS_ARCH_X86, CS_MODE_64, &capstone);
#endif
g_assert (err == CS_ERR_OK);
err = cs_option (capstone, CS_OPT_DETAIL, CS_OPT_ON);
g_assert (err == CS_ERR_OK);
insn = cs_malloc (capstone);
code = GSIZE_TO_POINTER (export);
size = max_size;
#ifdef HAVE_ARM64
{
GumAddress last_adrp;
guint last_adrp_reg;
GumMemoryRange bss_range;
bss_range.base_address = 0;
gum_module_enumerate_sections (libunwind, (GumFoundSectionFunc) frida_find_bss_range, &bss_range);
while (cs_disasm_iter (capstone, &code, &size, &address, insn))
{
if (insn->id == ARM64_INS_RET || insn->id == ARM64_INS_RETAA || insn->id == ARM64_INS_RETAB)
break;
if (insn->id == ARM64_INS_ADRP)
{
if (result != 0)
break;
last_adrp = (GumAddress) insn->detail->arm64.operands[1].imm;
last_adrp_reg = insn->detail->arm64.operands[0].reg;
}
else if (insn->id == ARM64_INS_ADD && insn->detail->arm64.operands[0].reg == last_adrp_reg)
{
GumAddress candidate;
gboolean is_bss;
candidate = last_adrp + (GumAddress) insn->detail->arm64.operands[2].imm;
is_bss = bss_range.base_address != 0 &&
bss_range.base_address <= candidate &&
candidate < bss_range.base_address + bss_range.size;
if (!is_bss)
{
if (result == 0)
{
result = candidate;
last_adrp = candidate;
}
else
{
result = candidate;
break;
}
}
}
else if (result != 0)
{
break;
}
}
}
#else
while (cs_disasm_iter (capstone, &code, &size, &address, insn))
{
if (insn->id == X86_INS_RET)
break;
if (insn->id == X86_INS_LEA)
{
const cs_x86_op * op = &insn->detail->x86.operands[1];
if (op->type == X86_OP_MEM && op->mem.base == X86_REG_RIP)
{
result = address + op->mem.disp * op->mem.scale;
break;
}
}
}
#endif
if (insn != NULL)
cs_free (insn, 1);
cs_close (&capstone);
beach:
g_clear_object (&libunwind);
return GSIZE_TO_POINTER (result);
}
#ifdef HAVE_ARM64
static gboolean
frida_find_bss_range (const GumSectionDetails * details, GumMemoryRange * range)
{
if (strcmp (details->name, "__bss") == 0)
{
range->base_address = details->address;
range->size = details->size;
return FALSE;
}
return TRUE;
}
static gboolean
frida_compute_vtable_shift (gpointer vtable, gssize * shift)
{
gboolean result = FALSE;
G_GNUC_UNUSED cs_err err;
csh capstone;
cs_insn * insn = NULL;
const uint8_t * code;
uint64_t address;
size_t size = 4;
cs_arch_register_arm64 ();
err = cs_open (CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN, &capstone);
g_assert (err == CS_ERR_OK);
insn = cs_malloc (capstone);
code = gum_strip_code_pointer (*(gpointer *) vtable);
address = GPOINTER_TO_SIZE (code);
if (cs_disasm_iter (capstone, &code, &size, &address, insn))
{
if (insn->id == ARM64_INS_RET || insn->id == ARM64_INS_RETAA || insn->id == ARM64_INS_RETAB)
*shift = 0;
else
*shift = -2 * GLIB_SIZEOF_VOID_P;
result = TRUE;
}
if (insn != NULL)
cs_free (insn, 1);
cs_close (&capstone);
return result;
}
#else
static gboolean
frida_compute_vtable_shift (gpointer vtable, gssize * shift)
{
GumAddress cursor = GPOINTER_TO_SIZE (vtable);
GumAddress error = cursor + 16 * GLIB_SIZEOF_VOID_P;
while (cursor < error && *(gpointer *) GSIZE_TO_POINTER (cursor) == NULL)
cursor += GLIB_SIZEOF_VOID_P;
if (cursor == error)
return FALSE;
if (frida_is_empty_function (GUM_ADDRESS (*(gpointer *) GSIZE_TO_POINTER (cursor))) &&
frida_is_empty_function (GUM_ADDRESS (*(gpointer *) GSIZE_TO_POINTER (cursor + GLIB_SIZEOF_VOID_P))))
{
*shift = cursor - GPOINTER_TO_SIZE (vtable);
}
else
{
*shift = cursor - GPOINTER_TO_SIZE (vtable) - 2 * GLIB_SIZEOF_VOID_P;
}
return TRUE;
}
static gboolean
frida_is_empty_function (GumAddress address)
{
gboolean matches = FALSE;
GumMemoryRange range;
GumMatchPattern * pattern;
range.base_address = address;
range.size = 6;
/*
* 55 push rbp
* 4889e5 mov rbp, rsp
* 5d pop rbp
* c3 ret
*/
pattern = gum_match_pattern_new_from_string ("55 48 89 e5 5d c3");
gum_memory_scan (&range, pattern, (GumMemoryScanMatchFunc) frida_has_first_match, &matches);
gum_match_pattern_unref (pattern);
return matches;
}
static gboolean
frida_has_first_match (GumAddress address, gsize size, gboolean * matches)
{
*matches = TRUE;
return FALSE;
}
#endif
#endif
+13 -37
View File
@@ -1,65 +1,41 @@
namespace Frida {
#if DARWIN
public sealed class UnwindSitter : Object {
public sealed class UnwindSitter : Object, Gum.UnwindSectionsProvider {
public weak ProcessInvader invader {
get;
construct;
}
private DyldFindUnwindSectionsFunc dyld_find_unwind_sections;
public unowned Gum.MemoryRange? range {
get { return _range; }
}
private const string LIBDYLD = "/usr/lib/system/libdyld.dylib";
[CCode (has_target = false)]
private delegate int DyldFindUnwindSectionsFunc (void * addr, void * info);
private Gum.MemoryRange? _range;
private Gum.UnwindBroker broker;
public UnwindSitter (ProcessInvader invader) {
Object (invader: invader);
}
construct {
var interceptor = Gum.Interceptor.obtain ();
_range = invader.get_memory_range ();
dyld_find_unwind_sections = (DyldFindUnwindSectionsFunc)
Gum.Process.find_module_by_name (LIBDYLD).find_export_by_name ("_dyld_find_unwind_sections");
interceptor.replace ((void *) dyld_find_unwind_sections, (void *) replacement_dyld_find_unwind_sections, this);
_hook_libunwind ();
broker = Gum.UnwindBroker.obtain ();
broker.add_sections_provider (this);
}
public override void dispose () {
var interceptor = Gum.Interceptor.obtain ();
_unhook_libunwind ();
interceptor.revert ((void *) dyld_find_unwind_sections);
broker.remove_sections_provider (this);
base.dispose ();
}
private static int replacement_dyld_find_unwind_sections (void * addr, void * info) {
unowned Gum.InvocationContext context = Gum.Interceptor.get_current_invocation ();
unowned UnwindSitter sitter = (UnwindSitter) context.get_replacement_data ();
Gum.MemoryRange range = sitter.invader.get_memory_range ();
var range_end = range.base_address + range.size;
var address = Gum.Address.from_pointer (addr);
#if ARM64
address &= 0x7ffffffffULL;
#endif
var is_ours = address >= range.base_address && address < range_end;
if (!is_ours)
return sitter.dyld_find_unwind_sections (addr, info);
_fill_unwind_sections (range.base_address, range_end, info);
return 1;
public bool fill (Gum.Address address, void * info) {
_fill_unwind_sections (_range.base_address, _range.base_address + _range.size, info);
return true;
}
public extern static void _fill_unwind_sections (Gum.Address invader_start, Gum.Address invader_end, void * info);
public extern static void _hook_libunwind ();
public extern static void _unhook_libunwind ();
}
#else
public sealed class UnwindSitter : Object {
+2 -1
View File
@@ -2,6 +2,7 @@
#include "pipe-glue.h"
#include <efni.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
@@ -66,7 +67,7 @@ _frida_pipe_transport_create_backend (gchar ** local_address, gchar ** remote_ad
fcntl (fd, F_SETFD, FD_CLOEXEC);
setsockopt (fd, SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, sizeof (no_sigpipe));
frida_unix_socket_tune_buffer_sizes (fd);
efni_unix_socket_tune_buffer_sizes (fd);
}
status = fileport_makeport (sockets[0], &local_wrapper);
+15 -2
View File
@@ -12,14 +12,27 @@ frida_pipe_get_sddl_string_for_pipe (void)
#define DACL_ACE_EVERYONE_RW L"(A;;GRGW;;;WD)"
#define SACL_START L"S:"
#define SACL_ACE_LOWINTEGRITY_NORW L"(ML;;NWNR;;;LW)"
#define DACL_ACE_SYSTEM_RW L"(A;;GRGW;;;SY)"
#define DACL_ACE_ADMINS_RW L"(A;;GRGW;;;BA)"
if (frida_pipe_is_windows_8_or_greater ())
{
return DACL_START_NOINHERIT DACL_ACE_APPCONTAINER_RW DACL_ACE_EVERYONE_RW SACL_START SACL_ACE_LOWINTEGRITY_NORW;
return DACL_START_NOINHERIT
DACL_ACE_APPCONTAINER_RW
DACL_ACE_EVERYONE_RW
DACL_ACE_SYSTEM_RW
DACL_ACE_ADMINS_RW
SACL_START
SACL_ACE_LOWINTEGRITY_NORW;
}
else if (frida_pipe_is_windows_vista_or_greater ())
{
return DACL_START_NOINHERIT DACL_ACE_EVERYONE_RW SACL_START SACL_ACE_LOWINTEGRITY_NORW;
return DACL_START_NOINHERIT
DACL_ACE_EVERYONE_RW
DACL_ACE_SYSTEM_RW
DACL_ACE_ADMINS_RW
SACL_START
SACL_ACE_LOWINTEGRITY_NORW;
}
else
{
+3 -1
View File
@@ -1,3 +1,5 @@
using Efni;
namespace Frida {
public sealed class PipeTransport : Object {
public string local_address {
@@ -157,7 +159,7 @@ namespace Frida {
UnixSocketAddressType type = UnixSocketAddress.abstract_names_supported ()
? UnixSocketAddressType.ABSTRACT
: UnixSocketAddressType.PATH;
var server_address = new UnixSocketAddress.with_type (path, -1, type);
var server_address = new UnixSocketAddress.with_type (path.to_utf8 (), type);
if (role == "server") {
var socket = new Socket (SocketFamily.UNIX, SocketType.STREAM, SocketProtocol.DEFAULT);
+344 -92
View File
@@ -14,7 +14,7 @@ if not meson.get_compiler('vala').version().endswith('-frida')
Frida currently relies on features not yet upstream. Grab the Frida-optimized
Vala sources from:
https://github.com/frida/vala
https://github.com/frida-forks/vala
and compile them with Meson.''')
endif
@@ -130,6 +130,10 @@ if host_os_family == 'darwin'
add_languages('objc', 'objcpp', native: false)
endif
if meson.is_cross_build()
add_languages('c', 'cpp', 'vala', native: true)
endif
python = import('python').find_installation()
if cc.get_argument_syntax() == 'msvc'
@@ -224,15 +228,21 @@ helper_name = 'frida-helper' + exe_suffix
agent_name = 'frida-agent' + shlib_suffix
gadget_name = 'frida-gadget' + shlib_suffix
root_asset_dir = get_option('libdir') / 'frida'
frida_libdir_name = f'frida-@api_version@'
root_asset_dir = get_option('libdir') / frida_libdir_name
if host_os_family == 'darwin'
asset_dir = root_asset_dir
default_asset_path_template = get_option('prefix') / get_option('libdir') / 'frida'
default_asset_path_template = get_option('prefix') / get_option('libdir') / frida_libdir_name
elif host_os_family == 'windows'
asset_dir = root_asset_dir / host_abi
asset_dir_modern = root_asset_dir / 'x86_64'
asset_dir_legacy = root_asset_dir / 'x86'
default_asset_path_template = get_option('prefix') / get_option('libdir') / frida_libdir_name / '<arch>'
else
asset_dir = root_asset_dir / host_cpu_mode
asset_dir_modern = root_asset_dir / '64'
asset_dir_legacy = root_asset_dir / '32'
default_asset_path_template = get_option('prefix') / get_option('libdir') / 'frida' / '<arch>'
default_asset_path_template = get_option('prefix') / get_option('libdir') / frida_libdir_name / '<arch>'
endif
asset_path_template = get_option('asset_path_template')
@@ -240,11 +250,22 @@ if asset_path_template == ''
asset_path_template = default_asset_path_template
endif
if get_option('assets') == 'embedded'
is_shared = get_option('default_library') == 'shared'
assets_mode = get_option('assets')
if assets_mode == 'auto'
assets_mode = is_shared ? 'installed' : 'embedded'
endif
cdata.set_quoted('FRIDA_LIBDIR', get_option('libdir'))
cdata.set_quoted('FRIDA_LIBDIR_NAME', frida_libdir_name)
if assets_mode == 'embedded'
cdata.set('HAVE_EMBEDDED_ASSETS', 1)
vala_flags += '--define=HAVE_EMBEDDED_ASSETS'
else
cdata.set_quoted('FRIDA_HELPER_PATH', asset_path_template / helper_name)
cdata.set_quoted('FRIDA_AGENT_PATH', asset_path_template / agent_name)
cdata.set_quoted('FRIDA_HELPER_NAME', helper_name)
cdata.set_quoted('FRIDA_AGENT_NAME', agent_name)
endif
cdata.set('HAVE_' + host_os_family.to_upper(), 1)
@@ -252,6 +273,15 @@ if host_os != host_os_family
cdata.set('HAVE_' + host_os.to_upper(), 1)
endif
host_is_prospero = host_os_family == 'freebsd' and cc.get_define('__PROSPERO__') != ''
prospero_crt_link_args = []
if host_is_prospero
cdata.set('HAVE_PROSPERO', 1)
vala_flags += '--define=PROSPERO'
prospero_crt_link_args += \
run_command(cc.cmd_array(), '-print-file-name=crt1.o', check: true).stdout().strip()
endif
cpu_defines = [
['x86', 'HAVE_I386'],
['x86_64', 'HAVE_I386'],
@@ -302,28 +332,30 @@ if host_os == 'linux'
endif
cdata.set('HAVE_' + libc.to_upper(), 1)
vala_flags += '--define=' + libc.to_upper()
elif host_os == 'android'
libc = 'bionic'
else
libc = 'other'
endif
if get_option('b_sanitize') == 'address'
cdata.set('HAVE_ASAN', 1)
endif
gum_dep = dependency('frida-gum-1.0', default_options: [
f'frida_version=@frida_version@',
'gumjs=enabled',
])
gumjs_dep = dependency('frida-gumjs-1.0')
gumjs_inspector_dep = dependency('frida-gumjs-inspector-1.0')
glib_version_req = '>=2.74'
glib_options = [
'default_library=static',
'cocoa=disabled',
'selinux=disabled',
'xattr=false',
'libmount=disabled',
'sysprof=disabled',
'tests=false',
'nls=disabled',
]
if host_os_family in ['darwin', 'qnx', 'none'] or host_os == 'android' or libc == 'uclibc'
glib_options += 'iconv=external'
endif
if optimize_for_prod
glib_options += [
'glib_debug=disabled',
@@ -332,27 +364,75 @@ if optimize_for_prod
]
endif
glib_dep = dependency('glib-2.0', version: glib_version_req, default_options: glib_options)
if glib_dep.type_name() == 'internal'
have_shared_glib = get_option('default_library') != 'static'
else
glib_flavor_src = '''
#include <glib.h>
#ifdef GLIB_STATIC_COMPILATION
# error GLib is static
#endif
'''
have_shared_glib = cc.compiles(glib_flavor_src, name: 'compiling with shared GLib', dependencies: [glib_dep])
endif
have_frida_glib = glib_dep.type_name() == 'internal' or cc.has_function('g_thread_set_callbacks', dependencies: [glib_dep])
if have_frida_glib
cdata.set('HAVE_FRIDA_GLIB', 1)
vala_flags += '--define=HAVE_FRIDA_GLIB'
endif
have_in_tree_payloads = not have_shared_glib
gum_dep = dependency('frida-gum-1.0', default_options: [
'default_library=static',
f'frida_version=@frida_version@',
'gumjs=' + (have_in_tree_payloads ? 'enabled' : 'disabled'),
'inspector=enabled',
])
if have_in_tree_payloads
gumjs_dep = dependency('frida-gumjs-1.0', default_options: ['default_library=static'])
endif
gumjs_inspector_dep = dependency('frida-gumjs-inspector-1.0', default_options: ['default_library=static'])
gee_options = [
'default_library=static',
'disable-introspection=true',
]
if optimize_for_prod
gee_options += 'disable-internal-asserts=true'
endif
glib_dep = dependency('glib-2.0', version: glib_version_req, default_options: glib_options)
gobject_dep = dependency('gobject-2.0')
gmodule_dep = dependency('gmodule-2.0')
gio_dep = dependency('gio-2.0')
gee_dep = dependency('gee-0.8', default_options: gee_options)
json_glib_dep = dependency('json-glib-1.0', version: '>=1.8', default_options: [
'default_library=static',
'introspection=disabled',
'gtk_doc=disabled',
'tests=false',
'nls=disabled',
])
efni_dep = dependency('efni-1.0', default_options: ['default_library=static'])
efni_libs = []
if efni_dep.type_name() == 'internal'
efni_libs += subproject('efni').get_variable('efni')
endif
efni_vapidir = efni_dep.get_variable('vapidir', default_value: '')
if efni_vapidir != ''
vala_flags += '--vapidir=' + efni_vapidir
endif
foreach pkg : efni_dep.get_variable('vala_packages', default_value: '').split()
vala_flags += '--pkg=' + pkg
endforeach
libsoup_dep = dependency('libsoup-3.0', default_options: [
'default_library=static',
'sqlite:default_library=static',
'gssapi=disabled',
'ntlm=disabled',
'brotli=disabled',
'zstd=disabled',
'tls_check=false',
'introspection=disabled',
'vapi=disabled',
@@ -361,19 +441,54 @@ libsoup_dep = dependency('libsoup-3.0', default_options: [
'tests=false',
'sysprof=disabled',
])
brotlidec_dep = dependency('libbrotlidec')
quickjs_dep = dependency('quickjs', required: get_option('barebone_backend'), default_options: [
'libc=false',
'bignum=true',
'atomics=disabled',
'stack_check=disabled',
if libsoup_dep.type_name() == 'internal' \
or cc.has_function('soup_websocket_connection_pause_input', dependencies: [libsoup_dep])
vala_flags += '--define=HAVE_SOUP_WEBSOCKET_BACKPRESSURE'
endif
brotlidec_dep = dependency('libbrotlidec', default_options: ['default_library=static'])
capstone_dep = dependency('capstone', default_options: [
'default_library=static',
'archs=all',
'use_arch_registration=true',
'x86_att_disable=true',
'cli=disabled',
])
lzfse_dep = dependency('lzfse', required: get_option('barebone_backend'))
if capstone_dep.type_name() != 'internal' \
and not cc.has_function('cs_arch_register_x86', dependencies: [capstone_dep])
add_project_arguments(
'-Dcs_arch_register_x86()=',
'-Dcs_arch_register_arm()=',
'-Dcs_arch_register_arm64()=',
'-Dcs_arch_register_mips()=',
language: ['c', 'objc'],
)
endif
quickjs_dep = dependency('quickjs', required: get_option('barebone_backend'), default_options: [
'default_library=static',
'libc=false',
'atomics=disabled',
'stack_check=' + ((host_os_family == 'none') ? 'enabled' : 'disabled'),
'stack_mode=' + ((host_os_family == 'none') ? 'optimize' : 'default'),
])
lzfse_dep = dependency('lzfse', required: false, default_options: ['default_library=static'])
lzfse_from_system = false
if not lzfse_dep.found() and not meson.is_cross_build()
if cc.has_header_symbol('lzfse.h', 'lzfse_decode_buffer')
lzfse_lib = cc.find_library('lzfse', required: false)
if lzfse_lib.found()
lzfse_dep = declare_dependency(dependencies: lzfse_lib)
lzfse_from_system = true
endif
endif
endif
if not lzfse_dep.found()
lzfse_dep = dependency('lzfse', required: get_option('barebone_backend'), default_options: ['default_library=static'])
endif
native_glib_dep = dependency('glib-2.0', version: glib_version_req, native: true, default_options: glib_options)
native_gio_dep = dependency('gio-2.0', native: true)
native_gee_dep = dependency('gee-0.8', native: true, default_options: gee_options)
native_brotlienc_dep = dependency('libbrotlienc', native: true)
native_brotlienc_dep = dependency('libbrotlienc', native: true, default_options: ['default_library=static'])
if host_os_family == 'windows'
gio_windows_dep = dependency('gio-windows-2.0')
@@ -381,12 +496,15 @@ else
gio_unix_dep = dependency('gio-unix-2.0')
endif
if host_os_family == 'linux'
libbpf_dep = dependency('libbpf')
have_local_backend = get_option('local_backend').allowed()
if host_os_family == 'linux' and have_local_backend
libbpf_dep = dependency('libbpf', default_options: ['default_library=static'])
endif
if host_os == 'android'
minizip_dep = dependency('minizip', required: false, default_options: [
'default_library=static',
'zlib=enabled',
'lzma=disabled',
])
@@ -430,7 +548,6 @@ else
codesign = ''
endif
have_local_backend = get_option('local_backend').allowed()
have_simmy_backend = get_option('simmy_backend') \
.disable_auto_if(host_os != 'macos') \
.require(host_os == 'macos', error_message: 'Simmy backend is only available on macOS') \
@@ -444,11 +561,12 @@ have_droidy_backend = get_option('droidy_backend') \
have_socket_backend = get_option('socket_backend').allowed()
have_barebone_backend = get_option('barebone_backend') \
.disable_if(not (quickjs_dep.found() and lzfse_dep.found())) \
.disable_auto_if(not host_docks_mobile_devices) \
.disable_auto_if(not host_docks_mobile_devices and host_os != 'android') \
.allowed()
compiler_backend_opt = get_option('compiler_backend') \
.disable_auto_if(host_os == 'watchos')
have_compiler_backend = false
compiler_backend_mode = ''
if compiler_backend_opt.allowed()
go = find_program('go', native: true, required: false)
if go.found()
@@ -459,9 +577,13 @@ if compiler_backend_opt.allowed()
files('tools' / 'cgo' / 'detect.py'),
go,
host_os,
host_os_family,
host_abi,
libc,
exe_suffix,
shlib_suffix,
cc.get_id(),
assets_mode,
'>>>', cc.cmd_array(), '<<<',
'>>>', ar, '<<<',
'>>>', nm, '<<<',
@@ -474,12 +596,33 @@ if compiler_backend_opt.allowed()
status = lines[0]
if status == 'ok'
have_compiler_backend = true
tokens = lines[1].split(' ')
go_mode = tokens[0]
go_config = tokens[1]
compiler_backend_static_compilation = go_mode == 'c-archive'
if compiler_backend_static_compilation
vala_flags += '--define=COMPILER_BACKEND_STATIC_COMPILATION'
cdata.set('HAVE_COMPILER_BACKEND', 1)
compiler_backend_mode = lines[1]
go_config = configure_file(
input: 'tools' / 'cgo' / 'go-config.json.in',
output: 'go-config.json',
configuration: {'GO_CONFIG': lines[2]},
)
if compiler_backend_mode == 'c-archive'
vala_flags += '--define=COMPILER_BACKEND_LINKED'
cdata.set('HAVE_COMPILER_BACKEND_LINKED', 1)
elif compiler_backend_mode == 'c-shared'
if assets_mode == 'installed'
vala_flags += '--define=COMPILER_BACKEND_INSTALLED_LIBRARY'
cdata.set_quoted('FRIDA_COMPILER_BACKEND_NAME', 'frida-compiler-backend' + shlib_suffix)
else
vala_flags += '--define=COMPILER_BACKEND_EMBEDDED_LIBRARY'
endif
elif compiler_backend_mode == 'pie'
if assets_mode == 'installed'
vala_flags += '--define=COMPILER_BACKEND_INSTALLED_EXECUTABLE'
cdata.set_quoted('FRIDA_COMPILER_BACKEND_NAME', 'frida-compiler-backend' + exe_suffix)
else
vala_flags += '--define=COMPILER_BACKEND_EMBEDDED_EXECUTABLE'
endif
else
error('Unsupported compiler backend mode: ' + compiler_backend_mode)
endif
else
error_message = lines[1]
@@ -490,9 +633,9 @@ if compiler_backend_opt.allowed()
endif
endif
elif compiler_backend_opt.enabled()
error('Need Go >= 1.24 to build the Compiler backend powered by ESBuild and typescript-go')
error('Need Go >= 1.26 to build the Compiler backend powered by ESBuild and typescript-go')
else
warning('Building without Compiler backend: Go >= 1.24 not found')
warning('Building without Compiler backend: Go >= 1.26 not found')
endif
endif
@@ -548,8 +691,10 @@ endif
gi_dep = dependency('gobject-introspection-1.0', required: false)
if gi_dep.found()
girdir = gi_dep.get_variable('girdir')
typelibdir = gi_dep.get_variable('typelibdir')
else
girdir = get_option('prefix') / get_option('datadir') / 'gir-1.0'
typelibdir = get_option('prefix') / get_option('libdir') / 'girepository-1.0'
endif
vapidir = get_option('prefix') / get_option('datadir') / 'vala' / 'vapi'
@@ -558,12 +703,15 @@ gum_vala_args = [
'--pkg=frida-gum-1.0',
'--vapidir=' + gum_dep.get_variable('frida_vapidir'),
]
gumjs_vala_args = ['--pkg=frida-gumjs-1.0', '--pkg=gio-2.0', gum_vala_args]
gumjs_inspector_vala_args = ['--pkg=frida-gumjs-inspector-1.0', gum_vala_args]
have_v8 = gumjs_dep.get_variable('gumjs_v8') == 'enabled'
if have_v8
vala_flags += ['--define=HAVE_V8']
if have_in_tree_payloads
gumjs_vala_args = ['--pkg=frida-gumjs-1.0', '--pkg=gio-2.0', gum_vala_args]
have_v8 = gumjs_dep.get_variable('gumjs_v8') == 'enabled'
if have_v8
vala_flags += ['--define=HAVE_V8']
endif
endif
backend_deps_private = []
@@ -571,37 +719,39 @@ backend_reqs_private = []
backend_libs_private = []
openssl_options = [
'default_library=static',
'cli=disabled',
]
if host_os_family == 'windows' and cc.get_argument_syntax() != 'msvc'
openssl_options += 'asm=disabled'
endif
openssl_dep = dependency('openssl',
required: have_fruity_backend or get_option('connectivity').enabled(),
required: get_option('connectivity').enabled(),
modules: cc.get_argument_syntax() == 'msvc' ? ['OpenSSL::SSL'] : [],
default_options: openssl_options,
)
idun_libs = []
have_idund_blob = false
if have_fruity_backend
foreach dep : ['libnghttp2', 'libngtcp2', 'libngtcp2_crypto_quictls', 'libusb-1.0']
backend_deps_private += dependency(dep)
backend_reqs_private += dep
endforeach
lwip_options = [
'ipv4=disabled',
'ipv6=enabled',
'dns=disabled',
'arp=disabled',
'ethernet=enabled',
]
if optimize_for_prod
lwip_options += 'lwip_debug=disabled'
else
lwip_options += 'lwip_debug=enabled'
idun_dep = dependency('idun-1.0', default_options: ['default_library=static'])
backend_reqs_private += 'idun-1.0'
if idun_dep.type_name() == 'internal'
idun_sp = subproject('idun')
idun_libs += idun_sp.get_variable('idun')
if assets_mode == 'embedded' and idun_sp.get_variable('build_daemon')
idund_raw = idun_sp.get_variable('idund')
have_idund_blob = true
vala_flags += '--define=HAVE_IDUND_BLOB'
endif
endif
backend_deps_private += dependency('lwip', default_options: lwip_options)
backend_reqs_private += 'lwip'
idun_vapidir = idun_dep.get_variable('vapidir', default_value: '')
if idun_vapidir != ''
vala_flags += '--vapidir=' + idun_vapidir
endif
foreach pkg : idun_dep.get_variable('vala_packages', default_value: '').split()
vala_flags += '--pkg=' + pkg
endforeach
endif
if quickjs_dep.found()
@@ -611,57 +761,117 @@ endif
if lzfse_dep.found()
backend_deps_private += lzfse_dep
backend_reqs_private += 'lzfse'
if lzfse_from_system
backend_libs_private += '-llzfse'
else
backend_reqs_private += 'lzfse'
endif
endif
if host_os_family == 'linux'
if host_os_family == 'linux' and have_local_backend
backend_deps_private += libbpf_dep
backend_reqs_private += 'libbpf'
endif
if host_os_family in ['darwin', 'freebsd'] or (host_os_family == 'linux' and cc.has_function('openpty', prefix: '#include <pty.h>'))
if host_os_family == 'darwin' \
or (host_os_family == 'freebsd' and not host_is_prospero) \
or (host_os_family == 'linux' and cc.has_function('openpty', prefix: '#include <pty.h>'))
vala_flags += '--define=HAVE_OPENPTY'
endif
if host_os == 'android'
termux_elf_cleaner = find_program('termux-elf-cleaner', native: true)
libselinux_dep = dependency('libselinux', version: '>=3.0')
libsepol_dep = dependency('libsepol', version: '>=3.0')
selinux_options = [
'default_library=static',
'regex=disabled',
]
libselinux_dep = dependency('libselinux', version: '>=3.0', default_options: selinux_options)
libsepol_dep = dependency('libsepol', version: '>=3.0', default_options: selinux_options)
backend_deps_private += [libselinux_dep, libsepol_dep]
backend_reqs_private += ['libselinux', 'libsepol']
else
termux_elf_cleaner = []
endif
tls_provider_dep = dependency('gioopenssl', required: get_option('connectivity'), default_options: [
'gnutls=disabled',
'openssl=enabled',
'libproxy=disabled',
'gnome_proxy=disabled',
'tests=false',
])
tls_provider_vala_args = []
if tls_provider_dep.found()
tls_provider_vala_args += [
'--pkg=gioopenssl',
'--vapidir=' + tls_provider_dep.get_variable('vapidir'),
]
cdata.set('HAVE_GIOOPENSSL', 1)
vala_flags += ['--define=HAVE_GIOOPENSSL']
backend_deps_private += tls_provider_dep
backend_reqs_private += 'gioopenssl'
use_apple_tls = false
if have_shared_glib
tls_provider_dep = gio_dep
else
apple_tls_opt = get_option('apple_tls')
if host_os_family == 'darwin' and not apple_tls_opt.disabled()
apple_tls_deployment_check = '''
#include <Availability.h>
#if defined (__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 150000
# error macOS deployment target below 15.0
#endif
#if defined (__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 180000
# error iOS deployment target below 18.0
#endif
#if defined (__TV_OS_VERSION_MIN_REQUIRED) && __TV_OS_VERSION_MIN_REQUIRED < 180000
# error tvOS deployment target below 18.0
#endif
#if defined (__WATCH_OS_VERSION_MIN_REQUIRED) && __WATCH_OS_VERSION_MIN_REQUIRED < 110000
# error watchOS deployment target below 11.0
#endif
'''
use_apple_tls = cc.compiles(apple_tls_deployment_check, name: 'Apple TLS backend deployment target')
if apple_tls_opt.enabled() and not use_apple_tls
error('apple_tls explicitly enabled but deployment target is too old')
endif
elif apple_tls_opt.enabled()
error('apple_tls is only supported on Darwin hosts')
endif
if use_apple_tls
tls_provider_name = 'gioapple'
tls_provider_define_name = 'HAVE_GIOAPPLE'
tls_provider_backend_options = [
'gnutls=disabled',
'openssl=disabled',
'apple=enabled',
]
else
tls_provider_name = 'gioopenssl'
tls_provider_define_name = 'HAVE_GIOOPENSSL'
tls_provider_backend_options = [
'gnutls=disabled',
'openssl=enabled',
'apple=disabled',
]
endif
tls_provider_dep = dependency(tls_provider_name,
required: get_option('connectivity'),
default_options: tls_provider_backend_options + [
'libproxy=disabled',
'gnome_proxy=disabled',
'tests=false',
],
)
if tls_provider_dep.found()
tls_provider_vala_args += [
'--pkg=' + tls_provider_name,
'--vapidir=' + tls_provider_dep.get_variable('vapidir'),
]
cdata.set(tls_provider_define_name, 1)
vala_flags += ['--define=' + tls_provider_define_name]
backend_deps_private += tls_provider_dep
backend_reqs_private += tls_provider_name
endif
endif
nice_dep = dependency('nice', required: get_option('connectivity'), default_options: [
'default_library=static',
'gupnp=disabled',
'gstreamer=disabled',
'crypto-library=openssl',
'crypto-library=auto',
'examples=disabled',
'tests=disabled',
'introspection=disabled',
])
if nice_dep.found()
usrsctp_dep = dependency('usrsctp', default_options: [
'default_library=static',
'sctp_inet=false',
'sctp_inet6=false',
'sctp_build_programs=false',
@@ -669,8 +879,12 @@ if nice_dep.found()
cdata.set('HAVE_NICE', 1)
vala_flags += ['--define=HAVE_NICE']
backend_deps_private += [nice_dep, openssl_dep, usrsctp_dep]
backend_reqs_private += ['nice', 'openssl', 'usrsctp']
backend_deps_private += [nice_dep, usrsctp_dep]
backend_reqs_private += ['nice', 'usrsctp']
if host_os != 'macos'
backend_deps_private += openssl_dep
backend_reqs_private += 'openssl'
endif
if host_os_family in ['darwin', 'freebsd', 'qnx']
cdata.set('HAVE_SCONN_LEN', 1)
@@ -680,20 +894,26 @@ else
endif
if host_os_family == 'windows'
backend_libs_private += ['-lsetupapi']
backend_libs_private += ['-lsetupapi', '-lshlwapi']
endif
if host_os_family == 'darwin'
backend_libs_private += ['-Wl,-framework,Foundation', '-lbsm']
backend_libs_private += ['-Wl,-framework,Foundation', '-Wl,-framework,CoreFoundation', '-lbsm']
endif
if host_os == 'macos'
backend_libs_private += ['-Wl,-framework,AppKit']
if have_compiler_backend
backend_libs_private += ['-Wl,-framework,CoreServices']
endif
endif
if host_os in ['ios', 'tvos']
backend_libs_private += ['-Wl,-framework,CoreGraphics', '-Wl,-framework,UIKit']
endif
if host_os_family == 'freebsd'
if host_os_family == 'freebsd' and not host_is_prospero
backend_libs_private += ['-lutil']
endif
if host_is_prospero
backend_libs_private += ['-lSceSystemService', '-lSceUserService']
endif
if get_option('devkits').length() != 0
if get_option('default_library') != 'static'
@@ -753,16 +973,30 @@ post_process = [
'@INPUT0@',
]
executable_no_exports_link_args = []
executable_no_exports_link_depends = []
if host_os_family == 'darwin'
script = meson.project_source_root() / 'tools' / 'executable-no-exports.symbols'
executable_no_exports_link_args = ['-Wl,-exported_symbols_list,' + script]
executable_no_exports_link_depends = [script]
elif host_os_family != 'windows'
script_name = (host_os == 'freebsd' and not host_is_prospero) ? 'executable-no-exports-freebsd.version' : 'executable-no-exports.version'
script = meson.project_source_root() / 'tools' / script_name
executable_no_exports_link_args = ['-Wl,--version-script,' + script]
executable_no_exports_link_depends = [script]
endif
compile_js_agent = files('tools' / 'compile-js-agent.py')
mapper_opt = get_option('mapper')
if mapper_opt.auto()
have_mapper = host_os_family == 'darwin'
have_mapper = host_os_family in ['windows', 'darwin', 'linux']
else
have_mapper = mapper_opt.enabled()
endif
if have_mapper
cdata.set('HAVE_MAPPER', 1)
vala_flags += '--define=HAVE_MAPPER'
endif
configure_file(
@@ -783,11 +1017,20 @@ if cc.get_id() == 'gcc' and cc.compiles('#if __GNUC__ >= 5\n#error NOTNEEDED\n#e
add_project_arguments('-w', language: 'c')
endif
vapidir = meson.current_source_dir() / 'vapi'
local_vapidir = meson.current_source_dir() / 'vapi'
vala_flags += [
'--vapidir=' + vapidir,
'--vapidir=' + local_vapidir,
'--pkg', 'config',
]
if gee_dep.type_name() not in ['internal', 'cmake']
gee_prefix = gee_dep.get_variable('prefix', default_value: '')
if gee_prefix != ''
gee_vapidir = gee_prefix / 'share' / 'vala' / 'vapi'
if fs.is_dir(gee_vapidir)
vala_flags += '--vapidir=' + gee_vapidir
endif
endif
endif
vala_flags += '--define=' + host_os_family.to_upper()
if host_os != host_os_family
@@ -799,13 +1042,22 @@ if host_abi != host_arch
endif
vala_flags += '--define=' + host_machine.endian().to_upper() + '_ENDIAN'
add_project_arguments(vala_flags, language: 'vala')
add_project_arguments('-I' + vapidir, language: 'c')
add_project_arguments('-I' + local_vapidir, language: 'c')
native_vala_flags = [
'--vapidir=' + meson.current_source_dir() / 'vapi',
'--vapidir=' + local_vapidir,
]
if native_gee_dep.type_name() not in ['internal', 'cmake']
native_gee_prefix = native_gee_dep.get_variable('prefix', default_value: '')
if native_gee_prefix != ''
native_gee_vapidir = native_gee_prefix / 'share' / 'vala' / 'vapi'
if fs.is_dir(native_gee_vapidir)
native_vala_flags += '--vapidir=' + native_gee_vapidir
endif
endif
endif
add_project_arguments(native_vala_flags, language: 'vala', native: true)
add_project_arguments('-I' + vapidir, language: 'c', native: true)
add_project_arguments('-I' + local_vapidir, language: 'c', native: true)
subdir('compat')
subdir('tools')
@@ -840,7 +1092,7 @@ summary(
summary(
{
'compat': compat_summary,
'assets': get_option('assets'),
'assets': assets_mode,
'mapper': have_mapper,
'connectivity': tls_provider_dep.found(),
'gadget': build_gadget,
+8 -2
View File
@@ -48,8 +48,8 @@ option('compiler_backend',
option('assets',
type: 'combo',
choices: ['embedded', 'installed'],
value: 'embedded',
choices: ['auto', 'embedded', 'installed'],
value: 'auto',
description: 'Whether assets such as frida-agent should be embedded vs installed and loaded at runtime'
)
@@ -143,6 +143,12 @@ option('connectivity',
description: 'Internet connectivity features (TLS and ICE support)'
)
option('apple_tls',
type: 'feature',
value: 'auto',
description: 'Use the Apple TLS backend (requires recent deployment targets); auto-detected when on a Darwin host'
)
option('mapper',
type: 'feature',
value: 'auto',
-1
View File
@@ -1 +0,0 @@
# No exported symbols.
-9
View File
@@ -1,9 +0,0 @@
{
global:
# FreeBSD needs these two:
__progname;
environ;
local:
*;
};
+2 -14
View File
@@ -2,18 +2,6 @@ portal_sources = [
'portal.vala',
]
extra_link_args = []
extra_link_depends = []
if host_os_family == 'darwin'
symlist = 'frida-portal.symbols'
extra_link_args += ['-Wl,-exported_symbols_list,' + meson.current_source_dir() / symlist]
extra_link_depends += [symlist]
elif host_os_family != 'windows'
symscript = 'frida-portal.version'
extra_link_args += ['-Wl,--version-script,' + meson.current_source_dir() / symscript]
extra_link_depends += [symscript]
endif
extra_deps = []
if tls_provider_dep.found()
extra_deps += tls_provider_dep
@@ -21,8 +9,8 @@ endif
raw_portal = executable('frida-portal-raw', portal_sources,
vala_args: [core_vala_args, tls_provider_vala_args, '--pkg=posix'],
link_args: extra_link_args,
link_depends: extra_link_depends,
link_args: executable_no_exports_link_args,
link_depends: executable_no_exports_link_depends,
dependencies: [core_dep] + extra_deps,
)
+3 -1
View File
@@ -39,7 +39,9 @@ namespace Frida.Portal {
};
private static int main (string[] args) {
#if HAVE_GIOOPENSSL
#if HAVE_GIOAPPLE
GIOApple.register ();
#elif HAVE_GIOOPENSSL
GIOOpenSSL.register ();
#endif
+1 -1
Submodule releng updated: 7ed66fe687...164a6a4117.
+11
View File
@@ -0,0 +1,11 @@
{
global:
# FreeBSD needs these two:
__progname;
environ;
# Allow users to load the server as a shared library with dlopen() and call main() directly
main;
local:
*;
};
+2 -1
View File
@@ -1 +1,2 @@
# No exported symbols.
# Allow users to load the server as a shared library with dlopen() and call main() directly
_main
-3
View File
@@ -1,8 +1,5 @@
{
global:
# FreeBSD needs these two:
__progname;
environ;
# Allow users to load the server as a shared library with dlopen() and call main() directly
main;
+6
View File
@@ -359,5 +359,11 @@
<true/>
<key>com.apple.private.amfi.can-execute-cdhash</key>
<true/>
<key>com.apple.private.FairPlayIOKitUserClient.access</key>
<true/>
<key>com.apple.security.iokit-user-client-class</key>
<array>
<string>com_apple_driver_FairPlayIOKitUserClient</string>
</array>
</dict>
</plist>
+8 -3
View File
@@ -19,7 +19,7 @@ if host_os_family == 'darwin'
extra_link_args += ['-Wl,-exported_symbols_list,' + meson.current_source_dir() / symlist]
extra_link_depends += [symlist]
elif host_os_family != 'windows'
symscript = 'frida-server.version'
symscript = (host_os == 'freebsd' and not host_is_prospero) ? 'frida-server-freebsd.version' : 'frida-server.version'
extra_link_args += ['-Wl,--version-script,' + meson.current_source_dir() / symscript]
extra_link_depends += [symscript]
endif
@@ -29,15 +29,20 @@ if host_os == 'macos'
extra_link_depends += [server_plist]
endif
server_deps = [core_dep]
if host_os_family == 'darwin'
server_deps += dependency('appleframeworks', modules: ['CoreFoundation'])
endif
raw_server = executable('frida-server-raw', server_sources,
vala_args: [core_vala_args, '--pkg=posix'],
link_args: extra_link_args,
link_depends: extra_link_depends,
dependencies: [core_dep],
dependencies: server_deps,
)
server_name = 'frida-server' + exe_suffix
need_universal_server = get_option('assets') == 'installed' and server_compat != ''
need_universal_server = assets_mode == 'installed' and server_compat != ''
server = custom_target('frida-server',
input: [raw_server, 'frida-server.xcent'],
+9
View File
@@ -29,6 +29,8 @@ void CFLog (CFLogLevel level, CFStringRef format, ...);
#elif defined (HAVE_ANDROID)
# include <android/log.h>
#elif defined (HAVE_PROSPERO)
# include <ps5/klog.h>
#else
# include <stdio.h>
#endif
@@ -137,6 +139,13 @@ frida_server_on_log_message (const gchar * log_domain, GLogLevelFlags log_level,
}
__android_log_write (priority, log_domain, message);
#elif defined (HAVE_PROSPERO)
(void) user_data;
if (log_domain != NULL)
klog_printf ("%s: %s\n", log_domain, message);
else
klog_printf ("%s\n", message);
#else
FILE * file = NULL;
const gchar * severity = NULL;
+4 -4
View File
@@ -1,3 +1,5 @@
using Efni;
namespace Frida.Server {
private static Application application;
@@ -104,9 +106,8 @@ namespace Frida.Server {
return true;
});
int prefix_pos = program_path.last_index_of (Config.FRIDA_PREFIX + "/");
if (prefix_pos != -1 && prefix_pos != 0) {
options.sysroot = program_path[:prefix_pos];
}
if (prefix_pos != -1 && prefix_pos != 0)
TemporaryDirectory.use_sysroot (program_path[:prefix_pos]);
#endif
PolicySoftenerFlavor softener_flavor = SYSTEM;
@@ -199,7 +200,6 @@ namespace Frida.Server {
private static int run_application (string? device_id, EndpointParameters endpoint_params, ControlServiceOptions options,
ReadyHandler on_ready) {
TemporaryDirectory.always_use ((directory != null) ? directory : DEFAULT_DIRECTORY);
TemporaryDirectory.use_sysroot (options.sysroot);
application = new Application (device_id, endpoint_params, options);
+41 -39
View File
@@ -1,15 +1,17 @@
[CCode (gir_namespace = "FridaDroidy", gir_version = "1.0")]
namespace Frida.Droidy {
public sealed class DeviceTracker : Object {
public signal void device_attached (DeviceDetails details);
using Efni;
[CCode (gir_namespace = "FridaAdb", gir_version = "1.0")]
namespace Frida {
public sealed class AdbDeviceTracker : Object {
public signal void device_attached (AdbDeviceDetails details);
public signal void device_detached (string serial);
private Client? client;
private AdbClient? client;
private Gee.HashMap<string, DeviceEntry> devices = new Gee.HashMap<string, DeviceEntry> ();
private Cancellable io_cancellable = new Cancellable ();
public async void open (Cancellable? cancellable = null) throws Error, IOError {
client = yield Client.open (cancellable);
client = yield AdbClient.open (cancellable);
client.message.connect (on_message);
try {
@@ -20,7 +22,7 @@ namespace Frida.Droidy {
client.message.disconnect (on_message);
client = null;
client = yield Client.open (cancellable);
client = yield AdbClient.open (cancellable);
var devices_encoded = yield client.request_data ("host:track-devices", cancellable);
yield update_devices (devices_encoded, cancellable);
}
@@ -117,12 +119,12 @@ namespace Frida.Droidy {
var still_attached = devices.has_key (entry.serial);
if (still_attached) {
entry.announced = true;
device_attached (new DeviceDetails (entry.serial, entry.name));
device_attached (new AdbDeviceDetails (entry.serial, entry.name));
}
}
private async string detect_name (string device_serial, Cancellable? cancellable) throws Error, IOError {
var output = yield ShellCommand.run ("getprop ro.product.model", device_serial, cancellable);
var output = yield AdbShellCommand.run ("getprop ro.product.model", device_serial, cancellable);
return output.chomp ();
}
@@ -149,7 +151,7 @@ namespace Frida.Droidy {
}
}
public sealed class DeviceDetails : Object {
public sealed class AdbDeviceDetails : Object {
public string serial {
get;
construct;
@@ -160,16 +162,16 @@ namespace Frida.Droidy {
construct;
}
public DeviceDetails (string serial, string name) {
public AdbDeviceDetails (string serial, string name) {
Object (serial: serial, name: name);
}
}
namespace ShellCommand {
namespace AdbShellCommand {
private const int CHUNK_SIZE = 4096;
public static async string run (string command, string device_serial, Cancellable? cancellable = null) throws Error, IOError {
var client = yield Client.open (cancellable);
var client = yield AdbClient.open (cancellable);
try {
yield client.request ("host:transport:" + device_serial, cancellable);
@@ -205,8 +207,8 @@ namespace Frida.Droidy {
}
}
public sealed class ShellSession : Object {
public signal void output (StdioPipe pipe, Bytes bytes);
public sealed class AdbShellSession : Object {
public signal void output (AdbStdioPipe pipe, Bytes bytes);
public signal void closed ();
private IOStream stream;
@@ -232,7 +234,7 @@ namespace Frida.Droidy {
public async void open (string device_serial, Cancellable? cancellable = null) throws Error, IOError {
assert (state == CLOSED);
var client = yield Client.open (cancellable);
var client = yield AdbClient.open (cancellable);
try {
yield client.request ("host:transport:" + device_serial, cancellable);
@@ -285,7 +287,7 @@ namespace Frida.Droidy {
return result.stdout_text;
}
public async ShellCommandResult run (string command, Cancellable? cancellable) throws Error, IOError {
public async AdbShellCommandResult run (string command, Cancellable? cancellable) throws Error, IOError {
if (state == CLOSED)
throw new Error.INVALID_OPERATION ("Shell session is closed");
@@ -524,7 +526,7 @@ namespace Frida.Droidy {
public string command;
private SourceFunc? handler;
public ShellCommandResult? result;
public AdbShellCommandResult? result;
public GLib.Error? error;
public ByteArray stdout_buffer = new ByteArray ();
@@ -550,7 +552,7 @@ namespace Frida.Droidy {
Bytes stdout_bytes = new Bytes.take ((owned) stdout_terminated);
Bytes stderr_bytes = new Bytes.take ((owned) stderr_terminated);
result = new ShellCommandResult (status, stdout_bytes, stderr_bytes);
result = new AdbShellCommandResult (status, stdout_bytes, stderr_bytes);
handler ();
handler = null;
@@ -589,12 +591,12 @@ namespace Frida.Droidy {
}
}
public enum StdioPipe {
public enum AdbStdioPipe {
STDOUT,
STDERR
}
public sealed class ShellCommandResult : Object {
public sealed class AdbShellCommandResult : Object {
public uint8 status {
get;
construct;
@@ -622,7 +624,7 @@ namespace Frida.Droidy {
construct;
}
public ShellCommandResult (uint8 status, Bytes stdout_bytes, Bytes stderr_bytes) {
public AdbShellCommandResult (uint8 status, Bytes stdout_bytes, Bytes stderr_bytes) {
Object (
status: status,
stdout_bytes: stdout_bytes,
@@ -631,12 +633,12 @@ namespace Frida.Droidy {
}
}
namespace FileSync {
namespace AdbFileSync {
private const size_t MAX_DATA_SIZE = 65536;
public static async void send (InputStream content, FileMetadata metadata, string remote_path, string device_serial,
public static async void send (InputStream content, AdbFileMetadata metadata, string remote_path, string device_serial,
Cancellable? cancellable = null) throws Error, IOError {
var client = yield Client.open (cancellable);
var client = yield AdbClient.open (cancellable);
try {
yield client.request ("host:transport:" + device_serial, cancellable);
@@ -686,7 +688,7 @@ namespace Frida.Droidy {
}
}
public sealed class FileMetadata : Object {
public sealed class AdbFileMetadata : Object {
public uint32 mode {
get;
set;
@@ -700,16 +702,16 @@ namespace Frida.Droidy {
}
}
public sealed class JDWPTracker : Object {
public sealed class AdbJDWPTracker : Object {
public signal void debugger_attached (uint pid);
public signal void debugger_detached (uint pid);
private Client? client;
private AdbClient? client;
private Gee.HashSet<uint> debugger_pids = new Gee.HashSet<uint> ();
private Cancellable io_cancellable = new Cancellable ();
public async void open (string device_serial, Cancellable? cancellable = null) throws Error, IOError {
client = yield Client.open (cancellable);
client = yield AdbClient.open (cancellable);
client.message.connect (on_message);
try {
@@ -768,7 +770,7 @@ namespace Frida.Droidy {
}
}
public sealed class Client : Object {
public sealed class AdbClient : Object {
public signal void closed ();
public signal void message (string payload);
@@ -793,7 +795,7 @@ namespace Frida.Droidy {
private const uint16 ADB_SERVER_DEFAULT_PORT = 5037;
private const size_t MAX_MESSAGE_LENGTH = 65536;
public static async Client open (Cancellable? cancellable = null) throws Error, IOError {
public static async AdbClient open (Cancellable? cancellable = null) throws Error, IOError {
SocketConnectable? connectable = null;
string? env_socket_address = Environment.get_variable ("ADB_SERVER_SOCKET");
string? env_server_address = Environment.get_variable ("ANDROID_ADB_SERVER_ADDRESS");
@@ -813,7 +815,7 @@ namespace Frida.Droidy {
else if (socket_type == "local" || socket_type == "localfilesystem") {
connectable = new UnixSocketAddress (socket_address);
} else if (socket_type == "localabstract" && UnixSocketAddress.abstract_names_supported ()) {
connectable = new UnixSocketAddress.with_type (socket_address, socket_address.length,
connectable = new UnixSocketAddress.with_type (socket_address.to_utf8 (),
ABSTRACT_PADDED);
}
#endif
@@ -847,10 +849,10 @@ namespace Frida.Droidy {
throw new Error.NOT_SUPPORTED ("%s", e.message);
}
return new Client (stream);
return new AdbClient (stream);
}
public Client (IOStream stream) {
public AdbClient (IOStream stream) {
Object (stream: stream);
}
@@ -896,10 +898,10 @@ namespace Frida.Droidy {
yield request_with_type (message, RequestType.PROTOCOL_CHANGE, cancellable);
}
public async SyncSession request_sync_session (Cancellable? cancellable = null) throws Error, IOError {
public async AdbSyncSession request_sync_session (Cancellable? cancellable = null) throws Error, IOError {
yield request ("sync:", cancellable);
var session = new SyncSession (this);
var session = new AdbSyncSession (this);
PendingResponse pending = null;
pending = new PendingResponse (RequestType.SYNC, () => {
@@ -1154,15 +1156,15 @@ namespace Frida.Droidy {
}
}
public sealed class SyncSession : Object {
public Client client {
public sealed class AdbSyncSession : Object {
public AdbClient client {
get;
construct;
}
private Promise<bool> io_request = new Promise<bool> ();
public SyncSession (Client client) {
public AdbSyncSession (AdbClient client) {
Object (client: client);
}
+5 -14
View File
@@ -1,3 +1,5 @@
using Efni;
namespace Frida {
public sealed class AgentContainer : Object, AgentSessionProvider {
public DBusConnection connection {
@@ -9,9 +11,7 @@ namespace Frida {
[CCode (has_target = false)]
private delegate void AgentMainFunc (string data, ref UnloadPolicy unload_policy, void * injector_state);
private AgentMainFunc main_impl;
#if LINUX
private FileDescriptor agent_ctrlfd_for_peer;
#else
#if !LINUX
private PipeTransport transport;
#endif
private string transport_address;
@@ -38,8 +38,7 @@ namespace Frida {
if (Posix.socketpair (Posix.AF_UNIX, Posix.SOCK_STREAM, 0, agent_ctrlfds) != 0)
throw new Error.NOT_SUPPORTED ("Unable to allocate socketpair");
var agent_ctrlfd = new FileDescriptor (agent_ctrlfds[0]);
container.agent_ctrlfd_for_peer = new FileDescriptor (agent_ctrlfds[1]);
container.transport_address = "";
container.transport_address = "socket:%d".printf (agent_ctrlfds[1]);
try {
Socket socket = new Socket.from_fd (agent_ctrlfd.handle);
@@ -114,18 +113,10 @@ namespace Frida {
private bool run () {
UnloadPolicy unload_policy = IMMEDIATE;
void * injector_state = null;
#if LINUX
var s = LinuxInjectorState ();
s.frida_ctrlfd = -1;
s.agent_ctrlfd = agent_ctrlfd_for_peer.steal ();
injector_state = &s;
#endif
string agent_parameters = transport_address + "|exit-monitor:off|thread-suspend-monitor:off";
main_impl (agent_parameters, ref unload_policy, injector_state);
main_impl (agent_parameters, ref unload_policy, null);
return true;
}
+1 -1
View File
@@ -11,7 +11,7 @@ helper.dex: build/frida-helper.jar
$<
mv build/classes.dex $@
build/frida-helper.jar: re/frida/Helper.java
build/frida-helper.jar: re/frida/Helper.java re/frida/HelperBackend.java
javac \
-cp .:$(android_jar) \
-bootclasspath $(android_jar) \
+163 -63
View File
@@ -1,66 +1,96 @@
namespace Frida {
public sealed class AndroidHelperClient : Object, AsyncInitable {
public signal void closed ();
using Efni;
public IOStream stream {
namespace Frida {
public sealed class AndroidHelperClient : Object {
public AndroidHelperTransport transport {
get;
construct;
}
private BufferedInputStream input;
private OutputStream output;
private Cancellable io_cancellable = new Cancellable ();
private AdbShellSession? shell;
private State state = CLOSED;
private Gee.Queue<Request> pending_requests = new Gee.ArrayQueue<Request> ();
private ByteArray pending_output = new ByteArray ();
private bool writing = false;
private enum State {
CLOSED,
OPEN
public AndroidHelperClient (AndroidHelperTransport transport) {
Object (transport: transport);
}
private const uint32 MAX_RESPONSE_SIZE = 100 * 1024 * 1024;
public static async AndroidHelperClient open (string serial, Cancellable? cancellable) throws Error, IOError {
string instance_id = Uuid.string_random ().replace ("-", "");
string helper_path = "/data/local/tmp/frida-helper-" + instance_id + ".dex";
public AndroidHelperClient (IOStream stream) {
Object (stream: stream);
}
var helper_dex = new MemoryInputStream.from_bytes (
new Bytes.static (Frida.Data.Android.get_helper_dex_blob ().data));
var helper_meta = new AdbFileMetadata ();
helper_meta.mode = 0100644;
helper_meta.time_modified = new DateTime.now_utc ();
yield AdbFileSync.send (helper_dex, helper_meta, helper_path, serial, cancellable);
construct {
input = (BufferedInputStream) Object.new (typeof (BufferedInputStream),
"base-stream", stream.get_input_stream (),
"close-base-stream", false,
"buffer-size", 128 * 1024);
output = stream.get_output_stream ();
var shell = new AdbShellSession ();
try {
var output = new StringBuilder ();
bool waiting = false;
var output_handler = shell.output.connect ((pipe, bytes) => {
if (pipe == STDOUT) {
output.append ((string) bytes.get_data ());
if (waiting)
open.callback ();
}
});
try {
yield shell.open (serial, cancellable);
state = OPEN;
shell.send_command (("CLASSPATH=%s app_process /data/local/tmp " +
"--nice-name=re.frida.helper re.frida.Helper %s; rm -f %s; echo BYE.")
.printf (helper_path, instance_id, helper_path));
process_incoming_responses.begin ();
while (!output.str.has_prefix ("READY.\n")) {
waiting = true;
yield;
waiting = false;
if (output.str.has_prefix ("BYE.\n"))
throw new Error.NOT_SUPPORTED ("Unable to start helper");
}
} finally {
shell.disconnect (output_handler);
}
var client = yield AdbClient.open (cancellable);
try {
yield client.request ("host:transport:" + serial, cancellable);
yield client.request_protocol_change ("localabstract:/frida-helper-" + instance_id,
cancellable);
} catch (GLib.Error e) {
client.close.begin ();
throw e;
}
var helper = new AndroidHelperClient (new AndroidHelperStreamTransport (client.stream));
helper.shell = shell;
helper.transport.closed.connect (helper.release_shell);
return helper;
} catch (GLib.Error e) {
shell.close.begin ();
if (e is IOError.CANCELLED)
throw (IOError) e;
throw new Error.NOT_SUPPORTED ("%s", e.message);
}
}
public async void close (Cancellable? cancellable) throws IOError {
io_cancellable.cancel ();
yield transport.close (cancellable);
var source = new IdleSource ();
source.set_callback (close.callback);
source.attach (MainContext.get_thread_default ());
yield;
try {
yield stream.close_async (Priority.DEFAULT, cancellable);
} catch (IOError e) {
if (shell != null) {
yield shell.close (cancellable);
shell = null;
}
ensure_closed ();
}
private void ensure_closed () {
if (state == CLOSED)
return;
state = CLOSED;
closed ();
private void release_shell () {
if (shell != null) {
shell.close.begin ();
shell = null;
}
}
public async HostApplicationInfo get_frontmost_application (FrontmostQueryOptions options,
@@ -72,12 +102,12 @@ namespace Frida {
.add_string_value (options.scope.to_nick ())
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
if (response.is_null ())
return HostApplicationInfo.empty ();
Json.Reader reader = new Json.Reader (response);
Json.Reader reader = make_json_reader_taking_node ((owned) response);
string? identifier = null;
string? name = null;
@@ -127,9 +157,9 @@ namespace Frida {
.add_string_value (options.scope.to_nick ())
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
Json.Reader reader = new Json.Reader (response);
Json.Reader reader = make_json_reader_taking_node ((owned) response);
int num_apps = reader.count_elements ();
if (num_apps == -1)
@@ -193,9 +223,9 @@ namespace Frida {
.add_string_value (options.scope.to_nick ())
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
Json.Reader reader = new Json.Reader (response);
Json.Reader reader = make_json_reader_taking_node ((owned) response);
int num_processes = reader.count_elements ();
if (num_processes == -1)
@@ -247,9 +277,9 @@ namespace Frida {
.add_int_value (uid)
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
var r = new Json.Reader (response);
var r = make_json_reader_taking_node ((owned) response);
parse_envelope_or_throw (r);
r.read_element (1);
@@ -317,9 +347,9 @@ namespace Frida {
.add_int_value (uid)
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
var r = new Json.Reader (response);
var r = make_json_reader_taking_node ((owned) response);
parse_envelope_or_throw (r);
}
@@ -331,9 +361,9 @@ namespace Frida {
.add_int_value ((int) pid)
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
var r = new Json.Reader (response);
var r = make_json_reader_taking_node ((owned) response);
parse_envelope_or_throw (r);
r.read_element (1);
@@ -343,6 +373,13 @@ namespace Frida {
return r.get_boolean_value ();
}
private async void request_ok (Json.Builder stanza, Cancellable? cancellable) throws Error, IOError {
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
var r = make_json_reader_taking_node ((owned) response);
parse_envelope_or_throw (r);
}
private void parse_envelope_or_throw (Json.Reader r) throws Error {
r.read_element (0);
string? kind = r.get_string_value ();
@@ -452,6 +489,76 @@ namespace Frida {
throw new Error.PROTOCOL ("Unexpected JSON type: %s", type.name ());
}
}
public interface AndroidHelperTransport : Object {
public signal void closed ();
public abstract async void close (Cancellable? cancellable) throws IOError;
public abstract async Json.Node request (Json.Node stanza, Cancellable? cancellable) throws Error, IOError;
}
public sealed class AndroidHelperStreamTransport : Object, AndroidHelperTransport {
public IOStream stream {
get;
construct;
}
private BufferedInputStream input;
private OutputStream output;
private Cancellable io_cancellable = new Cancellable ();
private State state = CLOSED;
private Gee.Queue<Request> pending_requests = new Gee.ArrayQueue<Request> ();
private ByteArray pending_output = new ByteArray ();
private bool writing = false;
private enum State {
CLOSED,
OPEN
}
private const uint32 MAX_RESPONSE_SIZE = 100 * 1024 * 1024;
public AndroidHelperStreamTransport (IOStream stream) {
Object (stream: stream);
}
construct {
input = (BufferedInputStream) Object.new (typeof (BufferedInputStream),
"base-stream", stream.get_input_stream (),
"close-base-stream", false,
"buffer-size", 128 * 1024);
output = stream.get_output_stream ();
state = OPEN;
process_incoming_responses.begin ();
}
public async void close (Cancellable? cancellable) throws IOError {
io_cancellable.cancel ();
var source = new IdleSource ();
source.set_callback (close.callback);
source.attach (MainContext.get_thread_default ());
yield;
try {
yield stream.close_async (Priority.DEFAULT, cancellable);
} catch (IOError e) {
}
ensure_closed ();
}
private void ensure_closed () {
if (state == CLOSED)
return;
state = CLOSED;
closed ();
}
public async Json.Node request (Json.Node stanza, Cancellable? cancellable) throws Error, IOError {
if (state == CLOSED)
@@ -487,13 +594,6 @@ namespace Frida {
return r.response;
}
private async void request_ok (Json.Builder stanza, Cancellable? cancellable) throws Error, IOError {
Json.Node response = yield request (stanza.get_root (), cancellable);
var r = new Json.Reader (response);
parse_envelope_or_throw (r);
}
private void write_request (Json.Node request) {
string request_str = Json.to_string (request, false);
uint8[] request_data = request_str.data;
Binary file not shown.
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+484 -50
View File
@@ -21,59 +21,60 @@ CORE_TAG_FIELD = f"{{{CORE_NAMESPACE}}}field"
CORE_TAG_CONSTRUCTOR = f"{{{CORE_NAMESPACE}}}constructor"
CORE_TAG_METHOD = f"{{{CORE_NAMESPACE}}}method"
OBJECT_TYPE_PATTERN = re.compile(r"\bpublic\s+(sealed )?(class|interface)\s+(\w+)\b")
INTERNAL_INTERFACES = {
"Frida.HostSessionHub",
"Frida.AgentMessageSink",
"FridaBase.AgentMessageSink",
"Json.Serializable",
}
OBJECT_TYPE_PATTERN = re.compile(r"\bpublic\s+(sealed |abstract )?(class|interface)\s+(\w+)\b")
TOPLEVEL_NAMES = [
"frida.vala",
"linux-kernel-image.vala",
"xnu-kernelcache.vala",
"package-manager.vala",
"control-service.vala",
"portal-service.vala",
"file-monitor.vala",
Path("compiler") / "compiler.vala",
]
def main():
parser = argparse.ArgumentParser(description="Generate refined Frida API definitions")
parser.add_argument('--output', dest='output_type', choices=['bundle', 'header', 'gir', 'vapi', 'vapi-stamp'], default='bundle')
parser.add_argument('frida_version', metavar='frida-version', type=str)
parser.add_argument('frida_major_version', metavar='frida-major-version', type=str)
parser.add_argument('frida_minor_version', metavar='frida-minor-version', type=str)
parser.add_argument('frida_micro_version', metavar='frida-micro-version', type=str)
parser.add_argument('frida_nano_version', metavar='frida-nano-version', type=str)
parser.add_argument('api_version', metavar='api-version', type=str)
parser.add_argument('core_header', metavar='/path/to/frida-core.h', type=argparse.FileType('r', encoding='utf-8'))
parser.add_argument('core_gir', metavar='/path/to/Frida-x.y.gir', type=argparse.FileType('r', encoding='utf-8'))
parser.add_argument('core_vapi', metavar='/path/to/frida-core.vapi', type=argparse.FileType('r', encoding='utf-8'))
parser.add_argument('base_header', metavar='/path/to/frida-base.h', type=argparse.FileType('r', encoding='utf-8'))
parser.add_argument('base_gir', metavar='/path/to/FridaBase-x.y.gir', type=argparse.FileType('r', encoding='utf-8'))
parser.add_argument('base_vapi', metavar='/path/to/frida-base.vapi', type=argparse.FileType('r', encoding='utf-8'))
parser.add_argument('--output', dest='output_type', choices=['bundle', 'header', 'gir', 'vapi', 'vapi-stamp', 'symbol-maps'], default='bundle')
parser.add_argument('output_dir', metavar='/output/dir')
parser.add_argument('extra_args', nargs='*')
args = parser.parse_args()
output_type = args.output_type
frida_version = args.frida_version
frida_version_components = (
args.frida_major_version,
args.frida_minor_version,
args.frida_micro_version,
args.frida_nano_version,
)
api_version = args.api_version
core_header = args.core_header.read()
core_gir = args.core_gir.read()
core_vapi = args.core_vapi.read()
base_header = args.base_header.read()
base_gir = args.base_gir.read()
base_vapi = args.base_vapi.read()
output_dir = Path(args.output_dir)
if output_type == 'symbol-maps':
emit_symbol_maps_from_source(output_dir)
return
extra = args.extra_args
frida_version = extra[0]
frida_version_components = tuple(extra[1:5])
api_version = extra[5]
if output_type == 'vapi-stamp':
(output_dir / f"frida-core-{api_version}.vapi.stamp").write_bytes(b"")
return
toplevel_names = [
"frida.vala",
"package-manager.vala",
"control-service.vala",
"portal-service.vala",
"file-monitor.vala",
Path("compiler") / "compiler.vala",
]
core_header = Path(extra[6]).read_text(encoding='utf-8')
core_gir = Path(extra[7]).read_text(encoding='utf-8')
core_vapi = Path(extra[8]).read_text(encoding='utf-8')
base_header = Path(extra[9]).read_text(encoding='utf-8')
base_gir = Path(extra[10]).read_text(encoding='utf-8')
base_vapi = Path(extra[11]).read_text(encoding='utf-8')
toplevel_sources = []
src_dir = Path(__file__).parent.parent.resolve()
for name in toplevel_names:
for name in TOPLEVEL_NAMES:
toplevel_sources.append((src_dir / name).read_text(encoding='utf-8'))
toplevel_code = "\n".join(toplevel_sources)
@@ -97,7 +98,7 @@ def main():
emit_header(api, output_dir)
if enable_gir:
emit_gir(api, core_gir, base_gir, output_dir)
emit_gir(api, core_gir, base_gir, output_dir, build_doc_index(src_dir))
if enable_vapi:
emit_vapi(api, output_dir)
@@ -139,6 +140,7 @@ def emit_header(api, output_dir):
output_header_file.write("\n\n/* Library lifetime */")
output_header_file.write("\nvoid frida_init (void);")
output_header_file.write("\nvoid frida_init_with_runtime (FridaRuntime runtime);")
output_header_file.write("\nvoid frida_shutdown (void);")
output_header_file.write("\nvoid frida_deinit (void);")
output_header_file.write("\nGMainContext * frida_get_main_context (void);")
@@ -203,7 +205,7 @@ def emit_header(api, output_dir):
output_header_file.write("\n\n#endif\n")
def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> str:
def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path, docs: DocIndex) -> str:
ET.register_namespace("", CORE_NAMESPACE)
ET.register_namespace("c", C_NAMESPACE)
ET.register_namespace("glib", GLIB_NAMESPACE)
@@ -238,10 +240,10 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> st
for elem in core_elements + base_elements:
if tag_name == "class":
for child in list(elem):
if (child.tag == CORE_TAG_IMPLEMENTS and child.get("name") in {"Frida.HostSessionHub", "Frida.AgentMessageSink",
"FridaBase.AgentMessageSink"}) \
if (child.tag == CORE_TAG_IMPLEMENTS and child.get("name") in INTERNAL_INTERFACES) \
or child.tag == CORE_TAG_FIELD \
or child.get("name").startswith("_"):
or child.get("name").startswith("_") \
or (child.tag == CORE_TAG_CONSTRUCTOR and is_async_constructor(child)):
elem.remove(child)
merged_namespace.append(elem)
@@ -249,6 +251,38 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> st
merge_and_transform_elements("interface", object_type_names)
merge_and_transform_elements("enumeration", enum_type_names | error_type_names)
function_names = {func.name for func in api.functions}
for source_root in [core_root, base_root]:
source_namespace = source_root.find("namespace", GIR_NAMESPACES)
for function in source_namespace.findall("function", GIR_NAMESPACES):
if function.get("name") in function_names:
merged_namespace.append(function)
for source_root in [core_root, base_root]:
for record in source_root.findall(".//record", GIR_NAMESPACES):
owner = record.get(f"{{{GLIB_NAMESPACE}}}is-gtype-struct-for")
if owner is not None and owner in object_type_names:
merged_namespace.append(record)
known_names = object_type_names | enum_type_names | error_type_names
referenced_names = set()
for elem in merged_namespace.iter():
type_name = elem.get("name")
if type_name is None:
continue
for prefix in ("Frida.", "FridaBase."):
if type_name.startswith(prefix):
referenced_names.add(type_name[len(prefix):])
break
needed_names = referenced_names - known_names
for source_root in [core_root, base_root]:
source_namespace = source_root.find("namespace", GIR_NAMESPACES)
for callback in source_namespace.findall("callback", GIR_NAMESPACES):
if callback.get("name") in needed_names:
merged_namespace.append(callback)
inject_documentation(merged_namespace, docs)
ET.indent(merged_root, space=" ")
result = ET.tostring(merged_root,
encoding="unicode",
@@ -260,15 +294,347 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> st
def filter_elements(elements: List[ET.Element], spec_set: Set[str]):
return [elem for elem in elements if elem.get("name") in spec_set]
def is_async_constructor(constructor: ET.Element) -> bool:
for type_elem in constructor.iter(f"{{{CORE_NAMESPACE}}}type"):
if type_elem.get("name") == "Gio.Cancellable":
return True
return False
@dataclass
class DocComment:
body: str
params: dict
returns: str
filename: str = "<generated>"
line: int = 0
@dataclass
class DocIndex:
types: dict # type_name -> DocComment
members: dict # (type_name, member_name) -> DocComment
enum_members: dict # (enum_name, member_name) -> DocComment
DOC_TYPE_PATTERN = re.compile(r"public\s+(?:sealed\s+|abstract\s+)?(class|interface|enum|errordomain)\s+(\w+)")
DOC_SIGNAL_PATTERN = re.compile(r"public\s+signal\s+\S.*?\b(\w+)\s*\(")
DOC_PROPERTY_PATTERN = re.compile(r"public\s+(?:unowned\s+)?\S.*?\b(\w+)\s*\{")
DOC_METHOD_PATTERN = re.compile(r"public\s+\S.*?\b(\w+)\s*\(")
DOC_ENUM_MEMBER_PATTERN = re.compile(r"([A-Z][A-Z0-9_]*)\b")
def build_doc_index(src_dir: Path) -> DocIndex:
repo_root = src_dir.parent
sources = []
for name in TOPLEVEL_NAMES:
path = src_dir / name
if path.exists():
sources.append(path)
base_dir = src_dir.parent / "lib" / "base"
if base_dir.is_dir():
sources.extend(sorted(base_dir.rglob("*.vala")))
index = DocIndex(types={}, members={}, enum_members={})
for path in sources:
try:
filename = path.relative_to(repo_root).as_posix()
except ValueError:
filename = path.name
_collect_docs_from_source(path.read_text(encoding="utf-8"), index, filename)
return index
def _collect_docs_from_source(source: str, index: DocIndex, filename: str):
lines = source.split("\n")
pending = None
current_type = None
current_is_enum = False
i = 0
n = len(lines)
while i < n:
line = lines[i]
stripped = line.strip()
if stripped.startswith("/**"):
start_line = i + 1
block = [line]
while "*/" not in lines[i]:
i += 1
block.append(lines[i])
pending = _parse_doc_block(block)
pending.filename = filename
pending.line = start_line
i += 1
continue
if stripped == "" or stripped.startswith("//") or stripped.startswith("["):
i += 1
continue
indent = len(line) - len(line.lstrip("\t"))
type_match = DOC_TYPE_PATTERN.match(stripped)
if indent == 1 and type_match is not None:
current_type = type_match.group(2)
current_is_enum = type_match.group(1) in ("enum", "errordomain")
if pending is not None:
index.types[current_type] = pending
pending = None
i += 1
continue
if pending is not None and current_type is not None and indent >= 2:
if current_is_enum:
member_match = DOC_ENUM_MEMBER_PATTERN.match(stripped)
if member_match is not None:
index.enum_members[(current_type, member_match.group(1))] = pending
else:
name = _parse_member_name(stripped, current_type)
if name is not None:
index.members[(current_type, name)] = pending
pending = None
i += 1
continue
pending = None
i += 1
def _parse_member_name(stripped: str, type_name: str):
if not stripped.startswith("public"):
return None
signal_match = DOC_SIGNAL_PATTERN.match(stripped)
if signal_match is not None:
return signal_match.group(1)
brace = stripped.find("{")
if brace != -1 and "(" not in stripped[:brace]:
property_match = DOC_PROPERTY_PATTERN.match(stripped)
if property_match is not None:
return property_match.group(1)
ctor_match = re.match(r"public\s+" + re.escape(type_name) + r"(?:\.(\w+))?\s*\(", stripped)
if ctor_match is not None:
return "new" if ctor_match.group(1) is None else ctor_match.group(1)
method_match = DOC_METHOD_PATTERN.match(stripped)
if method_match is not None:
return method_match.group(1)
return None
def _parse_doc_block(block: List[str]) -> DocComment:
text_lines = []
for raw in block:
s = raw.strip()
if s.startswith("/**"):
s = s[3:]
if s.endswith("*/"):
s = s[:-2]
s = s.strip()
if s.startswith("*"):
s = s[1:]
if s.startswith(" "):
s = s[1:]
text_lines.append(s.rstrip())
# Valadoc block tags: "@param <name> <description>" and "@return <description>".
body_lines = []
params = {}
returns_lines = []
target = body_lines
for line in text_lines:
param_match = re.match(r"@param\s+(\w+)\s*(.*)", line)
return_match = re.match(r"@returns?\s*(.*)", line)
if param_match is not None:
params[param_match.group(1)] = [param_match.group(2)]
target = params[param_match.group(1)]
continue
if return_match is not None:
returns_lines = [return_match.group(1)]
target = returns_lines
continue
target.append(line)
def finish(lines):
return _valadoc_to_markdown("\n".join(lines).strip())
return DocComment(
body=finish(body_lines),
params={k: finish(v) for k, v in params.items()},
returns=finish(returns_lines),
)
def _valadoc_to_markdown(text: str) -> str:
if not text:
return text
# Code blocks: {{{ ... }}} -> fenced ``` blocks.
def code_block(match):
code = match.group(1).strip("\n")
return "\n```\n" + code + "\n```\n"
text = re.sub(r"\{\{\{(.*?)\}\}\}", code_block, text, flags=re.DOTALL)
# Note: {@link ...} cross-references are resolved later, at injection time,
# where the full symbol table is available (see inject_documentation).
# Inline monospace: ``x`` -> `x` (valadoc) is already markdown-compatible.
# Bold: ''x'' -> **x**.
text = re.sub(r"''(.+?)''", r"**\1**", text)
# Italic: //x// -> *x*, but leave protocol-relative URLs (://) alone.
text = re.sub(r"(?<![:/])//(?!/)(.+?)//", r"*\1*", text)
return text
def inject_documentation(merged_namespace: ET.Element, docs: DocIndex):
glib_signal_tag = f"{{{GLIB_NAMESPACE}}}signal"
callable_tags = {
f"{{{CORE_NAMESPACE}}}method",
f"{{{CORE_NAMESPACE}}}constructor",
f"{{{CORE_NAMESPACE}}}function",
f"{{{CORE_NAMESPACE}}}virtual-method",
glib_signal_tag,
}
property_tag = f"{{{CORE_NAMESPACE}}}property"
member_tag = f"{{{CORE_NAMESPACE}}}member"
doc_tag = f"{{{CORE_NAMESPACE}}}doc"
# Build a symbol table so {@link ...} can be turned into real gi-docgen
# cross-references. Keys use Vala-style names (underscores); values carry the
# gi-docgen link kind and the GIR name (dashes for properties/signals).
type_kinds = {} # type_name -> gi-docgen kind ("class"/"iface"/"enum"/"error")
member_kinds = {} # (type_name, vala_member) -> (kind, gir_name)
for type_elem in list(merged_namespace):
type_name = type_elem.get("name")
if type_name is None:
continue
tag = type_elem.tag.split("}")[-1]
if tag == "class":
type_kinds[type_name] = "class"
elif tag == "interface":
type_kinds[type_name] = "iface"
elif tag == "enumeration":
type_kinds[type_name] = "error" \
if type_elem.get(f"{{{GLIB_NAMESPACE}}}error-domain") is not None \
else "enum"
for child in list(type_elem):
cname = child.get("name")
if cname is None:
continue
if child.tag == f"{{{CORE_NAMESPACE}}}method":
member_kinds[(type_name, cname)] = ("method", cname)
elif child.tag == f"{{{CORE_NAMESPACE}}}constructor":
member_kinds[(type_name, cname)] = ("ctor", cname)
elif child.tag == property_tag:
member_kinds[(type_name, cname.replace("-", "_"))] = ("property", cname)
elif child.tag == glib_signal_tag:
member_kinds[(type_name, cname.replace("-", "_"))] = ("signal", cname)
def resolve_links(text: str) -> str:
def repl(match):
target = re.sub(r"^Frida\.", "", match.group(1).strip())
parts = target.split(".")
if len(parts) == 1:
kind = type_kinds.get(parts[0])
if kind is not None:
return f"[{kind}@Frida.{parts[0]}]"
elif len(parts) == 2:
info = member_kinds.get((parts[0], parts[1]))
if info is not None:
kind, gir_name = info
if kind == "method":
return f"[method@Frida.{parts[0]}.{gir_name}]"
if kind == "ctor":
return f"[ctor@Frida.{parts[0]}.{gir_name}]"
if kind == "property":
return f"[property@Frida.{parts[0]}:{gir_name}]"
if kind == "signal":
return f"[signal@Frida.{parts[0]}::{gir_name}]"
return "`" + target + "`"
return re.sub(r"\{@link\s+([^}]+)\}", repl, text)
def set_doc(elem: ET.Element, text: str, filename: str = "<generated>",
line: int = 0):
if not text:
return
if elem.find(doc_tag) is not None:
return
doc = ET.Element(doc_tag)
doc.set("xml:space", "preserve")
# gi-docgen accesses filename/line unconditionally, so always emit them,
# mirroring what g-ir-scanner produces for hand-written C.
doc.set("filename", filename)
doc.set("line", str(line))
doc.text = resolve_links(text)
elem.insert(0, doc)
def member_doc(type_name: str, name: str):
comment = docs.members.get((type_name, name))
if comment is None:
for suffix in ("_finish", "_sync"):
if name.endswith(suffix):
comment = docs.members.get((type_name, name[: -len(suffix)]))
if comment is not None:
break
return comment
def apply_callable(elem: ET.Element, comment: DocComment):
set_doc(elem, comment.body, comment.filename, comment.line)
if comment.returns:
rv = elem.find(f"{{{CORE_NAMESPACE}}}return-value")
if rv is not None:
set_doc(rv, comment.returns, comment.filename, comment.line)
params_elem = elem.find(f"{{{CORE_NAMESPACE}}}parameters")
if params_elem is not None and comment.params:
for param in params_elem.findall(f"{{{CORE_NAMESPACE}}}parameter"):
text = comment.params.get(param.get("name"))
if text:
set_doc(param, text, comment.filename, comment.line)
for type_elem in list(merged_namespace):
type_name = type_elem.get("name")
if type_name is None:
continue
tag = type_elem.tag.split("}")[-1]
type_comment = docs.types.get(type_name)
if type_comment is not None and tag in ("class", "interface", "enumeration"):
set_doc(type_elem, type_comment.body, type_comment.filename,
type_comment.line)
for child in list(type_elem):
child_name = child.get("name")
if child_name is None:
continue
if child.tag in callable_tags:
lookup = child_name.replace("-", "_") if child.tag == glib_signal_tag \
else child_name
comment = member_doc(type_name, lookup)
if comment is not None:
apply_callable(child, comment)
elif child.tag == f"{{{CORE_NAMESPACE}}}constructor" \
and child_name == "new":
# Vala's implicit default constructor; give it a sensible
# description rather than leaving it bare.
set_doc(child, f"Creates a new [class@Frida.{type_name}].")
elif child.tag == property_tag:
vala_name = child_name.replace("-", "_")
comment = docs.members.get((type_name, vala_name))
if comment is not None:
set_doc(child, comment.body, comment.filename, comment.line)
# The C API also exposes the property through getter/setter
# methods; give them the same description.
accessors = {"get_" + vala_name, "set_" + vala_name}
for sibling in type_elem:
if sibling.tag == f"{{{CORE_NAMESPACE}}}method" \
and sibling.get("name") in accessors:
set_doc(sibling, comment.body, comment.filename,
comment.line)
elif child.tag == member_tag:
comment = docs.enum_members.get((type_name, child_name.upper()))
if comment is not None:
set_doc(child, comment.body, comment.filename, comment.line)
def emit_vapi(api, output_dir):
with OutputFile(output_dir / f"frida-core-{api.version}.vapi") as output_vapi_file:
output_vapi_file.write("[CCode (cheader_filename = \"frida-core.h\", cprefix = \"Frida\", lower_case_cprefix = \"frida_\")]")
output_vapi_file.write("\nnamespace Frida {")
output_vapi_file.write("\n\tpublic static void init ();")
output_vapi_file.write("\n\tpublic static void init_with_runtime (Frida.Runtime runtime);")
output_vapi_file.write("\n\tpublic static void shutdown ();")
output_vapi_file.write("\n\tpublic static void deinit ();")
output_vapi_file.write("\n\tpublic static unowned GLib.MainContext get_main_context ();")
for delegate in referenced_delegates(api):
output_vapi_file.write("\n\t" + delegate)
for object_type in api.object_types:
output_vapi_file.write("\n\n\t%s" % object_type.vapi_declaration)
sections = []
@@ -303,6 +669,46 @@ def emit_vapi(api, output_dir):
output_deps_file.write("glib-2.0\n")
output_deps_file.write("gobject-2.0\n")
output_deps_file.write("gio-2.0\n")
output_deps_file.write("json-glib-1.0\n")
def referenced_delegates(api) -> List[str]:
members = []
for object_type in api.object_types:
members.extend(object_type.vapi_properties)
members.extend(object_type.vapi_methods)
members.extend(object_type.vapi_signals)
if object_type.vapi_constructor is not None:
members.append(object_type.vapi_constructor)
for func in api.functions:
members.append(func.vapi_declaration)
text = "\n".join(members)
return [declaration for name, declaration in api.delegates
if re.search(r"\bFrida\." + name + r"\b", text) is not None]
def strip_internal_interfaces(declaration: str) -> str:
head, sep, rest = declaration.partition(" : ")
if not sep:
return declaration
bases_part, brace_sep, tail = rest.partition("{")
kept = [b.strip() for b in bases_part.split(",") if b.strip() not in INTERNAL_INTERFACES]
if not kept:
return f"{head} {brace_sep}{tail}"
return f"{head} : {', '.join(kept)} {brace_sep}{tail}"
def emit_symbol_maps_from_source(output_dir: Path):
with OutputFile(output_dir / 'frida-core.version') as f:
f.write("{\n")
f.write(" global:\n")
f.write(" frida_*;\n")
f.write(" _frida_*;\n")
f.write("\n")
f.write(" local:\n")
f.write(" *;\n")
f.write("};\n")
def parse_api(frida_version, frida_version_components, api_version, toplevel_code, core_header, core_vapi, base_header, base_vapi):
all_headers = core_header + "\n" + base_header
@@ -311,6 +717,7 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
enum_types = []
base_public_types = {
"SpawnGatingOptions": "ProcessMatchOptions",
"FrontmostQueryOptions": "SpawnOptions",
"ApplicationQueryOptions": "FrontmostQueryOptions",
"ProcessQueryOptions": "ApplicationQueryOptions",
@@ -325,6 +732,9 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
"EndpointParameters": "PortalService",
"AuthenticationService": "EndpointParameters",
"StaticAuthenticationService": "AuthenticationService",
"WebRequestHandler": "StaticAuthenticationService",
"WebRequest": "WebRequestHandler",
"WebResponse": "WebRequest",
}
internal_type_prefixes = [
"AgentMessageKind",
@@ -419,7 +829,7 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
object_type.c_method_prototypes.append(method_cprototype)
elif method_name == 'get_type':
object_type.c_get_type = method_cprototype.replace("G_GNUC_CONST ;", "G_GNUC_CONST;")
for d in re.finditer(r"^typedef.+?\(\*(" + object_type.c_name + r".+?)\) \(.+\);$", core_header, re.MULTILINE):
for d in re.finditer(r"^typedef.+?\(\*(" + object_type.c_name + r".+?)\) \(.+\);$", all_headers, re.MULTILINE):
delegate_cname = d.group(1)
if delegate_cname not in seen_cdelegates:
seen_cdelegates.add(delegate_cname)
@@ -446,8 +856,7 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
if stripped_line == "}":
ignoring = False
else:
if stripped_line.startswith("public abstract") \
or stripped_line.startswith("public class Promise") \
if stripped_line.startswith("public class Promise") \
or stripped_line.startswith("public interface Future") \
or stripped_line.startswith("public class CF"):
ignoring = True
@@ -467,7 +876,7 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
ignoring = True
else:
current_object_type = object_type_by_name[name]
current_object_type.vapi_declaration = stripped_line
current_object_type.vapi_declaration = strip_internal_interfaces(stripped_line)
elif stripped_line == "}":
current_enum = None
current_object_type = None
@@ -496,20 +905,31 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
enum.vapi_declaration = m.group(1)
enum.vapi_members.extend([line.lstrip() for line in m.group(2).strip().split("\n")])
functions = [f for f in parse_vapi_functions(base_vapi) if function_is_public(f.name)]
functions = [f for f in parse_vapi_functions(core_vapi + base_vapi) if function_is_public(f.name)]
for f in functions:
m = re.search(r"^[\w\*]+ frida_{}.+?;".format(f.name), all_headers, re.MULTILINE | re.DOTALL)
f.c_prototype = beautify_cprototype(m.group(0))
m = re.search(r"^(?:VALA_EXTERN )?[\w\*]+ frida_{}.+?;".format(f.name), all_headers, re.MULTILINE | re.DOTALL)
prototype = beautify_cprototype(m.group(0))
if prototype.startswith("VALA_EXTERN "):
prototype = prototype[12:]
f.c_prototype = prototype
return ApiSpec(frida_version, frida_version_components, api_version, object_types, functions, enum_types, error_types)
delegates = [(m.group(1), m.group(0).strip())
for m in re.finditer(r"^\tpublic delegate .+?\b(\w+) \(.*\);$", core_vapi + base_vapi, re.MULTILINE)]
return ApiSpec(frida_version, frida_version_components, api_version, object_types, functions, enum_types, error_types,
delegates)
def function_is_public(name):
return not name.startswith("_") and \
not name.startswith("throw_") and \
name not in MANUALLY_DECLARED_FUNCTIONS and \
name not in {
"on_pending_garbage",
"generate_certificate",
"get_dbus_context",
"invalidate_dbus_context",
"make_json_reader",
"make_json_reader_taking_node",
"make_parameters_dict",
"compute_system_parameters",
"parse_control_address",
@@ -519,10 +939,23 @@ def function_is_public(name):
"check_kernel_version",
"get_syscall_signatures",
"get_compat32_syscall_signatures",
"get_xnu_mach_traps",
"get_xnu_bsd_syscalls",
"make_stdio_pipes",
"make_stdio_pipe",
}
MANUALLY_DECLARED_FUNCTIONS = {
"init",
"init_with_runtime",
"shutdown",
"deinit",
"get_main_context",
"unref",
"version",
"version_string",
}
def parse_vala_object_types(source) -> List[ApiObjectType]:
return [ApiObjectType(m.group(3), m.group(2)) for m in OBJECT_TYPE_PATTERN.finditer(source, re.MULTILINE)]
@@ -538,6 +971,7 @@ class ApiSpec:
functions: List[ApiFunction]
enum_types: List[ApiEnum]
error_types: List[ApiEnum]
delegates: List[tuple]
class ApiEnum:
def __init__(self, name):
+82 -39
View File
@@ -3,6 +3,7 @@ fs = import('fs')
generate_api = [python, files('generate.py')]
generate_api_args = [
meson.current_build_dir(),
frida_version,
frida_major_version.to_string(),
frida_minor_version.to_string(),
@@ -15,7 +16,6 @@ generate_api_args = [
base_build_dir / 'frida-base.h',
base_build_dir / base_gir_name,
base_build_dir / f'frida-base-@[email protected]',
meson.current_build_dir(),
]
core_api = custom_target('frida-core-api',
@@ -44,6 +44,24 @@ glib_gir = fs.copyfile('GLib-2.0.gir', install: install_glib_girs, install_dir:
gobject_gir = fs.copyfile('GObject-2.0.gir', install: install_glib_girs, install_dir: girdir)
gio_gir = fs.copyfile('Gio-2.0.gir', install: install_glib_girs, install_dir: girdir)
g_ir_compiler = find_program('g-ir-compiler', required: false)
if g_ir_compiler.found()
custom_target('frida-core-typelib',
command: [
g_ir_compiler,
'--output', '@OUTPUT@',
'--includedir=' + girdir,
'--includedir=' + meson.current_source_dir(),
'@INPUT@',
],
input: core_public_gir,
output: f'Frida-@[email protected]',
depends: core,
install: true,
install_dir: typelibdir,
)
endif
core_public_vapi_stamp = custom_target('frida-core-vapi-stamp',
input: core_public_vapi,
output: f'frida-core-@[email protected]',
@@ -54,22 +72,26 @@ core_public_vapi_stamp = custom_target('frida-core-vapi-stamp',
],
)
core_public_lib = custom_target('frida-core-library',
input: [core] + internal_libs,
output: f'libfrida-core-@[email protected]',
command: [
python,
files('merge.py'),
lib,
libtool,
ar,
meson.current_build_dir(),
'@OUTPUT@',
'@INPUT@'
],
install: true,
install_dir: get_option('libdir'),
)
if is_shared
core_public_lib = core
else
core_public_lib = custom_target('frida-core-library',
input: [core] + internal_libs,
output: f'libfrida-core-@[email protected]',
command: [
python,
files('merge.py'),
lib,
libtool,
ar,
meson.current_build_dir(),
'@OUTPUT@',
'@INPUT@'
],
install: true,
install_dir: get_option('libdir'),
)
endif
core_public_extra_headers = []
if host_os == 'android'
@@ -96,29 +118,50 @@ installed_variables = {
'frida_vapidir': vapidir,
}
core_public_dep = declare_dependency(
sources: [core_public_h, core_public_gir, glib_gir, gobject_gir, gio_gir, core_public_vapi_stamp, core_public_extra_headers],
include_directories: include_directories('.'),
link_args: backend_libs_private,
link_with: core_public_lib,
dependencies: core_deps,
variables: uninstalled_variables,
)
core_public_dep_kwargs = {
'sources': [core_public_h, core_public_gir, glib_gir, gobject_gir, gio_gir, core_public_vapi_stamp] + core_public_extra_headers,
'include_directories': include_directories('.'),
'link_with': core_public_lib,
'dependencies': core_deps,
'variables': uninstalled_variables,
}
if not is_shared
core_public_dep_kwargs += {
'link_args': backend_libs_private,
}
endif
core_public_dep = declare_dependency(kwargs: core_public_dep_kwargs)
core_public_reqs = ['glib-2.0', 'gobject-2.0', 'gio-2.0', 'json-glib-1.0']
if host_os_family == 'windows'
core_public_reqs += 'gio-windows-2.0'
else
core_public_reqs += 'gio-unix-2.0'
endif
pkg = import('pkgconfig')
pkg.generate(
filebase: f'frida-core-@api_version@',
name: 'Frida Core Library',
version: frida_version,
description: 'Instrumentation toolkit for developers, reverse-engineers, and security researchers',
requires: ['glib-2.0', 'gobject-2.0', 'gio-2.0', 'json-glib-1.0'],
requires_private: ['gmodule-2.0', 'gee-0.8', 'libsoup-3.0', 'frida-gum-1.0', 'frida-gumjs-inspector-1.0', 'libbrotlidec'] + backend_reqs_private,
subdirs: f'frida-@api_version@',
libraries: core_public_lib,
libraries_private: backend_libs_private,
extra_cflags: ['-I${frida_includedir}'],
variables: installed_variables,
uninstalled_variables: uninstalled_variables,
)
pkg_kwargs = {
'filebase': f'frida-core-@api_version@',
'name': 'Frida Core Library',
'version': frida_version,
'description': 'Instrumentation toolkit for developers, reverse-engineers, and security researchers',
'requires': core_public_reqs,
'subdirs': f'frida-@api_version@',
'extra_cflags': ['-I${frida_includedir}'],
'variables': installed_variables,
'uninstalled_variables': uninstalled_variables,
}
if is_shared
pkg_kwargs += {
'libraries': ['-L${libdir}', f'-lfrida-core-@api_version@'],
}
else
pkg_kwargs += {
'libraries': core_public_lib,
'requires_private': ['gmodule-2.0', 'gee-0.8', 'libsoup-3.0', 'efni-1.0', 'frida-gum-1.0', 'frida-gumjs-inspector-1.0', 'libbrotlidec'] + backend_reqs_private,
'libraries_private': backend_libs_private,
}
endif
pkg.generate(kwargs: pkg_kwargs)
meson.override_dependency(f'frida-core-@api_version@', core_public_dep)
File diff suppressed because it is too large. Load diff
+4 -4
View File
@@ -13,9 +13,9 @@ dependencies = [
[[package]]
name = "bindgen"
version = "0.72.0"
version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags",
"cexpr",
@@ -63,9 +63,9 @@ checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "clang-sys"
version = "1.8.1"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
dependencies = [
"glob",
"libc",
+25
View File
@@ -4,6 +4,31 @@ version = "1.0.0"
edition = "2024"
build = "build.rs"
[lib]
name = "frida_barebone_agent"
path = "src/lib.rs"
crate-type = ["rlib", "staticlib"]
# The XNU flavour is a freestanding executable the host injects and calls into.
# The Linux flavour is the staticlib above, linked into a .ko by linux/Kbuild.
[[bin]]
name = "frida-barebone-agent"
path = "src/main.rs"
required-features = ["blob"]
[features]
# Flavors the host injects and calls into, as opposed to the module Linux loads.
blob = []
# The XNU flavour itself, and the two ways it is packaged: a blob the host injects
# into a running kernel, and a kext that kernel loads.
xnu-core = []
xnu = ["xnu-core", "blob"]
xnu-kext = ["xnu-core"]
win9x = ["blob"]
winnt = ["blob"]
linux = []
linux-injected = ["blob"]
[profile.release]
panic = "abort"
opt-level = "z"
+63 -17
View File
@@ -1,30 +1,75 @@
## Getting the C toolchain
## Getting the SDK and the devkit
npm install -g xpm
xpm install
export PATH=$PWD/xpacks/.bin:$PATH
The agent is built against the bare soft-float SDK, which carries picolibc and the
compiler runtime, and against a GumJS devkit built for the same machine. Which machine
is the target's, listed under Targets below; this walks through the XNU one. The SDK
is published prebuilt, and the devkit is built from frida-gum:
## How to build Gum
machine=none-arm64-softfloat_pic
./configure \
--host=aarch64-none-elf \
--enable-gumjs \
--with-devkits=gum,gumjs \
--with-devkit-symbol-scope=original
make
export GUMJS_DEVKIT_DIR=$PWD/build/bindings/gumjs/devkit
sdk=~/sdk-$machine
releng/deps.py sync sdk $machine $sdk
gum=~/src/frida-gum
mkdir -p $gum/build/$machine
(cd $gum/build/$machine \
&& CC=clang CXX=clang++ ../../configure --host=$machine --enable-gumjs \
--with-devkits=gumjs --with-devkit-symbol-scope=original \
&& make)
## Building
export PATH=$PWD/xpacks/.bin:$PATH
export CC_aarch64_unknown_none=aarch64-none-elf-gcc
export AR_aarch64_unknown_none=aarch64-none-elf-ar
export RANLIB_aarch64_unknown_none=aarch64-none-elf-ranlib
export GUMJS_DEVKIT_DIR=$gum/build/$machine/bindings/gumjs/devkit
export CC="clang -I$sdk/include -I$sdk/include/glib-2.0 -I$sdk/lib/glib-2.0/include \
-I$sdk/include/capstone -I$sdk/include/json-glib-1.0"
export RUSTFLAGS="-L native=$sdk/lib"
RUSTC_BOOTSTRAP=1 cargo build --release --features xnu \
--target ./aarch64-unknown-none-pic.json -Z json-target-spec \
--target-dir target-xnu -Z build-std=core,alloc,compiler_builtins
Every flavor names its target by path, as Rust has no built-in one for a
position-independent bare-metal image. The others differ only in feature, machine
and target spec, listed under Targets below.
## Targets
The agent supports four kernels. One of them has to be picked; none is a default,
since none follows from the host you build on:
- `xnu` — a freestanding ELF executable that the Barebone backend
injects into a running kernel from the outside, over a remote stub.
- `win9x` — the same, for a 32-bit Windows 9x guest.
- `winnt` — the same, for a Windows NT guest, either word size, on x86 or arm64.
- `linux` — a static library that becomes a kernel module, loaded from the
inside. See `linux/README.md`.
Each flavor has an SDK and a devkit of its own, since the addressing differs: an
image the host places goes in position-independent, a Linux module is absolute,
and NT's 64-bit half is called in the Microsoft ABI.
| Flavor | Machine | Rust target |
|---------|-----------------------------------|----------------------------------|
| `xnu` | `none-arm64-softfloat_pic` | `aarch64-unknown-none-pic.json` |
| `win9x` | `none-x86-softfloat_pic` | `i686-unknown-none-pic.json` |
| `winnt` | `none-x86-softfloat_pic` | `i686-unknown-none-pic.json` |
| `winnt` | `none-x86_64-softfloat_pic_msabi` | `x86_64-unknown-none-pic.json` |
| `winnt` | `none-arm64-softfloat_pic` | `aarch64-unknown-none-pic.json` |
| `linux` | `none-<arch>-softfloat_nopic` | see `linux/README.md` |
Everything above the `kernel` module is shared; each backend supplies the same
set of primitives (logging, allocation, threads, waiting, time, symbols) plus its
half of Gum's platform backend (`gum_xnu.rs` / `gum_windows.rs` / `gum_linux.rs`).
## Development loop
export FRIDA_BAREBONE_CONFIG=$PWD/etc/xnu.json
cargo build --release && make -C ~/src/frida-python && killall -9 qemu-system-aarch64 && sleep 2 && frida -D barebone -p 0
RUSTC_BOOTSTRAP=1 cargo build --release --features xnu \
--target ./aarch64-unknown-none-pic.json -Z json-target-spec \
--target-dir target-xnu -Z build-std=core,alloc,compiler_builtins \
&& make -C ~/src/frida-python \
&& killall -9 qemu-system-aarch64 && sleep 2 \
&& frida -D barebone -p 0
## Speeding up loop
@@ -32,6 +77,7 @@
-- \
-Dfrida-core:compat=disabled \
-Dfrida-core:local_backend=disabled \
-Dfrida-core:simmy_backend=disabled \
-Dfrida-core:fruity_backend=disabled \
-Dfrida-core:droidy_backend=disabled \
-Dfrida-core:socket_backend=disabled \
@@ -0,0 +1,21 @@
{
"abi": "softfloat",
"arch": "aarch64",
"crt-objects-fallback": "false",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32",
"disable-redzone": true,
"features": "+v8a,+strict-align,-neon,+reserve-x18",
"linker": "rust-lld",
"linker-flavor": "gnu-lld",
"llvm-target": "aarch64-unknown-none",
"max-atomic-width": 128,
"panic-strategy": "abort",
"plt-by-default": false,
"position-independent-executables": true,
"relocation-model": "pic",
"rustc-abi": "softfloat",
"stack-probes": {
"kind": "inline"
},
"target-pointer-width": 64
}
+150
View File
@@ -0,0 +1,150 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rel.dyn : {
_agent_relocs_start = .;
*(.rel.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(4) {
. = ALIGN(4);
__printk = .; . += 4;
_printk = .; . += 4;
_panic = .; . += 4;
_kthread_create_on_node = .; . += 4;
_wake_up_process = .; . += 4;
_fget = .; . += 4;
_kernel_read = .; . += 4;
_filp_open = .; . += 4;
_kernel_write = .; . += 4;
___kmalloc_noprof = .; . += 4;
_kmalloc = .; . += 4;
___kmalloc = .; . += 4;
_kfree = .; . += 4;
_vmalloc = .; . += 4;
_execmem_alloc = .; . += 4;
_set_memory_rw = .; . += 4;
_set_memory_ro = .; . += 4;
_set_memory_x = .; . += 4;
_set_memory_nx = .; . += 4;
_modules = .; . += 4;
_register_module_notifier = .; . += 4;
_unregister_module_notifier = .; . += 4;
_execmem_free = .; . += 4;
_module_alloc = .; . += 4;
_module_memfree = .; . += 4;
___init_waitqueue_head = .; . += 4;
_prepare_to_wait_event = .; . += 4;
_finish_wait = .; . += 4;
___wake_up = .; . += 4;
_autoremove_wake_function = .; . += 4;
_schedule_hrtimeout_range = .; . += 4;
_pci_get_domain_bus_and_slot = .; . += 4;
_pci_irq_vector = .; . += 4;
_of_irq_parse_and_map_pci = .; . += 4;
_pci_dev_put = .; . += 4;
_schedule = .; . += 4;
_ktime_get_mono_fast_ns = .; . += 4;
_ktime_get_real_ts64 = .; . += 4;
_getnstimeofday64 = .; . += 4;
_send_sig = .; . += 4;
_free_irq = .; . += 4;
_request_threaded_irq = .; . += 4;
_vmap = .; . += 4;
___pv_offset = .; . += 4;
_pgprot_kernel = .; . += 4;
_ioremap = .; . += 4;
_of_find_node_opts_by_path = .; . += 4;
_of_irq_get = .; . += 4;
_of_node_put = .; . += 4;
_init_task = .; . += 4;
_tasklist_lock = .; . += 4;
__raw_read_lock = .; . += 4;
__raw_read_unlock = .; . += 4;
__raw_read_lock_irqsave = .; . += 4;
__raw_read_unlock_irqrestore = .; . += 4;
__raw_write_lock = .; . += 4;
__raw_write_unlock = .; . += 4;
__raw_write_lock_irqsave = .; . += 4;
__raw_write_unlock_irqrestore = .; . += 4;
_detach_pid = .; . += 4;
_free_pids = .; . += 4;
_get_task_exe_file = .; . += 4;
_file_path = .; . += 4;
_fput = .; . += 4;
_copy_from_kernel_nofault = .; . += 4;
_get_task_mm = .; . += 4;
_kthread_use_mm = .; . += 4;
_kthread_unuse_mm = .; . += 4;
_mmput = .; . += 4;
_vm_mmap = .; . += 4;
_vm_munmap = .; . += 4;
_tracepoint_probe_register = .; . += 4;
_tracepoint_probe_unregister = .; . += 4;
___tracepoint_sched_process_fork = .; . += 4;
___tracepoint_sched_process_exit = .; . += 4;
___tracepoint_sched_process_exec = .; . += 4;
___tracepoint_task_rename = .; . += 4;
___set_task_comm = .; . += 4;
_vunmap = .; . += 4;
_arm_copy_to_user = .; . += 4;
_down_read = .; . += 4;
_up_read = .; . += 4;
_get_user_pages_unlocked = .; . += 4;
_get_user_pages_remote = .; . += 4;
_user_mode_thread = .; . += 4;
_fget_task = .; . += 4;
_find_task_by_vpid = .; . += 4;
___start_BTF = .; . += 4;
___stop_BTF = .; . += 4;
_call_usermodehelper_setup = .; . += 4;
_call_usermodehelper_exec = .; . += 4;
_arm_copy_from_user = .; . += 4;
_fixup_exception = .; . += 4;
_init_net = .; . += 4;
_sock_create_kern = .; . += 4;
_kernel_connect = .; . += 4;
_kernel_sendmsg = .; . += 4;
_kernel_recvmsg = .; . += 4;
_sock_release = .; . += 4;
_patch_text = .; . += 4;
}
/* Everything above is only ever read, so a process being instrumented can be shown the very
same pages; everything below it each copy needs its own of. */
. = ALIGN(4096);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
}
. = ALIGN(4);
_heap_start = .;
}
VERSION {
{
global:
_start;
local:
*;
};
}
+156
View File
@@ -0,0 +1,156 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rela.dyn : {
_agent_relocs_start = .;
*(.rela.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(8) {
. = ALIGN(8);
__printk = .; . += 8;
_printk = .; . += 8;
_panic = .; . += 8;
_kthread_create_on_node = .; . += 8;
_wake_up_process = .; . += 8;
_fget = .; . += 8;
_kernel_read = .; . += 8;
_filp_open = .; . += 8;
_kernel_write = .; . += 8;
___kmalloc_noprof = .; . += 8;
_kmalloc = .; . += 8;
___kmalloc = .; . += 8;
_kfree = .; . += 8;
_vmalloc = .; . += 8;
_execmem_alloc = .; . += 8;
_set_memory_rw = .; . += 8;
_set_memory_ro = .; . += 8;
_set_memory_x = .; . += 8;
_set_memory_nx = .; . += 8;
_modules = .; . += 8;
_register_module_notifier = .; . += 8;
_unregister_module_notifier = .; . += 8;
_execmem_free = .; . += 8;
_module_alloc = .; . += 8;
_module_memfree = .; . += 8;
___init_waitqueue_head = .; . += 8;
_prepare_to_wait_event = .; . += 8;
_finish_wait = .; . += 8;
___wake_up = .; . += 8;
_autoremove_wake_function = .; . += 8;
_schedule_hrtimeout_range = .; . += 8;
_pci_get_domain_bus_and_slot = .; . += 8;
_pci_irq_vector = .; . += 8;
_of_irq_parse_and_map_pci = .; . += 8;
_pci_dev_put = .; . += 8;
_schedule = .; . += 8;
_ktime_get_mono_fast_ns = .; . += 8;
_ktime_get_real_ts64 = .; . += 8;
_getnstimeofday64 = .; . += 8;
_send_sig = .; . += 8;
_free_irq = .; . += 8;
_request_threaded_irq = .; . += 8;
_generic_ioremap_prot = .; . += 8;
___ioremap = .; . += 8;
_vmap = .; . += 8;
_memstart_addr = .; . += 8;
_init_task = .; . += 8;
_tasklist_lock = .; . += 8;
__raw_read_lock = .; . += 8;
__raw_read_unlock = .; . += 8;
__raw_read_lock_irqsave = .; . += 8;
__raw_read_unlock_irqrestore = .; . += 8;
__raw_write_lock = .; . += 8;
__raw_write_unlock = .; . += 8;
__raw_write_lock_irqsave = .; . += 8;
__raw_write_unlock_irqrestore = .; . += 8;
_detach_pid = .; . += 8;
_free_pids = .; . += 8;
_get_task_exe_file = .; . += 8;
_file_path = .; . += 8;
_fput = .; . += 8;
_copy_from_kernel_nofault = .; . += 8;
_get_task_mm = .; . += 8;
_kthread_use_mm = .; . += 8;
_kthread_unuse_mm = .; . += 8;
_mmput = .; . += 8;
_vm_mmap = .; . += 8;
_vm_munmap = .; . += 8;
_tracepoint_probe_register = .; . += 8;
_tracepoint_probe_unregister = .; . += 8;
___tracepoint_sched_process_fork = .; . += 8;
___tracepoint_sched_process_exit = .; . += 8;
___tracepoint_sched_process_exec = .; . += 8;
___tracepoint_task_rename = .; . += 8;
___set_task_comm = .; . += 8;
_vunmap = .; . += 8;
___arch_copy_to_user = .; . += 8;
_down_read = .; . += 8;
_up_read = .; . += 8;
_get_user_pages_unlocked = .; . += 8;
_get_user_pages_remote = .; . += 8;
___arch_copy_from_user = .; . += 8;
_do_futex = .; . += 8;
_user_mode_thread = .; . += 8;
_apply_to_existing_page_range = .; . += 8;
_caches_clean_inval_pou = .; . += 8;
_do_madvise = .; . += 8;
_fget_task = .; . += 8;
_find_task_by_vpid = .; . += 8;
___start_BTF = .; . += 8;
___stop_BTF = .; . += 8;
_call_usermodehelper_setup = .; . += 8;
_call_usermodehelper_exec = .; . += 8;
_access_process_vm = .; . += 8;
_fixup_exception = .; . += 8;
_aarch64_insn_patch_text_nosync = .; . += 8;
_aarch64_insn_patch_text = .; . += 8;
_kthread_worker_fn = .; . += 8;
_vring_interrupt = .; . += 8;
_init_net = .; . += 8;
_sock_create_kern = .; . += 8;
_kernel_connect = .; . += 8;
_kernel_sendmsg = .; . += 8;
_kernel_recvmsg = .; . += 8;
_sock_release = .; . += 8;
_sock_def_readable = .; . += 8;
}
/* Everything above is only ever read, so a process being instrumented can be shown the very
same pages; everything below it each copy needs its own of. */
. = ALIGN(4096);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
}
. = ALIGN(8);
_heap_start = .;
}
VERSION {
{
global:
_start;
local:
*;
};
}
+146
View File
@@ -0,0 +1,146 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rel.dyn : {
_agent_relocs_start = .;
*(.rel.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(4) {
. = ALIGN(4);
__printk = .; . += 4;
_printk = .; . += 4;
_panic = .; . += 4;
_kthread_create_on_node = .; . += 4;
_wake_up_process = .; . += 4;
_fget = .; . += 4;
_kernel_read = .; . += 4;
_filp_open = .; . += 4;
_kernel_write = .; . += 4;
___kmalloc_noprof = .; . += 4;
_kmalloc = .; . += 4;
_kfree = .; . += 4;
_vmalloc = .; . += 4;
_execmem_alloc = .; . += 4;
_set_memory_rw = .; . += 4;
_set_memory_ro = .; . += 4;
_set_memory_x = .; . += 4;
_set_memory_nx = .; . += 4;
_modules = .; . += 4;
_register_module_notifier = .; . += 4;
_unregister_module_notifier = .; . += 4;
_text_poke_kgdb = .; . += 4;
_execmem_free = .; . += 4;
_module_alloc = .; . += 4;
_module_memfree = .; . += 4;
___init_waitqueue_head = .; . += 4;
_prepare_to_wait_event = .; . += 4;
_finish_wait = .; . += 4;
___wake_up = .; . += 4;
_autoremove_wake_function = .; . += 4;
_schedule_hrtimeout_range = .; . += 4;
_pci_get_domain_bus_and_slot = .; . += 4;
_pci_irq_vector = .; . += 4;
_pci_dev_put = .; . += 4;
_schedule = .; . += 4;
_ktime_get_mono_fast_ns = .; . += 4;
_ktime_get_real_ts64 = .; . += 4;
_send_sig = .; . += 4;
_free_irq = .; . += 4;
_request_threaded_irq = .; . += 4;
_vmap = .; . += 4;
_current_task = .; . += 4;
_ioremap = .; . += 4;
_ioremap_nocache = .; . += 4;
___default_kernel_pte_mask = .; . += 4;
_init_task = .; . += 4;
_tasklist_lock = .; . += 4;
__raw_read_lock = .; . += 4;
__raw_read_unlock = .; . += 4;
__raw_read_lock_irqsave = .; . += 4;
__raw_read_unlock_irqrestore = .; . += 4;
__raw_write_lock = .; . += 4;
__raw_write_unlock = .; . += 4;
__raw_write_lock_irqsave = .; . += 4;
__raw_write_unlock_irqrestore = .; . += 4;
_detach_pid = .; . += 4;
_free_pids = .; . += 4;
_get_task_exe_file = .; . += 4;
_file_path = .; . += 4;
_fput = .; . += 4;
_copy_from_kernel_nofault = .; . += 4;
_get_task_mm = .; . += 4;
_kthread_use_mm = .; . += 4;
_kthread_unuse_mm = .; . += 4;
_mmput = .; . += 4;
_vm_mmap = .; . += 4;
_vm_munmap = .; . += 4;
_tracepoint_probe_register = .; . += 4;
_tracepoint_probe_unregister = .; . += 4;
___tracepoint_sched_process_fork = .; . += 4;
___tracepoint_sched_process_exit = .; . += 4;
___tracepoint_sched_process_exec = .; . += 4;
_vunmap = .; . += 4;
__copy_to_user = .; . += 4;
_down_read = .; . += 4;
_up_read = .; . += 4;
_get_user_pages_unlocked = .; . += 4;
_get_user_pages_remote = .; . += 4;
_do_futex = .; . += 4;
_user_mode_thread = .; . += 4;
___start_BTF = .; . += 4;
___stop_BTF = .; . += 4;
_call_usermodehelper_setup = .; . += 4;
_call_usermodehelper_exec = .; . += 4;
__copy_from_user = .; . += 4;
_access_process_vm = .; . += 4;
_fget_task = .; . += 4;
_find_task_by_vpid = .; . += 4;
_init_net = .; . += 4;
_sock_create_kern = .; . += 4;
_kernel_connect = .; . += 4;
_kernel_sendmsg = .; . += 4;
_kernel_recvmsg = .; . += 4;
_sock_release = .; . += 4;
_register_die_notifier = .; . += 4;
_unregister_die_notifier = .; . += 4;
}
/* Everything above is only ever read, so a process being instrumented can be shown the very
same pages; everything below it each copy needs its own of. */
. = ALIGN(4096);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
}
. = ALIGN(4);
_heap_start = .;
}
VERSION {
{
global:
_start;
local:
*;
};
}
Loaded 100 of 510 files, more files were not shown because too many files have changed in this diff. Show more