Compare commits

...
943 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
415 changed files with 98889 additions and 32259 deletions

No files matched your search

+20 -9
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"' }
@@ -71,7 +71,7 @@ jobs:
--host=${{ matrix.id }} `
--enable-compiler-backend `
${{ env.FRIDA_CORE_OPTIONS }} `
-- --force-fallback-for=openssl
-- --force-fallback-for=openssl ${{ matrix.opts }}
.\make
env:
MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }}
@@ -451,7 +451,10 @@ 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
@@ -658,7 +661,7 @@ jobs:
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 }
- { 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
@@ -668,9 +671,17 @@ jobs:
with:
submodules: recursive
- uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{ matrix.msvc-arch }}
- 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:
@@ -721,7 +732,7 @@ jobs:
--enable-shared `
--without-prebuilds=sdk `
"--prefix=$env:RUNNER_TEMP\frida-prefix" `
-- --force-fallback-for=openssl,libnghttp2 -Dcompiler_backend=disabled -Dtests=disabled
-- --force-fallback-for=openssl,libnghttp2 -Dcompiler_backend=disabled -Dtests=disabled ${{ matrix.opts }}
- name: Compile
run: python releng/meson/meson.py compile -C build
+9 -1
View File
@@ -2,7 +2,15 @@
/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/
+1 -1
View File
@@ -43,7 +43,7 @@ platform-dependent (e.g. `linux-x86_64`).
## Build Requirements
- **Vala compiler**: Must be the Frida-optimized fork from
https://github.com/frida/vala (checks for `-frida` suffix
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
+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
+15 -11
View File
@@ -19,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")
@@ -592,8 +592,10 @@ def load_meson_options(top_builddir: Path,
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":
@@ -603,44 +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):
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):
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
+38
View File
@@ -13,6 +13,44 @@
# 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
+413 -35
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 ();
@@ -122,12 +128,43 @@ namespace Frida.Agent {
{
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);
@@ -157,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);
@@ -180,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;
@@ -266,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 {
@@ -307,6 +353,9 @@ namespace Frida.Agent {
bool enable_unwind_broker = true;
bool enable_exit_monitor = true;
bool enable_thread_suspend_monitor = true;
#if ANDROID && (ARM || ARM64)
bool in_emulated_realm = false;
#endif
foreach (unowned string option in tokens[1:]) {
if (option == "eternal")
ensure_eternalized ();
@@ -322,38 +371,47 @@ namespace Frida.Agent {
enable_exit_monitor = false;
else if (option == "thread-suspend-monitor:off")
enable_thread_suspend_monitor = false;
#if ANDROID && (ARM || ARM64)
else if (option == "realm:emulated")
in_emulated_realm = true;
#endif
}
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_broker)
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);
});
@@ -362,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);
@@ -1023,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 ();
}
@@ -1068,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;
@@ -1276,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;
@@ -1348,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)
@@ -1387,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");
@@ -1426,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);
@@ -1434,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);
@@ -1481,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) {
@@ -1641,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;
+8
View File
@@ -0,0 +1,8 @@
{
global:
frida_agent_main;
frida_prospero_agent_args;
local:
*;
};
+7 -2
View File
@@ -25,6 +25,9 @@ if build_agent
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
@@ -42,6 +45,8 @@ if build_agent
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
@@ -54,6 +59,8 @@ if build_agent
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,
@@ -89,8 +96,6 @@ if build_agent
install: assets_mode == 'installed' and not need_universal_agent,
install_dir: asset_dir,
)
agent_dep = declare_dependency(link_with: agent)
endif
if need_universal_agent
-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);
}
-797
View File
@@ -1,797 +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 {
if (_bytes == null)
_bytes = new Bytes.static (_data);
return _bytes;
}
}
public size_t size {
get {
return _data.length;
}
}
public ByteOrder byte_order {
get;
construct;
}
public uint pointer_size {
get;
construct;
}
private Bytes? _bytes;
private unowned uint8[] _data;
public Buffer (Bytes bytes, ByteOrder byte_order = HOST, uint pointer_size = (uint) sizeof (size_t)) {
Object (
byte_order: byte_order,
pointer_size: pointer_size
);
reset_bytes (bytes);
}
public Buffer.from_data (uint8[] data, ByteOrder byte_order = HOST, uint pointer_size = (uint) sizeof (size_t)) {
Object (
byte_order: byte_order,
pointer_size: pointer_size
);
reset_data (data);
}
public void reset_bytes (Bytes bytes) {
_bytes = bytes;
_data = bytes.get_data ();
}
public void reset_data (uint8[] data) {
_bytes = null;
_data = data;
}
public unowned uint8[] peek_data () {
return _data;
}
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 unowned string read_string (size_t offset) throws Error {
string * start = (string *) get_pointer (offset, sizeof (char));
size_t max_length = _data.length - offset;
string * end = memchr (start, 0, max_length);
if (end == null)
throw new Error.PROTOCOL ("Missing null character");
return start;
}
[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, _data.length - offset);
string * end = memchr (start, 0, max_length);
size_t n;
if (end != null)
n = end - start;
else
n = size;
return start->substring (0, (long) n);
}
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) {
if (_bytes == null)
return make_bytes_with_owner ((uint8 *) _data + offset, _data.length - size, this);
return _bytes[offset:offset + size];
}
public unowned uint8[] read_data (size_t offset, size_t size) {
unowned uint8 * ptr = get_pointer (offset, size);
return ((uint8[]) ptr)[:size];
}
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 (_data.length >= minimum_size);
return (uint8 *) _data + offset;
}
}
public sealed class BufferReader {
public Buffer buffer {
get;
}
public size_t offset {
get;
}
public size_t available {
get {
return _size - _offset;
}
}
private size_t _size;
public BufferReader (Buffer buffer) {
reset (buffer);
}
public void reset (Buffer buffer) {
_buffer = buffer;
_size = buffer.size;
_offset = 0;
}
public void reset_at (Buffer buffer, size_t offset) throws Error {
_buffer = buffer;
_size = buffer.size;
seek (offset);
}
public unowned BufferReader seek (size_t offset) throws Error {
if (offset > _size)
throw new Error.PROTOCOL ("Malformed buffer: truncated");
_offset = offset;
return this;
}
public unowned BufferReader skip (size_t n) throws Error {
check_available (n);
_offset += n;
return this;
}
public unowned BufferReader align (size_t alignment) throws Error {
size_t mask = alignment - 1;
size_t rem = _offset & mask;
if (rem != 0)
skip (alignment - rem);
return this;
}
public uint64 peek_pointer () throws Error {
var pointer_size = _buffer.pointer_size;
check_available (pointer_size);
return _buffer.read_pointer (_offset);
}
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 peek_int8 () throws Error {
check_available (sizeof (int8));
return _buffer.read_int8 (_offset);
}
public int8 read_int8 () throws Error {
check_available (sizeof (int8));
var val = _buffer.read_int8 (_offset);
_offset += sizeof (int8);
return val;
}
public uint8 peek_uint8 () throws Error {
check_available (sizeof (uint8));
return _buffer.read_uint8 (_offset);
}
public uint8 read_uint8 () throws Error {
check_available (sizeof (uint8));
var val = _buffer.read_uint8 (_offset);
_offset += sizeof (uint8);
return val;
}
public int16 peek_int16 () throws Error {
check_available (sizeof (int16));
return _buffer.read_int16 (_offset);
}
public int16 read_int16 () throws Error {
check_available (sizeof (int16));
var val = _buffer.read_int16 (_offset);
_offset += sizeof (int16);
return val;
}
public uint16 peek_uint16 () throws Error {
check_available (sizeof (uint16));
return _buffer.read_uint16 (_offset);
}
public uint16 read_uint16 () throws Error {
check_available (sizeof (uint16));
var val = _buffer.read_uint16 (_offset);
_offset += sizeof (uint16);
return val;
}
public int32 peek_int32 () throws Error {
check_available (sizeof (int32));
return _buffer.read_int32 (_offset);
}
public int32 read_int32 () throws Error {
check_available (sizeof (int32));
var val = _buffer.read_int32 (_offset);
_offset += sizeof (int32);
return val;
}
public uint32 peek_uint32 () throws Error {
check_available (sizeof (uint32));
return _buffer.read_uint32 (_offset);
}
public uint32 read_uint32 () throws Error {
check_available (sizeof (uint32));
var val = _buffer.read_uint32 (_offset);
_offset += sizeof (uint32);
return val;
}
public int64 peek_int64 () throws Error {
check_available (sizeof (int64));
return _buffer.read_int64 (_offset);
}
public int64 read_int64 () throws Error {
check_available (sizeof (int64));
var val = _buffer.read_int64 (_offset);
_offset += sizeof (int64);
return val;
}
public uint64 peek_uint64 () throws Error {
check_available (sizeof (uint64));
return _buffer.read_uint64 (_offset);
}
public uint64 read_uint64 () throws Error {
check_available (sizeof (uint64));
var val = _buffer.read_uint64 (_offset);
_offset += sizeof (uint64);
return val;
}
public float peek_float () throws Error {
check_available (sizeof (float));
return _buffer.read_float (_offset);
}
public float read_float () throws Error {
check_available (sizeof (float));
var val = _buffer.read_float (_offset);
_offset += sizeof (float);
return val;
}
public double peek_double () throws Error {
check_available (sizeof (double));
return _buffer.read_double (_offset);
}
public double read_double () throws Error {
check_available (sizeof (double));
var val = _buffer.read_double (_offset);
_offset += sizeof (double);
return val;
}
public unowned string peek_string () throws Error {
check_available (1);
return _buffer.read_string (_offset);
}
public unowned string read_string () throws Error {
check_available (1);
unowned string val = _buffer.read_string (_offset);
_offset += val.length + 1;
return val;
}
public string peek_fixed_string (size_t size) throws Error {
check_available (size);
return _buffer.read_fixed_string (_offset, size);
}
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 unowned uint8[] peek_data (size_t size) throws Error {
check_available (size);
return _buffer.read_data (_offset, size);
}
public unowned uint8[] read_data (size_t size) throws Error {
check_available (size);
unowned uint8[] slice = _buffer.read_data (_offset, size);
_offset += size;
return slice;
}
public Bytes read_bytes (size_t size) throws Error {
check_available (size);
var val = _buffer.read_bytes (_offset, size);
_offset += size;
return val;
}
public unowned uint8[] read_remaining_data () throws Error {
return read_data (available);
}
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 () {
-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);
});
}
-26
View File
@@ -1,26 +0,0 @@
#ifndef __FRIDA_DARWIN_H__
#define __FRIDA_DARWIN_H__
#ifdef HAVE_MACOS
#include <glib.h>
#include <xpc/xpc.h>
G_BEGIN_DECLS
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);
G_END_DECLS
#endif
#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);
}
}
}
+9
View File
@@ -123,6 +123,15 @@ 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);
}
+4 -18
View File
@@ -1,21 +1,16 @@
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',
]
@@ -23,19 +18,12 @@ base_sources = [
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'
@@ -94,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'
@@ -114,7 +100,7 @@ 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,
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],
)
@@ -122,7 +108,7 @@ base = static_library('frida-base-' + api_version, base_sources,
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()
+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);
+99 -50
View File
@@ -1,3 +1,5 @@
using Efni;
namespace Frida {
[DBus (name = "re.frida.HostSession17")]
public interface HostSession : Object {
@@ -124,8 +126,43 @@ namespace Frida {
public abstract async void acknowledge_spawn (HostChildInfo info, SpawnStartState start_state,
Cancellable? cancellable) throws GLib.Error;
public abstract uint64 allocate_memory (uint pid, uint64 near_address, uint64 max_distance, uint64 size,
uint64 alignment, Gum.PageProtection prot, Cancellable? cancellable) throws GLib.Error;
public abstract void protect_memory (uint pid, uint64 address, uint64 size, Gum.PageProtection prot,
Cancellable? cancellable) throws GLib.Error;
}
#if ANDROID
[DBus (name = "re.frida.NativeThreadRegistry17")]
public interface NativeThreadRegistry : Object {
public abstract NativeThreadInfo[] activate (Cancellable? cancellable) throws GLib.Error;
public abstract void deactivate (Cancellable? cancellable) throws GLib.Error;
public signal void thread_added (NativeThreadInfo thread);
public signal void thread_removed (uint id);
public signal void thread_renamed (uint id, string name);
}
[DBus (name = "re.frida.NativeCloak17")]
public interface NativeCloak : Object {
public abstract uint[] activate (Cancellable? cancellable) throws GLib.Error;
public signal void thread_cloaked (uint id);
public signal void thread_uncloaked (uint id);
}
public struct NativeThreadInfo {
public uint id;
public string name;
public NativeThreadInfo (uint id, string name) {
this.id = id;
this.name = name;
}
}
#endif
[DBus (name = "re.frida.AgentMessageSink17")]
public interface AgentMessageSink : Object {
public abstract async void post_messages (AgentMessage[] messages, uint batch_id,
@@ -1133,6 +1170,15 @@ namespace Frida {
}
}
#if WINDOWS
public struct WindowsInjectorState {
public Gum.MemoryRange mapped_range;
public Gum.MemoryRange worker_range;
public Gum.MemoryRange dbghelp_range;
public Gum.MemoryRange symsrv_range;
}
#endif
#if DARWIN
public struct DarwinInjectorState {
public Gum.MemoryRange? mapped_range;
@@ -1143,6 +1189,8 @@ namespace Frida {
public struct LinuxInjectorState {
public int frida_ctrlfd;
public int agent_ctrlfd;
public uint64 mapped_base;
public size_t mapped_size;
}
#endif
@@ -1152,6 +1200,14 @@ namespace Frida {
}
#endif
#if PROSPERO
public struct ProsperoInjectorState {
public int fifo_fd;
public int agent_ctrlfd;
public Gum.MemoryRange? mapped_range;
}
#endif
/**
* Why spawn gating was disabled on a {@link Device}.
*/
@@ -1273,6 +1329,9 @@ namespace Frida {
if (e is Frida.Error)
throw (Frida.Error) e;
if (e is Efni.Error)
throw error_from_efni ((Efni.Error) e);
if (e is IOError.CANCELLED)
throw (IOError) e;
@@ -1286,6 +1345,9 @@ namespace Frida {
if (e is Frida.Error)
throw (Frida.Error) e;
if (e is Efni.Error)
throw error_from_efni ((Efni.Error) e);
if (e is IOError.CANCELLED)
throw (IOError) e;
@@ -1298,6 +1360,41 @@ namespace Frida {
throw new Frida.Error.TRANSPORT ("%s", e.message);
}
namespace Marshal {
public T enum_from_nick<T> (string nick) throws Error {
try {
return Efni.Marshal.enum_from_nick<T> (nick);
} catch (Efni.Error e) {
throw error_from_efni (e);
}
}
public string enum_to_nick<T> (int val) {
return Efni.Marshal.enum_to_nick<T> (val);
}
}
public static Frida.Error error_from_efni (Efni.Error e) {
unowned string m = e.message;
if (e is Efni.Error.SERVER_NOT_RUNNING)
return new Frida.Error.SERVER_NOT_RUNNING ("%s", m);
if (e is Efni.Error.INVALID_ARGUMENT)
return new Frida.Error.INVALID_ARGUMENT ("%s", m);
if (e is Efni.Error.INVALID_OPERATION)
return new Frida.Error.INVALID_OPERATION ("%s", m);
if (e is Efni.Error.PERMISSION_DENIED)
return new Frida.Error.PERMISSION_DENIED ("%s", m);
if (e is Efni.Error.ADDRESS_IN_USE)
return new Frida.Error.ADDRESS_IN_USE ("%s", m);
if (e is Efni.Error.TIMED_OUT)
return new Frida.Error.TIMED_OUT ("%s", m);
if (e is Efni.Error.NOT_SUPPORTED)
return new Frida.Error.NOT_SUPPORTED ("%s", m);
if (e is Efni.Error.PROTOCOL)
return new Frida.Error.PROTOCOL ("%s", m);
return new Frida.Error.TRANSPORT ("%s", m);
}
public struct HostApplicationInfo {
public string identifier;
public string name;
@@ -2735,6 +2832,8 @@ namespace Frida {
public const string AGENT_SESSION_PROVIDER = "/re/frida/AgentSessionProvider";
public const string AGENT_SESSION = "/re/frida/AgentSession";
public const string AGENT_CONTROLLER = "/re/frida/AgentController";
public const string NATIVE_THREAD_REGISTRY = "/re/frida/NativeThreadRegistry";
public const string NATIVE_CLOAK = "/re/frida/NativeCloak";
public const string AGENT_MESSAGE_SINK = "/re/frida/AgentMessageSink";
public const string GADGET_SESSION = "/re/frida/GadgetSession";
public const string CHANNEL = "/re/frida/Channel";
@@ -2760,54 +2859,4 @@ namespace Frida {
return SERVICE + "/" + id.handle;
}
}
namespace Marshal {
public static T enum_from_nick<T> (string nick) throws Error {
var klass = (EnumClass) typeof (T).class_ref ();
var v = klass.get_value_by_nick (nick);
if (v == null)
throw new Error.INVALID_ARGUMENT ("Invalid %s", klass.get_type ().name ());
return (T) v.value;
}
public static string enum_to_nick<T> (int val) {
var klass = (EnumClass) typeof (T).class_ref ();
unowned EnumValue? v = klass.get_value (val);
if (v == null)
return "unknown-0x%x".printf (val);
return v.value_nick;
}
}
namespace Numeric {
public uint int64_hash (int64? val) {
uint64 v = (uint64) val.abs ();
return (uint) ((v >> 32) ^ (v & 0xffffffffU));
}
public bool int64_equal (int64? val_a, int64? val_b) {
int64 a = val_a;
int64 b = val_b;
return a == b;
}
public uint uint64_hash (uint64? val) {
uint64 v = val;
return (uint) ((v >> 32) ^ (v & 0xffffffffU));
}
public bool uint64_equal (uint64? val_a, uint64? val_b) {
uint64 a = val_a;
uint64 b = val_b;
return a == b;
}
public uint bytes_hash (Bytes b) {
return b.hash ();
}
public static bool bytes_equal (Bytes a, Bytes b) {
return a.compare (b) == 0;
}
}
}
-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)
{
+29 -87
View File
@@ -1,99 +1,22 @@
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);
}
}
@@ -726,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);
@@ -1116,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);
}
@@ -1165,7 +1092,7 @@ namespace Frida {
if (recv_queue.len == 0) {
#if HAVE_SOUP_WEBSOCKET_BACKPRESSURE
websocket.resume_input ();
resume_websocket_input ();
#endif
}
@@ -1182,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);
-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 ();
}
}
}
-432
View File
@@ -1,432 +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 = make_json_reader (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);
}
}
/*
* Constructs a Json.Reader for the given JSON string, working around the
* json_node_copy() bug in unpatched json-glib (GNOME bugzilla #774684).
*
* Json.Reader's 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, json_reader_end_member()'s walk
* via json_node_get_parent() touches a freed node — which on builds with
* glib checks enabled (e.g. Fedora) trips JSON_NODE_IS_VALID and aborts.
*
* We keep the original root alive by attaching it to the reader as object
* data; the destroy notify drops our reference when the reader is finalized.
*/
public Json.Reader make_json_reader (string json) throws GLib.Error {
return make_json_reader_taking_node (Json.from_string (json));
}
public Json.Reader make_json_reader_taking_node (owned Json.Node root) {
var reader = new Json.Reader (root);
reader.set_data<Json.Node> ("frida-json-root", (owned) root);
return reader;
}
}
+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;
-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);
}
}
}
}
+2
View File
@@ -1,3 +1,5 @@
using Efni;
namespace Frida.Gadget {
private sealed class Config : Object, Json.Serializable {
public Object interaction {
+4
View File
@@ -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],
-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'
+2
View File
@@ -1,3 +1,5 @@
using Efni;
namespace Frida {
public abstract class BaseAgentSession : Object, AgentSession {
public signal void closed ();
+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
}
+28 -21
View File
@@ -28,6 +28,7 @@
#undef feof
#undef ferror
#undef fgetwc
#undef fileno
#undef fopen
#undef fopen64
#undef fputwc
@@ -214,6 +215,7 @@ static void frida_stdio_unregister_stream (FILE * stream);
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);
@@ -251,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];
@@ -301,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;
}
@@ -638,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, ...)
@@ -1904,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);
@@ -1926,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);
@@ -1943,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)
{
+2
View File
@@ -1,3 +1,5 @@
using Efni;
namespace Frida {
public sealed class PortalClient : Object, AgentSessionProvider {
public signal void resume ();
+2
View File
@@ -1,3 +1,5 @@
using Efni;
namespace Frida {
public extern void libc_shim_prepare_to_deinit ();
#if DARWIN
+2
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);
+6 -6
View File
@@ -34,7 +34,7 @@ struct _FridaCreateArgs
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 gboolean frida_fill_info (const GumMachoSectionDetails * details, FridaFillInfoContext * ctx);
void
_frida_unwind_sitter_fill_unwind_sections (GumAddress invader_start, GumAddress invader_end, void * info)
@@ -70,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 ();
@@ -87,14 +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);
@@ -102,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;
+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);
+84 -55
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
@@ -273,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'],
@@ -340,9 +349,13 @@ glib_options = [
'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',
@@ -399,13 +412,27 @@ json_glib_dep = dependency('json-glib-1.0', version: '>=1.8', default_options: [
'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',
@@ -419,7 +446,13 @@ if libsoup_dep.type_name() == 'internal' \
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'])
capstone_dep = dependency('capstone', default_options: [
'default_library=static',
'archs=all',
'use_arch_registration=true',
'x86_att_disable=true',
'cli=disabled',
])
if capstone_dep.type_name() != 'internal' \
and not cc.has_function('cs_arch_register_x86', dependencies: [capstone_dep])
add_project_arguments(
@@ -433,9 +466,9 @@ endif
quickjs_dep = dependency('quickjs', required: get_option('barebone_backend'), default_options: [
'default_library=static',
'libc=false',
'bignum=true',
'atomics=disabled',
'stack_check=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
@@ -471,6 +504,7 @@ endif
if host_os == 'android'
minizip_dep = dependency('minizip', required: false, default_options: [
'default_library=static',
'zlib=enabled',
'lzma=disabled',
])
@@ -527,7 +561,7 @@ 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')
@@ -564,7 +598,11 @@ if compiler_backend_opt.allowed()
have_compiler_backend = true
cdata.set('HAVE_COMPILER_BACKEND', 1)
compiler_backend_mode = lines[1]
go_config = lines[2]
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'
@@ -595,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
@@ -688,54 +726,32 @@ 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
backend_deps_private += dependency('libnghttp2', default_options: ['default_library=static'])
backend_reqs_private += 'libnghttp2'
if host_os != 'macos'
backend_deps_private += dependency('libngtcp2', default_options: ['default_library=static'])
backend_reqs_private += 'libngtcp2'
ngtcp2_crypto_quictls_dep = dependency('libngtcp2_crypto_quictls', required: false, default_options: ['default_library=static'])
if ngtcp2_crypto_quictls_dep.found()
backend_deps_private += ngtcp2_crypto_quictls_dep
backend_reqs_private += 'libngtcp2_crypto_quictls'
else
backend_deps_private += dependency('libngtcp2_crypto_ossl', default_options: ['default_library=static'])
backend_reqs_private += 'libngtcp2_crypto_ossl'
vala_flags += '--define=HAVE_NGTCP2_CRYPTO_OSSL'
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
if have_frida_glib
libusb_dep = dependency('libusb-1.0', default_options: ['default_library=static'])
else
subproject('libusb', default_options: ['default_library=static'])
libusb_dep = dependency('libusb-1.0', default_options: ['default_library=static'])
endif
backend_deps_private += libusb_dep
backend_reqs_private += 'libusb-1.0'
lwip_options = [
'default_library=static',
'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'
endif
backend_deps_private += dependency('lwip', default_options: lwip_options)
backend_reqs_private += 'lwip'
endif
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()
@@ -757,14 +773,20 @@ if host_os_family == 'linux' and have_local_backend
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', default_options: ['default_library=static'])
libsepol_dep = dependency('libsepol', version: '>=3.0', default_options: ['default_library=static'])
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
@@ -879,13 +901,19 @@ if host_os_family == 'darwin'
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'
@@ -952,7 +980,7 @@ if host_os_family == 'darwin'
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') ? 'executable-no-exports-freebsd.version' : 'executable-no-exports.version'
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]
@@ -962,12 +990,13 @@ 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(
+1 -1
Submodule releng updated: abb8238970...164a6a4117.
+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>
+1 -1
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 = (host_os == 'freebsd') ? 'frida-server-freebsd.version' : '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
+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;
+2
View File
@@ -1,3 +1,5 @@
using Efni;
namespace Frida.Server {
private static Application application;
+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;
}
+86 -2
View File
@@ -1,3 +1,5 @@
using Efni;
namespace Frida {
public sealed class AndroidHelperClient : Object {
public AndroidHelperTransport transport {
@@ -5,10 +7,92 @@ namespace Frida {
construct;
}
private AdbShellSession? shell;
public AndroidHelperClient (AndroidHelperTransport transport) {
Object (transport: transport);
}
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";
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);
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);
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));
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 {
yield transport.close (cancellable);
if (shell != null) {
yield shell.close (cancellable);
shell = null;
}
}
private void release_shell () {
if (shell != null) {
shell.close.begin ();
shell = null;
}
}
public async HostApplicationInfo get_frontmost_application (FrontmostQueryOptions options,
Cancellable? cancellable) throws Error, IOError {
var stanza = new Json.Builder ();
@@ -408,13 +492,13 @@ namespace Frida {
}
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 signal void closed ();
public IOStream stream {
get;
construct;
Binary file not shown.
+39 -17
View File
@@ -18,7 +18,6 @@ import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.os.Process;
import android.util.Base64;
import android.util.Base64OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -645,7 +644,7 @@ public class HelperBackend {
"Unable to find application with identifier '" + pkgName + "'" +
((uid != 0) ? " belonging to uid " + uid : ""));
} catch (Throwable e) {
return error("NOT_SUPPORTED", e.toString());
return error("NOT_SUPPORTED", describe(e));
}
}
@@ -697,10 +696,13 @@ public class HelperBackend {
intent.setClassName(pkgName, activity);
}
if (mStartActivityAsUser != null)
startActivityViaAtm(intent, uid, pkgName);
else
startActivityViaAm(intent, uid, pkgName);
String callingPackage = ourCallingPackage();
int code = (mStartActivityAsUser != null)
? startActivityViaAtm(intent, uid, callingPackage)
: startActivityViaAm(intent, uid, callingPackage);
if (code < 0 || code > 3)
return error("NOT_SUPPORTED", "Activity launch was refused (start code " + code + ")");
return okVoid();
} catch (NameNotFoundException e) {
@@ -708,11 +710,18 @@ public class HelperBackend {
"Unable to find application with identifier '" + pkgName + "'" +
((uid != 0) ? " belonging to uid " + uid : ""));
} catch (Throwable e) {
return error("NOT_SUPPORTED", e.toString());
return error("NOT_SUPPORTED", describe(e));
}
}
private void startActivityViaAtm(Intent intent, int uid, String callingPackage) throws Exception {
private String ourCallingPackage() {
String[] packages = mPackageManager.getPackagesForUid(Process.myUid());
if (packages != null && packages.length > 0)
return packages[0];
return "com.android.shell";
}
private int startActivityViaAtm(Intent intent, int uid, String callingPackage) throws Exception {
if (mStartActivityAsUser == null)
throw new UnsupportedOperationException("startActivityAsUser unavailable");
@@ -729,10 +738,11 @@ public class HelperBackend {
args[mStartActivityCallingPackageIndex] = callingPackage;
args[mStartActivityUserIdIndex] = (uid != 0) ? uid : 0;
mStartActivityAsUser.invoke(mActivityTaskManager, args);
Object result = mStartActivityAsUser.invoke(mActivityTaskManager, args);
return (result instanceof Integer) ? (Integer) result : 0;
}
private void startActivityViaAm(Intent intent, int uid, String callingPackage) throws Exception {
private int startActivityViaAm(Intent intent, int uid, String callingPackage) throws Exception {
if (mStartActivityLegacy == null)
throw new UnsupportedOperationException("legacy startActivity unavailable");
@@ -753,7 +763,8 @@ public class HelperBackend {
if (mLegacyStartUserIdIndex != -1)
args[mLegacyStartUserIdIndex] = (uid != 0) ? uid : 0;
mStartActivityLegacy.invoke(mActivityManagerService, args);
Object result = mStartActivityLegacy.invoke(mActivityManagerService, args);
return (result instanceof Integer) ? (Integer) result : 0;
}
private JSONArray sendBroadcast(JSONArray request) throws JSONException {
@@ -782,7 +793,7 @@ public class HelperBackend {
"Unable to find application with identifier '" + pkgName + "'" +
((uid != 0) ? " belonging to uid " + uid : ""));
} catch (Throwable e) {
return error("NOT_SUPPORTED", e.toString());
return error("NOT_SUPPORTED", describe(e));
}
}
@@ -810,7 +821,7 @@ public class HelperBackend {
"Unable to find application with identifier '" + pkgName + "'" +
((uid != 0) ? " belonging to uid " + uid : ""));
} catch (Throwable e) {
return error("NOT_SUPPORTED", e.toString());
return error("NOT_SUPPORTED", describe(e));
}
}
@@ -837,7 +848,7 @@ public class HelperBackend {
return okBoolean(false);
} catch (Throwable e) {
return error("NOT_SUPPORTED", e.toString());
return error("NOT_SUPPORTED", describe(e));
}
}
@@ -869,6 +880,17 @@ public class HelperBackend {
return r;
}
private static String describe(Throwable t) {
Throwable cause = t;
while (cause instanceof InvocationTargetException && cause.getCause() != null)
cause = cause.getCause();
String message = cause.getMessage();
return (message != null)
? cause.getClass().getName() + ": " + message
: cause.getClass().getName();
}
private String getFrontmostPackageName() {
List<ActivityManager.RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
if (tasks.isEmpty()) {
@@ -959,10 +981,10 @@ public class HelperBackend {
icon.setBounds(0, 0, width, height);
icon.draw(canvas);
ByteArrayOutputStream output = new ByteArrayOutputStream();
bitmap.compress(CompressFormat.PNG, 100, new Base64OutputStream(output, Base64.NO_WRAP));
ByteArrayOutputStream png = new ByteArrayOutputStream();
bitmap.compress(CompressFormat.PNG, 100, png);
return output.toString();
return Base64.encodeToString(png.toByteArray(), Base64.NO_WRAP);
}
private Map<String, List<RunningAppProcessInfo>> getAppProcesses() {
+87 -11
View File
@@ -21,10 +21,19 @@ 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",
@@ -131,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);")
@@ -230,10 +240,10 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path, docs:
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)
@@ -241,6 +251,13 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path, docs:
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")
@@ -277,6 +294,12 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path, docs:
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
@@ -604,10 +627,14 @@ def emit_vapi(api, output_dir):
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 = []
@@ -642,6 +669,35 @@ 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:
@@ -800,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
@@ -821,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
@@ -850,17 +905,26 @@ 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",
@@ -881,6 +945,17 @@ def function_is_public(name):
"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)]
@@ -896,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):
+9 -2
View File
@@ -132,13 +132,20 @@ if not is_shared
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_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': ['glib-2.0', 'gobject-2.0', 'gio-2.0', 'json-glib-1.0'],
'requires': core_public_reqs,
'subdirs': f'frida-@api_version@',
'extra_cflags': ['-I${frida_includedir}'],
'variables': installed_variables,
@@ -151,7 +158,7 @@ if is_shared
else
pkg_kwargs += {
'libraries': core_public_lib,
'requires_private': ['gmodule-2.0', 'gee-0.8', 'libsoup-3.0', 'frida-gum-1.0', 'frida-gumjs-inspector-1.0', 'libbrotlidec'] + backend_reqs_private,
'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
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"
+62 -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
@@ -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:
*;
};
}
+154
View File
@@ -0,0 +1,154 @@
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;
_text_poke_kgdb = .; . += 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;
_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;
_vmap = .; . += 8;
_page_offset_base = .; . += 8;
_current_task = .; . += 8;
_cpu_current_top_of_stack = .; . += 8;
_register_die_notifier = .; . += 8;
_unregister_die_notifier = .; . += 8;
_ioremap = .; . += 8;
_ioremap_nocache = .; . += 8;
___default_kernel_pte_mask = .; . += 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;
__copy_to_user = .; . += 8;
_down_read = .; . += 8;
_up_read = .; . += 8;
_get_user_pages_unlocked = .; . += 8;
_get_user_pages_remote = .; . += 8;
_do_futex = .; . += 8;
_user_mode_thread = .; . += 8;
___start_BTF = .; . += 8;
___stop_BTF = .; . += 8;
_call_usermodehelper_setup = .; . += 8;
_call_usermodehelper_exec = .; . += 8;
__copy_from_user = .; . += 8;
_access_process_vm = .; . += 8;
_fget_task = .; . += 8;
_find_task_by_vpid = .; . += 8;
_init_net = .; . += 8;
_sock_create_kern = .; . += 8;
_kernel_connect = .; . += 8;
_kernel_sendmsg = .; . += 8;
_kernel_recvmsg = .; . += 8;
_sock_release = .; . += 8;
_sock_def_readable = .; . += 8;
_of_irq_parse_and_map_pci = .; . += 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:
*;
};
}
+97
View File
@@ -0,0 +1,97 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rel.dyn : {
_agent_relocs_start = .;
*(.rel.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(4) {
. = ALIGN(4);
_Get_Cur_VM_Handle = .; . += 4;
_Get_Sys_VM_Handle = .; . += 4;
_Schedule_Global_Event = .; . += 4;
_Create_Semaphore = .; . += 4;
_Wait_Semaphore = .; . += 4;
_Signal_Semaphore = .; . += 4;
_Get_Next_VM_Handle = .; . += 4;
_Release_Time_Slice = .; . += 4;
_Set_Global_Time_Out = .; . += 4;
_Cancel_Time_Out = .; . += 4;
_Get_System_Time = .; . += 4;
__HeapAllocate = .; . += 4;
__HeapFree = .; . += 4;
__PageAllocate = .; . += 4;
__PageFree = .; . += 4;
__CopyPageTable = .; . += 4;
__MapPhysToLinear = .; . += 4;
_VMM_Add_DDB = .; . += 4;
_VMM_Remove_DDB = .; . += 4;
__RegOpenKey = .; . += 4;
__RegCloseKey = .; . += 4;
__RegEnumKey = .; . += 4;
__RegQueryValueEx = .; . += 4;
__PageReserve = .; . += 4;
__PageCommit = .; . += 4;
_Hook_VMM_Fault = .; . += 4;
_Unhook_VMM_Fault = .; . += 4;
_Fatal_Error_Handler = .; . += 4;
_Get_Cur_Thread_Handle = .; . += 4;
_Get_Initial_Thread_Handle = .; . += 4;
_Get_Next_Thread_Handle = .; . += 4;
__Debug_Printf_Service = .; . += 4;
__ContextSwitch = .; . += 4;
__PageModifyPermissions = .; . += 4;
__GetCurrentContext = .; . += 4;
_VPICD_Virtualize_IRQ = .; . += 4;
_VPICD_Force_Default_Behavior = .; . += 4;
_VPICD_Phys_EOI = .; . += 4;
_VPICD_Physically_Unmask = .; . += 4;
__VWIN32_QueueUserApc = .; . += 4;
__VWIN32_Get_Thread_Context = .; . += 4;
__VWIN32_Set_Thread_Context = .; . += 4;
__VWIN32_CreateRing0Thread = .; . += 4;
_IFSMgr_Ring0_FileIO = .; . += 4;
_KERNEL32_Base = .; . += 4;
_KERNEL32_ProcessIdObfuscator = .; . += 4;
_KERNEL32_ModuleTable = .; . += 4;
__PageCommitPhys = .; . += 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 = .;
}
.bss : {
*(.bss*)
}
. = ALIGN(4);
_heap_start = .;
}
VERSION {
{
global:
_start;
frida_win9x_user_main;
local:
*;
};
}
+112
View File
@@ -0,0 +1,112 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rela.dyn : {
_agent_relocs_start = .;
*(.rela.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(8) {
. = ALIGN(8);
_ExAllocatePoolWithTag = .; . += 8;
_ExFreePoolWithTag = .; . += 8;
_PsCreateSystemThread = .; . += 8;
_PsGetCurrentThreadId = .; . += 8;
_KeInitializeEvent = .; . += 8;
_KeWaitForSingleObject = .; . += 8;
_KeSetEvent = .; . += 8;
_KeInitializeDpc = .; . += 8;
_PsSetCreateThreadNotifyRoutine = .; . += 8;
_PsRemoveCreateThreadNotifyRoutine = .; . += 8;
_KeInsertQueueDpc = .; . += 8;
_ZwYieldExecution = .; . += 8;
_KeExpandKernelStackAndCalloutEx = .; . += 8;
_KeBugCheckEx = .; . += 8;
_DbgPrint = .; . += 8;
_MmGetVirtualForPhysical = .; . += 8;
_MmMapIoSpace = .; . += 8;
_MmGetPhysicalAddress = .; . += 8;
_HalGetInterruptVector = .; . += 8;
_IoConnectInterrupt = .; . += 8;
_IoDisconnectInterrupt = .; . += 8;
_PsActiveProcessHead = .; . += 8;
_PsInitialSystemProcess = .; . += 8;
_PsGetProcessId = .; . += 8;
_PsGetProcessImageFileName = .; . += 8;
_PsGetProcessPeb = .; . += 8;
_PsGetCurrentThread = .; . += 8;
_PsGetThreadProcess = .; . += 8;
_PsGetThreadId = .; . += 8;
_PsGetThreadTeb = .; . += 8;
_PsGetContextThread = .; . += 8;
_PsSetContextThread = .; . += 8;
_KeStackAttachProcess = .; . += 8;
_KeUnstackDetachProcess = .; . += 8;
_ZwCreateFile = .; . += 8;
_ZwOpenKey = .; . += 8;
_ZwEnumerateKey = .; . += 8;
_ZwQueryValueKey = .; . += 8;
_ZwReadFile = .; . += 8;
_ZwWriteFile = .; . += 8;
_ZwEnumerateValueKey = .; . += 8;
_ZwDeviceIoControlFile = .; . += 8;
_ZwClose = .; . += 8;
_ZwCreateEvent = .; . += 8;
_ExAllocatePool2 = .; . += 8;
_MmCopyVirtualMemory = .; . += 8;
_KeIpiGenericCall = .; . += 8;
_MmIsAddressValid = .; . += 8;
_ZwClearEvent = .; . += 8;
_ZwWaitForSingleObject = .; . += 8;
_ZwAllocateVirtualMemory = .; . += 8;
_ZwFreeVirtualMemory = .; . += 8;
_ZwCreateThread = .; . += 8;
_PsProcessType = .; . += 8;
_ObReferenceObjectByHandle = .; . += 8;
_ObOpenObjectByPointer = .; . += 8;
_ExEventObjectType = .; . += 8;
_IoAllocateMdl = .; . += 8;
_IoFreeMdl = .; . += 8;
_MmBuildMdlForNonPagedPool = .; . += 8;
_MmMapLockedPagesSpecifyCache = .; . += 8;
_MmUnmapLockedPages = .; . += 8;
}
. = 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;
frida_winnt_user_main;
frida_winnt_user_bootstrap;
local:
*;
};
}
+111
View File
@@ -0,0 +1,111 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rel.dyn : {
_agent_relocs_start = .;
*(.rel.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(4) {
. = ALIGN(4);
_ExAllocatePoolWithTag = .; . += 4;
_ExFreePoolWithTag = .; . += 4;
_PsCreateSystemThread = .; . += 4;
_PsGetCurrentThreadId = .; . += 4;
_KeInitializeEvent = .; . += 4;
_KeWaitForSingleObject = .; . += 4;
_KeSetEvent = .; . += 4;
_KeInitializeDpc = .; . += 4;
_PsSetCreateThreadNotifyRoutine = .; . += 4;
_PsRemoveCreateThreadNotifyRoutine = .; . += 4;
_KeInsertQueueDpc = .; . += 4;
_ZwYieldExecution = .; . += 4;
_KeExpandKernelStackAndCalloutEx = .; . += 4;
_KeBugCheckEx = .; . += 4;
_DbgPrint = .; . += 4;
_MmGetVirtualForPhysical = .; . += 4;
_MmMapIoSpace = .; . += 4;
_MmGetPhysicalAddress = .; . += 4;
_HalGetInterruptVector = .; . += 4;
_IoConnectInterrupt = .; . += 4;
_IoDisconnectInterrupt = .; . += 4;
_PsActiveProcessHead = .; . += 4;
_PsInitialSystemProcess = .; . += 4;
_PsGetProcessId = .; . += 4;
_PsGetProcessImageFileName = .; . += 4;
_PsGetProcessPeb = .; . += 4;
_PsGetCurrentThread = .; . += 4;
_PsGetThreadProcess = .; . += 4;
_PsGetThreadId = .; . += 4;
_PsGetThreadTeb = .; . += 4;
_PsGetContextThread = .; . += 4;
_PsSetContextThread = .; . += 4;
_KeStackAttachProcess = .; . += 4;
_KeUnstackDetachProcess = .; . += 4;
_ZwCreateFile = .; . += 4;
_ZwOpenKey = .; . += 4;
_ZwEnumerateKey = .; . += 4;
_ZwQueryValueKey = .; . += 4;
_ZwReadFile = .; . += 4;
_ZwWriteFile = .; . += 4;
_ZwEnumerateValueKey = .; . += 4;
_ZwDeviceIoControlFile = .; . += 4;
_ZwClose = .; . += 4;
_ZwCreateEvent = .; . += 4;
_ExAllocatePool2 = .; . += 4;
_MmCopyVirtualMemory = .; . += 4;
_KeIpiGenericCall = .; . += 4;
_MmIsAddressValid = .; . += 4;
_ZwClearEvent = .; . += 4;
_ZwWaitForSingleObject = .; . += 4;
_ZwAllocateVirtualMemory = .; . += 4;
_ZwFreeVirtualMemory = .; . += 4;
_ZwCreateThread = .; . += 4;
_PsProcessType = .; . += 4;
_ObReferenceObjectByHandle = .; . += 4;
_ObOpenObjectByPointer = .; . += 4;
_ExEventObjectType = .; . += 4;
_IoAllocateMdl = .; . += 4;
_IoFreeMdl = .; . += 4;
_MmBuildMdlForNonPagedPool = .; . += 4;
_MmMapLockedPagesSpecifyCache = .; . += 4;
_MmUnmapLockedPages = .; . += 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 = .;
}
.bss : {
*(.bss*)
}
. = ALIGN(4);
_heap_start = .;
}
VERSION {
{
global:
_start;
frida_winnt_user_main;
frida_winnt_user_bootstrap;
local:
*;
};
}
+121
View File
@@ -0,0 +1,121 @@
SECTIONS {
/* The large code model gives every section an l-prefixed name of its own, so that
the compiler can reach it with a 64-bit displacement rather than assume 2GB. */
.text : {
*(.text.entrypoint)
*(.ltext.entrypoint)
*(.text*)
*(.ltext*)
}
.rela.dyn : {
_agent_relocs_start = .;
*(.rela.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
*(.lrodata*)
}
.kernel_addrs : ALIGN(8) {
. = ALIGN(8);
_ExAllocatePoolWithTag = .; . += 8;
_ExFreePoolWithTag = .; . += 8;
_PsCreateSystemThread = .; . += 8;
_PsGetCurrentThreadId = .; . += 8;
_KeInitializeEvent = .; . += 8;
_KeWaitForSingleObject = .; . += 8;
_KeSetEvent = .; . += 8;
_KeInitializeDpc = .; . += 8;
_PsSetCreateThreadNotifyRoutine = .; . += 8;
_PsRemoveCreateThreadNotifyRoutine = .; . += 8;
_KeInsertQueueDpc = .; . += 8;
_ZwYieldExecution = .; . += 8;
_KeExpandKernelStackAndCalloutEx = .; . += 8;
_KeBugCheckEx = .; . += 8;
_DbgPrint = .; . += 8;
_MmGetVirtualForPhysical = .; . += 8;
_MmMapIoSpace = .; . += 8;
_MmGetPhysicalAddress = .; . += 8;
_HalGetInterruptVector = .; . += 8;
_IoConnectInterrupt = .; . += 8;
_IoDisconnectInterrupt = .; . += 8;
_PsActiveProcessHead = .; . += 8;
_PsInitialSystemProcess = .; . += 8;
_PsGetProcessId = .; . += 8;
_PsGetProcessImageFileName = .; . += 8;
_PsGetProcessPeb = .; . += 8;
_PsGetCurrentThread = .; . += 8;
_PsGetThreadProcess = .; . += 8;
_PsGetThreadId = .; . += 8;
_PsGetThreadTeb = .; . += 8;
_PsGetContextThread = .; . += 8;
_PsSetContextThread = .; . += 8;
_KeStackAttachProcess = .; . += 8;
_KeUnstackDetachProcess = .; . += 8;
_ZwCreateFile = .; . += 8;
_ZwOpenKey = .; . += 8;
_ZwEnumerateKey = .; . += 8;
_ZwQueryValueKey = .; . += 8;
_ZwReadFile = .; . += 8;
_ZwWriteFile = .; . += 8;
_ZwEnumerateValueKey = .; . += 8;
_ZwDeviceIoControlFile = .; . += 8;
_ZwClose = .; . += 8;
_ZwCreateEvent = .; . += 8;
_ExAllocatePool2 = .; . += 8;
_MmCopyVirtualMemory = .; . += 8;
_KeIpiGenericCall = .; . += 8;
_MmIsAddressValid = .; . += 8;
_ZwClearEvent = .; . += 8;
_ZwWaitForSingleObject = .; . += 8;
_ZwAllocateVirtualMemory = .; . += 8;
_ZwFreeVirtualMemory = .; . += 8;
_ZwCreateThread = .; . += 8;
_PsProcessType = .; . += 8;
_ObReferenceObjectByHandle = .; . += 8;
_ObOpenObjectByPointer = .; . += 8;
_ExEventObjectType = .; . += 8;
_IoAllocateMdl = .; . += 8;
_IoFreeMdl = .; . += 8;
_MmBuildMdlForNonPagedPool = .; . += 8;
_MmMapLockedPagesSpecifyCache = .; . += 8;
_MmUnmapLockedPages = .; . += 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*)
*(.ldata*)
}
.bss : {
*(.bss*)
*(.lbss*)
}
. = ALIGN(8);
_heap_start = .;
}
VERSION {
{
global:
_start;
frida_winnt_user_main;
frida_winnt_user_bootstrap;
local:
*;
};
}
+112
View File
@@ -0,0 +1,112 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rela.dyn : {
_agent_relocs_start = .;
*(.rela.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(8) {
. = ALIGN(8);
_panic = .; . += 8;
_IOLog = .; . += 8;
_kalloc = .; . += 8;
_proc_iterate = .; . += 8;
_proc_find = .; . += 8;
_proc_rele = .; . += 8;
_proc_pid = .; . += 8;
_proc_best_name = .; . += 8;
_proc_task = .; . += 8;
_current_task = .; . += 8;
_current_thread = .; . += 8;
_get_task_map = .; . += 8;
_mach_vm_allocate = .; . += 8;
_mach_vm_deallocate = .; . += 8;
_mach_vm_protect = .; . += 8;
_mach_vm_remap = .; . += 8;
_vm_map_copyin = .; . += 8;
_vm_map_copyout = .; . += 8;
_thread_create = .; . += 8;
_thread_set_state = .; . += 8;
_thread_resume = .; . += 8;
_thread_terminate = .; . += 8;
_task_suspend = .; . += 8;
_task_resume = .; . += 8;
_task_clear_return_wait = .; . += 8;
_get_bsdtask_info = .; . += 8;
_thread_suspend = .; . += 8;
_kfree = .; . += 8;
_kernel_thread_start = .; . += 8;
_assert_wait = .; . += 8;
_assert_wait_timeout = .; . += 8;
_thread_block = .; . += 8;
_thread_wakeup = .; . += 8;
_thread_wakeup_prim = .; . += 8;
_mach_absolute_time = .; . += 8;
_absolutetime_to_nanoseconds = .; . += 8;
_clock_get_calendar_microtime = .; . += 8;
_ml_io_map = .; . += 8;
_ml_vtophys = .; . += 8;
_ml_static_vtop = .; . += 8;
__ZN9IOService11getPlatformEv = .; . += 8;
__ZN9IOServiceC2Ev = .; . += 8;
__ZN8OSSymbol17withCStringNoCopyEPKc = .; . += 8;
__ZN6OSData9withBytesEPKvj = .; . += 8;
_kalloc_data = .; . += 8;
_kfree_data = .; . += 8;
_wakeup = .; . += 8;
_sock_socket = .; . += 8;
_sock_connect = .; . += 8;
_sock_send = .; . += 8;
_sock_receive = .; . += 8;
_sock_close = .; . += 8;
_sysent = .; . += 8;
_task_restartable_ranges_register = .; . += 8;
_task_threads = .; . += 8;
_mach_vm_region = .; . += 8;
_mach_vm_region_recurse = .; . += 8;
_copyin = .; . += 8;
_copyout = .; . += 8;
_ulock_wake = .; . += 8;
_task_info = .; . += 8;
_convert_port_to_thread = .; . += 8;
_convert_port_to_thread_read = .; . += 8;
_convert_port_to_thread_inspect = .; . += 8;
}
. = ALIGN(16384);
_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:
*;
};
}
-59
View File
@@ -1,59 +0,0 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(8) {
. = ALIGN(8);
_panic = .; . += 8;
_IOLog = .; . += 8;
_kalloc = .; . += 8;
_kfree = .; . += 8;
_kernel_thread_start = .; . += 8;
_assert_wait = .; . += 8;
_assert_wait_timeout = .; . += 8;
_thread_block = .; . += 8;
_thread_wakeup = .; . += 8;
_mach_absolute_time = .; . += 8;
_absolutetime_to_nanoseconds = .; . += 8;
_clock_get_calendar_microtime = .; . += 8;
_ml_io_map = .; . += 8;
_ml_vtophys = .; . += 8;
__ZN9IOService11getPlatformEv = .; . += 8;
__ZN9IOServiceC2Ev = .; . += 8;
__ZN8OSSymbol17withCStringNoCopyEPKc = .; . += 8;
__ZN6OSData9withBytesEPKvj = .; . += 8;
_kalloc_data = .; . += 8;
_kfree_data = .; . += 8;
_wakeup = .; . += 8;
_sock_socket = .; . += 8;
_sock_connect = .; . += 8;
_sock_send = .; . += 8;
_sock_receive = .; . += 8;
_sock_close = .; . += 8;
}
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.bss : {
*(.bss*)
}
. = ALIGN(8);
_heap_start = .;
}
VERSION {
{
global:
_start;
local:
*;
};
}
@@ -0,0 +1,17 @@
{
"abi": "eabi",
"arch": "arm",
"crt-objects-fallback": "false",
"data-layout": "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64",
"features": "+v7,+thumb2,+thumb-mode,+soft-float,+strict-align,-neon",
"linker": "rust-lld",
"linker-flavor": "gnu-lld",
"llvm-floatabi": "soft",
"llvm-target": "armv7a-none-eabi",
"max-atomic-width": 64,
"panic-strategy": "abort",
"plt-by-default": false,
"position-independent-executables": true,
"relocation-model": "pic",
"target-pointer-width": 32
}
+147 -22
View File
@@ -8,12 +8,17 @@ fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let devkit_dir = PathBuf::from(env::var("GUMJS_DEVKIT_DIR").unwrap());
let cc_str = env::var("CC_aarch64_unknown_none")
let target = env::var("TARGET").unwrap();
let cc_str = env::var(format!("CC_{}", target.replace('-', "_")))
.or_else(|_| env::var("CC"))
.unwrap_or_else(|_| "cc".to_string());
let cc = Path::new(&cc_str);
let cc_include_paths = detect_gcc_include_paths(cc);
let cc_library_paths = detect_gcc_library_paths(cc);
// Same convention as cc-rs: the variable may carry flags, and a bare-metal
// compiler needs them to find its libc at all.
let mut cc_argv = cc_str.split_whitespace();
let cc = Path::new(cc_argv.next().unwrap());
let cc_args: Vec<&str> = cc_argv.collect();
let cc_include_paths = detect_gcc_include_paths(cc, &cc_args);
let cc_library_paths = detect_gcc_library_paths(cc, &cc_args);
let cc_clang_args: Vec<String> = cc_include_paths
.iter()
@@ -25,9 +30,15 @@ fn main() {
.header(devkit_dir.join("frida-gumjs.h").to_str().unwrap())
.clang_arg("-nostdinc")
.clang_arg("-target")
.clang_arg("aarch64-none-elf")
.clang_arg(format!("{}-none-elf", target.split('-').next().unwrap()))
.clang_args(cc_clang_args)
.clang_arg(format!("-I{}", devkit_dir.to_str().unwrap()))
.clang_arg("-include")
.clang_arg("stddef.h")
.clang_arg("-include")
.clang_arg("stdint.h")
.blocklist_type("max_align_t")
.blocklist_function("memcpy|memmove|memset|memcmp|bcmp|strlen")
.merge_extern_blocks(true)
.generate()
.expect("Unable to generate bindings");
@@ -36,25 +47,131 @@ fn main() {
.write_to_file(out_dir.join("bindings.rs"))
.expect("Couldn't write bindings");
println!(
"cargo:rustc-link-search=native={}",
devkit_dir.to_str().unwrap()
);
println!("cargo:rustc-link-lib=static=frida-gumjs");
for path in cc_library_paths {
println!("cargo:rustc-link-search=native={}", path.to_string_lossy());
// The Linux flavour is a staticlib: kbuild owns the final link, so it — not
// us — decides where the devkit and libc archives come from.
if env::var("CARGO_FEATURE_BLOB").is_ok() {
println!(
"cargo:rustc-link-search=native={}",
devkit_dir.to_str().unwrap()
);
println!("cargo:rustc-link-lib=static=frida-gumjs");
for path in cc_library_paths {
println!("cargo:rustc-link-search=native={}", path.to_string_lossy());
}
for arg in &cc_args {
if let Some(path) = arg.strip_prefix("-L") {
println!("cargo:rustc-link-search=native={path}");
}
}
println!("cargo:rustc-link-lib=static=c");
println!("cargo:rustc-link-lib=static=m");
println!("cargo:rustc-link-arg=--export-dynamic");
println!("cargo:rustc-link-arg=--emit-relocs");
let flavor = if env::var("CARGO_FEATURE_WIN9X").is_ok() {
"win9x"
} else if env::var("CARGO_FEATURE_WINNT").is_ok() {
"winnt"
} else if env::var("CARGO_FEATURE_LINUX_INJECTED").is_ok() {
"linux"
} else {
"xnu"
};
let arch = match target.split('-').next().unwrap() {
"i686" => "x86",
"armv7a" => "arm",
"aarch64" => "arm64",
other => other,
};
let script = format!("agent-{flavor}-{arch}.lds");
println!("cargo:rustc-link-arg=--script={script}");
println!("cargo:rustc-link-arg=--gc-sections");
// All of the agent lives in the library crate, so nothing in the binary
// references the entrypoint the host calls; keep the linker from
// dropping it along with the rest of the unreferenced archive members.
println!("cargo:rustc-link-arg=--undefined=_start");
let note = compile_version_note(&out_dir, cc, &cc_args, arch);
println!("cargo:rustc-link-arg={}", note.to_string_lossy());
}
println!("cargo:rustc-link-lib=static=c");
println!("cargo:rustc-link-lib=static=m");
println!("cargo:rustc-link-arg=--export-dynamic");
println!("cargo:rustc-link-arg=--emit-relocs");
println!("cargo:rustc-link-arg=--script=agent.lds");
println!("cargo:rustc-link-arg=--gc-sections");
if target.starts_with("i686") && env::var("CARGO_FEATURE_LINUX_INJECTED").is_ok() {
let shim = out_dir.join("regparm.o");
let status = Command::new(cc)
.args(&cc_args)
.args(["-m32", "-c", "-O2", "-fPIC", "-ffreestanding", "-o"])
.arg(&shim)
.arg("src/linux/regparm.c")
.status()
.expect("Couldn't run the compiler");
assert!(status.success(), "Couldn't compile the kernel-call shim");
println!("cargo:rustc-link-arg={}", shim.to_string_lossy());
println!("cargo:rerun-if-changed=src/linux/regparm.c");
}
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=GUMJS_DEVKIT_DIR");
println!("cargo:rerun-if-env-changed=FRIDA_VERSION");
println!("cargo:rerun-if-changed={}", devkit_dir.join("frida-gumjs.h").display());
}
pub fn detect_gcc_include_paths(gcc: &Path) -> Vec<PathBuf> {
fn compile_version_note(out_dir: &Path, cc: &Path, cc_args: &[&str], arch: &str) -> PathBuf {
let version = env::var("FRIDA_VERSION")
.ok()
.filter(|v| !v.is_empty())
.unwrap_or_else(|| "0.0.0".to_string());
let payload = format!(
"{{\"type\":\"frida\",\"name\":\"frida-barebone-agent\",\
\"version\":\"{version}\",\"architecture\":\"{arch}\"}}"
);
let source = out_dir.join("version-note.S");
fs::write(
&source,
format!(
".section .note.package,\"\",%note\n\
.balign 4\n\
.long 2f - 1f\n\
.long 4f - 3f\n\
.long 0xcafe1a7e\n\
1: .asciz \"FDO\"\n\
2: .balign 4\n\
3: .asciz {payload:?}\n\
4: .balign 4\n"
),
)
.expect("Couldn't write the version note");
let object = out_dir.join("version-note.o");
let status = Command::new(cc)
.args(cc_args)
.args(["-target", &llvm_target(), "-c", "-o"])
.arg(&object)
.arg(&source)
.status()
.expect("Couldn't run the compiler");
assert!(status.success(), "Couldn't assemble the version note");
object
}
fn llvm_target() -> String {
let target = env::var("TARGET").unwrap();
let spec = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join(format!("{target}.json"));
let text = fs::read_to_string(&spec).expect("Couldn't read the target specification");
let key = "\"llvm-target\"";
let at = text.find(key).expect("Target specification names no LLVM target");
let rest = &text[at + key.len()..];
let open = rest.find('"').unwrap() + 1;
let close = open + rest[open..].find('"').unwrap();
rest[open..close].to_string()
}
pub fn detect_gcc_include_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
let out = Command::new(gcc)
.args(args)
.args(["-xc", "-E", "-v", "-"])
.stdin(Stdio::null())
.output()
@@ -84,7 +201,7 @@ pub fn detect_gcc_include_paths(gcc: &Path) -> Vec<PathBuf> {
let p = Path::new(raw);
if p.exists() {
let canonical = fs::canonicalize(p).expect("Failed to canonicalize include path");
let canonical = without_extended_length_prefix(fs::canonicalize(p).expect("Failed to canonicalize include path"));
if !paths.contains(&canonical) {
paths.push(canonical);
}
@@ -94,8 +211,16 @@ pub fn detect_gcc_include_paths(gcc: &Path) -> Vec<PathBuf> {
paths
}
pub fn detect_gcc_library_paths(gcc: &Path) -> Vec<PathBuf> {
let out = Command::new(gcc).arg("-print-search-dirs").output()
fn without_extended_length_prefix(path: PathBuf) -> PathBuf {
let text = path.to_string_lossy();
match text.strip_prefix(r"\\?\") {
Some(rest) => PathBuf::from(rest),
None => path,
}
}
pub fn detect_gcc_library_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
let out = Command::new(gcc).args(args).arg("-print-search-dirs").output()
.expect("Failed to execute GCC to detect library paths");
let stdout = String::from_utf8_lossy(&out.stdout);
+5 -5
View File
@@ -1,8 +1,8 @@
export FRIDA_BAREBONE_CONFIG=$PWD/etc/xnu.json
export PYTHONPATH=$HOME/src/frida-python
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=/home/oleavr/src/frida-gum/build/bindings/gumjs/devkit
sdk=$HOME/src/frida-core/deps/sdk-none-arm64-softfloat_pic
export CC="clang --target=aarch64-none-elf -ffixed-x18 -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"
export GUMJS_DEVKIT_DIR=$HOME/src/frida-gum/build/none-arm64-softfloat_pic/bindings/gumjs/devkit
@@ -0,0 +1,8 @@
{
"agent": {
"transport": {
"type": "socket",
"path": "/tmp/frida-guest.sock"
}
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"agent": {
"transport": {
"type": "device",
"path": "/dev/frida"
}
}
}
+1
View File
@@ -20,6 +20,7 @@
}
},
"image": {
"type": "xnu-kernelcache",
"file": "/Users/oleavr/src/vphone-cli/vm/iPhone17,3_26.1_23B85_Restore/kernelcache.research.vresearch101",
"symbols": {
"panic": "0x12a2814",
+15
View File
@@ -0,0 +1,15 @@
{
"connection": {
"host": "127.0.0.1",
"port": 1234
},
"kernel": "win9x",
"agent": {
"path": "/home/oleavr/src/frida-core/src/barebone/agent/target-win9x/i686-unknown-none-pic/release/frida-barebone-agent",
"transport": {
"type": "hostlink",
"qmp": "unix:/tmp/claude-1000/-home-oleavr-src-frida-core/0f3e358c-8f66-4246-b793-deb2cd3727f3/scratchpad/qmp.sock",
"bus": "frida-vserial.0"
}
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"connection": {
"host": "127.0.0.1",
"port": 1234
},
"kernel": "winnt",
"agent": {
"path": "/home/oleavr/src/frida-core/src/barebone/agent/target-winnt/i686-unknown-none-pic/release/frida-barebone-agent",
"transport": {
"type": "hostlink",
"qmp": "unix:/tmp/qmp.sock",
"bus": "frida-vserial.0"
}
}
}
+1
View File
@@ -16,6 +16,7 @@
}
},
"image": {
"type": "xnu-kernelcache",
"file": "/Users/oleavr/src/frida-barebone-ios/iPhone11_8_iPhone12_1_14.0_18A5351d_Restore/kernelcache.research.iphone12b",
"base": "0xfffffff007004000",
"symbols": {
+1 -1
View File
@@ -5,6 +5,6 @@ const cm = new CModule(File.readAllBytes("./target/aarch64-unknown-none/release/
const start = new NativeFunction(cm._start, "pointer", []);
const bufferPhysicalAddress = start();
console.log("Ready! Buffer is at physical address:", bufferPhysicalAddress);
$gdb.continue();
$debugger.continue();
Object.assign(globalThis, { cm, start });
@@ -0,0 +1,20 @@
{
"arch": "x86",
"cpu": "pentium4",
"crt-objects-fallback": "false",
"data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
"features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float",
"linker": "rust-lld",
"linker-flavor": "gnu-lld",
"llvm-target": "i686-unknown-none-elf",
"max-atomic-width": 64,
"panic-strategy": "abort",
"plt-by-default": false,
"position-independent-executables": true,
"relocation-model": "pic",
"rustc-abi": "softfloat",
"stack-probes": {
"kind": "inline"
},
"target-pointer-width": 32
}
+20
View File
@@ -0,0 +1,20 @@
{
"arch": "x86",
"cpu": "pentium4",
"crt-objects-fallback": "false",
"data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
"features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float",
"linker": "rust-lld",
"linker-flavor": "gnu-lld",
"llvm-target": "i686-unknown-none-elf",
"max-atomic-width": 64,
"panic-strategy": "abort",
"plt-by-default": true,
"position-independent-executables": false,
"relocation-model": "static",
"rustc-abi": "softfloat",
"stack-probes": {
"kind": "inline"
},
"target-pointer-width": 32
}
+28
View File
@@ -0,0 +1,28 @@
MODULE_NAME ?= frida-agent
obj-m += $(MODULE_NAME).o
# frida-agent-prelinked.o is not built from source here: it is the Rust agent, the
# GumJS devkit and the C library collapsed into a single relocatable object by the prelink
# step in Makefile. Copy it in through if_changed rather than kbuild's _shipped rule,
# so the .cmd file modpost expects gets written.
$(MODULE_NAME)-y := frida-kmod.o frida-agent-prelinked.o
targets += frida-agent-prelinked.o
quiet_cmd_frida_agent_prelinked = COPY $@
cmd_frida_agent_prelinked = cp $< $@
$(obj)/frida-agent-prelinked.o: $(src)/frida-agent-prelinked.o.bin FORCE
$(call if_changed,frida_agent_prelinked)
# On x86 objtool validates every object going into a module and generates its ORC
# unwind tables. It was not built to read a JavaScript runtime linked against a
# freestanding libc: it gives up on the first Rust switch jump table it cannot
# follow, and the tables would only ever describe frames no kernel stack trace walks
# into. Both the blob and the module object it is linked into have to be excused,
# since objtool runs over each.
OBJECT_FILES_NON_STANDARD_frida-agent-prelinked.o := y
OBJECT_FILES_NON_STANDARD_$(MODULE_NAME).o := y
ccflags-y += $(FRIDA_KMOD_DEFS)
+206
View File
@@ -0,0 +1,206 @@
# Builds frida-agent.ko in two steps, because kbuild insists on owning the final
# link:
#
# 1. Collapse the Rust staticlib plus everything it needs — the GumJS devkit and
# the C library — into one relocatable object, and localize every symbol except
# the entry points frida-kmod.c calls. Localizing matters: Gum and the C library
# define names like memcpy and malloc that must not leak into the kernel's
# namespace.
# 2. Hand that object to kbuild as a shipped object, so vermagic, modinfo and
# CRCs all come from the target kernel's own build system.
#
# See README.md for usage.
# KDIR is the kernel to build against. When it is a source tree that was configured
# elsewhere — which is what Android's modules_prepare tarball gives — point KOUT at
# the prepared tree.
KDIR ?= /lib/modules/$(shell uname -r)/build
PROFILE ?= release
# The kernel's own name for the architecture, and the one Rust knows it by. Both
# follow from the machine this is built for, which defaults to the one building.
ARCH ?= $(shell uname -m \
| sed -e 's/^i[3-6]86$$/x86/' -e 's/^x86_64$$/x86/' -e 's/^aarch64$$/arm64/')
BITS ?= $(shell uname -m \
| sed -e 's/^i[3-6]86$$/32/' -e 's/^x86_64$$/64/' -e 's/^aarch64$$/64/')
ifeq ($(ARCH)-$(BITS),x86-32)
RUST_TARGET ?= i686-unknown-none
AGENT_TARGET_FLAGS ?= --target $(AGENT_DIR)/$(RUST_TARGET).json -Z json-target-spec
SDK_ARCH ?= x86
TOOLPREFIX ?=
AGENT_LD_EMULATION ?= elf_i386
else ifeq ($(ARCH)-$(BITS),x86-64)
RUST_TARGET ?= x86_64-unknown-none
SDK_ARCH ?= x86_64
TOOLPREFIX ?=
AGENT_LD_EMULATION ?= elf_x86_64
else ifeq ($(ARCH)-$(BITS),arm64-64)
RUST_TARGET ?= aarch64-unknown-none
SDK_ARCH ?= arm64
TOOLPREFIX ?= aarch64-none-elf-
else
$(error Unsupported ARCH "$(ARCH)" and BITS "$(BITS)"; this flavour covers \
x86/32, x86/64 and arm64/64)
endif
AGENT_TARGET_FLAGS ?= --target $(RUST_TARGET)
AGENT_DIR := $(CURDIR)/..
AGENT_TARGET_DIR := $(AGENT_DIR)/target-linux
AGENT_LIB := $(AGENT_TARGET_DIR)/$(RUST_TARGET)/$(PROFILE)/libfrida_barebone_agent.a
GUMJS_DEVKIT_DIR ?= $(error Set GUMJS_DEVKIT_DIR to the directory holding libfrida-gumjs.a)
# Any binutils that speak the target's ELF will do; nothing here is compiled with
# them. Building for the host's own architecture, that is the host's binutils.
AGENT_LD ?= $(TOOLPREFIX)ld
AGENT_AR ?= $(TOOLPREFIX)ar
AGENT_NM ?= $(TOOLPREFIX)nm
AGENT_OBJCOPY ?= $(TOOLPREFIX)objcopy
# The ABI the kernel imposes on anything linked into it. The flags only reach crates
# Cargo compiles, and the toolchain ships core, alloc and compiler_builtins prebuilt
# without them, so those three have to be rebuilt from source as well. That needs the
# rust-src component and an unstable flag, hence RUSTC_BOOTSTRAP.
#
# On x86_64 that is endbr64 on every address-taken function, since a kernel with
# CONFIG_X86_KERNEL_IBT faults an indirect call landing anywhere else, and switches
# compiled as comparisons rather than jump tables, which objtool cannot follow into.
# Soft-float, the kernel code model and the absent red zone come with the target
# itself. On arm64
# it is x18, which the kernel reserves for the shadow call stack, so nothing linked in
# may use it as a general-purpose register.
ifeq ($(ARCH)-$(BITS),x86-32)
AGENT_RUSTFLAGS ?=
AGENT_CFLAGS ?= --target=i386-none-elf \
-Xclang -target-feature -Xclang +soft-float -mno-mmx -mno-sse \
-mlong-double-64 -fno-pic --sysroot=$(FRIDA_SDK)
else ifeq ($(ARCH)-$(BITS),x86-64)
AGENT_RUSTFLAGS ?= -Zcf-protection=branch \
-Cllvm-args=--min-jump-table-entries=65536 \
-Crelocation-model=static -Zplt=yes
AGENT_CFLAGS ?= --target=x86_64-none-elf \
-Xclang -target-feature -Xclang +soft-float -mno-mmx -mno-sse -mno-red-zone \
-mlong-double-64 -mcmodel=kernel -fcf-protection=branch -fno-jump-tables \
-fno-pic --sysroot=$(FRIDA_SDK)
else
AGENT_RUSTFLAGS ?= -Zfixed-x18
AGENT_CFLAGS ?= --target=aarch64-none-elf -mabi=aapcs-soft -mgeneral-regs-only \
-ffixed-x18 -fno-pic --sysroot=$(FRIDA_SDK)
endif
AGENT_BUILD_STD ?= -Z build-std=core,alloc,compiler_builtins
# The devkit header reaches for GLib's and capstone's, which a devkit built against
# an SDK leaves in that SDK rather than beside itself. The sysroot's own include
# directory is spelled out because only clang's bare-metal driver, which it selects
# for arm64 but not for x86_64, searches there on its own.
AGENT_CFLAGS += -isystem $(FRIDA_SDK)/include \
-I$(FRIDA_SDK)/include/glib-2.0 \
-I$(FRIDA_SDK)/lib/glib-2.0/include \
-I$(FRIDA_SDK)/include/capstone \
-I$(FRIDA_SDK)/include/json-glib-1.0
# The soft-float SDK, whose libc is picolibc and whose compiler runtime is
# compiler-rt. GCC's own newlib and libgcc are hard-float and cannot be used here.
FRIDA_SDK ?= $(error Set FRIDA_SDK to a none-$(SDK_ARCH)-softfloat_nopic SDK prefix)
LIBM = $(FRIDA_SDK)/lib/libm.a
LIBBUILTINS = $(FRIDA_SDK)/lib/libclang_rt.builtins.a
# bindgen reads the devkit header through whatever this compiler reports as its
# include path, so it has to be one that knows where the SDK keeps its libc. It has
# to be clang either way: -mabi=aapcs-soft landed there in 19 and GCC has it at no
# version, and on x86_64 the soft-float ABI is reachable only as an -Xclang feature.
AGENT_CC ?= clang
PRELINKED := frida-agent-prelinked.o.bin
AGENT_LIB_SOFTFLOAT := $(AGENT_TARGET_DIR)/libfrida-barebone-agent-softfloat.a
# On arm64 the toolchain's own compiler_builtins is compiled for the ordinary AAPCS,
# where a double travels in d0: its __muldf3 opens with "fmov x14, d0". Everything
# else there is -mabi=aapcs-soft and passes that same double in x0. Whole-archiving
# the staticlib puts those definitions in front of the SDK's compiler-rt, which has
# the soft-float ones, and every double the C library computes then comes back
# garbage — quietly, since the arithmetic still runs. Drop them and let compiler-rt
# answer instead. x86_64 needs none of this: soft-float is part of the Rust target
# there, so the copy build-std compiles already passes doubles the way the C half
# expects.
ifeq ($(ARCH),arm64)
DROP_HARDFLOAT_BUILTINS = \
$(AGENT_AR) d $@ $$($(AGENT_NM) --print-armap $@ | sed -n 's/^__muldf3 in //p') && \
$(AGENT_AR) s $@
else
DROP_HARDFLOAT_BUILTINS = :
endif
# The agent puts malloc, the C library's locks and its process stubs on the kernel's
# own facilities, so picolibc's versions are duplicate definitions rather than
# fallbacks. Work out which members they live in instead of naming them: the set
# follows from what the agent defines, and picolibc moves things between releases.
LIBC := $(AGENT_TARGET_DIR)/libc-trimmed.a
ENTRY_POINTS := frida_agent_start frida_agent_stop \
gum_interceptor_obtain gum_interceptor_begin_transaction \
gum_interceptor_end_transaction gum_interceptor_replace \
gum_interceptor_revert
KEEP_GLOBAL := $(foreach s,$(ENTRY_POINTS),--keep-global-symbol=$(s))
# Two structures the reparenting path reaches into lost a member upstream, and
# neither change announces itself as anything frida-kmod.c could test for. The
# headers it is compiled against are the authority on both, so ask them.
FRIDA_KMOD_DEFS := \
$(if $(shell grep -l 'list_head[[:space:]]*thread_group;' \
$(KDIR)/include/linux/sched.h),-DFRIDA_HAVE_TASK_THREAD_GROUP) \
$(if $(shell grep -l 'spinlock_t[[:space:]]*lock;' \
$(KDIR)/include/linux/fs_struct.h),-DFRIDA_HAVE_FS_STRUCT_LOCK)
all: modules
# A module that dies in its init path is left wedged under its own name until the
# next boot, so bring-up wants a fresh one per attempt.
MODULE_NAME ?= frida-agent
modules: $(PRELINKED)
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) \
FRIDA_KMOD_DEFS="$(FRIDA_KMOD_DEFS)" MODULE_NAME=$(MODULE_NAME) modules
$(PRELINKED): $(AGENT_LIB_SOFTFLOAT) $(LIBC) prelink.lds
$(AGENT_LD) -r $(if $(AGENT_LD_EMULATION),-m $(AGENT_LD_EMULATION)) \
-T prelink.lds -o $@.tmp \
--whole-archive $(AGENT_LIB_SOFTFLOAT) --no-whole-archive \
--start-group \
$(GUMJS_DEVKIT_DIR)/libfrida-gumjs.a \
$(LIBC) \
$(LIBM) \
$(LIBBUILTINS) \
--end-group
$(AGENT_OBJCOPY) $(KEEP_GLOBAL) --strip-debug \
--remove-section=.llvmbc --remove-section=.llvmcmd $@.tmp $@
rm -f $@.tmp
$(AGENT_LIB_SOFTFLOAT): agent
cp $(AGENT_LIB) $@
$(DROP_HARDFLOAT_BUILTINS)
$(LIBC): export LC_ALL = C
$(LIBC): $(AGENT_LIB_SOFTFLOAT)
cp $(FRIDA_SDK)/lib/libc.a $@
$(AGENT_NM) --defined-only $(AGENT_LIB_SOFTFLOAT) \
| awk 'NF == 3 && $$2 ~ /^[TDBR]$$/ { print $$3 }' | sort -u > $@.agent-syms
$(AGENT_NM) --print-armap $@ | sed -n 's/^\([^ ]*\) in \(.*\)$$/\1 \2/p' | sort -u > $@.members
$(AGENT_AR) d $@ $$(join $@.agent-syms $@.members | awk '{ print $$2 }' | sort -u)
$(AGENT_AR) s $@
rm -f $@.agent-syms $@.members
agent:
cd $(AGENT_DIR) && RUSTC_BOOTSTRAP=1 RUSTFLAGS="$(AGENT_RUSTFLAGS)" \
CC_$(subst -,_,$(RUST_TARGET))="$(AGENT_CC) $(AGENT_CFLAGS)" \
cargo build --$(PROFILE) \
$(AGENT_TARGET_FLAGS) --features linux \
--target-dir $(AGENT_TARGET_DIR) $(AGENT_BUILD_STD)
clean:
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) clean
rm -f $(PRELINKED) $(PRELINKED).tmp $(AGENT_LIB_SOFTFLOAT) $(LIBC)
.PHONY: all modules agent clean
+291
View File
@@ -0,0 +1,291 @@
# Linux kernel module
The same agent that the Barebone backend injects into XNU from the outside, but
loaded from the inside as an ordinary kernel module.
## How it differs from the XNU flavor
| | XNU | Linux |
|-------------------|--------------------------------------------------|-------------------------------------------|
| Delivery | remote stub injects a freestanding ELF | `insmod frida-agent.ko` |
| Kernel primitives | addresses patched into `.kernel_addrs` by the host | direct calls into `frida-kmod.c` |
| Bootstrap config | GVariant blob written into guest memory | module parameters |
| Transport | virtio hostlink or vsock | `/dev/frida` character device |
| Page permissions | host rewrites descriptors over the hostlink | `set_memory_*()` |
| Writable alias | shadow copy, committed via physical-memory bridge | `vmap()` of the same pages |
| Symbols | table supplied by the host | kallsyms |
Because the Linux agent performs its own memory work, it never issues the
`RemapWritablePages`, `MemoryProtect` or `PatchCode` host RPCs. The wire protocol
is otherwise identical, so the host side sees the same `(yqv)` frames.
## Building
Four inputs: this repository, a GumJS devkit built for
`none-<arch>-softfloat_nopic`, the SDK it was built against, and the kernel the module is for. `Makefile`
covers x86_64 and arm64, and defaults to the architecture of the machine building.
Building for the running kernel is a good deal shorter than cross-building for a
device, so that walkthrough comes first; the arm64 one below it is the same steps
with the kernel obtained from elsewhere.
### Toolchain
rustup target add x86_64-unknown-none # or aarch64-unknown-none
rustup component add rust-src
sudo dnf install clang binutils kernel-devel-$(uname -r)
clang must be 19 or newer, since that is where `-mabi=aapcs-soft` landed; GCC does
not implement it at any version, nor does it reach x86_64's soft-float ABI. The
binutils are for the prelink, which reads the archive map that only GNU `nm` prints —
the LLVM one does not; cross-building wants the target's, natively the host's own
will do. The host compiler is for Cargo's own build scripts, which run on the machine
doing the building.
The commands below are run from the top of this repository, with `releng` checked
out — `git submodule update --init releng` if the clone did not recurse.
### For the running x86_64 kernel
No prebuilt bundles are published for `none-x86_64-softfloat_nopic`, so both inputs are
built locally, against the same SDK:
releng/deps.py build --bundle=sdk --host=none-x86_64-softfloat_nopic
mkdir -p ~/sdk-none-x86_64-softfloat_nopic
tar -C ~/sdk-none-x86_64-softfloat_nopic -xf deps/sdk-none-x86_64-softfloat_nopic.tar.xz
gum=subprojects/frida-gum
mkdir -p $gum/build/none-x86_64-softfloat_nopic
(cd $gum/build/none-x86_64-softfloat_nopic \
&& FRIDA_DEPS=$PWD/../../../../deps ../../configure \
--host=none-x86_64-softfloat_nopic \
--enable-gumjs \
--with-devkits=gumjs \
--with-devkit-symbol-scope=original \
&& make)
make -C src/barebone/agent/linux \
FRIDA_SDK=$HOME/sdk-none-x86_64-softfloat_nopic \
GUMJS_DEVKIT_DIR=$PWD/$gum/build/none-x86_64-softfloat_nopic/bindings/gumjs/devkit
Everything else — `KDIR`, the Rust target, the binutils, the kernel's own name for the
architecture — follows from the running kernel.
### For an arm64 device
Here there are published bundles to start from, and the kernel has to be assembled.
#### The devkit and the SDK
version=17.17.0
base=https://github.com/frida/frida/releases/download/$version
curl -LO $base/frida-gumjs-devkit-$version-none-arm64-softfloat_nopic.tar.xz
mkdir -p ~/gumjs-devkit
tar -C ~/gumjs-devkit -xf frida-gumjs-devkit-$version-none-arm64-softfloat_nopic.tar.xz
releng/deps.py sync sdk none-arm64-softfloat_nopic ~/sdk-none-arm64-softfloat_nopic
The SDK carries picolibc and the compiler-rt builtins the devkit expects to be linked
against, and `releng/deps.toml` pins which one. Mixing a devkit with an SDK built for
a different libc leaves undefined symbols at prelink time.
#### The kernel
Nothing here needs the device's own kernel to be built, but the config alone is not
enough either. `uname -r` ends in the GKI commit and the build number:
adb shell uname -r
6.1.145-android14-11-gc1de4747ac59-ab14219743
which is commit `c1de4747ac59` and build `14219743`. Three pieces are keyed to them:
commit=c1de4747ac59
build=14219743
ci=https://ci.android.com/builds/submitted/$build/kernel_aarch64/latest/raw
mkdir -p ~/kernel-prepared ~/kernel-source
curl -sSL $ci/modules_prepare_outdir.tar.gz | tar -xz -C ~/kernel-prepared
curl -sSL $ci/kernel_aarch64_Module.symvers -o ~/kernel-prepared/Module.symvers
curl -sSL https://android.googlesource.com/kernel/common/+archive/$commit.tar.gz \
| tar -xz -C ~/kernel-source
`modules_prepare_outdir.tar.gz` is the configured build tree with `scripts/` already
compiled as x86-64 binaries, so the kbuild half has to run on an x86-64 Linux host —
an arm64 one gets as far as `CC [M]` and then fails to run `fixdep`. `Module.symvers`
carries the CRCs `CONFIG_MODVERSIONS` checks. The top-level `Makefile` in that prepared tree is a stub
that includes the source's, hence both `KDIR` and `KOUT` below.
A different GKI build of the same branch does not substitute for these: vermagic is
compared verbatim, and it names the exact build.
#### Build
make -C src/barebone/agent/linux \
FRIDA_SDK=$HOME/sdk-none-arm64-softfloat_nopic \
GUMJS_DEVKIT_DIR=$HOME/gumjs-devkit \
AGENT_LD=aarch64-linux-gnu-ld \
AGENT_AR=aarch64-linux-gnu-ar \
AGENT_NM=aarch64-linux-gnu-nm \
AGENT_OBJCOPY=aarch64-linux-gnu-objcopy \
KDIR=$HOME/kernel-source \
KOUT=$HOME/kernel-prepared \
LLVM=1
`make` builds the Rust staticlib itself, prelinks it against the devkit and the SDK,
then hands the result to kbuild. The `AGENT_*` overrides are only needed because the
defaults name an `aarch64-none-elf-` GNU toolchain; drop them if that is what is
installed.
The prelink half runs anywhere; the kbuild half needs the x86-64 Linux host above.
Cross-building from macOS means running that step in a container holding the kernel
tree.
adb push src/barebone/agent/linux/frida-agent.ko /data/local/tmp/
## Loading
insmod frida-agent.ko
The module's init path returns as soon as the worker thread is running — watch
`dmesg` for `frida: listening on /dev/frida`. `rmmod frida-agent` unwinds in the
reverse order and does not return until the worker is off the module's text.
## Talking to it
The agent exposes `/dev/frida` and waits: one client at a time, `read`/`write` of the
same length-prefixed frames the other transports carry, with `poll` for readiness.
It never dials out, so there is no network namespace to get right and no ordering
requirement between loading the module and attaching a client.
On Android the node is created by devtmpfs without a policy-specific label, so an
SELinux-enforcing system needs the client's domain granted access to it before it can
open the device. A socket-based transport was the other option considered and is a
worse fit: `AF_VSOCK` only reaches a peer when a hypervisor is on the other side, and
having the kernel connect out to loopback needs something already listening before
the module loads.
### Reaching it from a host
Serve the Barebone device from a frida-server running on the target, so the `open()`
happens next to the device node and the host talks to an ordinary frida-server:
FRIDA_BAREBONE_CONFIG=/data/local/tmp/linux-kmod.json \
frida-server --device barebone -l 127.0.0.1:27042
`etc/linux-kmod.json` is that config. From the host, with `adb forward tcp:27042
tcp:27042` in place:
frida -H 127.0.0.1:27042 -p 0
## Injecting into userspace
Loaded on a live system the module doubles as a ptrace-free injector for ordinary
processes — how the Linux local backend reaches its targets when the module is
present, falling back to `ptrace` when it is not. There is no second device node;
the channel is `prctl()` with a magic option and a token in the fifth argument,
prctl(0x46524944, op, arg1, arg2, 0x1d5f9e6b2c7a4038)
and any call whose option or token does not match falls through to the real
prctl, so probing for the module looks like a stock kernel. `op` 32 pings and
returns the magic.
Allocate, free, read, write and spawn — the primitives the injector drives
against a target pid — require `CAP_SYS_ADMIN`. A spawned thread is a real
`CLONE_THREAD` sibling that adopts the target's mm, files, credentials,
namespaces, fs and SysV semaphore undo list, so it is a native member of the
group rather than a kernel thread wearing its address space.
Cloaking is Gum's own registry mirrored into the kernel so it holds against
`/proc` too: the module intercepts the `maps`, `smaps`, task and status readers
and drops cloaked threads, ranges and fds while keeping the counts consistent. A
process cloaks its own resources with the token alone — no capability — so an
injected agent self-cloaks by forwarding Gum's updates over the same channel,
and the loader adds the footprint the agent itself never sees. A cloaked thread
still sees through the cloak when it reads `/proc`, so the agent is not blind to
its own process.
## What the target kernel has to provide
- **x86_64 or arm64.** The register-level pieces — the page size, cache maintenance,
where the syscall ABI leaves prctl()'s arguments, and how a spawned thread's
registers are set up for its return to userspace — have a half for each. The XNU
flavor remains arm64-only.
- **`CONFIG_KPROBES` and `CONFIG_KALLSYMS_ALL`.** A good deal of what the shim
needs is compiled in but not exported — `kallsyms_lookup_name`,
`kallsyms_on_each_symbol` and `set_memory_*` all are, and GKI trims its export
table to the KMI symbol list on top of that. The shim resolves them by name
through a kprobe, which is the standard way back in. Without kallsyms the module
still loads but cannot look up symbols or change page permissions.
- **Unsigned modules.** `CONFIG_MODULE_SIG_FORCE` must be off. Android's
`CONFIG_MODULE_SIG_PROTECT` is fine: it only demands signatures for modules on
the GKI list.
## Kernel ABI constraints
A hardened kernel imposes an ABI on anything linked into it, and the FP constraint
below is why this flavor is built against a soft-float SDK
(`none-arm64-softfloat_nopic`, `none-x86_64-softfloat_nopic`) rather than an ordinary
bare-metal one. The remaining constraints are per-architecture.
- **FP/SIMD.** JavaScript numbers are doubles, so a hardfloat build would use FP
for as long as the runtime lives, and neither architecture lets a kernel thread
hold it for free — x86 wants every stretch bracketed by `kernel_fpu_begin()`, and
on arm64 kernels before 6.9 there is no correct way to hold it at all:
`kernel_neon_begin()` runs with preemption disabled so we cannot sleep under it,
and the scheduler does not save a kernel thread's FP registers — live values are
lost on preemption and the FP state of whichever user task was interrupted is
corrupted. Building everything soft-float removes the question rather than
answering it: `-mabi=aapcs-soft -mgeneral-regs-only` on arm64, and on x86_64
LLVM's `+soft-float`, which is also what Rust's own `x86_64-unknown-none` turns
on, so the two halves agree on where a double travels. `-mno-sse` alone does not
reach the ABI; and `long double` has to be narrowed to 64 bits with
`-mlong-double-64`, since soft-float has no lowering for x87's 80-bit format and
LLVM crashes rather than calling out to a builtin.
- **Indirect branch tracking (x86_64).** With `CONFIG_X86_KERNEL_IBT` an indirect
call landing on anything but an `endbr64` faults, so everything linked in is built
`-fcf-protection=branch` and the Rust half `-Zcf-protection=branch`.
- **Kernel code model and red zone (x86_64).** Modules are loaded into the top 2GB
of the address space and their relocations are resolved as if by `-mcmodel=kernel`,
and there is no red zone below the stack pointer for an interrupt to step on. Both
come with the Rust target; the C half is told explicitly.
- **objtool (x86_64).** It validates every object entering a module and builds its
ORC unwind tables, and was not written to read a JavaScript runtime linked against
a freestanding libc. `Kbuild` marks the prelinked blob `OBJECT_FILES_NON_STANDARD`.
- **Shadow call stack (arm64).** With `CONFIG_SHADOW_CALL_STACK` the kernel reserves x18, so
the SDK and Gum are built `-ffixed-x18` and the Rust half with `-Zfixed-x18`.
Anything linked in that uses x18 as a scratch register overwrites the kernel's SCS
pointer, and the thread then returns through whatever that address happens to hold.
The Rust flag only reaches crates Cargo compiles, so `core`, `alloc` and
`compiler_builtins` — which the toolchain ships prebuilt, and which between them
account for every x18 write that used to reach the module — are rebuilt from source
via `-Z build-std`. That needs `rustup component add rust-src`.
## The soft-float libc
The libc is picolibc and the compiler runtime is compiler-rt, both from the
soft-float SDK that `FRIDA_SDK` points at. `Makefile` trims a copy of its
`libc.a` before the prelink: the agent implements `malloc`, the C library's locks and
its process stubs over the kernel's own facilities, so picolibc's are duplicate
definitions rather than fallbacks. The set is derived from what the agent defines
rather than listed, since picolibc moves things between releases.
On arm64 those compiler-rt builtins are load-bearing in a way that is easy to miss.
Rust's toolchain carries its own `compiler_builtins`, compiled for the ordinary AAPCS
where a double travels in `d0`, and the prelink whole-archives the Rust staticlib — so
without the filtering step in `Makefile` those definitions answer the C library's calls
to `__muldf3` and friends, which pass the same double in `x0`. Nothing crashes. The
arithmetic simply returns garbage, and the first thing to notice is GLib deciding a
hash table need not grow. x86_64 needs none of that filtering: soft-float is part of
the Rust target there, so the copy `-Z build-std` compiles already agrees with the C
half.
## What a script may look like
The runtime compiles scripts on a kernel thread's stack, and QuickJS parses
expressions by recursion, so the ceiling is nesting rather than length: roughly
fourteen levels of parentheses or fifteen of object literals on arm64 Linux, whose
kernel stack is 16 KiB — as is x86_64's. A script that exceeds it fails to compile rather than taking
anything down, though the message is not always `stack overflow` — an over-deep
`if`/`else` chain reports `expecting ';'`. Long scripts are fine; deep ones are not.
File diff suppressed because it is too large. Load diff
+8
View File
@@ -0,0 +1,8 @@
SECTIONS
{
.init_array 0 : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
}
+4
View File
@@ -0,0 +1,4 @@
/frida-agent
/frida-agent.kext/
/frida-kext.o
/frida-signing.o
+37
View File
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>frida-agent</string>
<key>CFBundleIdentifier</key>
<string>re.frida.agent</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>frida-agent</string>
<key>CFBundlePackageType</key>
<string>KEXT</string>
<key>CFBundleShortVersionString</key>
<string>@FRIDA_VERSION@</string>
<key>CFBundleVersion</key>
<string>@BUNDLE_VERSION@</string>
<key>OSBundleRequired</key>
<string>Root</string>
<key>OSBundleLibraries</key>
<dict>
<key>com.apple.kpi.bsd</key>
<string>12.0</string>
<key>com.apple.kpi.libkern</key>
<string>12.0</string>
<key>com.apple.kpi.iokit</key>
<string>12.0</string>
<key>com.apple.kpi.mach</key>
<string>12.0</string>
<key>com.apple.kpi.unsupported</key>
<string>12.0</string>
</dict>
</dict>
</plist>
+88
View File
@@ -0,0 +1,88 @@
PROFILE ?= release
ARCH ?= arm64e
ifeq ($(ARCH),arm64e)
RUST_TARGET ?= arm64e-apple-darwin
CLANG_TARGET ?= arm64e-apple-macos11
else ifeq ($(ARCH),arm64)
RUST_TARGET ?= aarch64-apple-darwin
CLANG_TARGET ?= arm64-apple-macos11
else
RUST_TARGET ?= x86_64-apple-darwin
CLANG_TARGET ?= x86_64-apple-macos11
endif
AGENT_DIR := $(CURDIR)/..
AGENT_TARGET_DIR := $(AGENT_DIR)/target-xnu-kext
AGENT_LIB := $(AGENT_TARGET_DIR)/$(RUST_TARGET)/$(PROFILE)/libfrida_barebone_agent.a
SDK ?= $(shell xcrun --sdk macosx --show-sdk-path)
KERNEL_HEADERS := $(SDK)/System/Library/Frameworks/Kernel.framework/Headers
FRIDA_VERSION := $(or $(FRIDA_VERSION),0.0.0)
BUNDLE_VERSION := $(firstword $(subst -, ,$(FRIDA_VERSION)))
FRIDA_SDK ?= $(error Set FRIDA_SDK to a macos-$(ARCH)-kernel SDK prefix)
GUMJS_DEVKIT_DIR ?= $(error Set GUMJS_DEVKIT_DIR to the directory holding libfrida-gumjs.a)
AGENT_RUSTFLAGS ?= -Cembed-bitcode=no
AGENT_BUILD_STD ?= -Z build-std=core,alloc,compiler_builtins
GLUE_CFLAGS ?= -target $(CLANG_TARGET) -isysroot $(SDK) -I$(KERNEL_HEADERS) \
-mkernel -fno-builtin -fno-common -nostdinc -std=gnu11 \
-DKERNEL -DKERNEL_PRIVATE -DAPPLE -D__MACHO__=1
AGENT_CC ?= clang -target $(CLANG_TARGET) -mkernel -nostdlibinc \
-isystem $(FRIDA_SDK)/include \
-I$(FRIDA_SDK)/include/glib-2.0 \
-I$(FRIDA_SDK)/lib/glib-2.0/include \
-I$(FRIDA_SDK)/include/capstone \
-I$(FRIDA_SDK)/include/json-glib-1.0
stamp_version = sed -e 's/@FRIDA_VERSION@/$(FRIDA_VERSION)/' \
-e 's/@BUNDLE_VERSION@/$(BUNDLE_VERSION)/' Info.plist
BUNDLE := frida-agent.kext
BINARY := $(BUNDLE)/Contents/MacOS/frida-agent
all: $(BUNDLE)
skeleton: frida-kext.o Info.plist
mkdir -p $(BUNDLE)/Contents/MacOS
$(stamp_version) > $(BUNDLE)/Contents/Info.plist
$(shell xcrun --find ld) -kext -arch $(ARCH) -o $(BINARY) frida-kext.o
$(BUNDLE): frida-agent Info.plist
mkdir -p $(BUNDLE)/Contents/MacOS
$(stamp_version) > $(BUNDLE)/Contents/Info.plist
cp frida-agent $(BINARY)
frida-agent: frida-kext.o frida-signing.o agent
$(shell xcrun --find ld) -kext -arch $(ARCH) -o $@ \
frida-kext.o \
frida-signing.o \
$(AGENT_LIB) \
$(GUMJS_DEVKIT_DIR)/libfrida-gumjs.a \
$(FRIDA_SDK)/lib/libc.a \
$(FRIDA_SDK)/lib/libm.a \
$(FRIDA_SDK)/lib/libclang_rt.builtins.a
frida-kext.o: frida-kext.c
$(shell xcrun --find clang) $(GLUE_CFLAGS) -c $< -o $@
frida-signing.o: frida-signing.c
$(AGENT_CC) -I$(GUMJS_DEVKIT_DIR) -c $< -o $@
agent:
cd $(AGENT_DIR) && RUSTC_BOOTSTRAP=1 RUSTFLAGS="$(AGENT_RUSTFLAGS)" \
CC_$(subst -,_,$(RUST_TARGET))="$(AGENT_CC)" \
GUMJS_DEVKIT_DIR="$(GUMJS_DEVKIT_DIR)" \
cargo build --$(PROFILE) \
--target $(RUST_TARGET) --features xnu-kext \
--target-dir $(AGENT_TARGET_DIR) $(AGENT_BUILD_STD)
clean:
rm -rf $(BUNDLE) frida-agent frida-kext.o frida-signing.o
.PHONY: all agent clean skeleton
+123
View File
@@ -0,0 +1,123 @@
# The agent as a kext
The XNU flavour of the barebone agent is ordinarily a blob: a host with a debugger writes it into
a running kernel, tells it where that kernel keeps the things it calls, and starts it. The Linux
flavour can also be built as a module the kernel loads (`../linux`), and this is the same thing for
XNU -- a kext macOS loads.
What the two packagings share is everything the agent does. What differs is how it is put there,
how it is told about the kernel around it, and how the host reaches it.
## Building
Three things, in order. First an SDK for this kernel -- picolibc, GLib, QuickJS, capstone and the
rest, built for `macos-arm64e-kernel` and published prebuilt:
cd frida-core
releng/deps.py sync sdk macos-arm64e-kernel ~/sdk-macos-arm64e-kernel
It is built from the same sources every other flavour is, six of them pinned to a commit that
says what a Darwin kernel is not; see below. Then a GumJS devkit against it:
cd frida-gum
./configure --host=macos-arm64e-kernel --enable-gumjs --with-devkits=gumjs \
--with-devkit-symbol-scope=original
make
Then the kext itself:
make FRIDA_SDK=~/sdk-macos-arm64e-kernel \
GUMJS_DEVKIT_DIR=.../frida-gum/build/bindings/gumjs/devkit
which builds the agent as a static library for the kernel's ABI, `frida-kext.c` against the Kernel
framework's headers, and links the two with the devkit into `frida-agent.kext`.
`make skeleton` builds the glue alone, which is enough to check the pipeline without the rest.
## How it is told about the kernel
A kext may call what the kernel exports to kexts, and the agent calls a great deal more than that:
`proc_find` and `copyin` are exported, `task_threads` and `mach_vm_allocate` are not. So the
addresses arrive the way they do for the blob -- worked out by whoever knows this kernel -- and are
written in through `/dev/frida` before the agent is started:
ioctl(fd, FRIDA_IOC_SET_ADDR, &(FridaAddrRequest){ .name = "task_threads", .address = ... });
...
ioctl(fd, FRIDA_IOC_START);
A name nothing writes stays zero, and the agent already answers for that: every one of them is an
`Option` on the Rust side, and what needs it says so rather than calling through a null.
Once started, the same device carries the frames: read what the agent says, write what the host
says, framed with a four-byte little-endian length exactly as on every other transport.
## What the sources needed
An SDK for a Darwin kernel is a target nothing here had been built for, and six of the sources say
so. Each is a commit of its own on the fork, a few lines apiece:
- **picolibc** -- Mach-O has no `__attribute__((alias))`, so the aliasing macros fall through to
the assembler ones it already carries for Mach-O, and the names are spent before being spelled.
Its aarch64 assembly is written for ELF and its C stubs answer for it instead. The interrupt
vectors and the `exec` family are not built: one owns a machine this does not, the other crashes
the compiler.
- **compiler-rt** -- `apple_versioning.c` is stubs for macOS releases long past, written against a
header only userspace has.
- **libffi** -- the trampoline table is built out of what userspace has of Mach; whether that is
there is now asked of the header rather than of the system's name.
- **QuickJS**, **tinycc** and **libiconv** -- all three ask what to do on Apple by asking whether
they are on Apple. Where that means userspace -- a userspace malloc's header, a cache-control
header, a locale one -- they now ask whether they are freestanding as well.
## Which architecture, and where pointers are signed
Apple silicon runs an arm64e kernel and loads nothing else, so that is what `ARCH` defaults to,
and the SDK and the devkit are built for `macos-arm64e-kernel` to match.
Pointer authentication is the thing to get right there, and what makes it awkward is that Rust's
`arm64e-apple-darwin` is a tag rather than an ABI: it neither signs a function pointer nor
authenticates one.
_pick: adrp x0, 0 ; add x0, x0, #0x0 ; ret <- no signing
_call: mov x2, x0 ; mov x0, x1 ; br x2 <- no authenticating
So the line is drawn where the kernel is, and not inside:
- **Ourselves.** GLib, GumJS and the agent are built `-fno-ptrauth-calls`, so the C half calls a
Rust callback the way Rust hands it over -- plainly. Nothing there signs, so nothing there is
disappointed. `otool` says as much: not one `blraa` outside `__auth_stubs`.
- **The kernel calling us.** The glue keeps the kernel's own ABI, so the device table it registers
is written with `AUTH` relocations and the loader signs it as the kernel expects.
- **Us calling the kernel.** Through `__auth_stubs`, which the loader fills the same way.
- **Us handing the kernel a function to call.** A thread's entry is the one that matters, and the
agent signs it itself, as it already does for the kernel it is injected into. What it signs with
is no longer written down twice: the glue derives the discriminator from the kernel's own
headers with `ptrauth_type_discriminator`, and asserts that it is the number the blob flavour
carries -- 0xd507 for a `thread_continue_t`, which is what it was.
What this does not buy is pointer authentication *within* the kext, which is protection given up
rather than correctness lost. Getting it back wants rustc to grow the ABI, or every callback
wrapped in a C thunk.
None of this touches x86_64, which has no pointer authentication at all: an Intel kext wants only
its own SDK built the same way.
## Floating point
Every other kernel flavour is built soft-float, since a kernel does not save the floating-point
registers across a trap. This one cannot be: Darwin has no soft-float C ABI on either
architecture. arm64 refuses the type outright without one -- `ABI 'darwinpcs' does not support
it` -- and `-mabi=aapcs-soft` alongside it crashes both Apple's clang and upstream LLVM on the
first varargs function taking a double; x86_64 wants SSE to return one. A script engine is nothing
but doubles, so the floating-point registers stay in play, and what that costs at run time is a
thing to answer on the machine.
## Putting copies into processes
Not there yet. The blob's linker script marks out its own writable half and what a copy of it has
to relocate; a kext is laid out by the kernel's loader instead, so the glue says there is nothing
to copy from. Everything the agent does in the kernel itself is unaffected.
Carrying the blob's ELF and placing it the way the host does is not a way round that, either: on a
machine with SPTM, memory a kext allocates cannot be made executable, while a kext's own text is
mapped executable by the kernel's loader because it was signed.
+446
View File
@@ -0,0 +1,446 @@
#include <libkern/libkern.h>
#include <mach/kmod.h>
#include <mach/mach_types.h>
#include <miscfs/devfs/devfs.h>
#include <sys/conf.h>
#include <sys/errno.h>
#include <sys/ioccom.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/random.h>
#include <sys/uio.h>
#include <kern/locks.h>
#include <kern/thread.h>
#include <ptrauth.h>
extern int frida_agent_start (uint64_t own_base, uint64_t own_size);
extern void frida_agent_stop (void);
extern void frida_agent_wake (void);
#define FRIDA_KERNEL_ADDRS(X) \
X (panic) \
X (kalloc) X (kfree) X (kalloc_data) X (kfree_data) \
X (proc_iterate) X (proc_find) X (proc_rele) X (proc_pid) X (proc_best_name) X (proc_task) \
X (current_task) X (current_thread) X (get_task_map) X (get_bsdtask_info) \
X (mach_vm_allocate) X (mach_vm_deallocate) X (mach_vm_protect) X (mach_vm_remap) \
X (kernel_map) X (kernel_pmap) X (pmap_find_phys) X (ml_static_ptovirt) \
X (mach_vm_region) X (mach_vm_region_recurse) \
X (vm_map_copyin) X (vm_map_copyout) X (vm_map_wire_external) \
X (thread_create) X (thread_set_state) X (thread_resume) X (thread_suspend) \
X (thread_terminate) X (kernel_thread_start) \
X (task_suspend) X (task_resume) X (task_clear_return_wait) X (task_threads) X (task_info) \
X (task_restartable_ranges_register) \
X (assert_wait) X (assert_wait_timeout) X (thread_block) X (thread_wakeup) \
X (thread_wakeup_prim) X (wakeup) X (ulock_wake) \
X (mach_absolute_time) X (absolutetime_to_nanoseconds) X (clock_get_calendar_microtime) \
X (ml_io_map) X (ml_vtophys) X (ml_static_vtop) \
X (copyin) X (copyout) X (sysent) X (IOLog) \
X (convert_port_to_thread) X (convert_port_to_thread_read) \
X (convert_port_to_thread_inspect) \
X (sock_socket) X (sock_connect) X (sock_send) X (sock_receive) X (sock_close)
#define FRIDA_DEFINE_SLOT(name) void * _##name;
FRIDA_KERNEL_ADDRS (FRIDA_DEFINE_SLOT)
#undef FRIDA_DEFINE_SLOT
typedef struct _FridaKernelAddr FridaKernelAddr;
struct _FridaKernelAddr
{
const char * name;
void ** slot;
};
static const FridaKernelAddr frida_kernel_addrs[] = {
#define FRIDA_LIST_SLOT(name) { #name, &_##name },
FRIDA_KERNEL_ADDRS (FRIDA_LIST_SLOT)
#undef FRIDA_LIST_SLOT
};
extern void * frida_kext_init_start __asm ("section$start$__DATA$__mod_init_func");
extern void * frida_kext_init_end __asm ("section$end$__DATA$__mod_init_func");
const uintptr_t frida_agent_init_start = (uintptr_t) &frida_kext_init_start;
const uintptr_t frida_agent_init_end = (uintptr_t) &frida_kext_init_end;
const uintptr_t frida_agent_private_start = 0;
const uintptr_t frida_agent_heap_start = 0;
const uintptr_t frida_agent_relocs_start = 0;
const uintptr_t frida_agent_relocs_end = 0;
typedef int (* FridaProcCallout) (proc_t process, void * argument);
const unsigned frida_agent_disc_thread_continue =
ptrauth_type_discriminator (thread_continue_t);
const unsigned frida_agent_disc_proc_callout =
ptrauth_type_discriminator (FridaProcCallout);
_Static_assert (ptrauth_type_discriminator (thread_continue_t) == 0xd507,
"the agent signs a thread's entry with 0xd507");
int
getentropy (void * buffer, size_t size)
{
read_random (buffer, (u_int) size);
return 0;
}
void
sys_icache_invalidate (void * start, size_t size)
{
flush_dcache ((vm_offset_t) start, (unsigned) size, FALSE);
invalidate_icache ((vm_offset_t) start, (unsigned) size, FALSE);
}
#define FRIDA_IOC_SET_ADDR _IOW ('F', 1, FridaAddrRequest)
#define FRIDA_IOC_START _IO ('F', 2)
#define FRIDA_IOC_GET_IMAGE _IOR ('F', 3, FridaImageInfo)
typedef struct _FridaAddrRequest FridaAddrRequest;
typedef struct _FridaImageInfo FridaImageInfo;
struct _FridaAddrRequest
{
char name[64];
uint64_t address;
};
struct _FridaImageInfo
{
uint64_t base;
uint64_t size;
};
#define FRIDA_LINK_ROOM (1024 * 1024)
typedef struct _FridaLink FridaLink;
struct _FridaLink
{
lck_mtx_t * lock;
bool open;
uint8_t * to_host;
size_t to_host_len;
uint8_t * from_host;
size_t from_host_len;
};
static FridaLink frida_link;
static lck_grp_t * frida_lock_group;
static int frida_dev_open (dev_t dev, int flags, int devtype, struct proc * p);
static int frida_dev_close (dev_t dev, int flags, int devtype, struct proc * p);
static int frida_dev_read (dev_t dev, struct uio * uio, int ioflag);
static int frida_dev_write (dev_t dev, struct uio * uio, int ioflag);
static int frida_dev_ioctl (dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc * p);
static struct cdevsw frida_cdevsw = {
.d_open = frida_dev_open,
.d_close = frida_dev_close,
.d_read = frida_dev_read,
.d_write = frida_dev_write,
.d_ioctl = frida_dev_ioctl,
.d_stop = eno_stop,
.d_reset = eno_reset,
.d_ttys = NULL,
.d_select = eno_select,
.d_mmap = eno_mmap,
.d_strategy = eno_strat,
.d_reserved_1 = eno_getc,
.d_reserved_2 = eno_putc,
.d_type = 0
};
static int frida_major = -1;
static void * frida_node = NULL;
static bool frida_running = false;
static uint64_t frida_own_base = 0;
static uint64_t frida_own_size = 0;
kern_return_t
frida_kext_start (kmod_info_t * ki, void * d)
{
frida_own_base = ki->address;
frida_own_size = ki->size;
frida_lock_group = lck_grp_alloc_init ("frida", LCK_GRP_ATTR_NULL);
if (frida_lock_group == NULL)
return KERN_FAILURE;
frida_link.lock = lck_mtx_alloc_init (frida_lock_group, LCK_ATTR_NULL);
frida_major = cdevsw_add (-1, &frida_cdevsw);
if (frida_major == -1)
return KERN_FAILURE;
frida_node = devfs_make_node (makedev (frida_major, 0), DEVFS_CHAR, UID_ROOT, GID_WHEEL, 0600,
"frida");
if (frida_node == NULL)
{
cdevsw_remove (frida_major, &frida_cdevsw);
frida_major = -1;
return KERN_FAILURE;
}
return KERN_SUCCESS;
}
kern_return_t
frida_kext_stop (kmod_info_t * ki, void * d)
{
if (frida_running)
{
frida_agent_stop ();
frida_running = false;
}
if (frida_node != NULL)
{
devfs_remove (frida_node);
frida_node = NULL;
}
if (frida_major != -1)
{
cdevsw_remove (frida_major, &frida_cdevsw);
frida_major = -1;
}
return KERN_SUCCESS;
}
KMOD_EXPLICIT_DECL (re.frida.agent, "1.0", frida_kext_start, frida_kext_stop)
static int
frida_dev_open (dev_t dev, int flags, int devtype, struct proc * p)
{
int result = 0;
lck_mtx_lock (frida_link.lock);
if (frida_link.open)
{
result = EBUSY;
}
else
{
frida_link.to_host = _MALLOC (FRIDA_LINK_ROOM, M_TEMP, M_WAITOK);
frida_link.from_host = _MALLOC (FRIDA_LINK_ROOM, M_TEMP, M_WAITOK);
if (frida_link.to_host != NULL && frida_link.from_host != NULL)
{
frida_link.to_host_len = 0;
frida_link.from_host_len = 0;
frida_link.open = true;
}
else
{
result = ENOMEM;
}
}
lck_mtx_unlock (frida_link.lock);
return result;
}
static int
frida_dev_close (dev_t dev, int flags, int devtype, struct proc * p)
{
lck_mtx_lock (frida_link.lock);
frida_link.open = false;
if (frida_link.to_host != NULL)
{
_FREE (frida_link.to_host, M_TEMP);
frida_link.to_host = NULL;
}
if (frida_link.from_host != NULL)
{
_FREE (frida_link.from_host, M_TEMP);
frida_link.from_host = NULL;
}
wakeup (&frida_link);
lck_mtx_unlock (frida_link.lock);
return 0;
}
static int
frida_dev_read (dev_t dev, struct uio * uio, int ioflag)
{
int result = 0;
lck_mtx_lock (frida_link.lock);
while (frida_link.open && frida_link.to_host_len == 0)
{
if (msleep (&frida_link, frida_link.lock, PCATCH, "frida", NULL) != 0)
{
lck_mtx_unlock (frida_link.lock);
return EINTR;
}
}
if (frida_link.to_host_len != 0)
{
size_t n = MIN ((size_t) uio_resid (uio), frida_link.to_host_len);
result = uiomove ((const char *) frida_link.to_host, (int) n, uio);
if (result == 0)
{
memmove (frida_link.to_host, frida_link.to_host + n, frida_link.to_host_len - n);
frida_link.to_host_len -= n;
}
}
lck_mtx_unlock (frida_link.lock);
return result;
}
static int
frida_dev_write (dev_t dev, struct uio * uio, int ioflag)
{
size_t n;
int result;
lck_mtx_lock (frida_link.lock);
n = MIN ((size_t) uio_resid (uio), FRIDA_LINK_ROOM - frida_link.from_host_len);
if (n == 0)
{
lck_mtx_unlock (frida_link.lock);
return ENOBUFS;
}
result = uiomove ((char *) frida_link.from_host + frida_link.from_host_len, (int) n, uio);
if (result == 0)
frida_link.from_host_len += n;
lck_mtx_unlock (frida_link.lock);
if (result == 0 && frida_running)
frida_agent_wake ();
return result;
}
int
frida_kmod_link_open (void)
{
return frida_link.open ? 0 : -1;
}
void
frida_kmod_link_close (void)
{
}
int
frida_kmod_link_send (const void * data, size_t size)
{
lck_mtx_lock (frida_link.lock);
if (!frida_link.open || frida_link.to_host_len + size > FRIDA_LINK_ROOM)
{
lck_mtx_unlock (frida_link.lock);
return -1;
}
memcpy (frida_link.to_host + frida_link.to_host_len, data, size);
frida_link.to_host_len += size;
wakeup (&frida_link);
lck_mtx_unlock (frida_link.lock);
return 0;
}
bool
frida_kmod_link_pending (void)
{
bool pending;
lck_mtx_lock (frida_link.lock);
pending = frida_link.from_host_len != 0;
lck_mtx_unlock (frida_link.lock);
return pending;
}
ssize_t
frida_kmod_link_recv (void * data, size_t size)
{
size_t n;
lck_mtx_lock (frida_link.lock);
n = MIN (size, frida_link.from_host_len);
if (n != 0)
{
memcpy (data, frida_link.from_host, n);
memmove (frida_link.from_host, frida_link.from_host + n, frida_link.from_host_len - n);
frida_link.from_host_len -= n;
}
lck_mtx_unlock (frida_link.lock);
return (ssize_t) n;
}
static int
frida_dev_ioctl (dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc * p)
{
switch (cmd)
{
case FRIDA_IOC_SET_ADDR:
{
FridaAddrRequest * r = (FridaAddrRequest *) data;
size_t i;
r->name[sizeof (r->name) - 1] = '\0';
for (i = 0; i != sizeof (frida_kernel_addrs) / sizeof (frida_kernel_addrs[0]); i++)
{
if (strncmp (frida_kernel_addrs[i].name, r->name, sizeof (r->name)) == 0)
{
*frida_kernel_addrs[i].slot = (void *) (uintptr_t) r->address;
return 0;
}
}
return ENOENT;
}
case FRIDA_IOC_GET_IMAGE:
{
FridaImageInfo * info = (FridaImageInfo *) data;
info->base = frida_own_base;
info->size = frida_own_size;
return 0;
}
case FRIDA_IOC_START:
{
if (frida_running)
return EBUSY;
if (frida_agent_start (frida_own_base, frida_own_size) != 0)
return ENOMEM;
frida_running = true;
return 0;
}
default:
return ENOTTY;
}
}
+5
View File
@@ -0,0 +1,5 @@
#include <frida-gumjs.h>
#include <ptrauth.h>
_Static_assert (ptrauth_type_discriminator (__typeof__ (((GSourceFuncs *) 0)->prepare))
== 0xe4fe, "the kernel's own ABI still discriminates by type");
+154
View File
@@ -0,0 +1,154 @@
#!/usr/bin/env python3
"""Make a kernel the agent can write to and run code in."""
import struct
import sys
NOP = 0xD503201F
FORBIDS_BELOW = 0x3800
ALLOWS_BELOW = 0x2000
def main(source, destination):
image = bytearray(open(source, "rb").read())
secs = sections(image)
print("pages the pmap has locked down:")
locked = stop_refusing(image, secs, strings_saying(bytes(image), secs, b"locked down"),
"may be mapped")
print("what a table forbids underneath it:")
below = stop_forbidding_execution_below(image, secs)
print("executable mappings in the kernel's own pmap:")
executable = stop_refusing(image, secs,
strings_saying(bytes(image), secs, b"executable mapping"),
"may be made")
if not (locked and below and executable):
sys.exit("kernel does not look like one this tool knows")
open(destination, "wb").write(bytes(image))
print(f"\n{locked + below + executable} changes -> {destination}")
def stop_refusing(image, secs, saying, what):
undone = 0
for segment, name, addr, size, foff in secs:
if name != "__text":
continue
code = bytes(image[foff:foff + size])
blocks = {addr + b
for b in (start_of_block(code, s) for s in complaints_at(code, addr, saying))
if b is not None}
if not blocks:
continue
for where, at, word in branches_into(code, addr, blocks):
struct.pack_into("<I", image, foff + at, NOP)
print(f" {segment} {where:#x} {word:#010x} -> nop ({what})")
undone += 1
return undone
def stop_forbidding_execution_below(image, secs):
undone = 0
for segment, name, addr, size, foff in secs:
if name != "__text":
continue
code = bytes(image[foff:foff + size])
for i in range(0, len(code) - 4, 4):
word = struct.unpack_from("<I", code, i)[0]
builds_high = (word & 0xFF800000) == 0xF2800000 and ((word >> 21) & 3) == 3
if not builds_high or ((word >> 5) & 0xFFFF) != FORBIDS_BELOW:
continue
into = word & 0x1F
struct.pack_into("<I", image, foff + i, 0xF2E00000 | (ALLOWS_BELOW << 5) | into)
print(f" {segment} {addr + i:#x} {word:#010x} -> "
f"movk x{into}, #{ALLOWS_BELOW:#06x}, lsl 48"
f" (a table no longer forbids what is under it)")
undone += 1
return undone
def strings_saying(image, secs, wanted):
where = set()
at = image.find(wanted)
while at != -1:
start = image.rfind(b"\0", 0, at) + 1
for _, name, addr, size, foff in secs:
if name == "__cstring" and foff <= start < foff + size:
where.add(addr + (start - foff))
at = image.find(wanted, at + 1)
return where
def complaints_at(code, base, about):
said = []
for i in range(0, len(code) - 4, 4):
word = struct.unpack_from("<I", code, i)[0]
if (word & 0x9F000000) != 0x90000000:
continue
into = word & 0x1F
page = (((word >> 5) & 0x7FFFF) << 2) | ((word >> 29) & 3)
if page & (1 << 20):
page -= 1 << 21
page = ((base + i) & ~0xFFF) + (page << 12)
for j in range(i + 4, min(i + 48, len(code) - 4), 4):
after = struct.unpack_from("<I", code, j)[0]
if (after & 0xFF800000) == 0x91000000 and ((after >> 5) & 0x1F) == into:
if page + ((after >> 10) & 0xFFF) in about:
said.append(i)
break
return said
def start_of_block(code, at):
i = at
while i >= 4:
word = struct.unpack_from("<I", code, i - 4)[0]
if (word & 0xFC000000) in (0x94000000, 0x14000000):
return i
if (word & 0xFFFFFC1F) == 0xD65F0000 or word == 0xD65F0FFF:
return i
i -= 4
return None
def branches_into(code, base, blocks):
found = []
for i in range(0, len(code) - 4, 4):
word = struct.unpack_from("<I", code, i)[0]
if (word & 0xFF000010) == 0x54000000 or (word & 0x7E000000) == 0x34000000:
far, sign = (word >> 5) & 0x7FFFF, 1 << 18
elif (word & 0x7E000000) == 0x36000000:
far, sign = (word >> 5) & 0x3FFF, 1 << 13
else:
continue
if far & sign:
far -= sign << 1
if base + i + far * 4 in blocks:
found.append((base + i, i, word))
return found
def sections(image):
count = struct.unpack_from("<I", image, 16)[0]
at, found = 32, []
for _ in range(count):
kind, size = struct.unpack_from("<II", image, at)
if kind == 0x19:
how_many = struct.unpack_from("<I", image, at + 64)[0]
so = at + 72
for _ in range(how_many):
found.append((
image[so + 16:so + 32].rstrip(b"\0").decode(),
image[so:so + 16].rstrip(b"\0").decode(),
*struct.unpack_from("<QQ", image, so + 32),
struct.unpack_from("<I", image, so + 48)[0],
))
so += 80
at += size
return found
if len(sys.argv) != 3:
sys.exit(f"usage: {sys.argv[0]} <kernel> <patched-kernel>")
main(sys.argv[1], sys.argv[2])
-19
View File
@@ -1,19 +0,0 @@
{
"name": "frida-barebone-agent",
"version": "1.0.0",
"description": "xPack configuration for frida-barebone-agent",
"xpack": {
"minimumXpmRequired": "0.20.8",
"dependencies": {},
"devDependencies": {
"@xpack-dev-tools/aarch64-none-elf-gcc": {
"specifier": "14.2.1-1.1.1",
"local": "link",
"platforms": "all"
}
},
"properties": {},
"actions": {},
"buildConfigurations": {}
}
}
+7 -9
View File
@@ -1,7 +1,7 @@
use core::ffi::c_void;
use crate::bindings::{gpointer, gsize, GumPageProtection};
use crate::{gum, libc, xnu};
use crate::bindings::{gpointer, gsize, gum_try_mprotect, GumPageProtection};
use crate::{kernel, libc};
const GUM_PAGE_READ: GumPageProtection = 1;
const GUM_PAGE_EXECUTE: GumPageProtection = 4;
@@ -10,7 +10,7 @@ const GUM_PAGE_EXECUTE: GumPageProtection = 4;
// static trampoline `text` made executable, followed by a writable parameter
// table `map_size` bytes later (the trampolines reach their parameters via that
// fixed offset). The two halves are whole pages, so flipping the first to RX
// over the hostlink leaves the parameter table writable.
// leaves the parameter table writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ffi_tramp_embedder_map(
text: *const c_void,
@@ -19,7 +19,7 @@ pub unsafe extern "C" fn ffi_tramp_embedder_map(
parm_table: *mut *mut c_void,
) -> i32 {
unsafe {
let base = xnu::kalloc(map_size * 2);
let base = kernel::alloc_code(map_size * 2);
if base.is_null() {
return 0;
}
@@ -28,9 +28,9 @@ pub unsafe extern "C" fn ffi_tramp_embedder_map(
libc::__clear_cache(base, base.add(map_size));
let made_executable =
gum::gum_try_mprotect(base as gpointer, map_size as gsize, GUM_PAGE_READ | GUM_PAGE_EXECUTE);
gum_try_mprotect(base as gpointer, map_size as gsize, GUM_PAGE_READ | GUM_PAGE_EXECUTE);
if made_executable == 0 {
xnu::free(base, map_size * 2);
kernel::free_code(base, map_size * 2);
return 0;
}
@@ -46,7 +46,5 @@ pub unsafe extern "C" fn ffi_tramp_embedder_unmap(
_parm_table: *mut c_void,
map_size: usize,
) {
unsafe {
xnu::free(code_table as *mut u8, map_size * 2);
}
kernel::free_code(code_table as *mut u8, map_size * 2);
}
+47 -24
View File
@@ -1,46 +1,69 @@
use core::ptr;
use core::sync::atomic::{AtomicUsize, Ordering};
use crate::bindings::{g_wait_is_set, gint64, gpointer};
use crate::xnu;
use crate::kernel;
const G_WAIT_INFINITE: gint64 = -1;
pub static mut WAKEUP_TOKEN: u64 = 0;
pub fn wakeup_token() -> *const u8 {
ptr::addr_of!(WAKEUP_TOKEN) as *const u8
}
#[unsafe(no_mangle)]
pub extern "C" fn g_get_monotonic_time() -> gint64 {
let abstime = xnu::mach_absolute_time();
let time = xnu::absolutetime_to_nanoseconds(abstime);
(time / 1000) as gint64
kernel::monotonic_micros()
}
#[unsafe(no_mangle)]
pub extern "C" fn g_wait_sleep(token: gpointer, timeout_us: gint64) {
let wait_event = ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8;
let wait_result = if timeout_us == G_WAIT_INFINITE {
xnu::assert_wait(wait_event, xnu::THREAD_INTERRUPTIBLE)
let timeout = if timeout_us == G_WAIT_INFINITE {
None
} else {
xnu::assert_wait_timeout(
wait_event,
xnu::THREAD_INTERRUPTIBLE,
(timeout_us * 1000) as u32,
1, // TODO
)
Some(timeout_us as u64)
};
if wait_result != xnu::THREAD_WAITING {
panic!("assert_wait_timeout failed: {}", wait_result);
}
if unsafe { g_wait_is_set(token) != 0 } {
xnu::thread_wakeup(wait_event);
return;
}
let ours = is_loop_thread();
let event = if ours {
LOOP_TOKEN.store(token as usize, Ordering::Release);
ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8
} else {
token as *const u8
};
xnu::thread_block(None);
kernel::wait(event, timeout, &mut || unsafe { g_wait_is_set(token) != 0 });
if ours {
LOOP_TOKEN.store(0, Ordering::Release);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_wait_wake(_token: gpointer) {
xnu::thread_wakeup(ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8);
pub extern "C" fn g_wait_wake(token: gpointer) {
kernel::poke_loop_wakeup();
kernel::wake(ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8);
kernel::wake(token as *const u8);
}
pub fn is_loop_thread() -> bool {
kernel::current_thread_id() == LOOP_THREAD.load(Ordering::Acquire) as u64
}
pub fn own_the_loop() {
LOOP_THREAD.store(kernel::current_thread_id() as usize, Ordering::Release);
}
static LOOP_THREAD: AtomicUsize = AtomicUsize::new(0);
static LOOP_TOKEN: AtomicUsize = AtomicUsize::new(0);
#[unsafe(no_mangle)]
pub extern "C" fn g_io_channel_unix_new(_fd: i32) -> gpointer {
ptr::null_mut()
}
#[unsafe(no_mangle)]
pub extern "C" fn g_spawn_async_with_pipes() -> i32 {
0
}
+154 -76
View File
@@ -8,34 +8,39 @@ use crate::bindings::GThreadFunc;
use crate::bindings::{gpointer, gulong};
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use core::arch::asm;
use core::ffi::c_void;
use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use core::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering};
// Our implementation structures that overlay the opaque GLib structures
#[repr(C, align(8))]
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
struct MutexImpl {
lock: AtomicU32,
_padding: [u8; core::mem::size_of::<GMutex>() - core::mem::size_of::<AtomicU32>()],
}
#[repr(C, align(8))]
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
struct RecMutexImpl {
owner: AtomicU64, // Thread ID of the owner (uses the 'p' field)
owner: AtomicUsize, // Thread ID of the owner (uses the 'p' field)
count: AtomicU32, // Recursion count (uses i[0])
_unused: u32, // Unused (uses i[1])
}
#[repr(C, align(8))]
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
struct RWLockImpl {
state: AtomicU32, // Lock state (readers count + writer bit)
_padding: [u8; core::mem::size_of::<GRWLock>() - core::mem::size_of::<AtomicU32>()],
}
#[repr(C, align(8))]
// A generation counter rather than a flag: a flag stays set once signalled, so every
// later wait returns at once and the caller's loop spins instead of blocking.
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
struct CondImpl {
signal: AtomicU32, // Signal state
generation: AtomicU32,
_padding: [u8; core::mem::size_of::<GCond>() - core::mem::size_of::<AtomicU32>()],
}
@@ -144,10 +149,9 @@ pub extern "C" fn g_rec_mutex_clear(_rec_mutex: *mut GRecMutex) {
pub extern "C" fn g_rec_mutex_lock(rec_mutex: *mut GRecMutex) {
unsafe {
let impl_ = rec_mutex_impl(rec_mutex);
let current_thread = get_current_thread_id();
loop {
if rec_mutex_try_acquire(impl_, current_thread) {
if rec_mutex_try_acquire(impl_) {
break;
}
rec_mutex_wait(impl_);
@@ -159,9 +163,8 @@ pub extern "C" fn g_rec_mutex_lock(rec_mutex: *mut GRecMutex) {
pub extern "C" fn g_rec_mutex_trylock(rec_mutex: *mut GRecMutex) -> u32 {
unsafe {
let impl_ = rec_mutex_impl(rec_mutex);
let current_thread = get_current_thread_id();
if rec_mutex_try_acquire(impl_, current_thread) {
if rec_mutex_try_acquire(impl_) {
1
} else {
0
@@ -183,7 +186,7 @@ pub extern "C" fn g_rec_mutex_unlock(rec_mutex: *mut GRecMutex) {
.is_ok()
{
impl_.owner.store(0, Ordering::Relaxed);
lock_notify_all();
wake_waiters(&impl_.count);
break;
}
} else {
@@ -198,7 +201,10 @@ pub extern "C" fn g_rec_mutex_unlock(rec_mutex: *mut GRecMutex) {
}
}
unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl, current_thread: u64) -> bool {
// Asking the kernel who we are costs more than the uncontended acquisition itself, and
// every allocation takes this path, so the identity is only fetched once the lock turns
// out to be held.
unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl) -> bool {
let current_count = impl_.count.load(Ordering::Relaxed);
if current_count == 0 {
@@ -206,12 +212,12 @@ unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl, current_thread: u64) -
.compare_exchange_weak(0, 1, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
impl_.owner.store(current_thread, Ordering::Relaxed);
impl_.owner.store(crate::kernel::current_thread_id() as usize, Ordering::Relaxed);
return true;
}
} else {
let current_owner = impl_.owner.load(Ordering::Relaxed);
if current_owner == current_thread {
if current_owner == crate::kernel::current_thread_id() as usize {
if impl_.count
.compare_exchange_weak(
current_count,
@@ -230,7 +236,7 @@ unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl, current_thread: u64) -
unsafe fn rec_mutex_wait(impl_: &mut RecMutexImpl) {
while impl_.count.load(Ordering::Relaxed) != 0 {
unsafe { lock_wait_primitive() };
wait_on(&impl_.count, &mut || impl_.count.load(Ordering::Relaxed) == 0);
}
}
@@ -282,7 +288,7 @@ pub extern "C" fn g_rw_lock_writer_unlock(rw_lock: *mut GRWLock) {
unsafe {
let impl_ = rw_lock_impl(rw_lock);
impl_.state.store(0, Ordering::Release);
lock_notify_all();
wake_waiters(&impl_.state);
}
}
@@ -329,7 +335,7 @@ pub extern "C" fn g_rw_lock_reader_unlock(rw_lock: *mut GRWLock) {
.is_ok()
{
if current - 1 == 0 {
lock_notify_all();
wake_waiters(&impl_.state);
}
break;
}
@@ -355,13 +361,15 @@ unsafe fn rw_lock_reader_try_acquire(impl_: &mut RWLockImpl) -> bool {
unsafe fn rw_lock_wait_for_writers(impl_: &mut RWLockImpl) {
while impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT != 0 {
unsafe { lock_wait_primitive() };
wait_on(&impl_.state, &mut || {
impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT == 0
});
}
}
unsafe fn rw_lock_wait_for_all(impl_: &mut RWLockImpl) {
while impl_.state.load(Ordering::Relaxed) != 0 {
unsafe { lock_wait_primitive() };
wait_on(&impl_.state, &mut || impl_.state.load(Ordering::Relaxed) == 0);
}
}
@@ -369,7 +377,7 @@ unsafe fn rw_lock_wait_for_all(impl_: &mut RWLockImpl) {
pub extern "C" fn g_cond_init(cond: *mut GCond) {
unsafe {
let impl_ = cond_impl(cond);
impl_.signal.store(0, Ordering::Relaxed);
impl_.generation.store(0, Ordering::Relaxed);
}
}
@@ -379,11 +387,17 @@ pub extern "C" fn g_cond_clear(_cond: *mut GCond) {}
#[unsafe(no_mangle)]
pub extern "C" fn g_cond_wait(cond: *mut GCond, mutex: *mut GMutex) {
unsafe {
// Read the generation under the mutex, so a signal that lands after the
// unlock below is still observed as a change.
let seen = cond_impl(cond).generation.load(Ordering::Acquire);
g_mutex_unlock(mutex);
let impl_ = cond_impl(cond);
while impl_.signal.load(Ordering::Acquire) == 0 {
lock_wait_primitive();
while impl_.generation.load(Ordering::Acquire) == seen {
wait_on(&impl_.generation, &mut || {
impl_.generation.load(Ordering::Acquire) != seen
});
}
g_mutex_lock(mutex);
@@ -394,8 +408,8 @@ pub extern "C" fn g_cond_wait(cond: *mut GCond, mutex: *mut GMutex) {
pub extern "C" fn g_cond_signal(cond: *mut GCond) {
unsafe {
let impl_ = cond_impl(cond);
impl_.signal.store(1, Ordering::Release);
lock_notify_all();
impl_.generation.fetch_add(1, Ordering::Release);
wake_waiters(&impl_.generation);
}
}
@@ -403,22 +417,82 @@ pub extern "C" fn g_cond_signal(cond: *mut GCond) {
pub extern "C" fn g_cond_broadcast(cond: *mut GCond) {
unsafe {
let impl_ = cond_impl(cond);
impl_.signal.store(1, Ordering::Release);
lock_notify_all();
impl_.generation.fetch_add(1, Ordering::Release);
wake_waiters(&impl_.generation);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_cond_wait_until(cond: *mut GCond, mutex: *mut GMutex, _end_time: i64) -> u32 {
// TODO: Implement proper timeout handling
g_cond_wait(cond, mutex);
1
pub extern "C" fn g_cond_wait_until(cond: *mut GCond, mutex: *mut GMutex, end_time: i64) -> u32 {
unsafe {
let seen = cond_impl(cond).generation.load(Ordering::Acquire);
g_mutex_unlock(mutex);
let impl_ = cond_impl(cond);
let mut signalled = impl_.generation.load(Ordering::Acquire) != seen;
while !signalled && crate::kernel::monotonic_micros() < end_time {
wait_on(&impl_.generation, &mut || {
impl_.generation.load(Ordering::Acquire) != seen
});
signalled = impl_.generation.load(Ordering::Acquire) != seen;
}
g_mutex_lock(mutex);
if signalled { 1 } else { 0 }
}
}
// A half that runs on a system with slots of its own says so, and every GPrivate takes one. The
// map below is what the kernel halves use, and it costs a lock and a lookup per call, which is
// what code the Stalker writes does on every block it leaves.
pub struct ThreadSlots {
pub take: fn() -> u32,
pub get: fn(u32) -> *mut c_void,
pub set: fn(u32, *mut c_void),
}
#[cfg(any(feature = "win9x", feature = "winnt", feature = "xnu-core"))]
pub fn use_thread_slots(slots: &'static ThreadSlots) {
unsafe { SLOTS = Some(slots) };
}
fn slot_of(key: *mut GPrivate, slots: &ThreadSlots) -> u32 {
let taken = unsafe { (*key).p } as usize;
if taken != 0 {
return (taken - 1) as u32;
}
unsafe {
lock_acquire(&TLS_LOCK);
let mut taken = (*key).p as usize;
if taken == 0 {
taken = ((slots.take)() + 1) as usize;
(*key).p = taken as *mut c_void;
}
lock_release(&TLS_LOCK);
(taken - 1) as u32
}
}
fn slots() -> Option<&'static ThreadSlots> {
unsafe { SLOTS }
}
static mut SLOTS: Option<&'static ThreadSlots> = None;
#[unsafe(no_mangle)]
pub extern "C" fn g_private_get(key: *mut GPrivate) -> *mut c_void {
if let Some(slots) = slots() {
return (slots.get)(slot_of(key, slots));
}
unsafe {
let thread_id = get_current_thread_id();
let thread_id = crate::kernel::current_thread_id();
lock_acquire(&TLS_LOCK);
ensure_tls_storage();
@@ -437,8 +511,13 @@ pub extern "C" fn g_private_get(key: *mut GPrivate) -> *mut c_void {
#[unsafe(no_mangle)]
pub extern "C" fn g_private_set(key: *mut GPrivate, value: *mut c_void) {
if let Some(slots) = slots() {
(slots.set)(slot_of(key, slots), value);
return;
}
unsafe {
let thread_id = get_current_thread_id();
let thread_id = crate::kernel::current_thread_id();
lock_acquire(&TLS_LOCK);
ensure_tls_storage();
@@ -468,7 +547,9 @@ pub extern "C" fn _g_system_thread_create(
data: gpointer,
) -> *mut GSystemThread {
unsafe {
let system_thread = crate::xnu::kalloc(core::mem::size_of::<SystemThreadImpl>()) as *mut SystemThreadImpl;
crate::kernel::log("frida: glib is creating a thread\n\0");
let system_thread = crate::kernel::alloc(core::mem::size_of::<SystemThreadImpl>()) as *mut SystemThreadImpl;
g_mutex_init(&mut (*system_thread).mutex);
g_cond_init(&mut (*system_thread).cond);
@@ -478,12 +559,12 @@ pub extern "C" fn _g_system_thread_create(
(*system_thread).finished.store(0, Ordering::Relaxed);
(*system_thread).detached.store(0, Ordering::Relaxed);
let wrapper_data = crate::xnu::kalloc(core::mem::size_of::<ThreadWrapperData>()) as *mut ThreadWrapperData;
let wrapper_data = crate::kernel::alloc(core::mem::size_of::<ThreadWrapperData>()) as *mut ThreadWrapperData;
(*wrapper_data).func = func;
(*wrapper_data).data = data;
(*wrapper_data).system_thread = system_thread;
let _xnu_result = crate::xnu::kernel_thread_start(thread_wrapper, wrapper_data as *mut c_void);
let _xnu_result = crate::kernel::spawn_thread(thread_wrapper, wrapper_data as *mut c_void);
system_thread as *mut GSystemThread
}
@@ -496,8 +577,9 @@ extern "C" fn thread_wrapper(parameter: *mut c_void, _wait_result: i32) {
let data = (*wrapper_data).data;
let system_thread = (*wrapper_data).system_thread;
let current_thread_id = get_current_thread_id();
(*system_thread).thread_id.store(current_thread_id, Ordering::Relaxed);
let current_thread_id = crate::kernel::current_thread_id();
(*system_thread).thread_id.store(current_thread_id, Ordering::Release);
wake_waiters(&(*system_thread).thread_id);
func.unwrap()(data);
@@ -537,12 +619,10 @@ pub extern "C" fn _g_system_thread_wait(thread: *mut GSystemThread) {
unsafe {
let system_thread = thread as *mut SystemThreadImpl;
loop {
let thread_id = (*system_thread).thread_id.load(Ordering::Acquire);
if thread_id != 0 {
break;
}
core::hint::spin_loop();
while (*system_thread).thread_id.load(Ordering::Acquire) == 0 {
wait_on(&(*system_thread).thread_id, &mut || {
(*system_thread).thread_id.load(Ordering::Acquire) != 0
});
}
g_mutex_lock(&mut (*system_thread).mutex);
@@ -556,19 +636,16 @@ pub extern "C" fn _g_system_thread_wait(thread: *mut GSystemThread) {
}
unsafe fn cleanup_system_thread(system_thread: *mut SystemThreadImpl) {
crate::xnu::free(system_thread as *mut u8, core::mem::size_of::<SystemThreadImpl>());
crate::kernel::free(system_thread as *mut u8, core::mem::size_of::<SystemThreadImpl>());
}
unsafe fn cleanup_wrapper_data(wrapper_data: *mut ThreadWrapperData) {
crate::xnu::free(wrapper_data as *mut u8, core::mem::size_of::<ThreadWrapperData>());
crate::kernel::free(wrapper_data as *mut u8, core::mem::size_of::<ThreadWrapperData>());
}
unsafe fn lock_acquire(lock: &AtomicU32) {
loop {
if unsafe { lock_try_acquire(lock) } {
break;
}
unsafe { lock_wait(lock) };
while !unsafe { lock_try_acquire(lock) } {
wait_on(lock, &mut || lock.load(Ordering::Relaxed) == 0);
}
}
@@ -579,35 +656,36 @@ unsafe fn lock_try_acquire(lock: &AtomicU32) -> bool {
unsafe fn lock_release(lock: &AtomicU32) {
lock.store(0, Ordering::Release);
unsafe { lock_notify_all() };
wake_waiters(lock);
}
unsafe fn lock_wait(lock: &AtomicU32) {
while lock.load(Ordering::Relaxed) != 0 {
unsafe { lock_wait_primitive() };
}
// Sleeps until `ready` holds or the slice expires, whichever comes first.
//
// These used to spin on wfe/sev. A kernel thread that spins never reaches the
// scheduler, so a lost wakeup — or a genuine deadlock, which is how we found this —
// starves the CPU it is on until the hardware watchdog takes the machine down. The
// bounded sleep turns both into a stall that can be observed instead.
//
// The address of the word being waited on names the wait channel: XNU pairs
// assert_wait()/thread_wakeup() on it directly, while the Linux shim has a single
// channel and treats it as a hint. Every caller re-checks its own condition in a
// loop, so a spurious wakeup costs nothing either way.
fn wait_on<T>(word: &T, ready: &mut dyn FnMut() -> bool) {
crate::kernel::wait(token_of(word), Some(WAIT_SLICE_US), ready);
// The wait above returns without sleeping whenever `ready` already holds, so on
// its own it is not enough to keep a contended loop off the CPU: every caller
// here loops, and a loop in a kernel thread that never reaches the scheduler
// takes the machine down rather than merely running hot. Yield unconditionally.
crate::kernel::yield_now();
}
unsafe fn lock_wait_primitive() {
unsafe {
asm!("wfe", options(nomem, nostack));
}
fn wake_waiters<T>(word: &T) {
crate::kernel::wake(token_of(word));
}
unsafe fn lock_notify_all() {
unsafe {
asm!("sev", options(nomem, nostack));
}
fn token_of<T>(word: &T) -> *const u8 {
word as *const T as *const u8
}
// The thread pointer is exposed to JavaScript as a GumThreadId, so it must fit
// in a double without losing precision; the low bits keep it unique per thread.
const JS_SAFE_THREAD_ID_MASK: u64 = (1 << 48) - 1;
pub unsafe fn get_current_thread_id() -> u64 {
let thread_ptr: u64;
unsafe {
asm!("mrs {}, tpidr_el1", out(reg) thread_ptr, options(nomem, nostack));
}
thread_ptr & JS_SAFE_THREAD_ID_MASK
}
const WAIT_SLICE_US: u64 = 1000;
Loaded 100 of 415 files, more files were not shown because too many files have changed in this diff. Show more