Compare commits

..
450 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
363 changed files with 48382 additions and 32234 deletions

No files matched your search

+16 -5
View File
@@ -21,7 +21,7 @@ jobs:
matrix:
include:
- { 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: MINGW64, pkg: 'mingw-w64-x86_64-gcc' }
- { 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"' }
@@ -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
@@ -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:
+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);
+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);
+78 -52
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',
])
@@ -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'
@@ -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
@@ -886,9 +908,12 @@ 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'
@@ -955,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]
@@ -965,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: 963fe3826f...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() {
+2
View File
@@ -32,6 +32,8 @@ OBJECT_TYPE_PATTERN = re.compile(r"\bpublic\s+(sealed |abstract )?(class|interfa
TOPLEVEL_NAMES = [
"frida.vala",
"linux-kernel-image.vala",
"xnu-kernelcache.vala",
"package-manager.vala",
"control-service.vala",
"portal-service.vala",
+1 -1
View File
@@ -158,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
+364 -139
View File
@@ -1,6 +1,9 @@
using Efni;
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
namespace Frida.Barebone {
public sealed class AgentConnection : Object, AsyncInitable {
public signal void progress (string status, double fraction);
public signal void script_message (uint pid, AgentScriptId id, string json, Bytes? data);
private Cancellable io_cancellable = new Cancellable ();
@@ -14,54 +17,68 @@ namespace Frida.Barebone {
private uint pointer_size;
private BareboneInjectedAgentConfig agent_config;
private BareboneVsockTransportConfig? vsock_transport;
private string? hostlink_socket_path;
private bool pipe_listener_wanted;
private SocketService? pipe_service;
private IOStream? pipe_stream;
private SourceFunc? pipe_accept_handler;
private BareboneImageConfig? image_config;
private BareboneKernelKind kernel_kind;
private KernelRelocation? relocation;
private uint64 kernel_base;
private Machine machine;
private Allocator allocator;
private Allocator data_allocator;
private Gee.List<ModuleInfo> kernel_modules;
private Gee.List<SymbolInfo> kernel_symbols;
private Gee.Map<string, uint64?> kernel_notes;
private KernelFlavor flavor;
private Allocation elf_allocation;
private uint64 left_flag;
private uint64 fault_record;
private Gee.List<AgentSymbol> agent_symbols = new Gee.ArrayList<AgentSymbol> ();
private Gee.Map<string, SymbolInfo> resolved_symbols;
private Allocation config_allocation;
private Allocation? agent_stack;
private Gee.Map<uint16, Promise<Variant>> pending_requests = new Gee.HashMap<uint16, Promise<Variant>> ();
private Efni.Promise<bool>? listening;
private Gee.Map<uint16, Efni.Promise<Variant>> pending_requests = new Gee.HashMap<uint16, Efni.Promise<Variant>> ();
private uint16 next_request_id = 1;
private const size_t AGENT_STACK_SIZE = 512 * 1024;
private const int COMMAND_TIMEOUT_MS = 25000;
private const uint INJECT_MAX_ATTEMPTS = 100;
private const uint INJECT_POLL_INTERVAL_MS = 100;
private const uint LEAVE_MAX_ATTEMPTS = 40;
private const uint LEAVE_INTERVAL_MS = 50;
private const size_t FAULT_RECORD_SIZE = 32;
private const uint GREETING_TIMEOUT_MS = 20000;
public static async AgentConnection open (BareboneInjectedAgentConfig agent_config, BareboneImageConfig? image_config,
public AgentConnection (BareboneInjectedAgentConfig agent_config, BareboneImageConfig? image_config,
BareboneKernelKind kernel_kind, KernelRelocation? relocation, uint64 kernel_base, Machine machine,
Allocator allocator, Gee.List<ModuleInfo> kernel_modules, Gee.List<SymbolInfo> kernel_symbols,
Cancellable? cancellable) throws Error, IOError {
var connection = new AgentConnection () {
agent_config = agent_config,
image_config = image_config,
kernel_kind = kernel_kind,
relocation = relocation,
kernel_base = kernel_base,
machine = machine,
allocator = allocator,
kernel_modules = kernel_modules,
kernel_symbols = kernel_symbols,
};
Allocator allocator, Allocator? data_allocator, Gee.List<ModuleInfo> kernel_modules,
Gee.List<SymbolInfo> kernel_symbols, Gee.Map<string, uint64?> kernel_notes) {
this.agent_config = agent_config;
this.image_config = image_config;
this.kernel_kind = kernel_kind;
this.relocation = relocation;
this.kernel_base = kernel_base;
this.machine = machine;
this.allocator = allocator;
this.data_allocator = data_allocator ?? allocator;
this.kernel_modules = kernel_modules;
this.kernel_symbols = kernel_symbols;
this.kernel_notes = kernel_notes;
}
public async void open (Cancellable? cancellable) throws Efni.Error, IOError {
try {
yield connection.init_async (Priority.DEFAULT, cancellable);
yield init_async (Priority.DEFAULT, cancellable);
} catch (GLib.Error e) {
throw_api_error (e);
Efni.throw_api_error (e);
}
return connection;
}
/**
@@ -69,11 +86,8 @@ namespace Frida.Barebone {
* transport with it, such as the Linux kernel module exposing /dev/frida.
*/
public static async AgentConnection open_resident (IOStream stream, Cancellable? cancellable)
throws Error, IOError {
var connection = new AgentConnection () {
byte_order = ByteOrder.HOST,
pointer_size = (uint) sizeof (void *),
};
throws Efni.Error, IOError {
var connection = new AgentConnection.resident ();
connection.adopt_hostlink_streams (stream);
connection.process_incoming_messages.begin ();
@@ -81,16 +95,26 @@ namespace Frida.Barebone {
return connection;
}
private AgentConnection.resident () {
byte_order = ByteOrder.HOST;
pointer_size = (uint) sizeof (void *);
}
private const uint8 TRANSPORT_KIND_VIRTIO = 0;
private const uint8 TRANSPORT_KIND_VSOCK = 1;
private const uint8 TRANSPORT_KIND_VIRTIO_PCI = 2;
private const uint8 TRANSPORT_KIND_SERIAL = 3;
private const uint8 TRANSPORT_KIND_PIPE_VSOCK = 4;
private const uint8 TRANSPORT_KIND_PIPE_GOLDFISH = 5;
private async bool init_async (int io_priority, Cancellable? cancellable) throws Efni.Error, IOError {
progress ("Preparing the agent", 0.0);
private async bool init_async (int io_priority, Cancellable? cancellable) throws Error, IOError {
var transport_tag = yield resolve_transport (cancellable);
var gdb = machine.gdb;
byte_order = gdb.byte_order;
pointer_size = gdb.pointer_size;
var debugger = machine.debugger;
byte_order = debugger.byte_order;
pointer_size = debugger.pointer_size;
// Only a kernel shipped as an image carries its symbols in it; the rest name theirs
// elsewhere, and the layout is collected before this.
@@ -99,7 +123,7 @@ namespace Frida.Barebone {
Layout layout;
uint64 preferred_base = 0;
if (image_carries_symbols) {
var payload = yield Img4.parse_file (File.new_for_path (image_config.file), cancellable);
var payload = Img4.parse (((BareboneXnuKernelcacheConfig) image_config).kernelcache.blob);
var kernelcache = Layout.parse_kernelcache (payload.data);
preferred_base = kernelcache.preferred_address;
if (relocation != null) {
@@ -151,11 +175,11 @@ namespace Frida.Barebone {
KernelFlavor flavor;
if (kind == XNU) {
if (kernel_base == 0)
throw new Error.NOT_SUPPORTED ("Missing kernel_base");
throw new Efni.Error.NOT_SUPPORTED ("Missing kernel_base");
flavor = new XnuKernelFlavor (machine, kernel_base, symbols);
} else if (kind == LINUX) {
if (kernel_base == 0)
throw new Error.NOT_SUPPORTED ("Missing kernel_base");
throw new Efni.Error.NOT_SUPPORTED ("Missing kernel_base");
flavor = new LinuxKernelFlavor (machine, kernel_base, symbols);
} else if (kind == WIN9X) {
flavor = new Win9xKernelFlavor (machine, symbols);
@@ -171,10 +195,10 @@ namespace Frida.Barebone {
try {
elf = new Gum.ElfModule.from_blob (agent_config.image);
} catch (Gum.Error e) {
throw new Error.INVALID_ARGUMENT ("%s", e.message);
throw new Efni.Error.INVALID_ARGUMENT ("%s", e.message);
}
var raw_elf = gdb.make_buffer (new Bytes (elf.get_file_data ()));
var raw_elf = debugger.make_buffer (new Bytes (elf.get_file_data ()));
// Slots whose symbol is absent on this kernel are left zero; the agent's
// xnu.rs uses Option<fn> and falls back across per-kernel name variants.
elf.enumerate_symbols (s => {
@@ -184,7 +208,8 @@ namespace Frida.Barebone {
SymbolInfo? info = symbols[name];
if (info != null) {
size_t file_offset = (size_t) (sect.offset + (s.address - sect.address));
raw_elf.write_pointer (file_offset, kernel_base + info.offset);
uint64 value = info.per_cpu ? info.offset : kernel_base + info.offset;
raw_elf.write_pointer (file_offset, value);
}
}
return true;
@@ -194,11 +219,14 @@ namespace Frida.Barebone {
this.flavor = flavor;
progress ("Waiting for the target to be ready", 0.1);
yield flavor.prepare (cancellable);
size_t page_size = yield machine.query_page_size (cancellable);
elf_allocation = yield inject_elf (elf, raw_elf.bytes, page_size, machine, allocator, cancellable);
progress ("Injecting the agent", 0.2);
elf_allocation = yield inject_elf (elf, raw_elf.bytes, page_size, machine, allocator,
uploaded => progress ("Injecting the agent", 0.2 + 0.5 * uploaded), cancellable);
uint64 start_address = 0;
uint64 base_va = elf_allocation.virtual_address;
@@ -207,10 +235,14 @@ namespace Frida.Barebone {
start_address = base_va + e.address;
else if (e.name == "frida_agent_left")
left_flag = base_va + e.address;
else if (e.name == "frida_agent_fault")
fault_record = base_va + e.address;
if (e.type == FUNC && e.size != 0)
agent_symbols.add (new AgentSymbol (e.name, base_va + e.address, e.size));
return true;
});
if (start_address == 0)
throw new Error.INVALID_ARGUMENT ("Invalid agent: no _start symbol found");
throw new Efni.Error.INVALID_ARGUMENT ("Invalid agent: no _start symbol found");
XnuLayout? xnu_layout = null;
bool xnu_reads_its_own_processes = symbols.has_key ("proc_pid")
@@ -252,6 +284,8 @@ namespace Frida.Barebone {
// What a kernel does not say about itself and the agent cannot work out from where it
// runs: the host looks once and passes on what it found.
config_builder.open (new VariantType ("a(st)"));
foreach (var e in kernel_notes.entries)
config_builder.add ("(st)", e.key, e.value);
if (xnu_layout != null) {
config_builder.add ("(st)", "process.number", xnu_layout.number_offset);
config_builder.add ("(st)", "process.name", xnu_layout.name_offset);
@@ -266,28 +300,47 @@ namespace Frida.Barebone {
config_builder.close ();
var config_blob = config_builder.end ().get_data_as_bytes ();
config_allocation = yield allocator.allocate (config_blob.get_size (), 8, cancellable);
config_allocation = yield data_allocator.allocate (config_blob.get_size (), 8, cancellable);
yield machine.write_virtual (config_allocation.virtual_address, config_blob.get_data (),
cancellable);
yield machine.protect_pages (config_allocation.virtual_address, config_allocation.size, READ | WRITE,
cancellable);
yield protect_unless_already (machine, data_allocator, config_allocation.virtual_address,
config_allocation.size, READ | WRITE, cancellable);
var ia32 = machine as IA32Machine;
uint kernel_arguments = (ia32 != null) ? ia32.arguments_in_registers : 0;
if (ia32 != null)
ia32.arguments_in_registers = 0;
yield machine.invoke (start_address, {
config_allocation.virtual_address,
config_allocation.size
},
cancellable);
var x64 = machine as X64Machine;
if (x64 != null) {
agent_stack = yield allocator.allocate (AGENT_STACK_SIZE, 16, cancellable);
x64.call_stack = agent_stack.virtual_address + AGENT_STACK_SIZE;
}
progress ("Starting the agent", 0.75);
var start_gdb = machine.debugger as GDB.Client;
if (start_gdb != null)
start_gdb.suppress_solo = true;
try {
yield machine.invoke (start_address, {
config_allocation.virtual_address,
config_allocation.size
},
cancellable);
} finally {
if (start_gdb != null)
start_gdb.suppress_solo = false;
}
if (x64 != null)
x64.call_stack = 0;
if (ia32 != null)
ia32.arguments_in_registers = kernel_arguments;
progress ("Connecting to the agent", 0.9);
yield flavor.settle (cancellable);
if (qmp != null)
yield qmp.wait_until_hostlink_is_open (cancellable);
@@ -295,31 +348,125 @@ namespace Frida.Barebone {
process_incoming_messages.begin ();
yield wait_to_be_greeted (cancellable);
return true;
}
private async Variant resolve_transport (Cancellable? cancellable) throws Error, IOError {
private async void wait_to_be_greeted (Cancellable? cancellable) throws Efni.Error, IOError {
if (listening == null)
return;
var timeout = new TimeoutSource (GREETING_TIMEOUT_MS);
timeout.set_callback (() => {
if (!listening.future.ready)
listening.reject (new Efni.Error.TIMED_OUT ("The agent never announced itself"));
return false;
});
timeout.attach (MainContext.get_thread_default ());
try {
yield listening.future.wait_async (cancellable);
} finally {
timeout.destroy ();
}
}
private async Variant resolve_transport (Cancellable? cancellable) throws Efni.Error, IOError {
if (agent_config.transport is BareboneHostlinkTransportConfig)
return yield connect_virtio_transport ((BareboneHostlinkTransportConfig) agent_config.transport, cancellable);
if (agent_config.transport is BareboneVsockTransportConfig) {
var config = (BareboneVsockTransportConfig) agent_config.transport;
vsock_transport = config;
hostlink_socket_path = config.socket_path;
return new Variant.tuple ({
new Variant.byte (TRANSPORT_KIND_VSOCK),
new Variant.variant (new Variant.uint32 (config.port))
});
}
throw new Error.NOT_SUPPORTED ("Unsupported transport config");
if (agent_config.transport is BareboneSerialTransportConfig) {
yield connect_serial_transport ((BareboneSerialTransportConfig) agent_config.transport,
cancellable);
return new Variant.tuple ({
new Variant.byte (TRANSPORT_KIND_SERIAL),
new Variant.variant (new Variant.uint32 (0))
});
}
if (agent_config.transport is BareboneVsockPipeTransportConfig) {
var config = (BareboneVsockPipeTransportConfig) agent_config.transport;
pipe_listener_wanted = true;
yield open_pipe_listener (config.socket_path, cancellable);
return new Variant.tuple ({
new Variant.byte (TRANSPORT_KIND_PIPE_VSOCK),
new Variant.variant (new Variant.string (config.socket_path))
});
}
if (agent_config.transport is BareboneGoldfishPipeTransportConfig) {
var config = (BareboneGoldfishPipeTransportConfig) agent_config.transport;
pipe_listener_wanted = true;
yield open_pipe_listener (config.socket_path, cancellable);
return new Variant.tuple ({
new Variant.byte (TRANSPORT_KIND_PIPE_GOLDFISH),
new Variant.variant (new Variant.string (config.socket_path))
});
}
throw new Efni.Error.NOT_SUPPORTED ("Unsupported transport config");
}
private async void establish_hostlink (Cancellable? cancellable) throws Error, IOError {
if (vsock_transport == null)
private async void connect_serial_transport (BareboneSerialTransportConfig config,
Cancellable? cancellable) throws Efni.Error, IOError {
try {
#if WINDOWS
adopt_hostlink_streams (WindowsNamedPipe.open_client (config.path));
#else
var client = new SocketClient ();
adopt_hostlink_streams (yield client.connect_async (new UnixSocketAddress (config.path),
cancellable));
#endif
} catch (GLib.Error e) {
throw new Efni.Error.TRANSPORT ("Unable to reach the serial port of the guest: %s", e.message);
}
listening = new Efni.Promise<bool> ();
}
private async void open_pipe_listener (string path, Cancellable? cancellable) throws Efni.Error, IOError {
FileUtils.unlink (path);
var service = new SocketService ();
try {
SocketAddress effective;
service.add_address (new UnixSocketAddress (path), STREAM, DEFAULT, null, out effective);
} catch (GLib.Error e) {
throw new Efni.Error.TRANSPORT ("Unable to listen on %s: %s", path, e.message);
}
service.incoming.connect ((connection) => {
if (pipe_stream == null) {
pipe_stream = connection;
if (pipe_accept_handler != null) {
var handler = (owned) pipe_accept_handler;
pipe_accept_handler = null;
handler ();
}
}
return true;
});
service.start ();
pipe_service = service;
}
private async void establish_hostlink (Cancellable? cancellable) throws Efni.Error, IOError {
if (pipe_listener_wanted) {
while (pipe_stream == null) {
pipe_accept_handler = establish_hostlink.callback;
yield;
}
adopt_hostlink_streams (pipe_stream);
return;
}
if (hostlink_socket_path == null)
return;
#if WINDOWS
throw new Error.NOT_SUPPORTED ("Hostlink transport is not available on this OS");
#else
var address = new UnixSocketAddress (vsock_transport.socket_path);
var address = new UnixSocketAddress (hostlink_socket_path);
var client = new SocketClient ();
while (true) {
try {
@@ -334,11 +481,10 @@ namespace Frida.Barebone {
yield;
}
}
#endif
}
private async Variant connect_virtio_transport (BareboneHostlinkTransportConfig config, Cancellable? cancellable)
throws Error, IOError {
throws Efni.Error, IOError {
var qmp = yield QmpClient.open (config.qmp, 0, cancellable);
var link = yield qmp.open_hostlink (config.bus, cancellable);
this.qmp = qmp;
@@ -376,7 +522,7 @@ namespace Frida.Barebone {
output = hostlink.get_output_stream ();
}
private void rebase_layout (Layout layout, uint64 preferred_base, KernelRelocation reloc) throws Error {
private void rebase_layout (Layout layout, uint64 preferred_base, KernelRelocation reloc) throws Efni.Error {
foreach (var s in layout.symbols)
s.offset = reloc.runtime_offset (preferred_base + s.offset);
foreach (var m in layout.modules) {
@@ -415,14 +561,14 @@ namespace Frida.Barebone {
}
}
private async void give_the_memory_back (Cancellable? cancellable) throws Error, IOError {
private async void give_the_memory_back (Cancellable? cancellable) throws Efni.Error, IOError {
var timeout = new TimeoutSource (LEAVE_INTERVAL_MS);
timeout.set_callback (give_the_memory_back.callback);
timeout.attach (MainContext.get_thread_default ());
yield;
timeout.destroy ();
yield machine.gdb.stop (cancellable);
yield machine.debugger.stop (cancellable);
yield flavor.prepare (cancellable);
@@ -439,16 +585,16 @@ namespace Frida.Barebone {
yield flavor.settle (cancellable);
}
private async void wait_for_agent_to_leave (Cancellable? cancellable) throws Error, IOError {
private async void wait_for_agent_to_leave (Cancellable? cancellable) throws Efni.Error, IOError {
if (left_flag == 0)
return;
var gdb = machine.gdb;
var debugger = machine.debugger;
for (uint attempt = 0; attempt != LEAVE_MAX_ATTEMPTS; attempt++) {
yield gdb.stop (cancellable);
var flag = gdb.make_buffer (yield gdb.read_byte_array (left_flag, 4, cancellable));
yield debugger.stop (cancellable);
var flag = debugger.make_buffer (yield debugger.read_byte_array (left_flag, 4, cancellable));
bool left = flag.read_uint32 (0) != 0;
yield gdb.continue (cancellable);
yield debugger.resume (cancellable);
if (left) {
yield give_the_memory_back (cancellable);
return;
@@ -471,13 +617,13 @@ namespace Frida.Barebone {
// same data. The process receives the code as it is, thus only the writable half needs a new
// location, in memory that both sides can address.
public async void place_winnt_user_agent (uint pid, Cancellable? cancellable)
throws Error, IOError {
throws Efni.Error, IOError {
var response = yield execute_command (Command.PLACE_AGENT_IN_PROCESS,
new Variant.uint32 (pid), cancellable);
if (!response.is_of_type (VariantType.UINT32))
throw new Error.PROTOCOL ("Invalid place_agent_in_process response format");
throw new Efni.Error.PROTOCOL ("Invalid place_agent_in_process response format");
if (response.get_uint32 () == 0)
throw new Error.NOT_SUPPORTED ("Unable to place the agent in the process");
throw new Efni.Error.NOT_SUPPORTED ("Unable to place the agent in the process");
}
/**
@@ -485,7 +631,7 @@ namespace Frida.Barebone {
* copy reports having woken up in.
*/
public async uint inject_agent_into_process (uint pid, Cancellable? cancellable)
throws Error, IOError {
throws Efni.Error, IOError {
if (kernel_kind == WINNT) {
yield place_winnt_user_agent (pid, cancellable);
return yield start_winnt_agent_in_process (pid, cancellable);
@@ -497,12 +643,12 @@ namespace Frida.Barebone {
// The copy cannot answer from the thread that starts it. Thus ask again until it reports its
// process id.
private async uint start_winnt_agent_in_process (uint pid, Cancellable? cancellable)
throws Error, IOError {
throws Efni.Error, IOError {
for (uint attempt = 0; attempt != INJECT_MAX_ATTEMPTS; attempt++) {
var response = yield execute_command (Command.START_AGENT_IN_PROCESS,
new Variant.uint32 (pid), cancellable);
if (!response.is_of_type (VariantType.UINT32))
throw new Error.PROTOCOL ("Invalid start_agent_in_process response format");
throw new Efni.Error.PROTOCOL ("Invalid start_agent_in_process response format");
uint reached = response.get_uint32 ();
if (reached != 0)
@@ -515,19 +661,19 @@ namespace Frida.Barebone {
timeout.destroy ();
}
throw new Error.TIMED_OUT ("Timed out while starting the agent in the process");
throw new Efni.Error.TIMED_OUT ("Timed out while starting the agent in the process");
}
public signal void spawn_added (uint pid, string command_line, uint holder_pid);
public async Application[] enumerate_applications (string[] identifiers, Cancellable? cancellable)
throws Error, IOError {
throws Efni.Error, IOError {
var selected = new VariantBuilder (new VariantType ("as"));
foreach (unowned string identifier in identifiers)
selected.add ("s", identifier);
var response = yield execute_command (Command.ENUMERATE_APPLICATIONS, selected.end (), cancellable);
if (!response.check_format_string ("a(sss)", false))
throw new Error.PROTOCOL ("Invalid enumerate_applications response format");
throw new Efni.Error.PROTOCOL ("Invalid enumerate_applications response format");
var applications = new Application[response.n_children ()];
for (size_t i = 0; i != applications.length; i++) {
@@ -542,11 +688,11 @@ namespace Frida.Barebone {
}
public async Shortcut[] enumerate_shortcuts (uint helper_pid, Cancellable? cancellable)
throws Error, IOError {
throws Efni.Error, IOError {
var response = yield execute_command (Command.ENUMERATE_SHORTCUTS,
new Variant.boolean (false), cancellable, helper_pid);
if (!response.check_format_string ("a(ssss)", false))
throw new Error.PROTOCOL ("Invalid enumerate_shortcuts response format");
throw new Efni.Error.PROTOCOL ("Invalid enumerate_shortcuts response format");
var shortcuts = new Shortcut[response.n_children ()];
for (size_t i = 0; i != shortcuts.length; i++) {
@@ -593,51 +739,59 @@ namespace Frida.Barebone {
}
}
public async void gate_spawns (bool on, Cancellable? cancellable) throws Error, IOError {
public async void gate_spawns (bool on, Cancellable? cancellable) throws Efni.Error, IOError {
yield execute_command (Command.GATE_SPAWNS, new Variant.boolean (on), cancellable);
}
public async void arm_named_spawn (string name, Cancellable? cancellable) throws Efni.Error, IOError {
yield execute_command (Command.ARM_NAMED_SPAWN, new Variant.string (name), cancellable);
}
public async void disarm_named_spawn (string name, Cancellable? cancellable) throws Efni.Error, IOError {
yield execute_command (Command.DISARM_NAMED_SPAWN, new Variant.string (name), cancellable);
}
public async uint spawn_process (uint helper_pid, string command_line, Cancellable? cancellable)
throws Error, IOError {
throws Efni.Error, IOError {
return yield ask_for_a_spawn (helper_pid, new Variant.string (command_line), cancellable);
}
// A kernel that starts programs itself takes the words as they are, since a line to be
// split again is what Windows asks for and nothing else does.
public async uint spawn_program (string[] words, Cancellable? cancellable)
throws Error, IOError {
throws Efni.Error, IOError {
return yield ask_for_a_spawn (0, new Variant.strv (words), cancellable);
}
private async uint ask_for_a_spawn (uint helper_pid, Variant payload, Cancellable? cancellable)
throws Error, IOError {
throws Efni.Error, IOError {
var response = yield execute_command (Command.SPAWN_PROCESS, payload, cancellable,
helper_pid);
if (!response.is_of_type (VariantType.UINT32))
throw new Error.PROTOCOL ("Invalid spawn_process response format");
throw new Efni.Error.PROTOCOL ("Invalid spawn_process response format");
return response.get_uint32 ();
}
public async void resume_process (uint helper_pid, uint pid, Cancellable? cancellable)
throws Error, IOError {
throws Efni.Error, IOError {
yield execute_command (Command.RESUME_PROCESS, new Variant.uint32 (pid), cancellable,
helper_pid);
}
public async void detach_from_process (uint pid, Cancellable? cancellable) throws Error, IOError {
public async void detach_from_process (uint pid, Cancellable? cancellable) throws Efni.Error, IOError {
yield execute_command (Command.DETACH_FROM_PROCESS, new Variant.uint32 (pid), cancellable);
}
public async uint inject_into_process (uint pid, Cancellable? cancellable)
throws Error, IOError {
throws Efni.Error, IOError {
// The agent cannot answer from the context that does the work. Thus it reports the result at
// the next request.
for (uint attempt = 0; attempt != INJECT_MAX_ATTEMPTS; attempt++) {
var response = yield execute_command (Command.INJECT_INTO_PROCESS,
new Variant.uint32 (pid), cancellable);
if (!response.is_of_type (VariantType.UINT32))
throw new Error.PROTOCOL ("Invalid inject_into_process response format");
throw new Efni.Error.PROTOCOL ("Invalid inject_into_process response format");
uint reached = response.get_uint32 ();
if (reached != 0)
@@ -650,19 +804,19 @@ namespace Frida.Barebone {
timeout.destroy ();
}
throw new Error.TIMED_OUT ("Timed out while injecting into process");
throw new Efni.Error.TIMED_OUT ("Timed out while injecting into process");
}
public async HostProcessInfo[] enumerate_processes (Scope scope, uint[] pids, Cancellable? cancellable)
throws Error, IOError {
throws Efni.Error, IOError {
bool include_icons = scope == FULL;
var selected = new VariantBuilder (new VariantType ("au"));
foreach (uint pid in pids)
selected.add ("u", pid);
var request = new Variant.tuple ({ new Variant.boolean (include_icons), selected.end () });
var response = yield execute_command (Command.ENUMERATE_PROCESSES, request, cancellable);
if (!response.check_format_string ("a(usssaay)", false))
throw new Error.PROTOCOL ("Invalid enumerate_processes response format");
if (!response.check_format_string ("a(usssusuxaay)", false))
throw new Efni.Error.PROTOCOL ("Invalid enumerate_processes response format");
var processes = new HostProcessInfo[response.n_children ()];
for (size_t i = 0; i != processes.length; i++) {
@@ -676,9 +830,21 @@ namespace Frida.Barebone {
unowned string command_line = entry.get_child_value (2).get_string ();
if (command_line != "")
parameters["argv"] = argv_from_command_line (command_line);
parameters["uid"] = new Variant.uint32 (entry.get_child_value (4).get_uint32 ());
unowned string user = entry.get_child_value (5).get_string ();
if (user != "")
parameters["user"] = user;
parameters["ppid"] = new Variant.int64 (entry.get_child_value (6).get_uint32 ());
int64 started = entry.get_child_value (7).get_int64 ();
if (started != 0)
parameters["started"] = new DateTime.from_unix_utc (started).format_iso8601 ();
}
if (include_icons)
parameters["icons"] = icons_from_resources (entry.get_child_value (4));
parameters["icons"] = icons_from_resources (entry.get_child_value (8));
unowned string description = entry.get_child_value (3).get_string ();
string name = (description != "") ? description : basename_of (path);
@@ -852,30 +1018,30 @@ namespace Frida.Barebone {
private const uint32 MAX_ICON_DIMENSION = 256;
public async AgentScriptId create_script (string source, uint destination, Cancellable? cancellable)
throws Error, IOError {
throws Efni.Error, IOError {
var payload = new Variant ("s", source);
var response = yield execute_command (Command.CREATE_SCRIPT, payload, cancellable, destination);
if (!response.check_format_string ("u", false))
throw new Error.PROTOCOL ("Invalid create_script response format");
throw new Efni.Error.PROTOCOL ("Invalid create_script response format");
uint32 script_handle;
response.get ("u", out script_handle);
return AgentScriptId (script_handle);
}
public async void load_script (AgentScriptId script_id, uint destination, Cancellable? cancellable)
throws Error, IOError {
throws Efni.Error, IOError {
var payload = new Variant ("u", script_id.handle);
yield execute_command (Command.LOAD_SCRIPT, payload, cancellable, destination);
}
public async void destroy_script (AgentScriptId script_id, uint destination, Cancellable? cancellable)
throws Error, IOError {
throws Efni.Error, IOError {
var payload = new Variant ("u", script_id.handle);
yield execute_command (Command.DESTROY_SCRIPT, payload, cancellable, destination);
}
public async void post_script_message (AgentScriptId script_id, string message, Bytes? data, uint destination,
Cancellable? cancellable) throws Error, IOError {
Cancellable? cancellable) throws Efni.Error, IOError {
var payload = new Variant ("(us@ay)", script_id.handle, message,
new Variant.from_bytes (new VariantType ("ay"), data ?? new Bytes (new uint8[0]), true));
yield execute_command (Command.POST_SCRIPT_MESSAGE, payload, cancellable, destination);
@@ -883,26 +1049,26 @@ namespace Frida.Barebone {
// Zero selects the agent in the kernel. A different value selects a process that has a copy.
private async Variant execute_command (Command command, Variant payload, Cancellable? cancellable,
uint destination = 0) throws Error, IOError {
uint destination = 0) throws Efni.Error, IOError {
uint16 request_id = next_request_id++;
Bytes frame = frame_message (command, request_id, destination, payload);
var promise = new Promise<Variant> ();
var promise = new Efni.Promise<Variant> ();
pending_requests[request_id] = promise;
try {
yield write_frame (frame, cancellable);
} catch (GLib.Error e) {
pending_requests.unset (request_id);
throw new Error.TRANSPORT ("%s", e.message);
throw new Efni.Error.TRANSPORT ("%s", e.message);
}
var timeout_source = new TimeoutSource (COMMAND_TIMEOUT_MS);
timeout_source.set_callback (() => {
Promise<Variant>? p;
Efni.Promise<Variant>? p;
if (pending_requests.unset (request_id, out p))
p.reject (new Error.TIMED_OUT ("The %s command timed out", command.to_nick ()));
p.reject (new Efni.Error.TIMED_OUT ("The %s command timed out", command.to_nick ()));
return Source.REMOVE;
});
timeout_source.attach (MainContext.get_thread_default ());
@@ -910,23 +1076,76 @@ namespace Frida.Barebone {
Variant response = null;
try {
response = yield promise.future.wait_async (cancellable);
} catch (Efni.Error e) {
if (!(e is Efni.Error.TIMED_OUT))
throw e;
string? fault = yield describe_agent_fault (cancellable);
if (fault == null)
throw e;
throw new Efni.Error.TIMED_OUT ("%s; %s", e.message, fault);
} finally {
timeout_source.destroy ();
}
if (!response.check_format_string ("(bv)", false))
throw new Error.PROTOCOL ("Malformed reply from agent");
throw new Efni.Error.PROTOCOL ("Malformed reply from agent");
bool succeeded;
Variant detail;
response.get ("(bv)", out succeeded, out detail);
if (!succeeded)
throw new Error.NOT_SUPPORTED ("%s", detail.get_string ());
throw new Efni.Error.NOT_SUPPORTED ("%s", detail.get_string ());
return detail;
}
private async string? describe_agent_fault (Cancellable? cancellable) throws Efni.Error, IOError {
if (fault_record == 0)
return null;
var debugger = machine.debugger;
Buffer record;
try {
yield debugger.stop (cancellable);
record = debugger.make_buffer (yield debugger.read_byte_array (fault_record, FAULT_RECORD_SIZE,
cancellable));
yield debugger.resume (cancellable);
} catch (GLib.Error e) {
return null;
}
if (record.read_uint64 (0) == 0)
return null;
return ("the agent took an unhandled fault: vector 0x%" + uint64.FORMAT_MODIFIER
+ "x at %s, accessing 0x%" + uint64.FORMAT_MODIFIER + "x").printf (
record.read_uint64 (8), describe_program_counter (record.read_uint64 (16)),
record.read_uint64 (24));
}
private string describe_program_counter (uint64 pc) {
foreach (var symbol in agent_symbols) {
if (pc >= symbol.address && pc - symbol.address < symbol.size) {
return ("%s+0x%" + uint64.FORMAT_MODIFIER + "x").printf (symbol.name,
pc - symbol.address);
}
}
return ("pc 0x%" + uint64.FORMAT_MODIFIER + "x").printf (pc);
}
private class AgentSymbol {
public string name;
public uint64 address;
public uint64 size;
public AgentSymbol (string name, uint64 address, uint64 size) {
this.name = name;
this.address = address;
this.size = size;
}
}
private async void process_incoming_messages () {
reading = new Promise<bool> ();
reading = new Efni.Promise<bool> ();
try {
while (true) {
size_t header_size = 4;
@@ -954,7 +1173,7 @@ namespace Frida.Barebone {
if (byte_order != ByteOrder.HOST)
message = message.byteswap ();
if (!message.check_format_string ("(yquv)", false))
throw new Error.PROTOCOL ("Invalid message format");
throw new Efni.Error.PROTOCOL ("Invalid message format");
uint8 command_code;
uint16 request_id;
@@ -962,9 +1181,12 @@ namespace Frida.Barebone {
Variant payload;
message.get ("(yquv)", out command_code, out request_id, out destination, out payload);
if (command_code == Command.SPAWN_ADDED) {
if (command_code == Command.READY) {
if (listening != null && !listening.future.ready)
listening.resolve (true);
} else if (command_code == Command.SPAWN_ADDED) {
if (!payload.check_format_string ("(us)", false))
throw new Error.PROTOCOL ("Invalid spawn added payload format");
throw new Efni.Error.PROTOCOL ("Invalid spawn added payload format");
uint32 pid;
unowned string command_line;
@@ -973,7 +1195,7 @@ namespace Frida.Barebone {
spawn_added (pid, command_line, destination);
} else if (command_code == Command.SCRIPT_MESSAGE) {
if (!payload.check_format_string ("(usay)", false))
throw new Error.PROTOCOL ("Invalid script message payload format");
throw new Efni.Error.PROTOCOL ("Invalid script message payload format");
uint32 script_handle;
unowned string json;
@@ -988,7 +1210,7 @@ namespace Frida.Barebone {
} else if (command_code == Command.REMAP_WRITABLE_PAGES ||
command_code == Command.MEMORY_PROTECT ||
command_code == Command.PATCH_CODE) {
bool was_running = yield halt_guest (machine.gdb, io_cancellable);
bool was_running = yield halt_guest (machine.debugger, io_cancellable);
Variant result;
try {
@@ -998,17 +1220,17 @@ namespace Frida.Barebone {
result = yield protect_memory (payload, io_cancellable);
else
result = yield patch_code (payload, io_cancellable);
} catch (Error e) {
} catch (Efni.Error e) {
result = (command_code == Command.REMAP_WRITABLE_PAGES)
? new Variant.uint64 (0)
: new Variant.boolean (false);
}
yield resume_guest (machine.gdb, was_running, io_cancellable);
yield resume_guest (machine.debugger, was_running, io_cancellable);
yield send_reply (request_id, result);
} else if (command_code == Command.REPLY) {
Promise<Variant>? promise;
Efni.Promise<Variant>? promise;
if (pending_requests.unset (request_id, out promise))
promise.resolve (payload);
}
@@ -1019,9 +1241,9 @@ namespace Frida.Barebone {
reading.resolve (true);
}
private Promise<bool>? reading;
private Efni.Promise<bool>? reading;
private async void fill_until_n_bytes_available (size_t minimum) throws Error, IOError {
private async void fill_until_n_bytes_available (size_t minimum) throws Efni.Error, IOError {
size_t available = input.get_available ();
while (available < minimum) {
if (input.get_buffer_size () < minimum)
@@ -1032,17 +1254,17 @@ namespace Frida.Barebone {
n = yield input.fill_async ((ssize_t) (input.get_buffer_size () - available), Priority.DEFAULT,
io_cancellable);
} catch (GLib.Error e) {
throw new Error.TRANSPORT ("Connection closed");
throw new Efni.Error.TRANSPORT ("Connection closed");
}
if (n == 0)
throw new Error.TRANSPORT ("Connection closed");
throw new Efni.Error.TRANSPORT ("Connection closed");
available += n;
}
}
private async Variant remap_writable_pages (Variant payload, Cancellable? cancellable) throws Error, IOError {
private async Variant remap_writable_pages (Variant payload, Cancellable? cancellable) throws Efni.Error, IOError {
var physical_addresses = new Gee.ArrayList<uint64?> ();
for (size_t i = 0; i != payload.n_children (); i++) {
uint64 va = payload.get_child_value (i).get_uint64 ();
@@ -1054,7 +1276,7 @@ namespace Frida.Barebone {
return new Variant.uint64 (allocation.virtual_address);
}
private async Variant protect_memory (Variant payload, Cancellable? cancellable) throws Error, IOError {
private async Variant protect_memory (Variant payload, Cancellable? cancellable) throws Efni.Error, IOError {
uint64 address;
uint64 size;
uint32 prot;
@@ -1069,7 +1291,7 @@ namespace Frida.Barebone {
// CTRR/KTRR locks kernel text read-only against the guest CPU, so the agent cannot patch it
// even through a writable alias. The physical-memory bridge writes the backing store directly,
// which the lock does not cover, letting us land hooks in kernel and kext text.
private async Variant patch_code (Variant payload, Cancellable? cancellable) throws Error, IOError {
private async Variant patch_code (Variant payload, Cancellable? cancellable) throws Efni.Error, IOError {
uint64 va;
Variant bytes_value;
payload.get ("(t@ay)", out va, out bytes_value);
@@ -1099,7 +1321,7 @@ namespace Frida.Barebone {
}
private async void write_frame (Bytes frame, Cancellable? cancellable) throws GLib.Error {
var ours = new Promise<bool> ();
var ours = new Efni.Promise<bool> ();
var theirs = write_turn;
write_turn = ours;
@@ -1120,7 +1342,7 @@ namespace Frida.Barebone {
}
}
private Promise<bool>? write_turn;
private Efni.Promise<bool>? write_turn;
private Bytes frame_message (Command command, uint16 request_id, uint destination, Variant payload) {
var message = new Variant ("(yquv)", (uint8) command, request_id, destination, payload);
@@ -1134,30 +1356,33 @@ namespace Frida.Barebone {
}
private enum Command {
CREATE_SCRIPT = 1,
LOAD_SCRIPT = 2,
DESTROY_SCRIPT = 3,
POST_SCRIPT_MESSAGE = 4,
REMAP_WRITABLE_PAGES = 5,
MEMORY_PROTECT = 6,
PATCH_CODE = 7,
ENUMERATE_PROCESSES = 8,
INJECT_INTO_PROCESS = 9,
DETACH_FROM_PROCESS = 11,
PLACE_AGENT_IN_PROCESS = 12,
START_AGENT_IN_PROCESS = 13,
SPAWN_PROCESS = 14,
RESUME_PROCESS = 15,
STOP = 16,
GATE_SPAWNS = 17,
ENUMERATE_APPLICATIONS = 18,
ENUMERATE_SHORTCUTS = 19,
ENUMERATE_APPLICATIONS = 1,
ENUMERATE_SHORTCUTS = 2,
ENUMERATE_PROCESSES = 3,
GATE_SPAWNS = 4,
ARM_NAMED_SPAWN = 5,
DISARM_NAMED_SPAWN = 6,
SPAWN_PROCESS = 7,
RESUME_PROCESS = 8,
STOP = 9,
INJECT_INTO_PROCESS = 10,
PLACE_AGENT_IN_PROCESS = 11,
START_AGENT_IN_PROCESS = 12,
REMAP_WRITABLE_PAGES = 14,
MEMORY_PROTECT = 15,
PATCH_CODE = 16,
DETACH_FROM_PROCESS = 17,
CREATE_SCRIPT = 18,
DESTROY_SCRIPT = 19,
LOAD_SCRIPT = 20,
POST_SCRIPT_MESSAGE = 21,
REPLY = 128,
SCRIPT_MESSAGE = 129,
SPAWN_ADDED = 130;
SPAWN_ADDED = 130,
READY = 131;
public string to_nick () {
return Marshal.enum_to_nick<Command> (this);
return Efni.Marshal.enum_to_nick<Command> (this);
}
}
+20 -25
View File
@@ -1,44 +1,36 @@
## Getting the SDK and the devkit
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 flavor. Which flavor
is the target's, listed under Targets below; this walks through the XNU one. No
prebuilt bundles are published for these, so build both:
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:
machine=none-arm64-softfloat_nopic
machine=none-arm64-softfloat_pic
releng/deps.py build --bundle=sdk --host=$machine
mkdir -p ~/sdk-$machine
tar -C ~/sdk-$machine -xf deps/sdk-$machine.tar.xz
sdk=~/sdk-$machine
releng/deps.py sync sdk $machine $sdk
gum=~/src/frida-gum
mkdir -p $gum/build/$machine
(cd $gum/build/$machine \
&& FRIDA_DEPS=$HOME/src/frida-core/deps CC=clang CXX=clang++ AR=llvm-ar \
RANLIB=llvm-ranlib NM=llvm-nm STRIP=llvm-strip \
../../configure --host=$machine --enable-gumjs \
--with-devkits=gumjs \
&& python3 ../../releng/meson/meson.py configure -Ddevkit_symbol_scope=original \
&& ninja)
&& CC=clang CXX=clang++ ../../configure --host=$machine --enable-gumjs \
--with-devkits=gumjs --with-devkit-symbol-scope=original \
&& make)
## Building
sdk=~/sdk-$machine
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-softfloat --target-dir target-xnu \
-Z build-std=core,alloc,compiler_builtins
--target ./aarch64-unknown-none-pic.json -Z json-target-spec \
--target-dir target-xnu -Z build-std=core,alloc,compiler_builtins
Rust names no bare-metal target for the Windows guests, so the agent spells them
out and they are asked for by path:
RUSTC_BOOTSTRAP=1 cargo build --release --features win9x \
--target ./i686-unknown-none-pic.json -Z json-target-spec \
--target-dir target-win9x -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
@@ -48,7 +40,7 @@ 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.
- `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`.
@@ -58,10 +50,11 @@ and NT's 64-bit half is called in the Microsoft ABI.
| Flavor | Machine | Rust target |
|---------|-----------------------------------|----------------------------------|
| `xnu` | `none-arm64-softfloat_nopic` | `aarch64-unknown-none-softfloat` |
| `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
@@ -71,7 +64,9 @@ 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 --features xnu --target aarch64-unknown-none-softfloat \
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
@@ -4,7 +4,7 @@
"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",
"features": "+v8a,+strict-align,-neon,+reserve-x18",
"linker": "rust-lld",
"linker-flavor": "gnu-lld",
"llvm-target": "aarch64-unknown-none",
@@ -18,4 +18,4 @@
"kind": "inline"
},
"target-pointer-width": 64
}
}
+15
View File
@@ -20,10 +20,13 @@ SECTIONS {
_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;
@@ -43,10 +46,12 @@ SECTIONS {
_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;
@@ -84,6 +89,8 @@ SECTIONS {
___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;
@@ -91,12 +98,20 @@ SECTIONS {
_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
+22
View File
@@ -20,10 +20,13 @@ SECTIONS {
_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;
@@ -43,14 +46,17 @@ SECTIONS {
_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;
@@ -80,6 +86,8 @@ SECTIONS {
___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;
@@ -89,6 +97,11 @@ SECTIONS {
___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;
@@ -97,6 +110,15 @@ SECTIONS {
_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. */
+11
View File
@@ -20,10 +20,12 @@ SECTIONS {
_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;
@@ -54,6 +56,7 @@ SECTIONS {
_vmap = .; . += 4;
_current_task = .; . += 4;
_ioremap = .; . += 4;
_ioremap_nocache = .; . += 4;
___default_kernel_pte_mask = .; . += 4;
_init_task = .; . += 4;
_tasklist_lock = .; . += 4;
@@ -96,6 +99,14 @@ SECTIONS {
_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;
}
+17
View File
@@ -20,10 +20,13 @@ SECTIONS {
_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;
@@ -48,6 +51,7 @@ SECTIONS {
_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;
@@ -58,6 +62,7 @@ SECTIONS {
_register_die_notifier = .; . += 8;
_unregister_die_notifier = .; . += 8;
_ioremap = .; . += 8;
_ioremap_nocache = .; . += 8;
___default_kernel_pte_mask = .; . += 8;
_init_task = .; . += 8;
_tasklist_lock = .; . += 8;
@@ -86,6 +91,8 @@ SECTIONS {
___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;
@@ -100,6 +107,16 @@ SECTIONS {
_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. */
+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:
*;
};
}
+13
View File
@@ -25,7 +25,10 @@ SECTIONS {
_PsRemoveCreateThreadNotifyRoutine = .; . += 4;
_KeInsertQueueDpc = .; . += 4;
_ZwYieldExecution = .; . += 4;
_KeExpandKernelStackAndCalloutEx = .; . += 4;
_KeBugCheckEx = .; . += 4;
_DbgPrint = .; . += 4;
_MmGetVirtualForPhysical = .; . += 4;
_MmMapIoSpace = .; . += 4;
_MmGetPhysicalAddress = .; . += 4;
_HalGetInterruptVector = .; . += 4;
@@ -39,6 +42,7 @@ SECTIONS {
_PsGetCurrentThread = .; . += 4;
_PsGetThreadProcess = .; . += 4;
_PsGetThreadId = .; . += 4;
_PsGetThreadTeb = .; . += 4;
_PsGetContextThread = .; . += 4;
_PsSetContextThread = .; . += 4;
_KeStackAttachProcess = .; . += 4;
@@ -48,8 +52,17 @@ SECTIONS {
_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;
+13
View File
@@ -30,7 +30,10 @@ SECTIONS {
_PsRemoveCreateThreadNotifyRoutine = .; . += 8;
_KeInsertQueueDpc = .; . += 8;
_ZwYieldExecution = .; . += 8;
_KeExpandKernelStackAndCalloutEx = .; . += 8;
_KeBugCheckEx = .; . += 8;
_DbgPrint = .; . += 8;
_MmGetVirtualForPhysical = .; . += 8;
_MmMapIoSpace = .; . += 8;
_MmGetPhysicalAddress = .; . += 8;
_HalGetInterruptVector = .; . += 8;
@@ -44,6 +47,7 @@ SECTIONS {
_PsGetCurrentThread = .; . += 8;
_PsGetThreadProcess = .; . += 8;
_PsGetThreadId = .; . += 8;
_PsGetThreadTeb = .; . += 8;
_PsGetContextThread = .; . += 8;
_PsSetContextThread = .; . += 8;
_KeStackAttachProcess = .; . += 8;
@@ -53,8 +57,17 @@ SECTIONS {
_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;
+14 -1
View File
@@ -33,7 +33,12 @@ fn main() {
.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");
@@ -196,7 +201,7 @@ pub fn detect_gcc_include_paths(gcc: &Path, args: &[&str]) -> 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);
}
@@ -206,6 +211,14 @@ pub fn detect_gcc_include_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
paths
}
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");
+1 -1
View File
@@ -2,7 +2,7 @@ export FRIDA_BAREBONE_CONFIG=$PWD/etc/xnu.json
export PYTHONPATH=$HOME/src/frida-python
sdk=$HOME/src/frida-core/deps/sdk-none-arm64-softfloat_pic
export CC="clang --target=aarch64-none-elf -I$sdk/include -I$sdk/include/glib-2.0 -I$sdk/lib/glib-2.0/include \
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
+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",
+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 });
+7 -6
View File
@@ -11,21 +11,22 @@ 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-arm64-kernel`:
rest, built for `macos-arm64e-kernel` and published prebuilt:
cd frida-core
python3 -m releng.deps build --host macos-arm64-kernel
releng/deps.py sync sdk macos-arm64e-kernel ~/sdk-macos-arm64e-kernel
That fetches the same sources every other flavour is built from, six of them pinned to a commit
that says what a Darwin kernel is not; see below. Then a GumJS devkit against it:
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-arm64-kernel --enable-gumjs --disable-v8 --with-devkits=gumjs
./configure --host=macos-arm64e-kernel --enable-gumjs --with-devkits=gumjs \
--with-devkit-symbol-scope=original
make
Then the kext itself:
make FRIDA_SDK=.../deps/sdk-macos-arm64-kernel \
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
-1
View File
@@ -79,7 +79,6 @@ const unsigned frida_agent_disc_proc_callout =
_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)
{
+1
View File
@@ -42,6 +42,7 @@ pub extern "C" fn g_wait_sleep(token: gpointer, timeout_us: gint64) {
#[unsafe(no_mangle)]
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);
}
+1
View File
@@ -453,6 +453,7 @@ pub struct ThreadSlots {
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) };
}
+51 -24
View File
@@ -6,11 +6,9 @@ use crate::{
_GInterfaceInfo, _GTypeInfo, GObject, GObjectClass, GPrivate, GType, GumAddress,
GumExportDetails,
GumExportType_GUM_EXPORT_FUNCTION, GumFoundExportFunc, GumMemoryRange, GumModule,
GumFoundSymbolFunc, GumModuleInterface, GumSymbolDetails, GumSymbolType_GUM_SYMBOL_FUNCTION,
GumSymbolType_GUM_SYMBOL_OBJECT, GumSymbolType_GUM_SYMBOL_UNKNOWN, GumThreadId, GumTlsKey,
gssize, g_free, g_object_get_type, g_object_new,
GumModuleInterface, GumThreadId, GumTlsKey, g_free, g_object_get_type, g_object_new,
g_once_init_enter, g_once_init_leave, g_strdup, g_type_add_interface_static,
g_type_class_peek_parent, g_type_register_static, gchar, gboolean, gpointer, gsize, guint,
g_type_class_peek_parent, g_type_register_static, gchar, gpointer, gsize, guint,
},
gthread, kernel, libc,
};
@@ -24,9 +22,16 @@ use core::sync::atomic::{AtomicU32, Ordering};
#[cfg(feature = "linux")]
use crate::gum_linux::enumerate_exports_in_range;
#[cfg(any(feature = "linux", feature = "linux-injected"))]
use crate::bindings::{
GumFoundSymbolFunc, GumSymbolDetails, GumSymbolType_GUM_SYMBOL_FUNCTION,
GumSymbolType_GUM_SYMBOL_OBJECT, GumSymbolType_GUM_SYMBOL_UNKNOWN, gboolean, gssize,
};
#[cfg(any(feature = "linux", feature = "linux-injected"))]
use crate::gum_modules::enumerate_symbols_in_range;
#[cfg(any(feature = "win9x", feature = "winnt"))]
use crate::gum_windows::enumerate_exports_in_range;
use crate::gum_windows::{enumerate_exports_in_range, enumerate_imports_in_module};
#[cfg(any(feature = "win9x", feature = "winnt"))]
use crate::bindings::{GumFoundImportFunc, GumImportDetails, GumImportType_GUM_IMPORT_FUNCTION};
#[cfg(any(feature = "xnu-core", feature = "linux-injected"))]
use crate::gum_injected::enumerate_exports_in_range;
@@ -181,6 +186,11 @@ extern "C" fn gum_native_module_iface_init(g_iface: gpointer, _iface_data: gpoin
(*iface).get_path = Some(crate::signed_to_be_called_back(gum_native_module_get_path, 0));
(*iface).get_range = Some(crate::signed_to_be_called_back(gum_native_module_get_range, 0));
(*iface).enumerate_exports = Some(crate::signed_to_be_called_back(gum_native_module_enumerate_exports, 0));
#[cfg(any(feature = "win9x", feature = "winnt"))]
{
(*iface).enumerate_imports =
Some(crate::signed_to_be_called_back(gum_native_module_enumerate_imports, 0));
}
(*iface).find_export_by_name = Some(crate::signed_to_be_called_back(gum_native_module_find_export_by_name, 0));
#[cfg(any(feature = "linux", feature = "linux-injected"))]
{
@@ -314,6 +324,32 @@ unsafe extern "C" fn gum_native_module_enumerate_exports(
}
}
#[cfg(any(feature = "win9x", feature = "winnt"))]
unsafe extern "C" fn gum_native_module_enumerate_imports(
self_: *mut GumModule,
func: GumFoundImportFunc,
user_data: gpointer,
) {
unsafe {
let module = self_ as *mut GumNativeModule;
let base = (*module).range.base_address;
let mut on_import = |library: *const gchar, name: *const gchar, slot: u64, address: u64| {
let details = GumImportDetails {
type_: GumImportType_GUM_IMPORT_FUNCTION,
name,
module: library,
address,
slot,
};
func.unwrap()(&details as *const GumImportDetails, user_data) != 0
};
enumerate_imports_in_module(base, &mut on_import);
}
}
#[cfg(any(feature = "linux", feature = "linux-injected"))]
unsafe extern "C" fn gum_native_module_enumerate_symbols(
self_: *mut GumModule,
@@ -365,6 +401,9 @@ const SYMBOL_IS_FUNCTION: u8 = 2;
/// it returns false.
pub(crate) type FoundExportCallback<'a> = dyn FnMut(*const gchar, u64) -> bool + 'a;
#[cfg(any(feature = "win9x", feature = "winnt"))]
pub(crate) type FoundImportCallback<'a> = dyn FnMut(*const gchar, *const gchar, u64, u64) -> bool + 'a;
// Code slabs the agent allocates are normal writable RAM, so Gum can keep a real writable alias to
// them across the slab's lifetime. Pre-existing kernel text is write-protected — CTRR-locked on
// Apple silicon, STRICT_KERNEL_RWX on Linux — and can only be modified through a separate path, so
@@ -401,24 +440,6 @@ pub(crate) fn unregister_slab(start: u64) {
slab_unlock();
}
// For callers that must not block, primarily a fault handler, which can interrupt the code
// that holds the lock. Report a busy registry and let the caller decide.
pub(crate) fn is_agent_slab_if_idle(address: u64) -> Option<bool> {
if SLAB_LOCK
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
return None;
}
let found = unsafe {
(*core::ptr::addr_of!(SLABS))
.iter()
.any(|&(begin, end)| address >= begin && address < end)
};
slab_unlock();
Some(found)
}
pub(crate) fn is_agent_slab(address: u64) -> bool {
slab_lock();
let found = unsafe {
@@ -435,7 +456,8 @@ pub(crate) fn is_agent_slab(address: u64) -> bool {
mod symbolication {
use super::*;
use crate::bindings::{
GArray, GumAddress, GumDebugSymbolDetails, g_array_append_vals, g_array_new, gconstpointer,
GArray, GumAddress, GumDebugSymbolDetails, g_array_append_vals, g_array_new, gboolean,
gconstpointer,
};
use core::ffi::CStr;
@@ -483,6 +505,11 @@ mod symbolication {
return crate::xnu_mapped::export_named(name);
}
#[cfg(feature = "linux-injected")]
if crate::kernel::in_copy() {
return crate::kernel::export_named(name);
}
let table = unsafe { &*ptr::addr_of!(crate::SYMBOL_TABLE) };
match table.find_symbol_by_name(name) {
+254 -69
View File
@@ -8,26 +8,34 @@ use crate::{
FridaCommand,
bindings::{
_GumPageProtection_GUM_PAGE_EXECUTE, _GumPageProtection_GUM_PAGE_READ,
_GumPageProtection_GUM_PAGE_WRITE, _GumRwxSupport_GUM_RWX_FULL,
_GumRwxSupport_GUM_RWX_NONE, GArray,
GumDebugSymbolDetails, GumFoundRangeFunc, GumFoundThreadFunc, GumMemoryRange,
GumModuleRegistry, GumPageProtection, GumRangeDetails, GumRwxSupport, GumThreadDetails,
GumThreadId, GumThreadRegistry, gum_barebone_register_thread,
gum_barebone_unregister_thread,
g_array_append_vals, g_array_new, g_object_unref, g_strdup, g_variant_get_boolean,
g_variant_get_uint64, g_variant_new, g_variant_new_fixed_array, g_variant_type_free,
g_variant_type_new, g_variant_unref, gboolean, gchar, gconstpointer, gpointer, gsize, guint,
gum_barebone_register_module,
gum_barebone_try_remap_writable_pages as _gum_barebone_try_remap_writable_pages,
gum_mprotect, gum_query_page_size,
_GumPageProtection_GUM_PAGE_WRITE, _GumRwxSupport_GUM_RWX_NONE,
GumFoundRangeFunc, GumFoundThreadFunc, GumMemoryRange,
GumCpuContext, GumModuleRegistry, GumPageProtection, GumRangeDetails, GumRwxSupport,
GumThreadDetails, GumThreadFlags, GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT,
GumThreadFlags_GUM_THREAD_FLAGS_STATE, GumThreadId,
GumThreadRegistry, gum_barebone_register_thread, g_variant_get_uint64,
g_variant_new_fixed_array, g_variant_type_free, g_variant_type_new, g_variant_unref,
gboolean, gchar, gconstpointer, gpointer, gsize, guint, gum_mprotect, gum_query_page_size,
},
gum::{self, FoundExportCallback},
host_rpc, kernel, libc,
};
#[cfg(feature = "xnu-core")]
use crate::bindings::{g_object_unref, gum_barebone_register_module};
#[cfg(feature = "linux-injected")]
use crate::bindings::{
GError, GumBareboneStreamOps, g_io_error_from_errno, g_io_error_quark, g_set_error_literal,
g_strerror, gint, gssize, gum_barebone_unregister_thread,
};
#[cfg(not(feature = "xnu-kext"))]
use crate::bindings::{
g_variant_get_boolean, g_variant_new,
gum_barebone_try_remap_writable_pages as _gum_barebone_try_remap_writable_pages,
};
use alloc::collections::{BTreeMap, BTreeSet};
#[cfg(feature = "xnu-core")]
use alloc::format;
use alloc::vec::Vec;
use core::ffi::CStr;
use core::mem::size_of;
use core::ptr;
@@ -40,13 +48,6 @@ const MODULE_DIRECTORY: &str = "/System/Library/Extensions/";
#[cfg(feature = "xnu-core")]
const MODULE_SUFFIX: &str = ".kext";
#[cfg(feature = "linux-injected")]
const KERNEL_PATH: &str = "/boot/vmlinux";
#[cfg(feature = "linux-injected")]
const MODULE_DIRECTORY: &str = "/lib/modules/";
#[cfg(feature = "linux-injected")]
const MODULE_SUFFIX: &str = ".ko";
const SHADOW_HEADER: usize = 24;
#[cfg(feature = "xnu-core")]
@@ -84,7 +85,7 @@ pub extern "C" fn gum_barebone_query_stack_size() -> crate::bindings::gsize {
#[unsafe(no_mangle)]
pub extern "C" fn gum_query_rwx_support() -> GumRwxSupport {
#[cfg(feature = "linux-injected")]
return _GumRwxSupport_GUM_RWX_FULL;
return _GumRwxSupport_GUM_RWX_NONE;
#[cfg(not(feature = "linux-injected"))]
_GumRwxSupport_GUM_RWX_NONE
@@ -137,7 +138,7 @@ fn remap_agent_pages(first_page: gpointer, _n_pages: guint) -> gpointer {
fn writable_in_place(first_page: gpointer, n_pages: guint) -> bool {
let size = n_pages as usize * unsafe { gum_query_page_size() } as usize;
kernel::set_protection(first_page as u64, size, _GumPageProtection_GUM_PAGE_RWX)
kernel::set_protection(first_page as u64, size, GUM_PAGE_RWX)
}
#[cfg(not(feature = "xnu-kext"))]
@@ -175,8 +176,53 @@ fn remap_agent_pages_through_host(first_page: gpointer, n_pages: guint) -> gpoin
}
}
#[cfg(all(feature = "linux-injected", not(target_arch = "arm")))]
const GUM_PAGE_RWX: u32 = 7;
#[cfg(feature = "linux-injected")]
const _GumPageProtection_GUM_PAGE_RWX: u32 = 7;
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_stream_ops() -> *const GumBareboneStreamOps {
if kernel::in_copy() { &STREAM_OPS } else { ptr::null() }
}
#[cfg(feature = "linux-injected")]
static STREAM_OPS: GumBareboneStreamOps = GumBareboneStreamOps {
poll: Some(crate::linux::user::frida_poll),
read: Some(read_descriptor),
write: Some(write_descriptor),
close: Some(close_descriptor),
};
#[cfg(feature = "linux-injected")]
unsafe extern "C" fn read_descriptor(handle: gpointer, buffer: gpointer, count: gsize,
error: *mut *mut GError) -> gssize {
transferred(crate::linux::user::read(handle as i32, buffer as *mut u8, count as usize), error)
}
#[cfg(feature = "linux-injected")]
unsafe extern "C" fn write_descriptor(handle: gpointer, buffer: gconstpointer, count: gsize,
error: *mut *mut GError) -> gssize {
transferred(crate::linux::user::write(handle as i32, buffer as *const u8, count as usize),
error)
}
#[cfg(feature = "linux-injected")]
unsafe extern "C" fn close_descriptor(handle: gpointer, error: *mut *mut GError) -> gboolean {
(transferred(crate::linux::user::close(handle as i32), error) >= 0) as gboolean
}
#[cfg(feature = "linux-injected")]
fn transferred(result: isize, error: *mut *mut GError) -> gssize {
if result < 0 {
let errno = -result as gint;
unsafe {
g_set_error_literal(error, g_io_error_quark(), g_io_error_from_errno(errno) as gint,
g_strerror(errno))
};
return -1;
}
result as gssize
}
unsafe fn what_changed(first_page: u64, shadow: *const u8, total: usize) -> Option<(usize, usize)> {
let live = unsafe { core::slice::from_raw_parts(first_page as *const u8, total) };
@@ -221,17 +267,13 @@ pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages
#[cfg(feature = "xnu-kext")]
if let Some(base) = crate::xnu::the_page_behind(writable as u64) {
let size = _n_pages as usize * unsafe { gum_query_page_size() } as usize;
unsafe {
libc::__clear_cache(base as *const u8, (base + size as u64) as *const u8);
}
libc::__clear_cache(base as *const u8, (base + size as u64) as *const u8);
return;
}
if let Some(executable) = aliases().remove(&(writable as u64)) {
let size = _n_pages as usize * unsafe { gum_query_page_size() } as usize;
unsafe {
libc::__clear_cache(executable as *const u8, (executable + size as u64) as *const u8);
}
libc::__clear_cache(executable as *const u8, (executable + size as u64) as *const u8);
return;
}
@@ -239,10 +281,8 @@ pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages
#[cfg(all(feature = "linux-injected", not(target_arch = "arm")))]
{
let size = _n_pages as usize * unsafe { gum_query_page_size() } as usize;
unsafe {
libc::__clear_cache(writable as *const u8,
(writable as u64 + size as u64) as *const u8);
}
libc::__clear_cache(writable as *const u8,
(writable as u64 + size as u64) as *const u8);
}
return;
@@ -366,6 +406,7 @@ fn protect_here(address: u64, size: usize, prot: u32) -> bool {
ask_the_host_to_protect(address, size, prot)
}
#[cfg(not(feature = "xnu-kext"))]
pub fn ask_the_host_to_protect(address: u64, size: usize, prot: u32) -> bool {
if !crate::transport_is_up() {
return true;
@@ -381,6 +422,7 @@ pub fn ask_the_host_to_protect(address: u64, size: usize, prot: u32) -> bool {
granted
}
#[cfg(not(feature = "xnu-kext"))]
fn the_host_grants_it(address: u64, size: usize, prot: u32) -> bool {
unsafe {
let payload = g_variant_new(c"(ttu)".as_ptr(), address, size as u64, prot);
@@ -401,6 +443,7 @@ fn the_host_grants_it(address: u64, size: usize, prot: u32) -> bool {
// bridge, which leaves this CPU's TLB holding the stale translation. Stalker
// flips a slab page RW then RX in place, so without this the freeze never takes
// effect and executing the page faults with a permission abort.
#[cfg(not(feature = "xnu-kext"))]
unsafe fn flush_tlb_range(address: u64, size: u64) {
let page_size = unsafe { gum_query_page_size() } as u64;
let start = address & !(page_size - 1);
@@ -409,7 +452,7 @@ unsafe fn flush_tlb_range(address: u64, size: u64) {
unsafe { flush_pages(start, end, page_size) };
}
#[cfg(target_arch = "arm")]
#[cfg(all(not(feature = "xnu-kext"), target_arch = "arm"))]
unsafe fn flush_pages(start: u64, end: u64, page_size: u64) {
unsafe {
core::arch::asm!("dsb ish", options(nostack, preserves_flags));
@@ -423,7 +466,7 @@ unsafe fn flush_pages(start: u64, end: u64, page_size: u64) {
}
}
#[cfg(target_arch = "aarch64")]
#[cfg(all(not(feature = "xnu-kext"), target_arch = "aarch64"))]
unsafe fn flush_pages(start: u64, end: u64, page_size: u64) {
unsafe {
core::arch::asm!("dsb ish", options(nostack, preserves_flags));
@@ -503,7 +546,7 @@ fn release_pages_now(at: gpointer, size: gsize) {
#[cfg(feature = "xnu-kext")]
fn remember_slab_of(at: gpointer, size: gsize) {
unsafe { gum::register_slab(at as u64, size as usize) };
gum::register_slab(at as u64, size as usize);
crate::xnu::remember_what_we_took(at as u64, size as usize, true);
}
@@ -545,17 +588,17 @@ pub extern "C" fn gum_memory_allocate(
} else {
kernel::alloc_code(size as usize)
};
#[cfg(not(feature = "xnu-core"))]
#[cfg(all(not(feature = "xnu-core"), not(feature = "linux-injected")))]
let ptr = kernel::alloc_code(size as usize);
#[cfg(feature = "linux-injected")]
let ptr = kernel::alloc_heap(size as usize);
#[cfg(not(feature = "xnu-core"))]
let _ = address;
unsafe {
#[cfg(not(feature = "xnu-kext"))]
core::ptr::write_bytes(ptr, 0, size as usize);
#[cfg(not(feature = "xnu-kext"))]
unsafe { core::ptr::write_bytes(ptr, 0, size as usize) };
if may_run {
gum::register_slab(ptr as u64, size as usize);
}
if may_run {
gum::register_slab(ptr as u64, size as usize);
}
#[cfg(not(feature = "xnu-kext"))]
@@ -661,7 +704,7 @@ pub extern "C" fn gum_barebone_on_registry_deactivating(_registry: *mut GumModul
pub extern "C" fn gum_barebone_on_thread_registry_activating(registry: *mut GumThreadRegistry) {
unsafe { THREAD_REGISTRY = registry };
kernel::enumerate_threads(&mut |thread| announce_thread(thread.id));
kernel::enumerate_threads(&mut |thread| announce_thread(thread.id), false);
}
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
@@ -670,12 +713,12 @@ pub extern "C" fn gum_barebone_on_thread_registry_deactivating(_registry: *mut G
unsafe { THREAD_REGISTRY = ptr::null_mut() };
}
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
#[cfg(feature = "linux-injected")]
pub(crate) fn thread_appeared(id: u32) {
announce_thread(id);
}
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
#[cfg(feature = "linux-injected")]
pub(crate) fn thread_vanished(id: u32) {
let registry = unsafe { THREAD_REGISTRY };
if registry.is_null() {
@@ -703,17 +746,98 @@ static mut THREAD_REGISTRY: *mut GumThreadRegistry = ptr::null_mut();
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_enumerate_threads(func: GumFoundThreadFunc, user_data: gpointer) {
pub extern "C" fn gum_barebone_enumerate_threads(func: GumFoundThreadFunc, user_data: gpointer,
flags: GumThreadFlags) {
let Some(emit) = func else {
return;
};
let with_registers = (flags & GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT) != 0;
kernel::enumerate_threads(&mut |thread| {
let mut details: GumThreadDetails = unsafe { core::mem::zeroed() };
details.id = thread.id as GumThreadId;
if let Some(state) = thread.state {
details.flags |= GumThreadFlags_GUM_THREAD_FLAGS_STATE;
details.state = state;
}
if let Some(cpu) = thread.cpu_state {
details.flags |= GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT;
details.cpu_context = cpu_context_from(&cpu);
}
unsafe { emit(&details, user_data) };
});
}, with_registers);
}
#[cfg(target_arch = "aarch64")]
fn cpu_context_from(state: &kernel::CpuState) -> GumCpuContext {
GumCpuContext {
pc: state.pc,
sp: state.sp,
nzcv: state.nzcv,
x: state.x,
fp: state.fp,
lr: state.lr,
}
}
#[cfg(target_arch = "x86_64")]
fn cpu_context_from(state: &kernel::CpuState) -> GumCpuContext {
GumCpuContext {
rip: state.rip,
r15: state.r15,
r14: state.r14,
r13: state.r13,
r12: state.r12,
r11: state.r11,
r10: state.r10,
r9: state.r9,
r8: state.r8,
rdi: state.rdi,
rsi: state.rsi,
rbp: state.rbp,
rsp: state.rsp,
rbx: state.rbx,
rdx: state.rdx,
rcx: state.rcx,
rax: state.rax,
xmm: ptr::null_mut(),
}
}
#[cfg(target_arch = "x86")]
fn cpu_context_from(state: &kernel::CpuState) -> GumCpuContext {
GumCpuContext {
eip: state.eip,
edi: state.edi,
esi: state.esi,
ebp: state.ebp,
esp: state.esp,
ebx: state.ebx,
edx: state.edx,
ecx: state.ecx,
eax: state.eax,
xmm: ptr::null_mut(),
}
}
#[cfg(target_arch = "arm")]
fn cpu_context_from(state: &kernel::CpuState) -> GumCpuContext {
GumCpuContext {
cpsr: state.cpsr,
pc: state.pc,
sp: state.sp,
r8: state.r8,
r9: state.r9,
r10: state.r10,
r11: state.r11,
r12: state.r12,
lr: state.lr,
r: state.r,
..unsafe { core::mem::zeroed() }
}
}
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
@@ -727,7 +851,7 @@ pub extern "C" fn _gum_process_enumerate_ranges(
return;
};
let mut report = |base: u64, size: u64, protection: u32| {
let report = |base: u64, size: u64, protection: u32| {
if (protection & prot as u32) != prot as u32 {
return;
}
@@ -768,32 +892,49 @@ fn each_kernel_module_range(mut visit: impl FnMut(u64, u64)) {
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_query_protection(
address: gpointer,
pub extern "C" fn gum_memory_query_region(
address: gconstpointer,
range: *mut GumMemoryRange,
prot: *mut GumPageProtection,
) -> gboolean {
let Some(region) = region_at(address as u64).filter(|region| region.protection != 0) else {
return 0;
};
unsafe {
*range = GumMemoryRange {
base_address: region.base,
size: region.size as gsize,
};
*prot = region.protection as GumPageProtection;
}
1
}
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
fn region_at(address: u64) -> Option<kernel::MemoryRegion> {
#[cfg(feature = "linux-injected")]
if !kernel::in_copy() {
let mut protection = 0u32;
each_kernel_module_range(|base, size| {
if address as u64 >= base && (address as u64) < base + size {
protection = KERNEL_RANGE_PROTECTION;
}
});
if protection == 0 {
return 0;
return kernel_module_region_at(address);
}
kernel::region_at(address)
}
#[cfg(feature = "linux-injected")]
fn kernel_module_region_at(address: u64) -> Option<kernel::MemoryRegion> {
let mut found = None;
each_kernel_module_range(|base, size| {
if address >= base && address < base + size {
found = Some(kernel::MemoryRegion {
base,
size,
protection: KERNEL_RANGE_PROTECTION,
});
}
unsafe { *prot = protection as GumPageProtection };
return 1;
}
});
let protection = kernel::protection_at(address as u64);
if protection == 0 {
return 0;
}
unsafe { *prot = protection as GumPageProtection };
1
found
}
pub(crate) unsafe fn enumerate_exports_in_range(
@@ -844,3 +985,47 @@ pub(crate) unsafe fn enumerate_exports_in_range(
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
0
}
#[cfg(feature = "linux-injected")]
#[unsafe(no_mangle)]
pub extern "C" fn gum_thread_get_system_error() -> gint {
match errno_slot() {
Some(slot) => unsafe { *slot },
None => 0,
}
}
#[cfg(feature = "linux-injected")]
#[unsafe(no_mangle)]
pub extern "C" fn gum_thread_set_system_error(value: gint) {
if let Some(slot) = errno_slot() {
unsafe { *slot = value };
}
}
// errno is thread-local behind a function that hands back its address:
// __errno_location on glibc and musl, __errno on Bionic.
#[cfg(feature = "linux-injected")]
fn errno_slot() -> Option<*mut gint> {
use core::sync::atomic::{AtomicUsize, Ordering};
if !crate::kernel::in_copy() {
return None;
}
static LOCATE: AtomicUsize = AtomicUsize::new(0);
let mut locate = LOCATE.load(Ordering::Relaxed);
if locate == 0 {
locate = crate::kernel::export_named("__errno_location") as usize;
if locate == 0 {
locate = crate::kernel::export_named("__errno") as usize;
}
if locate == 0 {
return None;
}
LOCATE.store(locate, Ordering::Relaxed);
}
let locate: unsafe extern "C" fn() -> *mut gint = unsafe { core::mem::transmute(locate) };
Some(unsafe { locate() })
}
+3 -4
View File
@@ -6,15 +6,14 @@ use crate::{
bindings::{
_GumPageProtection_GUM_PAGE_EXECUTE, _GumPageProtection_GUM_PAGE_READ,
_GumPageProtection_GUM_PAGE_WRITE, _GumRwxSupport_GUM_RWX_NONE, GArray,
GumDebugSymbolDetails, GumMemoryRange, GumModuleRegistry, GumPageProtection, GumRwxSupport,
g_array_append_vals, g_array_new, g_object_unref, g_pattern_spec_free, g_pattern_spec_match_string,
GumDebugSymbolDetails, GumModuleRegistry, GumPageProtection, GumRwxSupport,
g_array_append_vals, g_array_new, g_pattern_spec_free, g_pattern_spec_match_string,
g_pattern_spec_new, g_strdup, gboolean, gchar, gconstpointer, gpointer, gsize, guint,
gum_barebone_register_module, gum_mprotect,
gum_mprotect,
},
gum::{self, FoundExportCallback},
kernel,
};
use alloc::format;
use core::ffi::CStr;
use core::ptr;
+311 -52
View File
@@ -3,25 +3,240 @@
use crate::{
bindings::{
_GumPageProtection_GUM_PAGE_EXECUTE, _GumRwxSupport_GUM_RWX_FULL, GumMemoryRange,
GumModuleRegistry, GumPageProtection, GumRwxSupport, g_object_unref, gboolean, gpointer,
gsize, guint, gum_barebone_register_module, gum_mprotect, GumFoundRangeFunc,
GumFoundThreadFunc, GumModifyThreadFlags, GumModifyThreadFunc, GumRangeDetails,
GumThreadDetails, GumThreadFlags, GumThreadId, GumThreadRegistry,
gum_thread_details_copy,
_GumPageProtection_GUM_PAGE_EXECUTE, _GumRwxSupport_GUM_RWX_FULL, GError,
GIOErrorEnum_G_IO_ERROR_BROKEN_PIPE, GIOErrorEnum_G_IO_ERROR_FAILED,
GIOErrorEnum_G_IO_ERROR_INVALID_ARGUMENT, GIOErrorEnum_G_IO_ERROR_NOT_FOUND,
GIOErrorEnum_G_IO_ERROR_PERMISSION_DENIED, GumBareboneStreamOps, GumMemoryRange,
GumModuleRegistry, GumPageProtection, GumRwxSupport, g_free, g_io_error_quark, g_malloc,
g_object_unref, g_set_error_literal, g_utf8_to_utf16, g_utf16_to_utf8, gboolean, gchar,
gint, gpointer, gsize, gssize, guint, gum_barebone_register_module, gum_mprotect,
GumFoundRangeFunc, GumFoundThreadFunc, GumModifyThreadFlags, GumModifyThreadFunc,
GumRangeDetails, GumThreadDetails, GumThreadFlags, GumThreadId, GumThreadRegistry,
gconstpointer, gum_thread_details_copy,
},
gum::{self, FoundExportCallback},
gum::{self, FoundExportCallback, FoundImportCallback},
kernel,
};
use crate::bindings::{GumCpuContext, GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT};
use alloc::ffi::CString;
#[cfg(not(feature = "winnt"))]
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use core::ptr;
#[cfg(target_arch = "x86")]
macro_rules! windows_fn {
($($argument:ty),* $(,)?) => { unsafe extern "stdcall" fn($($argument),*) };
($($argument:ty),* $(,)? => $result:ty) => {
unsafe extern "stdcall" fn($($argument),*) -> $result
};
}
#[cfg(target_arch = "x86_64")]
macro_rules! windows_fn {
($($argument:ty),* $(,)?) => { unsafe extern "win64" fn($($argument),*) };
($($argument:ty),* $(,)? => $result:ty) => {
unsafe extern "win64" fn($($argument),*) -> $result
};
}
#[cfg(target_arch = "aarch64")]
macro_rules! windows_fn {
($($argument:ty),* $(,)?) => { unsafe extern "C" fn($($argument),*) };
($($argument:ty),* $(,)? => $result:ty) => {
unsafe extern "C" fn($($argument),*) -> $result
};
}
#[allow(unused_imports)]
pub(crate) use windows_fn;
pub struct FileApi {
pub read_file: windows_fn!(usize, *mut u8, u32, *mut u32, usize => i32),
pub write_file: windows_fn!(usize, *const u8, u32, *mut u32, usize => i32),
pub close_handle: windows_fn!(usize => i32),
pub format_message: windows_fn!(u32, usize, u32, u32, *mut u8, u32, usize => u32),
}
pub struct TextApi {
pub multi_byte_to_wide_char: windows_fn!(u32, u32, *const u8, i32, *mut u16, i32 => i32),
pub wide_char_to_multi_byte:
windows_fn!(u32, u32, *const u16, i32, *mut u8, i32, usize, usize => i32),
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_platform() -> *const crate::bindings::gchar {
c"windows".as_ptr() as *const crate::bindings::gchar
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_thread_get_system_error() -> crate::bindings::gint {
kernel::thread_error() as crate::bindings::gint
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_thread_set_system_error(value: crate::bindings::gint) {
kernel::set_thread_error(value as u32)
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_ansi_string_to_utf8(str_ansi: *const gchar, length: gint) -> *mut gchar {
let ansi = unsafe { bytes_of(str_ansi as *const u8, length) };
match kernel::text_api() {
Some(api) => ansi_to_utf8(api, ansi),
None => handed_to_glib(ansi.iter().map(|&byte| byte as char).collect::<String>().as_bytes()),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_ansi_string_from_utf8(str_utf8: *const gchar) -> *mut gchar {
match kernel::text_api() {
Some(api) => ansi_from_utf8(api, str_utf8),
None => {
let utf8 = unsafe { core::ffi::CStr::from_ptr(str_utf8) }.to_str().unwrap();
let latin1: Vec<u8> =
utf8.chars().map(|c| if (c as u32) <= 0xff { c as u8 } else { b'?' }).collect();
handed_to_glib(&latin1)
}
}
}
unsafe fn bytes_of<'a>(str: *const u8, length: gint) -> &'a [u8] {
let length = if length < 0 {
unsafe { core::ffi::CStr::from_ptr(str as *const core::ffi::c_char) }.to_bytes().len()
} else {
length as usize
};
unsafe { core::slice::from_raw_parts(str, length) }
}
fn ansi_to_utf8(api: &TextApi, ansi: &[u8]) -> *mut gchar {
let count = unsafe {
(api.multi_byte_to_wide_char)(CP_ACP, 0, ansi.as_ptr(), ansi.len() as i32,
ptr::null_mut(), 0)
};
let mut wide = alloc::vec![0u16; count as usize];
unsafe {
(api.multi_byte_to_wide_char)(CP_ACP, 0, ansi.as_ptr(), ansi.len() as i32,
wide.as_mut_ptr(), count);
g_utf16_to_utf8(wide.as_ptr(), count as _, ptr::null_mut(), ptr::null_mut(),
ptr::null_mut())
}
}
fn ansi_from_utf8(api: &TextApi, utf8: *const gchar) -> *mut gchar {
unsafe {
let wide = g_utf8_to_utf16(utf8, -1, ptr::null_mut(), ptr::null_mut(), ptr::null_mut());
let size = (api.wide_char_to_multi_byte)(CP_ACP, 0, wide, -1, ptr::null_mut(), 0, 0, 0);
let ansi = g_malloc(size as gsize) as *mut u8;
(api.wide_char_to_multi_byte)(CP_ACP, 0, wide, -1, ansi, size, 0, 0);
g_free(wide as gpointer);
ansi as *mut gchar
}
}
fn handed_to_glib(bytes: &[u8]) -> *mut gchar {
unsafe {
let copy = g_malloc((bytes.len() + 1) as gsize) as *mut u8;
ptr::copy_nonoverlapping(bytes.as_ptr(), copy, bytes.len());
*copy.add(bytes.len()) = 0;
copy as *mut gchar
}
}
const CP_ACP: u32 = 0;
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_stream_ops() -> *const GumBareboneStreamOps {
if kernel::in_copy() { &STREAM_OPS } else { ptr::null() }
}
static STREAM_OPS: GumBareboneStreamOps = GumBareboneStreamOps {
poll: Some(kernel::poll_handles),
read: Some(read_handle),
write: Some(write_handle),
close: Some(close_handle),
};
unsafe extern "C" fn read_handle(handle: gpointer, buffer: gpointer, count: gsize,
error: *mut *mut GError) -> gssize {
let mut transferred = 0u32;
let succeeded = unsafe {
(kernel::file_api().read_file)(handle as usize, buffer as *mut u8, count as u32,
&mut transferred, 0)
};
if succeeded == 0 {
return failed(error);
}
transferred as gssize
}
unsafe extern "C" fn write_handle(handle: gpointer, buffer: gconstpointer, count: gsize,
error: *mut *mut GError) -> gssize {
let mut transferred = 0u32;
let succeeded = unsafe {
(kernel::file_api().write_file)(handle as usize, buffer as *const u8, count as u32,
&mut transferred, 0)
};
if succeeded == 0 {
return failed(error);
}
transferred as gssize
}
unsafe extern "C" fn close_handle(handle: gpointer, error: *mut *mut GError) -> gboolean {
let succeeded = unsafe { (kernel::file_api().close_handle)(handle as usize) };
if succeeded == 0 {
failed(error);
return 0;
}
1
}
fn failed(error: *mut *mut GError) -> gssize {
let code = kernel::thread_error();
let message = CString::new(describe_error(code)).unwrap();
unsafe {
g_set_error_literal(error, g_io_error_quark(), io_error_from_win32(code) as gint,
message.as_ptr())
};
-1
}
fn describe_error(code: u32) -> String {
let mut ansi = [0u8; 512];
let length = unsafe {
(kernel::file_api().format_message)(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, code, 0,
ansi.as_mut_ptr(), ansi.len() as u32, 0)
};
let utf8 = ansi_to_utf8(kernel::text_api().unwrap(), &ansi[..length as usize]);
let message = unsafe { core::ffi::CStr::from_ptr(utf8) }.to_str().unwrap().trim_end();
let described = String::from(message);
unsafe { g_free(utf8 as gpointer) };
described
}
fn io_error_from_win32(code: u32) -> u32 {
match code {
ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND => GIOErrorEnum_G_IO_ERROR_NOT_FOUND,
ERROR_ACCESS_DENIED => GIOErrorEnum_G_IO_ERROR_PERMISSION_DENIED,
ERROR_INVALID_HANDLE | ERROR_INVALID_PARAMETER => GIOErrorEnum_G_IO_ERROR_INVALID_ARGUMENT,
ERROR_BROKEN_PIPE => GIOErrorEnum_G_IO_ERROR_BROKEN_PIPE,
_ => GIOErrorEnum_G_IO_ERROR_FAILED,
}
}
const FORMAT_MESSAGE_IGNORE_INSERTS: u32 = 0x200;
const FORMAT_MESSAGE_FROM_SYSTEM: u32 = 0x1000;
const ERROR_FILE_NOT_FOUND: u32 = 2;
const ERROR_PATH_NOT_FOUND: u32 = 3;
const ERROR_ACCESS_DENIED: u32 = 5;
const ERROR_INVALID_HANDLE: u32 = 6;
const ERROR_INVALID_PARAMETER: u32 = 87;
const ERROR_BROKEN_PIPE: u32 = 109;
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_stack_size() -> crate::bindings::gsize {
if crate::on_js_thread() {
@@ -70,16 +285,23 @@ pub extern "C" fn gum_memory_dispose_writable_pages(_writable: gpointer, _n_page
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_query_protection(
address: gpointer,
pub extern "C" fn gum_memory_query_region(
address: gconstpointer,
range: *mut GumMemoryRange,
prot: *mut GumPageProtection,
) -> gboolean {
let protection = kernel::protection_at(address as usize);
if protection == 0 {
let Some(region) = kernel::region_at(address as usize).filter(|region| region.protection != 0)
else {
return 0;
}
};
unsafe { *prot = protection as GumPageProtection };
unsafe {
*range = GumMemoryRange {
base_address: region.base,
size: region.size as gsize,
};
*prot = region.protection as GumPageProtection;
}
1
}
@@ -130,26 +352,10 @@ pub extern "C" fn gum_memory_free(address: gpointer, size: gsize) -> gboolean {
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
#[cfg(any(feature = "win9x", feature = "winnt"))]
if crate::running_in_a_process() {
register_the_process_modules(registry);
return;
}
unsafe {
let modules = &*core::ptr::addr_of!(crate::MODULE_INFO);
for module_info in modules.iter() {
let path = format!("{}{}", kernel::MODULE_DIRECTORY, module_info.name);
let range = GumMemoryRange {
base_address: module_info.offset as u64,
size: module_info.size as gsize,
};
let module = gum::gum_native_module_new(&path, &module_info.version, &range);
gum_barebone_register_module(registry, module);
g_object_unref(module as gpointer);
}
} else {
register_the_kernel_modules(registry);
}
}
@@ -178,7 +384,7 @@ pub(crate) fn watch_the_loader() {
}
}
#[cfg(any(feature = "win9x", feature = "winnt"))]
#[cfg(feature = "winnt")]
pub(crate) fn forget_the_loader() {
let Some(entries) = kernel::loader_entry_points() else {
return;
@@ -202,7 +408,7 @@ pub(crate) fn loader_load() -> gpointer {
unsafe { LOADER_LOAD }
}
#[cfg(any(feature = "win9x", feature = "winnt"))]
#[cfg(feature = "win9x")]
pub(crate) fn loader_load_with_flags() -> gpointer {
unsafe { LOADER_LOAD_WITH_FLAGS }
}
@@ -298,19 +504,41 @@ static mut LOADER_UNLOAD: gpointer = ptr::null_mut();
fn register_the_process_modules(registry: *mut GumModuleRegistry) {
for module in kernel::enumerate_modules() {
#[cfg(any(feature = "win9x", feature = "winnt"))]
known_mut().insert(module.base);
let range = GumMemoryRange {
base_address: module.base,
size: module.size as gsize,
};
register_module(registry, &module.path, "", module.base, module.size);
}
}
unsafe {
let native = gum::gum_native_module_new(&module.path, "", &range);
gum_barebone_register_module(registry, native);
g_object_unref(native as gpointer);
}
fn register_the_kernel_modules(registry: *mut GumModuleRegistry) {
#[cfg(feature = "winnt")]
for module in crate::winnt::enumerate_kernel_modules() {
register_module(registry, &module.path, "", module.base, module.size);
}
#[cfg(not(feature = "winnt"))]
for module_info in unsafe { &*core::ptr::addr_of!(crate::MODULE_INFO) }.iter() {
let path = format!("{}{}", kernel::MODULE_DIRECTORY, module_info.name);
register_module(
registry,
&path,
&module_info.version,
module_info.offset as u64,
module_info.size as u64,
);
}
}
fn register_module(registry: *mut GumModuleRegistry, path: &str, version: &str, base: u64, size: u64) {
let range = GumMemoryRange {
base_address: base,
size: size as gsize,
};
unsafe {
let module = gum::gum_native_module_new(path, version, &range);
gum_barebone_register_module(registry, module);
g_object_unref(module as gpointer);
}
}
@@ -347,7 +575,7 @@ pub extern "C" fn _gum_process_enumerate_ranges(
pub extern "C" fn gum_barebone_on_thread_registry_activating(registry: *mut GumThreadRegistry) {
unsafe { THREAD_REGISTRY = registry };
kernel::enumerate_threads(&mut |thread| announce_thread(thread.id));
kernel::enumerate_threads(&mut |thread| announce_thread(thread.id), false);
if kernel::watches_threads() {
kernel::watch_threads(announce_thread, announce_thread_is_gone);
@@ -398,10 +626,6 @@ fn forget_the_threads() {
}
}
pub(crate) fn thread_appeared(id: u32) {
announce_thread(id);
}
pub(crate) fn thread_appeared_at(id: u32, routine: usize, parameter: usize) {
let registry = unsafe { THREAD_REGISTRY };
if registry.is_null() {
@@ -446,11 +670,13 @@ fn announce_thread_is_gone(id: u32) {
static mut THREAD_REGISTRY: *mut GumThreadRegistry = ptr::null_mut();
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_enumerate_threads(func: GumFoundThreadFunc, user_data: gpointer) {
pub extern "C" fn gum_barebone_enumerate_threads(func: GumFoundThreadFunc, user_data: gpointer,
flags: GumThreadFlags) {
let Some(emit) = func else {
return;
};
let wanted = (flags & GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT) != 0;
kernel::enumerate_threads(&mut |thread| {
let mut details: GumThreadDetails = unsafe { core::mem::zeroed() };
details.flags = 0;
@@ -462,13 +688,14 @@ pub extern "C" fn gum_barebone_enumerate_threads(func: GumFoundThreadFunc, user_
}
unsafe { emit(&details, user_data) };
});
}, wanted);
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_find_thread_by_id(thread_id: GumThreadId, _flags: GumThreadFlags)
pub extern "C" fn gum_barebone_find_thread_by_id(thread_id: GumThreadId, flags: GumThreadFlags)
-> *mut GumThreadDetails {
let Some(thread) = kernel::find_thread(thread_id as u32) else {
let wanted = (flags & GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT) != 0;
let Some(thread) = kernel::find_thread(thread_id as u32, wanted) else {
return ptr::null_mut();
};
@@ -577,6 +804,30 @@ pub(crate) fn cpu_state_from(context: &GumCpuContext) -> kernel::CpuState {
}
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn cpu_context_from(state: &kernel::CpuState) -> GumCpuContext {
GumCpuContext {
pc: state.pc,
sp: state.sp,
nzcv: state.nzcv,
x: state.x,
fp: state.fp,
lr: state.lr,
}
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn cpu_state_from(context: &GumCpuContext) -> kernel::CpuState {
kernel::CpuState {
pc: context.pc,
sp: context.sp,
nzcv: context.nzcv,
x: context.x,
fp: context.fp,
lr: context.lr,
}
}
pub(crate) fn fault_type_of(code: u32) -> crate::bindings::GumExceptionType {
use crate::bindings::*;
@@ -616,6 +867,14 @@ const INTEGER_OVERFLOW: u32 = 0xc000_0095;
const BREAKPOINT: u32 = 0x8000_0003;
const SINGLE_STEP: u32 = 0x8000_0004;
pub(crate) unsafe fn enumerate_imports_in_module(base: u64, callback: &mut FoundImportCallback) {
if crate::running_in_a_process() {
kernel::enumerate_imports(base as _, &mut |library, name, slot, address| {
callback(library as *const crate::bindings::gchar, name as *const crate::bindings::gchar, slot, address)
});
}
}
pub(crate) unsafe fn enumerate_exports_in_range(
start_address: u64,
end_address: u64,
@@ -101,6 +101,7 @@ impl Drop for Hostlink {
}
}
#[cfg(feature = "xnu-kext")]
pub fn a_turn_is_wanted() -> bool {
unsafe { frida_kmod_link_pending() }
}
@@ -115,5 +116,6 @@ unsafe extern "C" {
fn frida_kmod_link_close();
fn frida_kmod_link_send(data: *const c_void, size: usize) -> c_int;
fn frida_kmod_link_recv(data: *mut c_void, size: usize) -> isize;
#[cfg(feature = "xnu-kext")]
fn frida_kmod_link_pending() -> bool;
}
+441
View File
@@ -0,0 +1,441 @@
use core::cell::UnsafeCell;
use core::ffi::c_void;
use core::ptr;
use alloc::collections::VecDeque;
use alloc::vec::Vec;
use crate::kernel;
use crate::winnt::windows_fn;
pub struct Hostlink {
state: UnsafeCell<Inner>,
on_rx: Option<fn(&[u8])>,
}
struct Inner {
port: *mut c_void,
event: *mut c_void,
}
fn issue<F>(event: *mut c_void, request: F) -> Option<usize>
where
F: FnOnce(*mut c_void, *mut c_void) -> i32,
{
let mut status_block = [0usize; STATUS_BLOCK_WORDS];
unsafe {
(_ZwClearEvent)(event);
}
let status = request(event, status_block.as_mut_ptr() as *mut c_void);
if status == STATUS_PENDING {
unsafe {
(_ZwWaitForSingleObject)(event, 0, ptr::null());
}
}
if status < 0 || (status_block[STATUS_BLOCK_STATUS] as i32) < 0 {
return None;
}
Some(status_block[STATUS_BLOCK_COUNT])
}
fn create_event() -> Option<*mut c_void> {
let mut event: *mut c_void = ptr::null_mut();
let status = unsafe {
(_ZwCreateEvent)(&mut event, EVENT_ALL_ACCESS, ptr::null_mut(), SYNCHRONIZATION_EVENT, 0)
};
if status < 0 {
return None;
}
Some(event)
}
impl Hostlink {
pub fn init(on_rx: Option<fn(&[u8])>, wake_token: *const u8) -> Result<Self, ()> {
let port = open_first_serial_port()?;
let event = create_event().ok_or(())?;
let reader_event = create_event().ok_or(())?;
unsafe {
WAKE_TOKEN = wake_token;
READER_PORT = port;
READER_EVENT = reader_event;
}
kernel::spawn_thread(read_from_host, ptr::null_mut());
Ok(Self {
state: UnsafeCell::new(Inner { port, event }),
on_rx,
})
}
pub fn send(&self, payload: &[u8]) {
let s = unsafe { &*self.state.get() };
write_all(s.port, s.event, &(payload.len() as u32).to_le_bytes());
write_all(s.port, s.event, payload);
}
pub fn process(&self) {
loop {
let Some(frame) = take_frame() else {
return;
};
if let Some(on_rx) = self.on_rx {
on_rx(&frame);
}
}
}
pub fn shutdown(&self) {
let s = unsafe { &*self.state.get() };
unsafe {
(_ZwClose)(s.port);
(_ZwClose)(s.event);
}
}
}
unsafe extern "C" fn read_from_host(_parameter: *mut c_void, _wait_result: i32) {
let port = unsafe { READER_PORT };
let event = unsafe { READER_EVENT };
let mut length = [0u8; FRAME_LENGTH_SIZE];
loop {
if !read_exactly(port, event, &mut length) {
return;
}
let mut frame = alloc::vec![0u8; u32::from_le_bytes(length) as usize];
if !read_exactly(port, event, &mut frame) {
return;
}
put_frame(frame);
kernel::wake(unsafe { WAKE_TOKEN });
}
}
fn read_exactly(port: *mut c_void, event: *mut c_void, buffer: &mut [u8]) -> bool {
let mut read = 0;
while read != buffer.len() {
let mut offset = 0i64;
let moved = issue(event, |event, status_block| unsafe {
(_ZwReadFile)(
port,
event,
ptr::null_mut(),
ptr::null_mut(),
status_block,
buffer.as_mut_ptr().add(read),
(buffer.len() - read) as u32,
&mut offset,
ptr::null_mut(),
)
});
let Some(moved) = moved else {
return false;
};
if moved == 0 {
return false;
}
read += moved;
}
true
}
fn write_all(port: *mut c_void, event: *mut c_void, buffer: &[u8]) {
let mut written = 0;
while written != buffer.len() {
let mut offset = 0i64;
let moved = issue(event, |event, status_block| unsafe {
(_ZwWriteFile)(
port,
event,
ptr::null_mut(),
ptr::null_mut(),
status_block,
buffer.as_ptr().add(written),
(buffer.len() - written) as u32,
&mut offset,
ptr::null_mut(),
)
});
let Some(moved) = moved else {
return;
};
if moved == 0 {
return;
}
written += moved;
}
}
pub fn a_turn_is_wanted() -> bool {
A_TURN_IS_WANTED.load(Ordering::Acquire)
}
static A_TURN_IS_WANTED: AtomicBool = AtomicBool::new(false);
fn put_frame(frame: Vec<u8>) {
lock_frames();
unsafe {
(*ptr::addr_of_mut!(FRAMES)).push_back(frame);
}
unlock_frames();
A_TURN_IS_WANTED.store(true, Ordering::Release);
}
fn take_frame() -> Option<Vec<u8>> {
lock_frames();
let frame = unsafe { (*ptr::addr_of_mut!(FRAMES)).pop_front() };
let drained = unsafe { (*ptr::addr_of!(FRAMES)).is_empty() };
unlock_frames();
if drained {
A_TURN_IS_WANTED.store(false, Ordering::Release);
}
frame
}
fn lock_frames() {
while FRAMES_LOCK
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
kernel::yield_now();
}
}
fn unlock_frames() {
FRAMES_LOCK.store(0, Ordering::Release);
}
static mut FRAMES: VecDeque<Vec<u8>> = VecDeque::new();
static FRAMES_LOCK: AtomicU32 = AtomicU32::new(0);
static mut WAKE_TOKEN: *const u8 = ptr::null();
static mut READER_PORT: *mut c_void = ptr::null_mut();
static mut READER_EVENT: *mut c_void = ptr::null_mut();
fn open_first_serial_port() -> Result<*mut c_void, ()> {
let ports = open_key(SERIAL_PORT_KEY)?;
let mut name = [0u8; VALUE_NAME_SIZE];
let mut size = 0u32;
let status = unsafe {
(_ZwEnumerateValueKey)(ports, 0, VALUE_NAME_INFORMATION, name.as_mut_ptr(),
name.len() as u32, &mut size)
};
unsafe {
(_ZwClose)(ports);
}
if status < 0 {
return Err(());
}
let length = u32::from_le_bytes(name[VALUE_NAME_LENGTH..VALUE_NAME_LENGTH + 4]
.try_into()
.unwrap()) as usize;
open_device(&name[VALUE_NAME_OFFSET..VALUE_NAME_OFFSET + length])
}
fn open_key(path: &[u16]) -> Result<*mut c_void, ()> {
let mut handle: *mut c_void = ptr::null_mut();
let mut name = unicode_string(path);
let mut attributes = object_attributes(&mut name);
let status =
unsafe { (_ZwOpenKey)(&mut handle, KEY_READ, attributes.as_mut_ptr() as *mut c_void) };
if status < 0 {
return Err(());
}
Ok(handle)
}
fn open_device(path: &[u8]) -> Result<*mut c_void, ()> {
let mut handle: *mut c_void = ptr::null_mut();
let mut name = UnicodeString {
length: path.len() as u16,
maximum_length: path.len() as u16,
buffer: path.as_ptr() as *const u16,
};
let mut attributes = object_attributes(&mut name);
let mut status_block = [0usize; STATUS_BLOCK_WORDS];
let status = unsafe {
(_ZwCreateFile)(
&mut handle,
GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE,
attributes.as_mut_ptr() as *mut c_void,
status_block.as_mut_ptr() as *mut c_void,
ptr::null_mut(),
0,
0,
FILE_OPEN,
0,
ptr::null_mut(),
0,
)
};
if status < 0 {
return Err(());
}
configure_port(handle);
Ok(handle)
}
fn configure_port(port: *mut c_void) {
let mut baud_rate = [0u32; 1];
baud_rate[0] = BAUD_RATE;
control_port(port, SET_BAUD_RATE, baud_rate.as_ptr() as *const u8,
core::mem::size_of_val(&baud_rate) as u32);
let line_control = [ONE_STOP_BIT, NO_PARITY, DATA_BITS];
control_port(port, SET_LINE_CONTROL, line_control.as_ptr(),
core::mem::size_of_val(&line_control) as u32);
let hand_flow = [0u32; HAND_FLOW_WORDS];
control_port(port, SET_HAND_FLOW, hand_flow.as_ptr() as *const u8,
core::mem::size_of_val(&hand_flow) as u32);
control_port(port, SET_DTR, core::ptr::null(), 0);
control_port(port, SET_RTS, core::ptr::null(), 0);
let mut timeouts = [0u32; TIMEOUT_WORDS];
timeouts[READ_INTERVAL_TIMEOUT] = FOREVER;
timeouts[READ_TOTAL_TIMEOUT_MULTIPLIER] = FOREVER;
timeouts[READ_TOTAL_TIMEOUT_CONSTANT] = ALMOST_FOREVER;
control_port(port, SET_TIMEOUTS, timeouts.as_ptr() as *const u8,
core::mem::size_of_val(&timeouts) as u32);
}
fn control_port(port: *mut c_void, code: u32, input: *const u8, length: u32) {
let Some(event) = create_event() else {
return;
};
issue(event, |event, status_block| unsafe {
(_ZwDeviceIoControlFile)(
port,
event,
ptr::null_mut(),
ptr::null_mut(),
status_block,
code,
input,
length,
ptr::null_mut(),
0,
)
});
unsafe {
(_ZwClose)(event);
}
}
#[repr(C)]
struct UnicodeString {
length: u16,
maximum_length: u16,
buffer: *const u16,
}
fn unicode_string(text: &[u16]) -> UnicodeString {
let bytes = (text.len() * 2) as u16;
UnicodeString {
length: bytes,
maximum_length: bytes,
buffer: text.as_ptr(),
}
}
fn object_attributes(name: &mut UnicodeString) -> [usize; OBJECT_ATTRIBUTES_WORDS] {
let mut attributes = [0usize; OBJECT_ATTRIBUTES_WORDS];
attributes[0] = core::mem::size_of::<[usize; OBJECT_ATTRIBUTES_WORDS]>();
attributes[2] = name as *mut UnicodeString as usize;
attributes[3] = OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE;
attributes
}
const FRAME_LENGTH_SIZE: usize = 4;
const STATUS_BLOCK_WORDS: usize = 2;
const STATUS_BLOCK_COUNT: usize = 1;
const STATUS_BLOCK_STATUS: usize = 0;
const OBJECT_ATTRIBUTES_WORDS: usize = 6;
const SERIAL_PORT_KEY: &[u16] = &[
0x5c, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x52, 0x59, 0x5c, 0x4d, 0x41, 0x43, 0x48, 0x49, 0x4e,
0x45, 0x5c, 0x48, 0x41, 0x52, 0x44, 0x57, 0x41, 0x52, 0x45, 0x5c, 0x44, 0x45, 0x56, 0x49, 0x43,
0x45, 0x4d, 0x41, 0x50, 0x5c, 0x53, 0x45, 0x52, 0x49, 0x41, 0x4c, 0x43, 0x4f, 0x4d, 0x4d,
];
const VALUE_NAME_INFORMATION: u32 = 0;
const VALUE_NAME_SIZE: usize = 512;
const VALUE_NAME_LENGTH: usize = 0x08;
const VALUE_NAME_OFFSET: usize = 0x0c;
const SET_BAUD_RATE: u32 = 0x001b_0004;
const SET_LINE_CONTROL: u32 = 0x001b_000c;
const SET_TIMEOUTS: u32 = 0x001b_001c;
const SET_DTR: u32 = 0x001b_0024;
const SET_RTS: u32 = 0x001b_0030;
const SET_HAND_FLOW: u32 = 0x001b_0064;
const BAUD_RATE: u32 = 921_600;
const DATA_BITS: u8 = 8;
const NO_PARITY: u8 = 0;
const ONE_STOP_BIT: u8 = 0;
const HAND_FLOW_WORDS: usize = 4;
const TIMEOUT_WORDS: usize = 5;
const READ_INTERVAL_TIMEOUT: usize = 0;
const READ_TOTAL_TIMEOUT_MULTIPLIER: usize = 1;
const READ_TOTAL_TIMEOUT_CONSTANT: usize = 2;
const FOREVER: u32 = u32::MAX;
const ALMOST_FOREVER: u32 = u32::MAX - 1;
const STATUS_PENDING: i32 = 0x103;
const SYNCHRONIZATION_EVENT: u32 = 1;
const EVENT_ALL_ACCESS: u32 = 0x1f_0003;
const KEY_READ: u32 = 0x2_0019;
const GENERIC_READ: u32 = 0x8000_0000;
const GENERIC_WRITE: u32 = 0x4000_0000;
const SYNCHRONIZE: u32 = 0x0010_0000;
const FILE_OPEN: u32 = 1;
const OBJ_CASE_INSENSITIVE: usize = 0x40;
const OBJ_KERNEL_HANDLE: usize = 0x200;
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
unsafe extern "C" {
static _ZwOpenKey: windows_fn!(*mut *mut c_void, u32, *mut c_void => i32);
static _ZwEnumerateValueKey: windows_fn!(
*mut c_void, u32, u32, *mut u8, u32, *mut u32 => i32);
static _ZwCreateFile: windows_fn!(
*mut *mut c_void, u32, *mut c_void, *mut c_void, *mut i64, u32, u32, u32, u32,
*mut c_void, u32 => i32);
static _ZwReadFile: windows_fn!(
*mut c_void, *mut c_void, *mut c_void, *mut c_void, *mut c_void, *mut u8, u32, *mut i64,
*mut u32 => i32);
static _ZwWriteFile: windows_fn!(
*mut c_void, *mut c_void, *mut c_void, *mut c_void, *mut c_void, *const u8, u32, *mut i64,
*mut u32 => i32);
static _ZwDeviceIoControlFile: windows_fn!(
*mut c_void, *mut c_void, *mut c_void, *mut c_void, *mut c_void, u32, *const u8, u32,
*mut u8, u32 => i32);
static _ZwClose: windows_fn!(*mut c_void => i32);
static _ZwCreateEvent: windows_fn!(*mut *mut c_void, u32, *mut c_void, u32, u8 => i32);
static _ZwClearEvent: windows_fn!(*mut c_void => i32);
static _ZwWaitForSingleObject: windows_fn!(*mut c_void, u8, *const i64 => i32);
static _ObReferenceObjectByHandle: windows_fn!(
*mut c_void, u32, *mut c_void, u8, *mut *mut c_void, *mut c_void => i32);
static _KeWaitForSingleObject: windows_fn!(*mut c_void, u32, u32, u8, *const i64 => i32);
}
+89 -15
View File
@@ -51,7 +51,9 @@ const F_MULTIPORT: u64 = 1u64 << 1;
const INT_VRING: u32 = 1;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const PCI_CONFIG_ADDRESS: u16 = 0xcf8;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const PCI_CONFIG_DATA: u16 = 0xcfc;
const PCI_VENDOR_VIRTIO: u16 = 0x1af4;
@@ -62,14 +64,18 @@ const PCI_COMMAND: u8 = 0x04;
const PCI_CAP_LIST_POINTER: u8 = 0x34;
const PCI_INTERRUPT_LINE: u8 = 0x3c;
const PCI_BASE_ADDRESS_0: u8 = 0x10;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const PCI_INTERRUPT_PIN: u8 = 0x3d;
const PCI_COMMAND_INTX_DISABLE: u32 = 0x400;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const ISA_BRIDGE_DEVFN: u8 = 0x08;
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
const ECAM_DEVFN_SHIFT: usize = 12;
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
const ECAM_BUS_SIZE: u64 = 256 * 4096;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const PIRQ_ROUTE: u8 = 0x60;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const PIRQ_DISABLED: u32 = 1 << 7;
const PCI_COMMAND_MEMORY: u32 = 1 << 1;
@@ -185,6 +191,8 @@ struct Vq {
}
impl Vq {
fn new(regs: &Regs, sel: u16, size: u16) -> Self {
let size = size.min(regs.queue_max(sel));
let d = dma_page_alloc();
let a = dma_page_alloc();
let u = dma_page_alloc();
@@ -475,7 +483,7 @@ impl Hostlink {
let s = unsafe { &mut *self.state.get() };
A_TURN_IS_WANTED.store(false, core::sync::atomic::Ordering::Release);
s.regs.isr_ack();
s.regs.interrupt_is_ours();
self.ctrl_complete();
self.ctrl_prime_rx(QSZ as usize);
@@ -848,15 +856,24 @@ impl Hostlink {
}
extern "C" fn isr_wake(token: *mut c_void, _refcon: *mut c_void, _nub: *mut c_void, _src: i32) {
unsafe {
if let Some(regs) = ISR_REGS {
regs.isr_ack();
let up = unsafe {
match ISR_REGS {
Some(regs) => {
regs.interrupt_is_ours();
true
}
None => false,
}
};
if !up {
return;
}
A_TURN_IS_WANTED.store(true, core::sync::atomic::Ordering::Release);
crate::nudge_the_loop(token as *const u8);
}
#[cfg(not(feature = "xnu-core"))]
pub fn a_turn_is_wanted() -> bool {
A_TURN_IS_WANTED.load(core::sync::atomic::Ordering::Acquire)
}
@@ -893,9 +910,7 @@ impl Regs {
while r8(p.common, COMMON_STATUS) != 0 {
left -= 1;
if left == 0 {
unsafe {
kernel::log("virtio: reset not taken\n");
}
kernel::log("virtio: reset not taken\n");
break;
}
}
@@ -944,12 +959,25 @@ impl Regs {
}
}
fn queue_max(&self, sel: u16) -> u16 {
match self {
Regs::Mmio(base) => {
let base = *base;
w32(base, QSEL, sel as u32);
r32(base, QNUM_MAX) as u16
}
Regs::Pci(p) => {
w16(p.common, COMMON_QSEL, sel);
r16(p.common, COMMON_QNUM)
}
}
}
fn queue_prepare(&self, sel: u16, size: u16, desc: u64, avail: u64, used: u64) -> u16 {
match self {
Regs::Mmio(base) => {
let base = *base;
w32(base, QSEL, sel as u32);
debug_assert!(r32(base, QNUM_MAX) as u16 >= size);
w32(base, QNUM, size as u32);
w64(base, QDESC_LO, QDESC_HI, desc);
w64(base, QAVAIL_LO, QAVAIL_HI, avail);
@@ -959,7 +987,6 @@ impl Regs {
}
Regs::Pci(p) => {
w16(p.common, COMMON_QSEL, sel);
debug_assert!(r16(p.common, COMMON_QNUM) >= size);
w16(p.common, COMMON_QNUM, size);
w64(p.common, COMMON_QDESC, COMMON_QDESC + 4, desc);
w64(p.common, COMMON_QAVAIL, COMMON_QAVAIL + 4, avail);
@@ -984,21 +1011,22 @@ impl Regs {
}
}
fn isr_ack(&self) -> u8 {
fn interrupt_is_ours(&self) -> bool {
match self {
Regs::Mmio(base) => {
if (r32(*base, ISR) & INT_VRING) != 0 {
w32(*base, ISR_ACK, INT_VRING);
}
0
true
}
Regs::Pci(p) => r8(p.isr, 0),
Regs::Pci(p) => r8(p.isr, 0) != 0,
}
}
}
#[derive(Copy, Clone)]
struct Bus {
#[cfg(any(target_arch = "x86", target_arch = "x86_64", feature = "linux-injected"))]
number: u8,
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
config: *mut u8,
@@ -1013,7 +1041,11 @@ impl Bus {
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
fn first(ecam: u64) -> Self {
let config = kernel::map_io(ecam, ECAM_BUS_SIZE) as *mut u8;
Bus { number: 0, config }
Bus {
#[cfg(feature = "linux-injected")]
number: 0,
config,
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
@@ -1024,7 +1056,9 @@ impl Bus {
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
fn function(&self, devfn: u8) -> PciDevice {
PciDevice {
#[cfg(feature = "linux-injected")]
bus: self.number,
#[cfg(feature = "linux-injected")]
devfn,
config: unsafe { self.config.add((devfn as usize) << ECAM_DEVFN_SHIFT) },
}
@@ -1033,7 +1067,9 @@ impl Bus {
#[derive(Copy, Clone)]
struct PciDevice {
#[cfg(any(target_arch = "x86", target_arch = "x86_64", feature = "linux-injected"))]
bus: u8,
#[cfg(any(target_arch = "x86", target_arch = "x86_64", feature = "linux-injected"))]
devfn: u8,
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
config: *mut u8,
@@ -1113,7 +1149,7 @@ impl PciDevice {
#[cfg(feature = "linux-injected")]
fn irq_line(&self) -> Option<u32> {
kernel::pci_interrupt(self.bus, self.devfn)
self.line_in_config().or_else(|| kernel::pci_interrupt(self.bus, self.devfn))
}
#[cfg(not(feature = "linux-injected"))]
@@ -1149,6 +1185,10 @@ impl PciDevice {
// Point the link at the interrupt in the config space of the device.
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn route_interrupt_line(&self) {
let Some(line) = self.interrupt_line_to_use() else {
return;
};
let link = ((self.read_config_byte(PCI_INTERRUPT_PIN) - 1) + (self.devfn >> 3) - 1) & 3;
let router = PciDevice {
bus: 0,
@@ -1157,9 +1197,36 @@ impl PciDevice {
let offset = PIRQ_ROUTE + link;
let shift = (offset & 3) * 8;
let route = (self.irq_line().unwrap() & !PIRQ_DISABLED) << shift;
let route = (line & !PIRQ_DISABLED) << shift;
let others = router.read_config(offset) & !(0xff << shift);
router.write_config(offset, others | route);
self.write_config_byte(PCI_INTERRUPT_LINE, line as u8);
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn interrupt_line_to_use(&self) -> Option<u32> {
if let Some(line) = self.line_in_config() {
return Some(line);
}
let router = PciDevice {
bus: 0,
devfn: ISA_BRIDGE_DEVFN,
};
let routes = router.read_config(PIRQ_ROUTE);
(0..4)
.map(|link| (routes >> (link * 8)) & 0xff)
.find(|candidate| (candidate & PIRQ_DISABLED) == 0)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64", feature = "linux-injected"))]
fn line_in_config(&self) -> Option<u32> {
match self.read_config_byte(PCI_INTERRUPT_LINE) {
0 => None,
line => Some(line as u32),
}
}
fn map_bar_region(&self, bar: u8, offset: u32, length: u32) -> *mut u8 {
@@ -1180,6 +1247,13 @@ impl PciDevice {
(self.read_config(offset) >> ((offset & 3) * 8)) as u8
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn write_config_byte(&self, offset: u8, value: u8) {
let shift = (offset & 3) * 8;
let others = self.read_config(offset) & !(0xff << shift);
self.write_config(offset, others | ((value as u32) << shift));
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn read_config(&self, offset: u8) -> u32 {
outl(PCI_CONFIG_ADDRESS, self.config_address(offset));
-2
View File
@@ -8,8 +8,6 @@ use core::sync::atomic::{AtomicU32, Ordering};
use alloc::vec::Vec;
use crate::kernel;
const AF_VSOCK: c_int = 40;
const SOCK_STREAM: c_int = 1;
const VMADDR_CID_HOST: u32 = 2;
+2 -2
View File
@@ -87,13 +87,13 @@ fn write_identity(company: &[u8], program: &[u8], into: &mut [u8]) -> usize {
};
let mut written = 0;
let mut put = |byte: u8, into: &mut [u8], written: &mut usize| {
let put = |byte: u8, into: &mut [u8], written: &mut usize| {
if *written != into.len() {
into[*written] = byte;
*written += 1;
}
};
let mut put_word = |word: &[u8], into: &mut [u8], written: &mut usize| {
let put_word = |word: &[u8], into: &mut [u8], written: &mut usize| {
for byte in word {
if byte.is_ascii_alphanumeric() {
put(byte.to_ascii_lowercase(), into, written);
+7
View File
@@ -37,6 +37,7 @@ pub type ThreadEntry = unsafe extern "C" fn(parameter: *mut c_void, wait_result:
pub struct ThreadInfo {
pub id: u32,
pub state: Option<crate::bindings::GumThreadState>,
pub cpu_state: Option<CpuState>,
}
@@ -98,6 +99,12 @@ pub struct CpuState {
pub eax: u32,
}
pub struct MemoryRegion {
pub base: u64,
pub size: u64,
pub protection: u32,
}
// What the host looked up before the agent started, for the kernels that say nothing about
// their own layout and leave nowhere for the agent to read it.
pub fn take_note_of(what: &str, number: u64) {
+353 -64
View File
@@ -25,19 +25,12 @@ use core::ptr;
use core::sync::atomic::{AtomicU32, Ordering};
use bindings::{
GAsyncResult, GBytes, GError, GMainContext, GObject, GSource, GSourceFunc, GSourceFuncs,
GVariant, GumMemoryRange, gboolean, g_main_context_acquire, g_main_context_check,
g_main_context_dispatch, g_main_context_prepare, g_main_context_query, g_main_context_release,
g_main_context_wakeup,
g_source_attach, g_source_new, g_source_unref,
GAsyncResult, GBytes, GError, GMainContext, GObject, GVariant, GumMemoryRange,
GumScript, GumScriptBackend, g_error_free, g_free, g_main_context_iteration,
g_main_context_push_thread_default, g_memdup2, g_object_unref, g_variant_check_format_string,
g_variant_get, g_variant_get_boolean, g_variant_get_child_value, g_variant_get_data,
g_variant_get_size, g_variant_get_string, g_variant_get_strv,
g_variant_get, g_variant_get_data, g_variant_get_size, g_variant_get_string,
g_variant_get_uint32, g_variant_new, g_variant_new_from_data, g_variant_new_string,
g_variant_new_tuple, g_variant_new_uint32, g_variant_type_free, g_variant_type_new,
g_variant_builder_add, g_variant_builder_add_value, g_variant_builder_close,
g_variant_builder_end, g_variant_builder_new, g_variant_builder_open,
g_variant_new_fixed_array, g_variant_get_fixed_array, g_bytes_new, g_bytes_get_data,
g_bytes_unref, GVariantType,
g_variant_unref, gchar, gpointer, gsize, gum_script_backend_create,
@@ -47,12 +40,20 @@ use bindings::{
gum_script_scheduler_get_js_context, gum_script_set_message_handler, gum_script_unload,
gum_script_unload_finish, gum_stalker_exclude,
};
#[cfg(any(feature = "blob", feature = "xnu-core"))]
use bindings::{
GSource, GSourceFunc, GSourceFuncs, gboolean, g_main_context_wakeup, g_source_attach,
g_source_new, g_source_unref, g_variant_builder_add, g_variant_builder_add_value,
g_variant_builder_close, g_variant_builder_end, g_variant_builder_new, g_variant_builder_open,
g_variant_get_boolean, g_variant_get_child_value,
};
mod ffi;
mod glib;
mod gthread;
mod gum;
mod libc;
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected", feature = "xnu-core"))]
mod ring;
pub mod kernel;
@@ -91,6 +92,8 @@ mod winnt_user;
mod gum_injected;
#[cfg(feature = "blob")]
mod hostlink_virtio;
#[cfg(feature = "winnt")]
mod hostlink_serial;
#[cfg(feature = "xnu-core")]
mod hostlink_vsock;
#[cfg(feature = "xnu-core")]
@@ -141,29 +144,32 @@ mod bindings {
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum FridaCommand {
CreateScript = 1,
LoadScript = 2,
DestroyScript = 3,
PostScriptMessage = 4,
RemapWritablePages = 5,
MemoryProtect = 6,
PatchCode = 7,
EnumerateProcesses = 8,
InjectIntoProcess = 9,
AllocateShared = 10,
DetachFromProcess = 11,
PlaceAgentInProcess = 12,
StartAgentInProcess = 13,
SpawnProcess = 14,
ResumeProcess = 15,
Stop = 16,
GateSpawns = 17,
EnumerateApplications = 18,
EnumerateShortcuts = 19,
EnumerateApplications = 1,
EnumerateShortcuts = 2,
EnumerateProcesses = 3,
GateSpawns = 4,
ArmNamedSpawn = 5,
DisarmNamedSpawn = 6,
SpawnProcess = 7,
ResumeProcess = 8,
Stop = 9,
InjectIntoProcess = 10,
PlaceAgentInProcess = 11,
StartAgentInProcess = 12,
AllocateShared = 13,
RemapWritablePages = 14,
MemoryProtect = 15,
PatchCode = 16,
DetachFromProcess = 17,
CreateScript = 18,
DestroyScript = 19,
LoadScript = 20,
PostScriptMessage = 21,
Reply = 128,
ScriptMessage = 129,
SpawnAdded = 130,
Ready = 131,
}
impl core::fmt::Display for FridaCommand {
@@ -173,6 +179,7 @@ impl core::fmt::Display for FridaCommand {
FridaCommand::EnumerateApplications => write!(f, "EnumerateApplications"),
FridaCommand::EnumerateShortcuts => write!(f, "EnumerateShortcuts"),
FridaCommand::SpawnAdded => write!(f, "SpawnAdded"),
FridaCommand::Ready => write!(f, "Ready"),
FridaCommand::CreateScript => write!(f, "CreateScript"),
FridaCommand::LoadScript => write!(f, "LoadScript"),
FridaCommand::DestroyScript => write!(f, "DestroyScript"),
@@ -180,6 +187,8 @@ impl core::fmt::Display for FridaCommand {
FridaCommand::RemapWritablePages => write!(f, "RemapWritablePages"),
FridaCommand::MemoryProtect => write!(f, "MemoryProtect"),
FridaCommand::PatchCode => write!(f, "PatchCode"),
FridaCommand::ArmNamedSpawn => write!(f, "ArmNamedSpawn"),
FridaCommand::DisarmNamedSpawn => write!(f, "DisarmNamedSpawn"),
FridaCommand::EnumerateProcesses => write!(f, "EnumerateProcesses"),
FridaCommand::InjectIntoProcess => write!(f, "InjectIntoProcess"),
FridaCommand::AllocateShared => write!(f, "AllocateShared"),
@@ -285,9 +294,6 @@ mod entrypoint_blob {
unsafe extern "C" fn worker(_parameter: *mut c_void, _wait_result: i32) {
unsafe {
#[cfg(not(feature = "linux-injected"))]
kernel::install_fault_reporter();
crate::run_constructors();
init_gum();
@@ -298,11 +304,13 @@ mod entrypoint_blob {
SYMBOL_TABLE = symbol_table;
OWN_RANGE = own_range;
kernel::install_fault_reporter();
#[cfg(feature = "winnt")]
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
kernel::disarm_patchguard();
#[cfg(feature = "linux-injected")]
{
kernel::install_fault_reporter();
crate::gum_btf::publish();
}
crate::gum_btf::publish();
let wake_token = ptr::addr_of_mut!(glib::WAKEUP_TOKEN) as *const u8;
// Install this before the loop waits the first time, thus a copy placed in a process later
@@ -324,14 +332,30 @@ mod entrypoint_blob {
hostlink_virtio::Hostlink::init_pci(ecam, Some(on_frame_from_host), wake_token)
.unwrap(),
),
#[cfg(feature = "linux-injected")]
TransportConfig::PipeVsock { path } => Transport::PipeVsock(
linux::hostlink_pipe_vsock::Hostlink::init(&path, Some(on_frame_from_host), wake_token)
.unwrap(),
),
#[cfg(feature = "linux-injected")]
TransportConfig::PipeGoldfish { path } => Transport::PipeGoldfish(
linux::hostlink_pipe_goldfish::Hostlink::init(&path, Some(on_frame_from_host), wake_token)
.unwrap(),
),
#[cfg(feature = "xnu-core")]
TransportConfig::Vsock { host_port } => Transport::Vsock(
hostlink_vsock::Hostlink::init(host_port, Some(on_frame_from_host), wake_token)
.unwrap(),
),
#[cfg(feature = "winnt")]
TransportConfig::Serial => Transport::Serial(
hostlink_serial::Hostlink::init(Some(on_frame_from_host), wake_token).unwrap(),
),
};
transport_set(transport);
tell_the_host_we_are_listening();
let context = adopt_js_context();
run_main_loop(context);
@@ -419,6 +443,38 @@ mod entrypoint_blob {
#[cfg(not(any(feature = "win9x", feature = "winnt", feature = "linux-injected")))]
{ let _ = ecam; panic!("virtio-pci is not this kernel's") }
}
3 => {
#[cfg(feature = "winnt")]
{ TransportConfig::Serial }
#[cfg(not(feature = "winnt"))]
{ panic!("serial is NT's") }
}
4 => {
#[cfg(feature = "linux-injected")]
{
let raw = g_variant_get_string(transport_cfg_inner, ptr::null_mut());
let path = core::ffi::CStr::from_ptr(raw as *const core::ffi::c_char)
.to_str()
.unwrap_or("")
.into();
TransportConfig::PipeVsock { path }
}
#[cfg(not(feature = "linux-injected"))]
{ panic!("pipe-vsock is linux-injected's") }
}
5 => {
#[cfg(feature = "linux-injected")]
{
let raw = g_variant_get_string(transport_cfg_inner, ptr::null_mut());
let path = core::ffi::CStr::from_ptr(raw as *const core::ffi::c_char)
.to_str()
.unwrap_or("")
.into();
TransportConfig::PipeGoldfish { path }
}
#[cfg(not(feature = "linux-injected"))]
{ panic!("pipe-goldfish is linux-injected's") }
}
_ => panic!("Unsupported transport kind: {}", transport_kind),
};
g_variant_unref(transport_cfg_inner);
@@ -677,10 +733,6 @@ mod entrypoint_linux {
STOP_REQUESTED.load(Ordering::Acquire)
}
fn start_worker() {
kernel::spawn_thread(worker, 12345usize as *mut c_void);
}
unsafe extern "C" fn worker(_parameter: *mut c_void, _wait_result: i32) {
unsafe {
kernel::log("frida: worker entry\n\0");
@@ -722,8 +774,14 @@ pub use entrypoint_blob::_start;
pub enum Transport {
#[cfg(feature = "blob")]
Virtio(hostlink_virtio::Hostlink),
#[cfg(feature = "winnt")]
Serial(hostlink_serial::Hostlink),
#[cfg(feature = "xnu-core")]
Vsock(hostlink_vsock::Hostlink),
#[cfg(feature = "linux-injected")]
PipeVsock(linux::hostlink_pipe_vsock::Hostlink),
#[cfg(feature = "linux-injected")]
PipeGoldfish(linux::hostlink_pipe_goldfish::Hostlink),
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
CharDevice(hostlink_chardev::Hostlink),
}
@@ -733,8 +791,14 @@ impl Transport {
match self {
#[cfg(feature = "blob")]
Transport::Virtio(h) => h.send(payload),
#[cfg(feature = "winnt")]
Transport::Serial(h) => h.send(payload),
#[cfg(feature = "xnu-core")]
Transport::Vsock(h) => h.send(payload),
#[cfg(feature = "linux-injected")]
Transport::PipeVsock(h) => h.send(payload),
#[cfg(feature = "linux-injected")]
Transport::PipeGoldfish(h) => h.send(payload),
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
Transport::CharDevice(h) => h.send(payload),
}
@@ -744,8 +808,14 @@ impl Transport {
match self {
#[cfg(feature = "blob")]
Transport::Virtio(h) => h.shutdown(),
#[cfg(feature = "winnt")]
Transport::Serial(h) => h.shutdown(),
#[cfg(feature = "xnu-core")]
Transport::Vsock(_) => {}
#[cfg(feature = "linux-injected")]
Transport::PipeVsock(h) => h.shutdown(),
#[cfg(feature = "linux-injected")]
Transport::PipeGoldfish(h) => h.shutdown(),
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
Transport::CharDevice(_) => {}
}
@@ -755,8 +825,14 @@ impl Transport {
match self {
#[cfg(feature = "blob")]
Transport::Virtio(h) => h.process(),
#[cfg(feature = "winnt")]
Transport::Serial(h) => h.process(),
#[cfg(feature = "xnu-core")]
Transport::Vsock(h) => h.process(),
#[cfg(feature = "linux-injected")]
Transport::PipeVsock(h) => h.process(),
#[cfg(feature = "linux-injected")]
Transport::PipeGoldfish(h) => h.process(),
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
Transport::CharDevice(h) => h.process(),
}
@@ -765,7 +841,7 @@ impl Transport {
// Configuration space is reached through I/O ports where there are any, and only a machine
// without them is told where it is mapped instead.
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
#[cfg(all(feature = "blob", any(target_arch = "aarch64", target_arch = "arm")))]
unsafe fn where_configuration_space_is_mapped(transport: *mut GVariant) -> u64 {
use crate::bindings::{g_variant_get_child_value, g_variant_get_uint64, g_variant_unref};
@@ -778,7 +854,7 @@ unsafe fn where_configuration_space_is_mapped(transport: *mut GVariant) -> u64 {
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[cfg(all(feature = "blob", any(target_arch = "x86", target_arch = "x86_64")))]
unsafe fn where_configuration_space_is_mapped(_transport: *mut GVariant) -> u64 {
0
}
@@ -786,10 +862,16 @@ unsafe fn where_configuration_space_is_mapped(_transport: *mut GVariant) -> u64
#[cfg(feature = "blob")]
pub enum TransportConfig {
Virtio { mmio: u64, irq: u32 },
#[cfg(feature = "winnt")]
Serial,
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected"))]
VirtioPci { ecam: u64 },
#[cfg(feature = "xnu-core")]
Vsock { host_port: u32 },
#[cfg(feature = "linux-injected")]
PipeVsock { path: alloc::string::String },
#[cfg(feature = "linux-injected")]
PipeGoldfish { path: alloc::string::String },
}
static mut TRANSPORT_DRIVER: *mut Transport = core::ptr::null_mut();
@@ -802,6 +884,25 @@ fn transport_set(driver: Transport) {
}
}
#[cfg(feature = "blob")]
fn tell_the_host_we_are_listening() {
unsafe {
let message = g_variant_new(
c"(yquv)".as_ptr(),
FridaCommand::Ready as u8 as u32,
0u32,
0u32,
g_variant_new(c"()".as_ptr()),
);
if let Some(serialized) = serialize_message(message) {
send_frame(&serialized);
}
g_variant_unref(message);
}
}
#[inline(always)]
fn send_frame(frame: &[u8]) {
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected", feature = "xnu-core"))]
@@ -840,10 +941,16 @@ static mut SCRIPTS: BTreeMap<u32, *mut GumScript> = BTreeMap::new();
static NEXT_SCRIPT_ID: AtomicU32 = AtomicU32::new(1);
// A copy of the agent runs the same code, but it cannot share what gets written to. Thus keep
// the writable half as it is before anything writes to it.
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected", feature = "xnu-core"))]
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected", feature = "xnu"))]
pub(crate) unsafe fn preserve_writable_half() {
let size = writable_half_size();
let pristine = kernel::alloc(size);
// A few-hundred-KiB kmalloc needs an order-9 contiguous block, which a fragmented guest can
// refuse; the pristine copy only feeds process-copies, so proceed without it rather than
// faulting into a NULL destination.
if pristine.is_null() {
return;
}
unsafe {
ptr::copy_nonoverlapping(writable_half_start() as *const u8, pristine, size);
PRISTINE_WRITABLE_HALF = pristine;
@@ -908,7 +1015,6 @@ pub(crate) fn writable_half_start() -> usize {
unsafe { frida_agent_private_start }
}
#[cfg(all(any(feature = "win9x", feature = "winnt", feature = "linux-injected",
feature = "xnu-core"), not(feature = "xnu-kext")))]
pub(crate) fn writable_half_start() -> usize {
@@ -928,9 +1034,11 @@ fn writable_half_size() -> usize {
// A relocation names the slot first, and the rest of it says the same thing as the value that
// the slot already holds.
#[cfg(any(target_arch = "x86", target_arch = "arm"))]
#[cfg(all(any(feature = "win9x", feature = "winnt", feature = "linux-injected",
feature = "xnu-core"), any(target_arch = "x86", target_arch = "arm")))]
const RELOCATION_SIZE: usize = 8;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
#[cfg(all(any(feature = "win9x", feature = "winnt", feature = "linux-injected",
feature = "xnu-core"), any(target_arch = "x86_64", target_arch = "aarch64")))]
const RELOCATION_SIZE: usize = 24;
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected", feature = "xnu-core"))]
@@ -963,6 +1071,7 @@ unsafe extern "C" {
// A copy runs at a base of its own, thus the half that placed it there says where. The
// Stalker keeps out of this range: it must not instrument the runtime it runs on.
#[cfg(any(feature = "win9x", feature = "winnt", feature = "xnu-core"))]
pub(crate) unsafe fn set_own_range(base: u64, size: u64) {
unsafe {
OWN_RANGE = GumMemoryRange {
@@ -972,12 +1081,21 @@ pub(crate) unsafe fn set_own_range(base: u64, size: u64) {
}
}
#[cfg(feature = "winnt")]
pub(crate) fn own_code() -> (usize, usize) {
let start = entrypoint_blob::_start as *const () as usize;
(start, (&raw const _agent_private_start as usize) - start)
}
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
pub(crate) fn own_range() -> (usize, usize) {
let range = unsafe { ptr::addr_of!(OWN_RANGE).read() };
(range.base_address as usize, range.size as usize)
}
#[cfg(feature = "win9x")]
pub(crate) fn own_range_contains(address: u32) -> bool {
let range = unsafe { ptr::addr_of!(OWN_RANGE).read() };
let base = range.base_address as u32;
@@ -1000,14 +1118,27 @@ unsafe fn init_gum() {
// The Exceptor installs fault handling for the full machine, which only the kernel half may
// do. A copy in a process would take it from the half that uses it.
#[cfg(any(feature = "blob", feature = "xnu-core"))]
pub(crate) unsafe fn init_gum_without_exceptor() {
unsafe { init_gum_with_exceptor(false) };
unsafe {
init_gum_with_exceptor(false);
warm_async_io();
}
}
pub(crate) unsafe fn warm_async_io() {
unsafe {
let task = bindings::g_task_new(core::ptr::null_mut(), core::ptr::null_mut(), None,
core::ptr::null_mut());
bindings::g_object_unref(task as bindings::gpointer);
}
}
unsafe fn init_gum_with_exceptor(exceptor: bool) {
unsafe {
bindings::g_set_panic_handler(Some(signed_to_be_called_back(frida_panic_handler, 0)), ptr::null_mut());
bindings::gum_init_embedded();
bindings::glib_enable_io_features();
if exceptor {
bindings::gum_exceptor_obtain();
}
@@ -1062,7 +1193,6 @@ fn destination_of(variant: *mut GVariant) -> u32 {
pub(crate) unsafe fn adopt_js_context() -> *mut GMainContext {
unsafe {
JS_THREAD_ID = kernel::current_thread_id();
let context = gum_script_scheduler_get_js_context(gum_script_backend_get_scheduler());
// Acquires the context as well, which is what lets this thread run the jobs the
@@ -1076,6 +1206,7 @@ pub(crate) unsafe fn adopt_js_context() -> *mut GMainContext {
static mut JS_THREAD_ID: u64 = 0;
#[cfg(any(feature = "blob", feature = "xnu-core"))]
pub(crate) fn on_js_thread() -> bool {
let id = unsafe { JS_THREAD_ID };
id != 0 && kernel::current_thread_id() == id
@@ -1087,6 +1218,33 @@ pub(crate) static STOP_REQUESTED: core::sync::atomic::AtomicBool =
#[unsafe(no_mangle)]
pub static mut frida_agent_left: u32 = 0;
#[cfg(any(feature = "win9x", all(feature = "linux-injected", target_arch = "aarch64")))]
pub(crate) fn note_unhandled_fault(vector: u64, pc: u64, address: u64) {
unsafe {
let fault = &raw mut frida_agent_fault;
(*fault).count += 1;
(*fault).vector = vector;
(*fault).pc = pc;
(*fault).address = address;
}
}
#[unsafe(no_mangle)]
pub static mut frida_agent_fault: AgentFault = AgentFault {
count: 0,
vector: 0,
pc: 0,
address: 0,
};
#[repr(C)]
pub struct AgentFault {
pub count: u64,
pub vector: u64,
pub pc: u64,
pub address: u64,
}
pub(crate) fn stop_requested() -> bool {
STOP_REQUESTED.load(Ordering::Acquire)
}
@@ -1125,6 +1283,21 @@ fn kernel_half_has_work() -> bool {
return true;
}
#[cfg(feature = "winnt")]
if hostlink_serial::a_turn_is_wanted() {
return true;
}
#[cfg(feature = "linux-injected")]
if linux::hostlink_pipe_vsock::a_turn_is_wanted() {
return true;
}
#[cfg(feature = "linux-injected")]
if linux::hostlink_pipe_goldfish::a_turn_is_wanted() {
return true;
}
#[cfg(feature = "xnu-kext")]
if hostlink_chardev::a_turn_is_wanted() {
return true;
@@ -1148,7 +1321,19 @@ fn kernel_half_has_work() -> bool {
}
#[cfg(feature = "linux-injected")]
if kernel::a_copy_has_something_to_say() || kernel::a_spawn_is_held() {
if kernel::a_copy_is_asking() {
return true;
}
#[cfg(feature = "linux-injected")]
if kernel::a_copy_has_something_to_say() || kernel::a_spawn_is_held()
|| kernel::a_pending_spawn_waits()
{
return true;
}
#[cfg(all(feature = "linux-injected", target_arch = "aarch64"))]
if kernel::a_copy_wants_executable() {
return true;
}
@@ -1162,24 +1347,33 @@ fn serve_the_kernel_half() {
#[cfg(feature = "xnu-core")]
relay_frames_from_targets();
#[cfg(feature = "linux-injected")]
kernel::drain_pending_spawn();
#[cfg(feature = "linux-injected")]
kernel::tell_of_held_spawns(&mut |id, program| {
let said = alloc::ffi::CString::new(program).unwrap();
tell_the_host_of_a_spawn(id, said.as_ptr() as *const u8);
});
unsafe { transport_get_unchecked().process() };
transport_get_unchecked().process();
#[cfg(feature = "win9x")]
serve_deferred_work();
#[cfg(all(feature = "linux-injected", target_arch = "aarch64"))]
kernel::serve_executable_requests();
#[cfg(feature = "linux-injected")]
kernel::serve_register_requests();
#[cfg(feature = "linux-injected")]
kernel::report_what_the_copies_hit();
relay_frames_from_targets();
}
#[cfg(any(feature = "blob", feature = "xnu-core"))]
pub(crate) fn watch_for_work(main_context: *mut GMainContext, ready: fn() -> bool, serve: fn()) {
unsafe {
WORK_READY = Some(ready);
@@ -1189,11 +1383,31 @@ pub(crate) fn watch_for_work(main_context: *mut GMainContext, ready: fn() -> boo
sign_what_the_loop_calls_back();
let source = g_source_new(&raw mut WORK_FUNCS, core::mem::size_of::<GSource>() as u32);
WORK_SOURCE = source;
g_source_attach(source, main_context);
g_source_unref(source);
}
}
#[cfg(any(feature = "blob", feature = "xnu-core"))]
static mut WORK_SOURCE: *mut GSource = ptr::null_mut();
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected"))]
static mut WAKEUP_POLLFD: bindings::GPollFD =
bindings::GPollFD { fd: -1, events: 0, revents: 0, user_data: ptr::null_mut() };
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected"))]
pub(crate) fn watch_a_descriptor(context: *mut GMainContext, fd: i32,
poll: unsafe extern "C" fn(*mut bindings::GPollFD, u32, i32) -> i32) {
const G_IO_IN: u16 = 1;
unsafe {
WAKEUP_POLLFD =
bindings::GPollFD { fd, events: G_IO_IN, revents: 0, user_data: ptr::null_mut() };
bindings::g_source_add_poll(WORK_SOURCE, ptr::addr_of_mut!(WAKEUP_POLLFD));
bindings::g_main_context_set_poll_func(context, Some(poll));
}
}
#[cfg(feature = "xnu-kext")]
pub(crate) unsafe fn signed_to_be_called_back<T>(callback: T, discriminator: core::ffi::c_uint) -> T {
unsafe {
@@ -1217,9 +1431,12 @@ unsafe fn sign_what_the_loop_calls_back() {
}
}
#[cfg(any(feature = "blob", feature = "xnu-core"))]
static mut WORK_READY: Option<fn() -> bool> = None;
#[cfg(any(feature = "blob", feature = "xnu-core"))]
static mut WORK_SERVE: Option<fn()> = None;
#[cfg(any(feature = "blob", feature = "xnu-core"))]
unsafe extern "C" fn work_prepare(source: *mut GSource, timeout: *mut i32) -> gboolean {
unsafe {
*timeout = -1;
@@ -1228,6 +1445,7 @@ unsafe extern "C" fn work_prepare(source: *mut GSource, timeout: *mut i32) -> gb
}
}
#[cfg(any(feature = "blob", feature = "xnu-core"))]
unsafe extern "C" fn work_check(_source: *mut GSource) -> gboolean {
let Some(ready) = (unsafe { ptr::addr_of!(WORK_READY).read() }) else {
return 0;
@@ -1236,6 +1454,7 @@ unsafe extern "C" fn work_check(_source: *mut GSource) -> gboolean {
ready() as gboolean
}
#[cfg(any(feature = "blob", feature = "xnu-core"))]
unsafe extern "C" fn work_dispatch(_source: *mut GSource, _callback: GSourceFunc,
_data: gpointer) -> gboolean {
if let Some(serve) = unsafe { ptr::addr_of!(WORK_SERVE).read() } {
@@ -1245,6 +1464,7 @@ unsafe extern "C" fn work_dispatch(_source: *mut GSource, _callback: GSourceFunc
1
}
#[cfg(any(feature = "blob", feature = "xnu-core"))]
static mut WORK_FUNCS: GSourceFuncs = GSourceFuncs {
prepare: Some(work_prepare),
check: Some(work_check),
@@ -1265,6 +1485,7 @@ pub(crate) unsafe fn dispatch_pending_work(main_context: *mut GMainContext) {
static SERVICE_CONTEXT: core::sync::atomic::AtomicUsize =
core::sync::atomic::AtomicUsize::new(0);
#[cfg(any(feature = "blob", feature = "xnu-core"))]
pub(crate) fn nudge_the_loop(token: *const u8) {
let context = SERVICE_CONTEXT.load(Ordering::Acquire) as *mut GMainContext;
if !context.is_null() {
@@ -1398,6 +1619,10 @@ fn process_incoming_message(variant: *mut GVariant) {
FridaCommand::PostScriptMessage => Some(handle_post_script_message(payload_variant)),
#[cfg(any(feature = "win9x", feature = "linux-injected", feature = "xnu-core"))]
FridaCommand::GateSpawns => Some(handle_gate_spawns(payload_variant)),
#[cfg(feature = "linux-injected")]
FridaCommand::ArmNamedSpawn => Some(handle_arm_named_spawn(payload_variant)),
#[cfg(feature = "linux-injected")]
FridaCommand::DisarmNamedSpawn => Some(handle_disarm_named_spawn(payload_variant)),
#[cfg(any(feature = "win9x", feature = "winnt"))]
FridaCommand::EnumerateApplications => Some(handle_enumerate_applications(payload_variant)),
#[cfg(feature = "xnu-core")]
@@ -1405,6 +1630,8 @@ fn process_incoming_message(variant: *mut GVariant) {
kernel::list_applications_when_the_loop_can(request_id);
None
}
#[cfg(feature = "linux-injected")]
FridaCommand::EnumerateApplications => Some(handle_enumerate_no_applications()),
#[cfg(any(feature = "win9x", feature = "winnt"))]
FridaCommand::EnumerateShortcuts => Some(handle_enumerate_shortcuts()),
#[cfg(any(
@@ -1581,7 +1808,6 @@ fn serve_pending_detach() {
send_command_reply(request_id, response);
}
#[cfg(feature = "xnu-core")]
pub static mut ASKED_THE_HOST: u32 = 0;
@@ -1676,6 +1902,16 @@ fn what_is_installed() -> HandlerResponse {
}
}
#[cfg(feature = "linux-injected")]
fn handle_enumerate_no_applications() -> HandlerResponse {
unsafe {
let list_type = g_variant_type_new(c"a(sss)".as_ptr() as *const gchar);
let list = g_variant_builder_end(g_variant_builder_new(list_type));
g_variant_type_free(list_type);
HandlerResponse::success(list)
}
}
#[cfg(any(feature = "win9x", feature = "winnt"))]
fn handle_enumerate_applications(payload: *mut GVariant) -> HandlerResponse {
unsafe {
@@ -1692,7 +1928,7 @@ fn handle_enumerate_applications(payload: *mut GVariant) -> HandlerResponse {
let path = as_text(path, &mut path_text);
let identity = kernel::identify_image(path as *const u8);
let named = if unsafe { identity.read() } != 0 { identity as *const gchar } else {
let named = if identity.read() != 0 { identity as *const gchar } else {
identifier
};
@@ -1716,15 +1952,17 @@ fn handle_enumerate_applications(payload: *mut GVariant) -> HandlerResponse {
}
}
#[cfg(any(feature = "win9x", feature = "winnt"))]
struct SelectedIdentifiers {
names: *mut *const gchar,
count: usize,
}
#[cfg(any(feature = "win9x", feature = "winnt"))]
impl SelectedIdentifiers {
unsafe fn from(variant: *mut GVariant) -> Self {
let mut count: gsize = 0;
let names = unsafe { g_variant_get_strv(variant, &mut count) };
let names = unsafe { bindings::g_variant_get_strv(variant, &mut count) };
SelectedIdentifiers { names, count: count as usize }
}
@@ -1737,12 +1975,14 @@ impl SelectedIdentifiers {
}
}
#[cfg(any(feature = "win9x", feature = "winnt"))]
impl Drop for SelectedIdentifiers {
fn drop(&mut self) {
unsafe { g_free(self.names as gpointer) };
}
}
#[cfg(any(feature = "win9x", feature = "winnt"))]
unsafe fn c_string_equal(a: *const gchar, b: *const gchar) -> bool {
unsafe {
let mut offset = 0;
@@ -1807,7 +2047,21 @@ fn as_text<'a>(bytes: &[u8], into: &'a mut [u8]) -> *const gchar {
fn handle_gate_spawns(payload: *mut GVariant) -> HandlerResponse {
kernel::gate_spawns(unsafe { g_variant_get_boolean(payload) } != 0);
HandlerResponse::success(unsafe { g_variant_new_uint32(0) })
}
#[cfg(feature = "linux-injected")]
fn handle_arm_named_spawn(payload: *mut GVariant) -> HandlerResponse {
let name = unsafe { core::ffi::CStr::from_ptr(g_variant_get_string(payload, ptr::null_mut())) };
kernel::arm_named_spawn(name.to_bytes());
HandlerResponse::success(unsafe { g_variant_new_uint32(0) })
}
#[cfg(feature = "linux-injected")]
fn handle_disarm_named_spawn(payload: *mut GVariant) -> HandlerResponse {
let name = unsafe { core::ffi::CStr::from_ptr(g_variant_get_string(payload, ptr::null_mut())) };
kernel::disarm_named_spawn(name.to_bytes());
HandlerResponse::success(unsafe { g_variant_new_uint32(0) })
}
@@ -1846,8 +2100,8 @@ fn handle_enumerate_processes(payload: *mut GVariant) -> HandlerResponse {
let selection = g_variant_get_child_value(payload, 1);
let wanted = SelectedPids::from(selection);
let list_type = g_variant_type_new(c"a(usssaay)".as_ptr() as *const gchar);
let process_type = g_variant_type_new(c"(usssaay)".as_ptr() as *const gchar);
let list_type = g_variant_type_new(c"a(usssusuxaay)".as_ptr() as *const gchar);
let process_type = g_variant_type_new(c"(usssusuxaay)".as_ptr() as *const gchar);
let icons_type = g_variant_type_new(c"aay".as_ptr() as *const gchar);
let byte_type = g_variant_type_new(c"y".as_ptr() as *const gchar);
let builder = g_variant_builder_new(list_type);
@@ -1858,6 +2112,10 @@ fn handle_enumerate_processes(payload: *mut GVariant) -> HandlerResponse {
g_variant_builder_add(builder, c"s".as_ptr(), c"".as_ptr());
g_variant_builder_add(builder, c"s".as_ptr(), c"".as_ptr());
g_variant_builder_add(builder, c"s".as_ptr(), kernel_name());
g_variant_builder_add(builder, c"u".as_ptr(), 0u32);
g_variant_builder_add(builder, c"s".as_ptr(), c"".as_ptr());
g_variant_builder_add(builder, c"u".as_ptr(), 0u32);
g_variant_builder_add(builder, c"x".as_ptr(), 0i64);
g_variant_builder_open(builder, icons_type);
g_variant_builder_close(builder);
g_variant_builder_close(builder);
@@ -1879,6 +2137,10 @@ fn handle_enumerate_processes(payload: *mut GVariant) -> HandlerResponse {
c"s".as_ptr(),
text_or_empty(kernel::describe_process(&process)),
);
g_variant_builder_add(builder, c"u".as_ptr(), process.uid);
g_variant_builder_add(builder, c"s".as_ptr(), text_or_empty(process.user));
g_variant_builder_add(builder, c"u".as_ptr(), process.ppid);
g_variant_builder_add(builder, c"x".as_ptr(), process.started);
g_variant_builder_open(builder, icons_type);
if include_icons && !process.path.is_null() {
@@ -1909,11 +2171,23 @@ fn handle_enumerate_processes(payload: *mut GVariant) -> HandlerResponse {
}
}
#[cfg(any(
feature = "win9x",
feature = "winnt",
feature = "linux-injected",
feature = "xnu-core"
))]
struct SelectedPids {
pids: *const u32,
count: usize,
}
#[cfg(any(
feature = "win9x",
feature = "winnt",
feature = "linux-injected",
feature = "xnu-core"
))]
impl SelectedPids {
unsafe fn from(variant: *mut GVariant) -> Self {
let mut count: gsize = 0;
@@ -1940,7 +2214,7 @@ fn kernel_name() -> *const gchar {
c"ntoskrnl.exe".as_ptr() as *const gchar
}
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[cfg(feature = "linux-injected")]
fn kernel_name() -> *const gchar {
c"kernel".as_ptr() as *const gchar
}
@@ -1950,6 +2224,12 @@ fn kernel_name() -> *const gchar {
c"kernel".as_ptr() as *const gchar
}
#[cfg(any(
feature = "win9x",
feature = "winnt",
feature = "linux-injected",
feature = "xnu-core"
))]
const KERNEL_PID: u32 = 0;
#[cfg(any(
@@ -2185,7 +2465,6 @@ fn handle_create_script(payload_variant: *mut GVariant, request_id: u16) -> Opti
return Some(HandlerResponse::error("Invalid payload format: expected string"));
}
let source = g_variant_get_string(payload_variant, core::ptr::null_mut());
gum_script_backend_create(
gum_script_backend_obtain_qjs(),
c"agent.js".as_ptr(),
@@ -2195,7 +2474,6 @@ fn handle_create_script(payload_variant: *mut GVariant, request_id: u16) -> Opti
Some(signed_to_be_called_back(on_script_created, 0)),
Box::into_raw(Box::new(request_id)) as *mut c_void,
);
None
}
}
@@ -2296,7 +2574,7 @@ unsafe extern "C" fn frida_message_handler(
}
}
#[cfg(any(feature = "win9x", feature = "winnt"))]
#[cfg(feature = "win9x")]
pub(crate) fn routed_arena() -> u64 {
unsafe { ROUTED_ARENA }
}
@@ -2346,7 +2624,6 @@ fn handle_load_script(payload_variant: *mut GVariant, request_id: u16) -> Option
Some(signed_to_be_called_back(on_script_loaded, 0)),
Box::into_raw(Box::new(request_id)) as *mut c_void,
);
None
}
}
@@ -2383,7 +2660,6 @@ fn handle_destroy_script(payload_variant: *mut GVariant, request_id: u16) -> Opt
Some(signed_to_be_called_back(on_script_destroyed, 0)),
Box::into_raw(Box::new(request_id)) as *mut c_void,
);
None
}
}
@@ -2483,14 +2759,27 @@ pub struct KernelAllocator;
unsafe impl GlobalAlloc for KernelAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
kernel::alloc(layout.size())
let mut memory: *mut c_void = ptr::null_mut();
let alignment = layout.align().max(SMALLEST_ALIGNMENT);
if unsafe { posix_memalign(&mut memory, alignment, layout.size()) } != 0 {
return ptr::null_mut();
}
memory as *mut u8
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
kernel::free(ptr, layout.size());
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
unsafe { free(ptr as *mut c_void) };
}
}
const SMALLEST_ALIGNMENT: usize = 2 * core::mem::size_of::<*const u8>();
unsafe extern "C" {
fn posix_memalign(memory: *mut *mut c_void, alignment: usize, size: usize) -> i32;
fn free(memory: *mut c_void);
}
#[global_allocator]
static GLOBAL: KernelAllocator = KernelAllocator;
extern crate alloc;
+6 -14
View File
@@ -134,24 +134,16 @@ pub extern "C" fn __aeabi_uwrite8(value: u64, address: *mut u8) -> u64 {
value
}
#[cfg(all(target_arch = "aarch64", not(any(feature = "linux-injected", feature = "xnu-core"))))]
#[unsafe(no_mangle)]
pub extern "C" fn __clear_cache(_start: *const u8, _end: *const u8) {
unsafe {
core::arch::asm!(
"ic iallu", // Invalidate all instruction caches
"dsb sy", // Data synchronization barrier
"isb", // Instruction synchronization barrier
options(nomem, nostack),
);
}
}
// Invalidating the whole instruction cache is the kernel's to do, and the copy runs where that
// instruction is not allowed. Line by line is what both halves may ask for.
#[cfg(all(target_arch = "aarch64", any(feature = "linux-injected", feature = "xnu-core")))]
#[cfg(target_arch = "aarch64")]
#[unsafe(no_mangle)]
pub extern "C" fn __clear_cache(start: *const u8, end: *const u8) {
#[cfg(feature = "winnt")]
if crate::winnt_user::flush_code(start, end) {
return;
}
#[cfg(feature = "xnu-core")]
let told = crate::kernel::cache_shape();
#[cfg(not(feature = "xnu-core"))]
+92 -17
View File
@@ -1,3 +1,4 @@
use core::mem::size_of;
use core::sync::atomic::{AtomicU32, Ordering};
// What the kernel half and the copy leave for each other. Both reach it by its own address, so
@@ -12,10 +13,6 @@ impl Arena {
Arena { begins }
}
pub fn at_offset(&self, offset: usize) -> usize {
self.begins + offset
}
// Until the frames flow, what the copy has to say is left here for the kernel half to read.
pub fn say(&self, msg: &str) {
let said = msg.as_bytes();
@@ -28,14 +25,6 @@ impl Arena {
}
}
pub fn said(&self) -> &str {
let bytes =
unsafe { core::slice::from_raw_parts((self.begins + SAID) as *const u8, SAID_SIZE) };
let length = bytes.iter().position(|byte| *byte == 0).unwrap_or(SAID_SIZE);
core::str::from_utf8(&bytes[..length]).unwrap_or("")
}
pub fn page_size(&self) -> u32 {
self.word(PAGE_SIZE).load(Ordering::Acquire)
}
@@ -44,10 +33,6 @@ impl Arena {
unsafe { ((self.begins + offset) as *mut u64).write_volatile(value) };
}
pub fn progress(&self) -> u32 {
self.word(PROGRESS).load(Ordering::Acquire)
}
pub fn note(&self, step: u32) {
self.word(PROGRESS).store(step, Ordering::Release);
}
@@ -125,6 +110,84 @@ impl Arena {
super::user::say_something();
}
#[cfg(target_arch = "aarch64")]
pub fn request_executable(&self, address: u64, size: u64) -> u32 {
self.leave(EXEC_ADDR, address);
self.leave(EXEC_SIZE, size);
let seq = self.word(EXEC_SEQ).load(Ordering::Relaxed).wrapping_add(1).max(1);
self.word(EXEC_SEQ).store(seq, Ordering::Release);
seq
}
#[cfg(target_arch = "aarch64")]
pub fn executable_settled(&self, seq: u32) -> bool {
self.word(EXEC_ACK).load(Ordering::Acquire) == seq
}
#[cfg(target_arch = "aarch64")]
pub fn executable_was_granted(&self) -> bool {
self.word(EXEC_OK).load(Ordering::Acquire) != 0
}
pub fn wants_executable(&self) -> Option<(u64, u64)> {
let seq = self.word(EXEC_SEQ).load(Ordering::Acquire);
if seq == 0 || seq == self.word(EXEC_ACK).load(Ordering::Acquire) {
return None;
}
Some((self.read_wide(EXEC_ADDR), self.read_wide(EXEC_SIZE)))
}
#[cfg(target_arch = "aarch64")]
pub fn grant_executable(&self, ok: bool) {
self.word(EXEC_OK).store(ok as u32, Ordering::Release);
let seq = self.word(EXEC_SEQ).load(Ordering::Acquire);
self.word(EXEC_ACK).store(seq, Ordering::Release);
}
pub fn request_registers(&self, thread: u32) -> u32 {
self.word(REG_TID).store(thread, Ordering::Release);
let seq = self.word(REG_SEQ).load(Ordering::Relaxed).wrapping_add(1).max(1);
self.word(REG_SEQ).store(seq, Ordering::Release);
seq
}
pub fn registers_settled(&self, seq: u32) -> bool {
self.word(REG_ACK).load(Ordering::Acquire) == seq
}
pub fn registers_were_captured(&self) -> bool {
self.word(REG_OK).load(Ordering::Acquire) != 0
}
pub fn captured_registers(&self, into: &mut [u64]) {
for (slot, word) in into.iter_mut().enumerate() {
*word = self.read_wide(REG_STATE + slot * size_of::<u64>());
}
}
pub fn wants_registers(&self) -> Option<u32> {
let seq = self.word(REG_SEQ).load(Ordering::Acquire);
if seq == 0 || seq == self.word(REG_ACK).load(Ordering::Acquire) {
return None;
}
Some(self.word(REG_TID).load(Ordering::Acquire))
}
pub fn deliver_registers(&self, state: Option<&[u64]>) {
if let Some(words) = state {
for (slot, word) in words.iter().enumerate() {
self.leave(REG_STATE + slot * size_of::<u64>(), *word);
}
}
self.word(REG_OK).store(state.is_some() as u32, Ordering::Release);
let seq = self.word(REG_SEQ).load(Ordering::Acquire);
self.word(REG_ACK).store(seq, Ordering::Release);
}
fn read_wide(&self, offset: usize) -> u64 {
unsafe { ((self.begins + offset) as *const u64).read_volatile() }
}
fn word(&self, offset: usize) -> &AtomicU32 {
unsafe { &*((self.begins + offset) as *const AtomicU32) }
}
@@ -145,10 +208,22 @@ const THREADS: usize = 304;
const THREAD_SLOTS: u32 = 32;
pub const SAID_SIZE: usize = 192;
pub const REPORTED: usize = 0;
pub const HOME: usize = 4;
pub const SAYS: usize = 8;
pub const HEARS: usize = 52;
pub const TO_KERNEL: usize = 12;
pub const WOKEN: usize = 16;
const EXEC_SEQ: usize = 432;
const EXEC_ACK: usize = 436;
const EXEC_ADDR: usize = 440;
const EXEC_SIZE: usize = 448;
#[cfg(target_arch = "aarch64")]
const EXEC_OK: usize = 456;
const REG_SEQ: usize = 460;
const REG_ACK: usize = 464;
const REG_TID: usize = 468;
const REG_OK: usize = 472;
const REG_STATE: usize = 480;
+7 -1
View File
@@ -78,6 +78,10 @@ pub fn install_fault_reporter() {
(primitives().install_fault_reporter)()
}
pub fn poke_loop_wakeup() {
(primitives().poke_loop_wakeup)()
}
pub fn select_user() {
unsafe { ACTIVE = &super::user::USER };
}
@@ -97,7 +101,7 @@ static KERNEL: Primitives = Primitives {
panic: super::native::panic,
alloc: super::native::alloc,
free: super::native::free,
alloc_heap: super::native::alloc,
alloc_heap: super::native::alloc_heap,
alloc_code: super::native::alloc_code,
free_code: super::native::free_code,
page_size: super::native::page_size,
@@ -112,6 +116,7 @@ static KERNEL: Primitives = Primitives {
current_process_id: super::native::current_process_id,
current_thread_id: super::native::current_thread_id,
install_fault_reporter: super::native::install_fault_reporter,
poke_loop_wakeup: super::native::poke_loop_wakeup,
};
pub struct Primitives {
@@ -134,4 +139,5 @@ pub struct Primitives {
pub current_process_id: fn() -> u32,
pub current_thread_id: fn() -> u64,
pub install_fault_reporter: fn(),
pub poke_loop_wakeup: fn(),
}
@@ -0,0 +1,216 @@
// Linux hostlink over the Android emulator's QEMU pipe, which is how the emulator
// carries adb as well: open /dev/qemu_pipe, name the "pipe:unix:<path>" service,
// and the emulator joins that stream to the host UNIX socket <path>, where
// frida-core listens. Framed like the other hostlinks, with a 4-byte
// little-endian length in front of each payload.
//
// Every open of the pipe is a channel of its own, handed out by the guest's own
// driver, so the agent shares no device with the drivers already running.
use core::cell::UnsafeCell;
use core::ffi::{c_int, c_void};
use core::ptr;
use core::sync::atomic::{AtomicPtr, AtomicU32, Ordering};
use alloc::vec::Vec;
use super::native;
const O_RDWR: c_int = 2;
const PIPE_PATH: &str = "/dev/qemu_pipe\0";
const LAST_ERRNO: usize = 4095;
static PENDING: AtomicU32 = AtomicU32::new(0);
static WAKE_TOKEN: AtomicPtr<u8> = AtomicPtr::new(ptr::null_mut());
struct Shared(UnsafeCell<Vec<u8>>);
unsafe impl Sync for Shared {}
static INCOMING: Shared = Shared(UnsafeCell::new(Vec::new()));
static INCOMING_LOCK: AtomicU32 = AtomicU32::new(0);
static PIPE: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());
pub fn a_turn_is_wanted() -> bool {
PENDING.load(Ordering::Acquire) != 0
}
struct Inner {
file: *mut c_void,
rx_lenbuf: [u8; 4],
rx_lenhave: usize,
rx_buf: Vec<u8>,
rx_have: usize,
rx_need: usize,
}
pub struct Hostlink {
state: UnsafeCell<Inner>,
on_rx: Option<fn(&[u8])>,
}
unsafe impl Send for Hostlink {}
unsafe impl Sync for Hostlink {}
impl Hostlink {
pub fn init(path: &str, on_rx: Option<fn(&[u8])>, wake_token: *const u8) -> Result<Self, ()> {
WAKE_TOKEN.store(wake_token as *mut u8, Ordering::Release);
let file = open_the_pipe()?;
PIPE.store(file, Ordering::Release);
let mut service = Vec::with_capacity(path.len() + 12);
service.extend_from_slice(b"pipe:unix:");
service.extend_from_slice(path.as_bytes());
service.push(0);
if write_all(file, &service).is_err() {
return Err(());
}
native::spawn_thread(read_forever, ptr::null_mut());
Ok(Hostlink {
state: UnsafeCell::new(Inner {
file,
rx_lenbuf: [0; 4],
rx_lenhave: 0,
rx_buf: Vec::new(),
rx_have: 0,
rx_need: 0,
}),
on_rx,
})
}
pub fn send(&self, payload: &[u8]) {
let s = unsafe { &*self.state.get() };
let length = (payload.len() as u32).to_le_bytes();
let _ = write_all(s.file, &length);
let _ = write_all(s.file, payload);
}
pub fn process(&self) {
PENDING.store(0, Ordering::Release);
let arrived = take_what_arrived();
if arrived.is_empty() {
return;
}
let s = unsafe { &mut *self.state.get() };
let mut at = 0;
while at < arrived.len() {
if s.rx_lenhave < 4 {
let want = core::cmp::min(4 - s.rx_lenhave, arrived.len() - at);
s.rx_lenbuf[s.rx_lenhave..s.rx_lenhave + want]
.copy_from_slice(&arrived[at..at + want]);
s.rx_lenhave += want;
at += want;
if s.rx_lenhave < 4 {
break;
}
s.rx_need = u32::from_le_bytes(s.rx_lenbuf) as usize;
s.rx_have = 0;
s.rx_buf.resize(s.rx_need, 0);
}
let want = core::cmp::min(s.rx_need - s.rx_have, arrived.len() - at);
let lo = s.rx_have;
s.rx_buf[lo..lo + want].copy_from_slice(&arrived[at..at + want]);
s.rx_have += want;
at += want;
if s.rx_have < s.rx_need {
break;
}
let frame = core::mem::take(&mut s.rx_buf);
let need = s.rx_need;
s.rx_lenhave = 0;
s.rx_have = 0;
s.rx_need = 0;
if let Some(cb) = self.on_rx {
cb(&frame[..need]);
}
}
}
pub fn shutdown(&self) {
PIPE.store(ptr::null_mut(), Ordering::Release);
}
}
fn open_the_pipe() -> Result<*mut c_void, ()> {
let file = unsafe { _filp_open(PIPE_PATH.as_ptr(), O_RDWR, 0) };
if (file as usize) > usize::MAX - LAST_ERRNO {
return Err(());
}
Ok(file)
}
// The pipe blocks until the host has something, so a thread of its own is what
// reads it; the loop is told there is a turn to take rather than asking.
unsafe extern "C" fn read_forever(_parameter: *mut c_void, _wait_result: i32) {
let mut chunk = [0u8; 4096];
loop {
let file = PIPE.load(Ordering::Acquire);
if file.is_null() {
return;
}
let n = read_some(file, &mut chunk);
if n <= 0 {
return;
}
held(|| unsafe { (*INCOMING.0.get()).extend_from_slice(&chunk[..n as usize]) });
PENDING.store(1, Ordering::Release);
crate::nudge_the_loop(WAKE_TOKEN.load(Ordering::Acquire) as *const u8);
}
}
fn take_what_arrived() -> Vec<u8> {
held(|| core::mem::take(unsafe { &mut *INCOMING.0.get() }))
}
fn held<T>(action: impl FnOnce() -> T) -> T {
while INCOMING_LOCK
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
core::hint::spin_loop();
}
let outcome = action();
INCOMING_LOCK.store(0, Ordering::Release);
outcome
}
fn read_some(file: *mut c_void, into: &mut [u8]) -> isize {
let mut position: i64 = 0;
unsafe { native::pipe_read(file, into.as_mut_ptr(), into.len(), &mut position) }
}
fn write_all(file: *mut c_void, bytes: &[u8]) -> Result<(), ()> {
let mut sent = 0;
while sent < bytes.len() {
let mut position: i64 = 0;
let n = unsafe {
native::pipe_write(file, bytes[sent..].as_ptr(), bytes.len() - sent, &mut position)
};
if n <= 0 {
return Err(());
}
sent += n as usize;
}
Ok(())
}
unsafe extern "C" {
static _filp_open: unsafe extern "C" fn(*const u8, c_int, c_int) -> *mut c_void;
}
@@ -0,0 +1,280 @@
// Linux hostlink over AF_VSOCK, bridged to the host by the Android emulator's
// pipe-over-vsock connector: connect to CID 2 (host) on port 5002, send a
// NUL-terminated "pipe:unix:<path>" handshake, and the emulator connects that
// stream to the host UNIX socket <path>, where frida-core listens. Framed the
// same way as the other hostlinks (4-byte little-endian length prefix).
//
// The agent uses the guest kernel's own socket API, so it never drives a virtio
// device and never fights the guest's own drivers.
use core::cell::UnsafeCell;
use core::ffi::{c_int, c_void};
use core::ptr;
use core::sync::atomic::{AtomicPtr, AtomicU32, AtomicUsize, Ordering};
use alloc::vec::Vec;
const AF_VSOCK: c_int = 40;
const SOCK_STREAM: c_int = 1;
const VMADDR_CID_HOST: u32 = 2;
const MSG_DONTWAIT: c_int = 0x40;
const PIPE_CONNECTOR_PORT: u32 = 5002;
#[repr(C)]
struct SockaddrVm {
svm_family: u16,
svm_reserved1: u16,
svm_port: u32,
svm_cid: u32,
svm_zero: [u8; 4],
}
#[repr(C)]
struct Kvec {
iov_base: *mut c_void,
iov_len: usize,
}
type Socket = *mut c_void;
static PENDING: AtomicU32 = AtomicU32::new(0);
static WAKE_TOKEN: AtomicPtr<u8> = AtomicPtr::new(ptr::null_mut());
static ORIG_DATA_READY: AtomicUsize = AtomicUsize::new(0);
pub fn a_turn_is_wanted() -> bool {
PENDING.load(Ordering::Acquire) != 0
}
struct Inner {
so: Socket,
rx_lenbuf: [u8; 4],
rx_lenhave: usize,
rx_buf: Vec<u8>,
rx_have: usize,
rx_need: usize,
}
pub struct Hostlink {
state: UnsafeCell<Inner>,
on_rx: Option<fn(&[u8])>,
}
unsafe impl Send for Hostlink {}
impl Hostlink {
pub fn init(path: &str, on_rx: Option<fn(&[u8])>, wake_token: *const u8) -> Result<Self, ()> {
WAKE_TOKEN.store(wake_token as *mut u8, Ordering::Release);
unsafe {
let mut so: Socket = ptr::null_mut();
if _sock_create_kern(_init_net_addr(), AF_VSOCK, SOCK_STREAM, 0, &mut so) != 0
|| so.is_null()
{
return Err(());
}
let addr = SockaddrVm {
svm_family: AF_VSOCK as u16,
svm_reserved1: 0,
svm_port: PIPE_CONNECTOR_PORT,
svm_cid: VMADDR_CID_HOST,
svm_zero: [0; 4],
};
if _kernel_connect(
so,
&addr as *const _ as *const c_void,
core::mem::size_of::<SockaddrVm>() as c_int,
0,
) != 0
{
_sock_release(so);
return Err(());
}
hook_data_ready(so);
let hostlink = Hostlink {
state: UnsafeCell::new(Inner {
so,
rx_lenbuf: [0; 4],
rx_lenhave: 0,
rx_buf: Vec::new(),
rx_have: 0,
rx_need: 0,
}),
on_rx,
};
let mut handshake = Vec::with_capacity(path.len() + 12);
handshake.extend_from_slice(b"pipe:unix:");
handshake.extend_from_slice(path.as_bytes());
handshake.push(0);
send_all(so, &handshake);
Ok(hostlink)
}
}
pub fn send(&self, payload: &[u8]) {
let s = unsafe { &*self.state.get() };
let length = (payload.len() as u32).to_le_bytes();
unsafe {
send_all(s.so, &length);
send_all(s.so, payload);
}
}
pub fn process(&self) {
PENDING.store(0, Ordering::Release);
let s = unsafe { &mut *self.state.get() };
loop {
if s.rx_lenhave < 4 {
let lo = s.rx_lenhave;
let n = unsafe { recv_nonblocking(s.so, &mut s.rx_lenbuf[lo..4]) };
if n == 0 {
return;
}
s.rx_lenhave += n;
if s.rx_lenhave < 4 {
continue;
}
s.rx_need = u32::from_le_bytes(s.rx_lenbuf) as usize;
s.rx_have = 0;
s.rx_buf.resize(s.rx_need, 0);
}
while s.rx_have < s.rx_need {
let lo = s.rx_have;
let hi = s.rx_need;
let n = unsafe { recv_nonblocking(s.so, &mut s.rx_buf[lo..hi]) };
if n == 0 {
return;
}
s.rx_have += n;
}
let frame = core::mem::take(&mut s.rx_buf);
let need = s.rx_need;
s.rx_lenhave = 0;
s.rx_have = 0;
s.rx_need = 0;
if let Some(cb) = self.on_rx {
cb(&frame[..need]);
}
}
}
pub fn shutdown(&self) {
let s = unsafe { &*self.state.get() };
unsafe { _sock_release(s.so) };
}
}
// The vsock stack calls sk->sk_data_ready(sk) via a CONFIG_CFI_CLANG-checked indirect
// call, so the replacement must carry the type id of a void(struct sock*) callback;
// sock_def_readable is the kernel's own one. Store the original and chain to it so the
// socket's normal wakeups still fire.
unsafe fn hook_data_ready(so: Socket) {
let (Some(sk_off), Some(dr_off)) = (
super::layout::field_offset("socket", "sk"),
super::layout::field_offset("sock", "sk_data_ready"),
) else {
return;
};
let sk = unsafe { *((so as usize + sk_off) as *const usize) };
if sk == 0 {
return;
}
let slot = (sk + dr_off) as *mut usize;
ORIG_DATA_READY.store(unsafe { *slot }, Ordering::Release);
let thunk =
super::native::kcfi_thunk(on_data_ready as *const () as usize, sock_def_readable_ref());
unsafe { *slot = thunk };
}
fn sock_def_readable_ref() -> Option<usize> {
let addr = unsafe { _sock_def_readable_addr() };
if addr == 0 { None } else { Some(addr) }
}
unsafe extern "C" fn on_data_ready(sk: *mut c_void) {
PENDING.fetch_add(1, Ordering::Release);
let token = WAKE_TOKEN.load(Ordering::Acquire);
if !token.is_null() {
crate::nudge_the_loop(token as *const u8);
}
let orig = ORIG_DATA_READY.load(Ordering::Acquire);
if orig != 0 {
let f: unsafe extern "C" fn(*mut c_void) = unsafe { core::mem::transmute(orig) };
unsafe { f(sk) };
}
}
unsafe fn recv_nonblocking(so: Socket, dst: &mut [u8]) -> usize {
let mut vec = Kvec {
iov_base: dst.as_mut_ptr() as *mut c_void,
iov_len: dst.len(),
};
let mut msg = [0u64; 16];
let n = unsafe {
_kernel_recvmsg(
so,
msg.as_mut_ptr() as *mut c_void,
&mut vec,
1,
dst.len(),
MSG_DONTWAIT,
)
};
if n <= 0 { 0 } else { n as usize }
}
unsafe fn send_all(so: Socket, bytes: &[u8]) {
let mut gone = 0;
while gone < bytes.len() {
let mut vec = Kvec {
iov_base: unsafe { bytes.as_ptr().add(gone) } as *mut c_void,
iov_len: bytes.len() - gone,
};
let mut msg = [0u64; 16];
let n = unsafe {
_kernel_sendmsg(
so,
msg.as_mut_ptr() as *mut c_void,
&mut vec,
1,
bytes.len() - gone,
)
};
if n <= 0 {
return;
}
gone += n as usize;
}
}
unsafe fn _init_net_addr() -> *mut c_void {
ptr::addr_of!(_init_net) as *mut c_void
}
unsafe fn _sock_def_readable_addr() -> usize {
unsafe { _sock_def_readable }.map_or(0, |f| f as usize)
}
unsafe extern "C" {
static _init_net: c_void;
static _sock_create_kern:
unsafe extern "C" fn(*mut c_void, c_int, c_int, c_int, *mut Socket) -> c_int;
static _kernel_connect:
unsafe extern "C" fn(Socket, *const c_void, c_int, c_int) -> c_int;
static _kernel_sendmsg:
unsafe extern "C" fn(Socket, *mut c_void, *mut Kvec, usize, usize) -> c_int;
static _kernel_recvmsg:
unsafe extern "C" fn(Socket, *mut c_void, *mut Kvec, usize, usize, c_int) -> c_int;
static _sock_release: unsafe extern "C" fn(Socket);
static _sock_def_readable: Option<unsafe extern "C" fn()>;
}
+494 -68
View File
@@ -2,9 +2,16 @@ use core::ffi::{c_int, c_void};
use core::ptr;
use alloc::collections::BTreeMap;
use alloc::string::String;
use super::layout::{field_offset, struct_size};
#[cfg(not(target_arch = "x86"))]
use crate::bindings::{
gum_interceptor_begin_transaction, gum_interceptor_end_transaction, gum_interceptor_obtain,
gum_interceptor_replace_fast,
};
use super::layout::field_offset;
#[cfg(target_arch = "aarch64")]
use super::layout::struct_size;
use super::STACK_SPAN;
use super::native;
use super::processes::task_with_id;
@@ -39,7 +46,6 @@ pub fn inject_into_process(id: u32) -> u32 {
task,
base: home.base,
arena: home.arena,
seen_by_the_copy: home.seen_by_the_copy,
stack: home.stack,
says: ptr::null_mut(),
hears: ptr::null_mut(),
@@ -52,8 +58,11 @@ pub fn inject_into_process(id: u32) -> u32 {
fn take_what_the_copy_opened(placed: &mut Placement) {
let arena = Arena::at(placed.arena);
placed.says = native::take_the_file(arena.says_through());
placed.hears = native::take_the_file(arena.hears_through());
let says_fd = arena.says_through();
let hears_fd = arena.hears_through();
let copy = COPY_TASK.load(core::sync::atomic::Ordering::Acquire) as *mut c_void;
placed.says = native::take_the_file_of(copy, says_fd);
placed.hears = native::take_the_file_of(copy, hears_fd);
listen_to_the_copy(placed.says);
}
@@ -63,7 +72,7 @@ fn listen_to_the_copy(says: *mut c_void) {
unsafe extern "C" fn carry_what_the_copy_says(argument: *mut c_void, _reason: i32) {
while native::wait_for_a_word(argument) {
native::wake(argument as *const u8);
crate::nudge_the_loop(crate::glib::wakeup_token());
}
}
@@ -71,7 +80,6 @@ struct Placement {
task: usize,
base: usize,
arena: usize,
seen_by_the_copy: usize,
stack: usize,
says: *mut c_void,
hears: *mut c_void,
@@ -86,7 +94,6 @@ fn pid_reported_by(placed: &Placement) -> u32 {
struct Home {
base: usize,
arena: usize,
seen_by_the_copy: usize,
stack: usize,
}
@@ -126,16 +133,12 @@ fn map_and_start(id: u32) -> Option<Home> {
start(base, where_the_copy_sees_it, stack);
Some(Home {
base,
arena,
seen_by_the_copy: where_the_copy_sees_it,
stack,
})
Some(Home { base, arena, stack })
}
// The kernel half reaches the same pages by an address of its own, so what the two leave for
// each other needs neither the address space borrowed nor a copy made.
#[cfg(target_arch = "aarch64")]
fn borrowed_memory() -> Option<usize> {
let at = field_offset("task_struct", "mm")?;
@@ -185,12 +188,13 @@ fn map_a_copy() -> Option<usize> {
let shared = crate::writable_half_start() - own;
let private = crate::writable_half_size();
let executable_up_front = if cfg!(target_arch = "aarch64") { 0 } else { PROT_EXEC };
let base = unsafe {
_vm_mmap(
ptr::null_mut(),
0,
size + ARENA_SIZE,
PROT_READ | PROT_WRITE | PROT_EXEC,
PROT_READ | PROT_WRITE | executable_up_front,
MAP_PRIVATE | MAP_ANONYMOUS,
0,
)
@@ -199,6 +203,9 @@ fn map_a_copy() -> Option<usize> {
return None;
}
#[cfg(target_arch = "aarch64")]
keep_the_copy_in_small_pages(base, size + ARENA_SIZE)?;
give(base, own, shared)?;
let rebased = native::alloc(private);
@@ -207,9 +214,79 @@ fn map_a_copy() -> Option<usize> {
native::free(rebased, private);
handed_over?;
#[cfg(target_arch = "aarch64")]
make_the_code_executable(base, shared)?;
Some(base)
}
#[cfg(target_arch = "aarch64")]
fn keep_the_copy_in_small_pages(base: usize, size: usize) -> Option<()> {
let memory = borrowed_memory()? as *mut c_void;
unsafe { _do_madvise(memory, base, size, MADV_NOHUGEPAGE) };
Some(())
}
#[cfg(target_arch = "aarch64")]
fn make_the_code_executable(base: usize, size: usize) -> Option<()> {
let memory = borrowed_memory()? as *mut c_void;
unsafe {
let apply = _apply_to_existing_page_range?;
let clean_to_unification = _caches_clean_inval_pou?;
if apply(memory, base, size, frida_pte_exec_trampoline, ptr::null_mut()) != 0 {
return None;
}
clean_to_unification(base, base + size);
flush_user_translations();
}
Some(())
}
#[cfg(target_arch = "aarch64")]
core::arch::global_asm!(
".pushsection .text,\"ax\",%progbits",
".balign 4",
".word 0xa8320d42",
".globl frida_pte_exec_trampoline",
".type frida_pte_exec_trampoline,%function",
"frida_pte_exec_trampoline:",
"bti c",
"b {inner}",
".popsection",
inner = sym grant_user_execute,
);
#[cfg(target_arch = "aarch64")]
unsafe extern "C" {
fn frida_pte_exec_trampoline(pte: *mut c_void, address: usize, data: *mut c_void) -> c_int;
}
#[cfg(target_arch = "aarch64")]
unsafe extern "C" fn grant_user_execute(entry: *mut c_void, _address: usize, _data: *mut c_void) -> c_int {
let entry = entry as *mut u64;
let revised = (unsafe { entry.read_volatile() } | PTE_READ_ONLY) & !PTE_USER_EXECUTE_NEVER;
unsafe { entry.write_volatile(revised) };
0
}
#[cfg(target_arch = "aarch64")]
unsafe fn flush_user_translations() {
unsafe {
core::arch::asm!(
"dsb ishst",
"tlbi vmalle1is",
"dsb ish",
"isb",
options(nostack, preserves_flags),
);
}
}
#[cfg(target_arch = "aarch64")]
const PTE_READ_ONLY: u64 = 1 << 7;
#[cfg(target_arch = "aarch64")]
const PTE_USER_EXECUTE_NEVER: u64 = 1 << 54;
fn give(destination: usize, source: usize, size: usize) -> Option<()> {
let opened = unsafe { open_user_access() };
let left =
@@ -287,6 +364,8 @@ pub unsafe extern "C" fn frida_cb_user(argument: *mut c_void) -> c_int {
let (arena, bootstrap, stack) = unsafe { ((*entry).arena, (*entry).bootstrap, (*entry).stack) };
native::free(entry as *mut u8, size_of::<Entry>());
COPY_TASK.store(native::current_task() as usize, core::sync::atomic::Ordering::Release);
let Some(places) = describe_registers() else {
return 0;
};
@@ -309,11 +388,13 @@ pub unsafe extern "C" fn frida_cb_user(argument: *mut c_void) -> c_int {
// What a task starts with in userland is at the top of the kernel stack it was given.
#[cfg(target_arch = "aarch64")]
fn registers_of_this_task(places: &Places) -> usize {
let stack = unsafe {
((native::current_task() as usize + places.stack_of_task) as *const usize).read()
};
registers_of_task(native::current_task() as usize, places)
}
stack + STACK_SPAN - places.size
fn registers_of_task(task: usize, places: &Places) -> usize {
let stack = unsafe { ((task + places.stack_of_task) as *const usize).read() };
stack + STACK_SPAN - TOP_OF_STACK_PADDING - places.size
}
#[cfg(target_arch = "x86_64")]
@@ -335,6 +416,273 @@ const TOP_OF_STACK_PADDING: usize = 8;
#[cfg(not(any(target_arch = "x86", target_arch = "arm")))]
const TOP_OF_STACK_PADDING: usize = 0;
pub fn thread_state(id: u32) -> Option<crate::bindings::GumThreadState> {
use crate::bindings::{
GumThreadState_GUM_THREAD_HALTED, GumThreadState_GUM_THREAD_RUNNING,
GumThreadState_GUM_THREAD_STOPPED, GumThreadState_GUM_THREAD_UNINTERRUPTIBLE,
GumThreadState_GUM_THREAD_WAITING,
};
let task = native::task_for_thread(id) as usize;
if task == 0 {
return None;
}
let at = field_offset("task_struct", "__state")?;
let running = unsafe { ((task + at) as *const u32).read() };
Some(if running == 0 {
GumThreadState_GUM_THREAD_RUNNING
} else if running & TASK_UNINTERRUPTIBLE != 0 {
GumThreadState_GUM_THREAD_UNINTERRUPTIBLE
} else if running & TASK_INTERRUPTIBLE != 0 {
GumThreadState_GUM_THREAD_WAITING
} else if running & (TASK_STOPPED | TASK_TRACED) != 0 {
GumThreadState_GUM_THREAD_STOPPED
} else {
GumThreadState_GUM_THREAD_HALTED
})
}
const TASK_INTERRUPTIBLE: u32 = 0x0001;
const TASK_UNINTERRUPTIBLE: u32 = 0x0002;
const TASK_STOPPED: u32 = 0x0004;
const TASK_TRACED: u32 = 0x0008;
fn registers_of_thread(id: u32) -> Option<usize> {
let task = native::task_for_thread(id) as usize;
if task == 0 {
return None;
}
let mut places = describe_registers()?;
if places.stack_of_task == 0 {
places.stack_of_task = field_offset("task_struct", "stack")?;
}
Some(registers_of_task(task, &places))
}
#[cfg(target_arch = "aarch64")]
pub fn capture_registers(id: u32) -> Option<crate::kernel::CpuState> {
let regs = registers_of_thread(id)?;
let general = regs as *const u64;
let mut x = [0u64; 29];
for slot in 0..29 {
x[slot] = unsafe { general.add(slot).read() };
}
Some(crate::kernel::CpuState {
x,
fp: unsafe { general.add(29).read() },
lr: unsafe { general.add(30).read() },
sp: unsafe { general.add(31).read() },
pc: unsafe { general.add(32).read() },
nzcv: unsafe { general.add(33).read() },
})
}
#[cfg(target_arch = "x86_64")]
pub fn capture_registers(id: u32) -> Option<crate::kernel::CpuState> {
let regs = registers_of_thread(id)?;
let at = |slot: usize| unsafe { ((regs + slot * 8) as *const u64).read() };
Some(crate::kernel::CpuState {
r15: at(0),
r14: at(1),
r13: at(2),
r12: at(3),
rbp: at(4),
rbx: at(5),
r11: at(6),
r10: at(7),
r9: at(8),
r8: at(9),
rax: at(10),
rcx: at(11),
rdx: at(12),
rsi: at(13),
rdi: at(14),
rip: at(16),
rsp: at(19),
})
}
#[cfg(target_arch = "x86")]
pub fn capture_registers(id: u32) -> Option<crate::kernel::CpuState> {
let regs = registers_of_thread(id)?;
let at = |slot: usize| unsafe { ((regs + slot * 4) as *const u32).read() };
Some(crate::kernel::CpuState {
ebx: at(0),
ecx: at(1),
edx: at(2),
esi: at(3),
edi: at(4),
ebp: at(5),
eax: at(6),
eip: at(12),
esp: at(15),
})
}
#[cfg(target_arch = "arm")]
pub fn capture_registers(id: u32) -> Option<crate::kernel::CpuState> {
let regs = registers_of_thread(id)?;
let at = |slot: usize| unsafe { ((regs + slot * 4) as *const u32).read() };
let mut r = [0u32; 8];
for slot in 0..8 {
r[slot] = at(slot);
}
Some(crate::kernel::CpuState {
pc: at(15),
sp: at(13),
cpsr: at(16),
r,
r8: at(8),
r9: at(9),
r10: at(10),
r11: at(11),
r12: at(12),
lr: at(14),
})
}
#[cfg(target_arch = "aarch64")]
pub const REGISTER_WORDS: usize = 34;
#[cfg(target_arch = "x86_64")]
pub const REGISTER_WORDS: usize = 17;
#[cfg(target_arch = "x86")]
pub const REGISTER_WORDS: usize = 9;
#[cfg(target_arch = "arm")]
pub const REGISTER_WORDS: usize = 17;
#[cfg(target_arch = "aarch64")]
pub fn words_from_registers(state: &crate::kernel::CpuState) -> [u64; REGISTER_WORDS] {
let mut words = [0u64; REGISTER_WORDS];
words[..29].copy_from_slice(&state.x);
words[29] = state.fp;
words[30] = state.lr;
words[31] = state.sp;
words[32] = state.pc;
words[33] = state.nzcv;
words
}
#[cfg(target_arch = "aarch64")]
pub fn registers_from_words(words: &[u64; REGISTER_WORDS]) -> crate::kernel::CpuState {
let mut x = [0u64; 29];
x.copy_from_slice(&words[..29]);
crate::kernel::CpuState {
x,
fp: words[29],
lr: words[30],
sp: words[31],
pc: words[32],
nzcv: words[33],
}
}
#[cfg(target_arch = "x86_64")]
pub fn words_from_registers(state: &crate::kernel::CpuState) -> [u64; REGISTER_WORDS] {
[
state.rip, state.r15, state.r14, state.r13, state.r12, state.r11, state.r10, state.r9,
state.r8, state.rdi, state.rsi, state.rbp, state.rsp, state.rbx, state.rdx, state.rcx,
state.rax,
]
}
#[cfg(target_arch = "x86_64")]
pub fn registers_from_words(words: &[u64; REGISTER_WORDS]) -> crate::kernel::CpuState {
crate::kernel::CpuState {
rip: words[0],
r15: words[1],
r14: words[2],
r13: words[3],
r12: words[4],
r11: words[5],
r10: words[6],
r9: words[7],
r8: words[8],
rdi: words[9],
rsi: words[10],
rbp: words[11],
rsp: words[12],
rbx: words[13],
rdx: words[14],
rcx: words[15],
rax: words[16],
}
}
#[cfg(target_arch = "x86")]
pub fn words_from_registers(state: &crate::kernel::CpuState) -> [u64; REGISTER_WORDS] {
[
state.eip as u64,
state.edi as u64,
state.esi as u64,
state.ebp as u64,
state.esp as u64,
state.ebx as u64,
state.edx as u64,
state.ecx as u64,
state.eax as u64,
]
}
#[cfg(target_arch = "x86")]
pub fn registers_from_words(words: &[u64; REGISTER_WORDS]) -> crate::kernel::CpuState {
crate::kernel::CpuState {
eip: words[0] as u32,
edi: words[1] as u32,
esi: words[2] as u32,
ebp: words[3] as u32,
esp: words[4] as u32,
ebx: words[5] as u32,
edx: words[6] as u32,
ecx: words[7] as u32,
eax: words[8] as u32,
}
}
#[cfg(target_arch = "arm")]
pub fn words_from_registers(state: &crate::kernel::CpuState) -> [u64; REGISTER_WORDS] {
let mut words = [0u64; REGISTER_WORDS];
for slot in 0..8 {
words[slot] = state.r[slot] as u64;
}
words[8] = state.r8 as u64;
words[9] = state.r9 as u64;
words[10] = state.r10 as u64;
words[11] = state.r11 as u64;
words[12] = state.r12 as u64;
words[13] = state.sp as u64;
words[14] = state.lr as u64;
words[15] = state.pc as u64;
words[16] = state.cpsr as u64;
words
}
#[cfg(target_arch = "arm")]
pub fn registers_from_words(words: &[u64; REGISTER_WORDS]) -> crate::kernel::CpuState {
let mut r = [0u32; 8];
for slot in 0..8 {
r[slot] = words[slot] as u32;
}
crate::kernel::CpuState {
pc: words[15] as u32,
sp: words[13] as u32,
cpsr: words[16] as u32,
r,
r8: words[8] as u32,
r9: words[9] as u32,
r10: words[10] as u32,
r11: words[11] as u32,
r12: words[12] as u32,
lr: words[14] as u32,
}
}
#[cfg(target_arch = "x86_64")]
unsafe fn write_selectors(registers: usize, _places: &Places) {
unsafe {
@@ -456,33 +804,62 @@ pub fn a_copy_has_something_to_say() -> bool {
unsafe { placements() }.values().any(|placed| what_it_says(placed).is_some())
}
pub fn report_what_the_copies_hit() {
#[cfg(target_arch = "aarch64")]
pub fn a_copy_wants_executable() -> bool {
unsafe { placements() }
.values()
.any(|placed| Arena::at(placed.arena).wants_executable().is_some())
}
#[cfg(target_arch = "aarch64")]
pub fn serve_executable_requests() {
for placed in unsafe { placements() }.values() {
let Some(hit) = what_it_says(placed) else {
let arena = Arena::at(placed.arena);
let Some((address, size)) = arena.wants_executable() else {
continue;
};
bump_to(placed.arena + super::arena::PROGRESS, NOTHING_MORE);
let granted = make_a_range_executable(placed.task, address as usize, size as usize);
arena.grant_executable(granted);
}
}
#[cfg(target_arch = "aarch64")]
fn make_a_range_executable(task: usize, base: usize, size: usize) -> bool {
let memory = unsafe { _get_task_mm(task as *mut c_void) };
if memory.is_null() {
return false;
}
unsafe { _kthread_use_mm(memory) };
let granted = make_the_code_executable(base, size).is_some();
unsafe { _kthread_unuse_mm(memory) };
unsafe { _mmput(memory) };
granted
}
pub fn a_copy_is_asking() -> bool {
unsafe { placements() }.values().any(|placed| {
let arena = Arena::at(placed.arena);
let said = arena.said();
if hit == super::user::SPOKE {
native::log(&alloc::format!("copy in {}: {}\n", pid_reported_by(placed), said));
} else if hit == super::user::PANICKED {
native::log(&alloc::format!("copy in {} gave up: {}\n", pid_reported_by(placed), said));
} else {
let kind = read_address(placed.arena + super::arena::FAULT_KIND);
native::log(&alloc::format!(
"copy in {} died on signal {} ({}) at {:#x}, pc {}, lr {}, image at {:#x}, {}\n",
pid_reported_by(placed),
kind as u32,
(kind >> 32) as u32,
read_address(placed.arena + super::arena::FAULT_ADDRESS),
relative_to_image(read_address(placed.arena + super::arena::FAULT_PC), placed),
relative_to_image(read_address(placed.arena + super::arena::FAULT_LR), placed),
placed.base,
said
));
arena.wants_registers().is_some() || arena.wants_executable().is_some()
})
}
pub fn serve_register_requests() {
for placed in unsafe { placements() }.values() {
let arena = Arena::at(placed.arena);
let Some(thread) = arena.wants_registers() else {
continue;
};
let words = capture_registers(thread).map(|state| words_from_registers(&state));
arena.deliver_registers(words.as_ref().map(|w| w.as_slice()));
}
}
pub fn report_what_the_copies_hit() {
for placed in unsafe { placements() }.values() {
if what_it_says(placed).is_none() {
continue;
}
bump_to(placed.arena + super::arena::PROGRESS, NOTHING_MORE);
}
}
@@ -500,19 +877,11 @@ fn bump_to(address: usize, value: u32) {
unsafe { (address as *mut u32).write_volatile(value) };
}
fn relative_to_image(address: u64, placed: &Placement) -> String {
let base = placed.base as u64;
let size = crate::own_range().1 as u64;
if address >= base && address - base < size {
alloc::format!("image+{:#x}", address - base)
} else {
alloc::format!("{:#x}", address)
}
}
const NOTHING_MORE: u32 = 0;
fn watch_the_threads() {
return;
#[allow(unreachable_code)]
if WATCHING.swap(true, core::sync::atomic::Ordering::AcqRel) {
return;
}
@@ -526,6 +895,8 @@ fn watch_the_threads() {
}
pub fn hold_what_is_spawned() {
return;
#[allow(unreachable_code)]
if HOLDING.swap(true, core::sync::atomic::Ordering::AcqRel) {
return;
}
@@ -558,6 +929,49 @@ pub unsafe extern "C" fn frida_cb_exec(_data: *mut c_void, task: *mut c_void, _w
native::wake(task as *const u8);
}
#[cfg(not(target_arch = "x86"))]
pub fn watch_for_named_spawns() {
if RENAME_WATCHING.swap(true, core::sync::atomic::Ordering::AcqRel) {
return;
}
unsafe {
let interceptor = gum_interceptor_obtain();
gum_interceptor_begin_transaction(interceptor);
gum_interceptor_replace_fast(interceptor, ___set_task_comm as *mut c_void,
frida_replaced_set_task_comm as *mut c_void, &raw mut ORIGINAL_SET_TASK_COMM,
ptr::null());
gum_interceptor_end_transaction(interceptor);
}
}
#[cfg(target_arch = "x86")]
pub fn watch_for_named_spawns() {
}
#[cfg(not(target_arch = "x86"))]
static RENAME_WATCHING: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
#[cfg(not(target_arch = "x86"))]
static mut ORIGINAL_SET_TASK_COMM: *mut c_void = ptr::null_mut();
#[cfg(not(target_arch = "x86"))]
unsafe extern "C" fn frida_replaced_set_task_comm(task: *mut c_void, buf: *const core::ffi::c_char,
exec: c_int) {
let name = unsafe { core::ffi::CStr::from_ptr(buf) };
if super::spawn::claim_if_wanted(name.to_bytes()) {
if let Some(id) = id_of(task as usize) {
native::send_signal(STOP, task as usize);
super::spawn::note_pending_spawn(id, name.to_bytes());
native::wake(ptr::null());
}
}
let original: unsafe extern "C" fn(*mut c_void, *const core::ffi::c_char, c_int) =
unsafe { core::mem::transmute(ORIGINAL_SET_TASK_COMM) };
unsafe { original(task, buf, exec) };
}
static HOLDING: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
const STOP: c_int = 19;
@@ -586,9 +1000,7 @@ fn note_a_thread(task: usize, is_gone: bool) {
};
Arena::at(placed.arena).note_a_thread(thread, is_gone);
if !placed.hears.is_null() {
native::leave_a_word(placed.hears);
}
wake_the_copy(placed);
}
fn group_of(task: usize) -> Option<u32> {
@@ -603,10 +1015,12 @@ pub fn id_of(task: usize) -> Option<u32> {
Some(unsafe { ((task + at) as *const u32).read() })
}
static COPY_TASK: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
static WATCHING: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
pub fn detach_from_process(id: u32) -> bool {
let Some(placed) = (unsafe { placements() }.remove(&id)) else {
let Some(placed) = unsafe { placements() }.remove(&id) else {
return false;
};
@@ -640,9 +1054,7 @@ pub fn stop_copies() {
fn ask_it_to_leave(placed: &Placement) {
let arena = Arena::at(placed.arena);
arena.tell_it_to_go();
if !placed.hears.is_null() {
native::leave_a_word(placed.hears);
}
wake_the_copy(placed);
let waited_on = &placed.arena as *const usize as *const u8;
while !arena.has_gone() {
@@ -676,20 +1088,18 @@ fn take_back_what_it_was_given(placed: &Placement, memory: *mut c_void) {
const LEAVING_GRACE_US: u64 = 100_000;
pub fn tell_the_copy(id: u32) {
let Some(placed) = (unsafe { placements() }).get_mut(&id) else {
let Some(placed) = (unsafe { placements() }).get(&id) else {
return;
};
bump(placed.arena + WOKEN);
if placed.hears.is_null() {
return;
}
native::leave_a_word(placed.hears);
wake_the_copy(placed);
}
fn read_address(address: usize) -> u64 {
unsafe { (address as *const u64).read_volatile() }
fn wake_the_copy(placed: &Placement) {
if !placed.hears.is_null() {
native::leave_a_word(placed.hears);
}
}
fn read_word(address: usize) -> u32 {
@@ -734,12 +1144,11 @@ const PROT_WRITE: usize = 2;
const PROT_EXEC: usize = 4;
const MAP_PRIVATE: usize = 2;
const MAP_ANONYMOUS: usize = 0x20;
#[cfg(target_arch = "aarch64")]
const MADV_NOHUGEPAGE: c_int = 15;
const FIRST_ERROR_ADDRESS: usize = usize::MAX - 4095;
const PAGE_SIZE: usize = 4096;
const FOLL_WRITE: c_int = 1;
const FUTEX_WAIT: c_int = 128;
const FUTEX_WAKE: c_int = 129;
const WAKE_EVERY_WAITER: u32 = i32::MAX as u32;
const CLONE_VM: usize = 0x100;
const CLONE_FS: usize = 0x200;
@@ -770,6 +1179,8 @@ unsafe extern "C" {
static ___tracepoint_sched_process_exit: *mut c_void;
static ___tracepoint_sched_process_exec: *mut c_void;
#[cfg(not(target_arch = "x86"))]
static ___set_task_comm: unsafe extern "C" fn(*mut c_void, *const core::ffi::c_char, c_int);
#[cfg(not(target_arch = "x86"))]
static _vunmap: unsafe extern "C" fn(*mut c_void);
#[cfg(not(any(target_arch = "arm", target_arch = "x86", target_arch = "x86_64")))]
static ___arch_copy_to_user: unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> usize;
@@ -800,6 +1211,20 @@ unsafe extern "C" {
#[cfg(not(target_arch = "x86"))]
static _user_mode_thread:
unsafe extern "C" fn(unsafe extern "C" fn(*mut c_void) -> c_int, *mut c_void, usize) -> c_int;
#[cfg(target_arch = "aarch64")]
static _apply_to_existing_page_range: Option<
unsafe extern "C" fn(
*mut c_void,
usize,
usize,
unsafe extern "C" fn(*mut c_void, usize, *mut c_void) -> c_int,
*mut c_void,
) -> c_int,
>;
#[cfg(target_arch = "aarch64")]
static _caches_clean_inval_pou: Option<unsafe extern "C" fn(usize, usize)>;
#[cfg(target_arch = "aarch64")]
static _do_madvise: unsafe extern "C" fn(*mut c_void, usize, usize, c_int) -> c_int;
}
#[cfg(target_arch = "x86")]
@@ -859,6 +1284,7 @@ const EXEC_PROBE: unsafe extern "C" fn(*mut c_void, *mut c_void, c_int, *mut c_v
const EXEC_PROBE: unsafe extern "C" fn(*mut c_void, *mut c_void, c_int, *mut c_void) =
frida_cb_exec;
#[cfg(target_arch = "x86")]
const THREAD_APPEARED_PROBE: unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void) =
frida_kcb_thread_appeared;
+21 -5
View File
@@ -1,4 +1,7 @@
use core::ffi::{c_int, c_long, c_void};
use core::ffi::{c_int, c_void};
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use core::ffi::c_long;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use core::ptr;
use crate::bindings::{
@@ -90,7 +93,7 @@ unsafe fn build_resume_stub() {
stub.write(0xbf);
(stub.add(1) as *mut u32).write((&raw mut RESUME_CONTEXT) as u32);
stub.add(5).write(0xe9);
let rel = (frida_i386_resume as usize).wrapping_sub(stub as usize + 10) as u32;
let rel = (frida_i386_resume as *const () as usize).wrapping_sub(stub as usize + 10) as u32;
(stub.add(6) as *mut u32).write(rel);
RESUME_STUB = stub as usize;
}
@@ -316,7 +319,7 @@ pub fn install() {
unsafe {
let addr = target as *mut u32;
_patch_text(addr.add(1) as *mut c_void, frida_arm_fixup as usize as u32);
_patch_text(addr.add(1) as *mut c_void, frida_arm_fixup as *const () as usize as u32);
_patch_text(addr as *mut c_void, 0xe51ff004);
}
}
@@ -449,7 +452,7 @@ pub fn install() {
unsafe { FRIDA_ARM64_ORIGINAL_CONT = target.add(4) as u64 };
let jump = detour_to(frida_arm64_fixup as usize);
let jump = detour_to(frida_arm64_fixup as *const () as usize);
unsafe {
let addr = target as *mut u32;
_aarch64_insn_patch_text_nosync(addr.add(2) as *mut c_void, jump[2]);
@@ -523,13 +526,26 @@ unsafe fn recovered(regs: *mut c_void) -> bool {
)
};
if handled == 0 {
return false;
let esr: u64;
unsafe { core::arch::asm!("mrs {}, esr_el1", out(reg) esr, options(nomem, nostack)) };
crate::note_unhandled_fault(esr, faulted_at as u64, accessed as u64);
context.pc = frida_arm64_park as *const () as usize as u64;
unsafe { restore_context(regs, &context) };
return true;
}
unsafe { restore_context(regs, &context) };
true
}
#[cfg(target_arch = "aarch64")]
extern "C" fn frida_arm64_park() -> ! {
static mut PARK_TOKEN: u8 = 0;
loop {
crate::kernel::wait(core::ptr::addr_of!(PARK_TOKEN), Some(1_000_000), &mut || false);
}
}
#[cfg(target_arch = "aarch64")]
unsafe fn context_of(regs: *mut c_void) -> GumCpuContext {
let r = regs as *const u64;
+2
View File
@@ -82,6 +82,8 @@ pub fn wait(_token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut()
unsafe { frida_kmod_wait_commit(seq, timeout_us.map_or(-1, |us| us as i64)) };
}
pub fn poke_loop_wakeup() {}
pub fn wake(_token: *const u8) {
unsafe { frida_kmod_wake() };
}
+27 -6
View File
@@ -1,7 +1,7 @@
use alloc::string::String;
use alloc::vec::Vec;
use core::ffi::c_void;
use core::ffi::{CStr, c_void};
use core::ptr;
use crate::bindings::{
@@ -14,6 +14,7 @@ use crate::bindings::{
};
use crate::bindings::{GumElfSymbolBind_GUM_ELF_BIND_LOCAL, GumElfSymbolDetails};
use crate::gum::{self, FoundExportCallback, FoundSymbolCallback};
use crate::kernel::MemoryRegion;
use alloc::ffi::CString;
use super::user::{EXECUTABLE, READABLE, WRITABLE, contents_of};
@@ -82,7 +83,7 @@ fn announce(image: &Image) {
let module = gum::gum_native_module_new(&image.path, "", &range);
unsafe {
gum_barebone_register_module(unsafe { REGISTRY }, module);
gum_barebone_register_module(REGISTRY, module);
g_object_unref(module as gpointer);
}
}
@@ -111,6 +112,22 @@ pub fn enumerate_exports_in_range(from: u64, to: u64, found: &mut FoundExportCal
}
}
pub fn export_named(wanted: &str) -> u64 {
let mut found: u64 = 0;
for image in mapped_images() {
enumerate_exports_in_range(image.base, image.base + image.size, &mut |name, address| {
if !name.is_null() && unsafe { CStr::from_ptr(name as *const _) }.to_bytes() == wanted.as_bytes() {
found = address;
}
found == 0
});
if found != 0 {
break;
}
}
found
}
pub fn enumerate_symbols_in_range(from: u64, to: u64, found: &mut FoundSymbolCallback<'_>) {
let Some(image) = mapped_images().into_iter().find(|image| image.base == from) else {
return;
@@ -257,9 +274,9 @@ pub fn enumerate_ranges(found: &mut dyn FnMut(u64, u64, u32)) {
}
}
pub fn protection_at(address: u64) -> u32 {
pub fn region_at(address: u64) -> Option<MemoryRegion> {
if super::user::range_is_ours(address) {
return 0;
return None;
}
let listed = contents_of(c"/proc/self/maps");
@@ -270,11 +287,15 @@ pub fn protection_at(address: u64) -> u32 {
};
if address >= mapping.start && address < mapping.end {
return mapping.protection;
return Some(MemoryRegion {
base: mapping.start,
size: mapping.end - mapping.start,
protection: mapping.protection,
});
}
}
0
None
}
fn mapped_images() -> Vec<Image> {
+3
View File
@@ -27,6 +27,9 @@ mod arena;
#[cfg(feature = "linux-injected")]
mod facade;
#[cfg(feature = "linux-injected")]
pub mod hostlink_pipe_goldfish;
#[cfg(feature = "linux-injected")]
pub mod hostlink_pipe_vsock;
#[cfg(feature = "linux-injected")]
mod injection;
#[cfg(feature = "linux-injected")]
-5
View File
@@ -651,10 +651,6 @@ impl Regions {
}
}
fn reads_as_pointer(address: usize) -> bool {
address >= LOWEST_MAPPING && address & (OFFSET_SIZE - 1) == 0
}
fn try_read_word(address: usize) -> Option<usize> {
Some(usize::from_ne_bytes(try_read(address)?))
}
@@ -767,7 +763,6 @@ const REGION_WINDOW: usize = 512;
const MAX_REGIONS: usize = 16;
const OFFSET_SIZE: usize = 4;
const SYMBOL_SIZE: usize = if WORD == 8 { 24 } else { 16 };
const LOWEST_MAPPING: usize = 1 << 20;
const MAX_SYMBOLS: usize = 1 << 20;
const MAX_EXPORTS: usize = 1 << 16;
const EXPORTS_SAMPLED: usize = 64;
+269 -38
View File
@@ -8,6 +8,8 @@ use core::ffi::{c_char, c_int, c_uint, c_void};
use core::ptr;
use core::ptr::read_volatile;
use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
#[cfg(target_arch = "aarch64")]
use core::sync::atomic::AtomicUsize;
use crate::kernel::ThreadEntry;
@@ -57,8 +59,12 @@ pub fn release_interrupt() {
unsafe { _free_irq(interrupt.irq, interrupt.target) };
}
pub fn take_the_file(descriptor: u32) -> *mut c_void {
unsafe { _fget(descriptor) }
pub fn take_the_file_of(task: *mut c_void, descriptor: u32) -> *mut c_void {
unsafe { _fget_task(task, descriptor) }
}
pub fn task_for_thread(id: u32) -> *mut c_void {
unsafe { _find_task_by_vpid(id) }
}
pub fn let_the_file_go(file: *mut c_void) {
@@ -67,16 +73,122 @@ pub fn let_the_file_go(file: *mut c_void) {
pub fn wait_for_a_word(file: *mut c_void) -> bool {
let mut byte = 0u8;
let mut at = 0i64;
let mut position: i64 = 0;
unsafe { _kernel_read(file, &mut byte, 1, &mut at) == 1 }
unsafe { _kernel_read(file, &mut byte, 1, &mut position) == 1 }
}
pub fn leave_a_word(file: *mut c_void) {
let byte = 0u8;
let mut at = 0i64;
// kernel_read and kernel_write take the position by reference; the pair above
// hands them a literal one because the file it reads is a counter.
pub unsafe fn pipe_read(file: *mut c_void, into: *mut u8, count: usize, position: *mut i64) -> isize {
unsafe { _kernel_read(file, into, count, position) }
}
unsafe { _kernel_write(file, &byte, 1, &mut at) };
#[cfg(not(target_arch = "x86"))]
pub unsafe fn pipe_write(file: *mut c_void, from: *const u8, count: usize, position: *mut i64) -> isize {
let write: unsafe extern "C" fn(*mut c_void, *const u8, usize, *mut i64) -> isize =
unsafe { core::mem::transmute(_kernel_write) };
unsafe { write(file, from, count, position) }
}
#[cfg(target_arch = "x86")]
pub unsafe fn pipe_write(file: *mut c_void, from: *const u8, count: usize, position: *mut i64) -> isize {
unsafe { _kernel_write(file, from, count, position) }
}
#[cfg(not(target_arch = "x86"))]
pub fn leave_a_word(file: *mut c_void) {
let one: u64 = 1;
unsafe { _kernel_write(file, &one as *const u64 as *const u8, 8, 0) };
}
#[cfg(target_arch = "x86")]
pub fn leave_a_word(file: *mut c_void) {
let one: u64 = 1;
unsafe { _kernel_write(file, &one as *const u64 as *const u8, 8, core::ptr::null_mut()) };
}
// A CONFIG_CFI_CLANG kernel checks the 4-byte type id in front of any function it
// calls indirectly. Our callbacks carry no such id, so hand the kernel a small
// executable thunk instead: the matching id (copied from a kernel function of the
// same signature), a BTI landing pad, then a direct branch to the real callback.
#[cfg(target_arch = "aarch64")]
static THREAD_THUNK: AtomicUsize = AtomicUsize::new(0);
#[cfg(target_arch = "aarch64")]
static INTERRUPT_THUNK: AtomicUsize = AtomicUsize::new(0);
#[cfg(target_arch = "aarch64")]
pub(crate) fn kcfi_thunk(callback: usize, type_ref: Option<usize>) -> usize {
let Some(ref_fn) = type_ref else { return callback; };
let type_id = unsafe { ((ref_fn - 4) as *const u32).read() };
let thunk = alloc_code(32) as usize;
if thunk == 0 {
return callback;
}
// The callback lives in the agent, which may be far beyond a direct branch's +-128MB, so
// load its full address and branch indirectly. The agent's execmem pages are not BTI-guarded,
// so br needs no landing pad at the callback. Layout, with the type id in the word before the
// entry so the caller's CONFIG_CFI_CLANG check passes:
// +0 type id
// +4 bti c <- entry (thunk + 4)
// +8 ldr x16, #8 (loads the address at +16)
// +12 br x16
// +16 callback address (u64)
let insns: [u32; 6] = [
type_id,
0xd503245f, // bti c
0x58000050, // ldr x16, #8
0xd61f0200, // br x16
(callback & 0xffff_ffff) as u32,
(callback >> 32) as u32,
];
set_protection(thunk as u64, 32, GUM_PAGE_EXECUTE);
patch_text(thunk as u64, insns.as_ptr() as *const u8, 24);
thunk + 4
}
#[cfg(not(target_arch = "aarch64"))]
pub(crate) fn kcfi_thunk(callback: usize, _type_ref: Option<usize>) -> usize {
callback
}
#[cfg(target_arch = "aarch64")]
fn kcfi_thunk_cached(cache: &AtomicUsize, callback: usize, type_ref: Option<usize>) -> usize {
let existing = cache.load(Ordering::Acquire);
if existing != 0 {
return existing;
}
let thunk = kcfi_thunk(callback, type_ref);
cache.store(thunk, Ordering::Release);
thunk
}
#[cfg(target_arch = "aarch64")]
fn thread_entry() -> unsafe extern "C" fn(*mut c_void) -> c_int {
let ref_fn = unsafe { _kthread_worker_fn.map(|f| f as usize) };
unsafe {
core::mem::transmute(kcfi_thunk_cached(&THREAD_THUNK, frida_cb_thread as *const () as usize,
ref_fn))
}
}
#[cfg(not(target_arch = "aarch64"))]
fn thread_entry() -> unsafe extern "C" fn(*mut c_void) -> c_int {
THREAD_ENTRY
}
#[cfg(target_arch = "aarch64")]
fn interrupt_entry() -> unsafe extern "C" fn(c_int, *mut c_void) -> c_int {
let ref_fn = unsafe { _vring_interrupt.map(|f| f as usize) };
unsafe {
core::mem::transmute(kcfi_thunk_cached(&INTERRUPT_THUNK,
frida_cb_interrupt as *const () as usize, ref_fn))
}
}
#[cfg(not(target_arch = "aarch64"))]
fn interrupt_entry() -> unsafe extern "C" fn(c_int, *mut c_void) -> c_int {
INTERRUPT_ENTRY
}
pub fn spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> isize {
@@ -84,8 +196,9 @@ pub fn spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> isize {
unsafe {
trampoline.write(Trampoline { entry, parameter });
let te = thread_entry();
let task = _kthread_create_on_node(
THREAD_ENTRY,
te,
trampoline as *mut c_void,
NUMA_NO_NODE,
c"frida".as_ptr(),
@@ -107,8 +220,10 @@ pub fn alloc(size: usize) -> *mut u8 {
unsafe {
if let Some(f) = ___kmalloc_noprof {
f(size, GFP_KERNEL)
} else if let Some(f) = _kmalloc {
f(size, GFP_KERNEL)
} else {
_kmalloc.unwrap()(size, GFP_KERNEL)
___kmalloc.unwrap()(size, GFP_KERNEL)
}
}
}
@@ -117,6 +232,17 @@ pub fn free(ptr: *mut u8, _size: usize) {
unsafe { _kfree(ptr) };
}
pub fn alloc_heap(size: usize) -> *mut u8 {
unsafe {
if let Some(f) = _vmalloc {
return f(size);
}
}
alloc(size)
}
#[cfg(target_arch = "x86")]
pub fn alloc_code(size: usize) -> *mut u8 {
unsafe {
@@ -183,14 +309,15 @@ pub fn patch_text(address: u64, data: *const u8, len: usize) -> bool {
let count = len / 4;
let mut addrs = alloc::vec::Vec::with_capacity(count);
let mut insns = alloc::vec::Vec::with_capacity(count);
for i in 0..count {
addrs.push((address as usize + (i * 4)) as *mut c_void);
insns.push(unsafe { (data as *const u32).add(i).read_unaligned() });
let insn = unsafe { (data as *const u32).add(i).read_unaligned() };
let at = (address as usize + (i * 4)) as *mut c_void;
if unsafe { _aarch64_insn_patch_text_nosync(at, insn) } != 0 {
return false;
}
}
unsafe { _aarch64_insn_patch_text(addrs.as_mut_ptr(), insns.as_ptr(), count as c_int) == 0 }
true
}
#[cfg(target_arch = "arm")]
@@ -248,7 +375,9 @@ pub fn set_protection(address: u64, size: usize, protection: u32) -> bool {
true
}
#[cfg(not(target_arch = "x86"))]
const GUM_PAGE_WRITE: u32 = 2;
#[cfg(not(target_arch = "x86"))]
const GUM_PAGE_EXECUTE: u32 = 4;
pub fn current_process_id() -> u32 {
@@ -288,6 +417,8 @@ pub fn wake(_token: *const u8) {
unsafe { ___wake_up(waiters(), TASK_NORMAL, WAKE_EVERY_WAITER, ptr::null_mut()) };
}
pub fn poke_loop_wakeup() {}
fn queue_this_thread(entry: &mut [usize; WAIT_ENTRY_WORDS]) -> *mut c_void {
let entry = entry.as_mut_ptr();
@@ -306,7 +437,6 @@ fn queue_this_thread(entry: &mut [usize; WAIT_ENTRY_WORDS]) -> *mut c_void {
fn waiters() -> *mut c_void {
let queue = (&raw mut WAITERS) as *mut c_void;
if !WAITERS_CLAIMED.swap(true, Ordering::AcqRel) {
unsafe {
___init_waitqueue_head(queue, c"frida".as_ptr(), (&raw mut WAITERS_KEY) as *mut c_void)
@@ -331,16 +461,34 @@ pub fn pci_interrupt(bus: u8, devfn: u8) -> Option<u32> {
return None;
}
let line = unsafe { _pci_irq_vector(device, 0) };
// pci_irq_vector arrived in 4.10; older kernels resolve a legacy INTx line through the
// device-tree PCI mapping instead.
let line = if let Some(f) = pci_irq_vector() {
unsafe { f(device, 0) }
} else if let Some(f) = unsafe { _of_irq_parse_and_map_pci } {
unsafe { f(device, 0, 0) }
} else {
-1
};
unsafe { _pci_dev_put(device) };
if line < 0 {
if line <= 0 {
return None;
}
Some(line as u32)
}
#[cfg(target_arch = "x86")]
fn pci_irq_vector() -> Option<unsafe extern "C" fn(*mut c_void, c_uint) -> c_int> {
Some(_pci_irq_vector)
}
#[cfg(not(target_arch = "x86"))]
fn pci_irq_vector() -> Option<unsafe extern "C" fn(*mut c_void, c_uint) -> c_int> {
unsafe { _pci_irq_vector }
}
#[cfg(target_arch = "arm")]
pub fn mmio_interrupt(mmio_base: u64) -> Option<u32> {
let path = alloc::format!("/virtio_mmio@{:x}\0", mmio_base);
@@ -374,10 +522,26 @@ pub fn monotonic_micros() -> i64 {
pub fn wall_clock_micros() -> (u32, u32) {
let mut now = Timespec64 { tv_sec: 0, tv_nsec: 0 };
unsafe { _ktime_get_real_ts64(&mut now) };
unsafe {
if let Some(f) = ktime_get_real_ts64() {
f(&mut now);
} else if let Some(f) = _getnstimeofday64 {
f(&mut now);
}
}
(now.tv_sec as u32, (now.tv_nsec / 1000) as u32)
}
#[cfg(target_arch = "x86")]
fn ktime_get_real_ts64() -> Option<unsafe extern "C" fn(*mut Timespec64)> {
Some(_ktime_get_real_ts64)
}
#[cfg(not(target_arch = "x86"))]
fn ktime_get_real_ts64() -> Option<unsafe extern "C" fn(*mut Timespec64)> {
unsafe { _ktime_get_real_ts64 }
}
// Linux keeps the running task where the architecture puts it: in a register
// of its own on arm64, and behind the per-CPU segment elsewhere, at an offset
// the host resolves along with everything else.
@@ -461,7 +625,7 @@ pub fn install_interrupt_handler(
_request_threaded_irq(
irq,
INTERRUPT_ENTRY,
interrupt_entry(),
None,
IRQF_SHARED,
c"frida".as_ptr(),
@@ -475,14 +639,40 @@ pub fn map_io(phys_addr: u64, size: u64) -> *mut c_void {
unsafe { _ioremap(phys_addr as u32, size as usize) }
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[cfg(target_arch = "x86_64")]
pub fn map_io(phys_addr: u64, size: u64) -> *mut c_void {
unsafe {
if let Some(f) = _ioremap {
return f(phys_addr as usize, size as usize);
}
if let Some(f) = _ioremap_nocache {
return f(phys_addr as usize, size as usize);
}
}
core::ptr::null_mut()
}
#[cfg(target_arch = "x86")]
pub fn map_io(phys_addr: u64, size: u64) -> *mut c_void {
unsafe { _ioremap(phys_addr as usize, size as usize) }
}
#[cfg(not(any(target_arch = "arm", target_arch = "x86", target_arch = "x86_64")))]
pub fn map_io(phys_addr: u64, size: u64) -> *mut c_void {
unsafe { _generic_ioremap_prot(phys_addr, size as usize, page_of(DEVICE_MEMORY) as usize) }
let prot = page_of(DEVICE_MEMORY);
if let Some(f) = unsafe { _generic_ioremap_prot } {
return unsafe { f(phys_addr, size as usize, prot as usize) };
}
// Kernels predating generic_ioremap_prot (added in 5.x) map through __ioremap, which takes the
// same phys/size and a pgprot carrying the device-memory attributes.
#[cfg(target_arch = "aarch64")]
if let Some(f) = unsafe { ___ioremap } {
return unsafe { f(phys_addr, size as usize, prot) };
}
core::ptr::null_mut()
}
pub fn map_pages(pages: *mut c_void, count: usize) -> *mut c_void {
@@ -508,7 +698,7 @@ const PAGE_KERNEL: u64 = 0x163;
#[cfg(target_arch = "aarch64")]
fn page_of(memory: u64) -> u64 {
PTE_TYPE_PAGE | PTE_AF | PTE_SHARED | PTE_WRITE | PTE_PXN | PTE_UXN | (attribute_of(memory) << 2)
PTE_TYPE_PAGE | PTE_AF | PTE_SHARED | PTE_WRITE | PTE_DIRTY | PTE_PXN | PTE_UXN | (attribute_of(memory) << 2)
}
#[cfg(target_arch = "aarch64")]
@@ -532,17 +722,26 @@ fn attribute_of(memory: u64) -> u64 {
#[cfg(target_arch = "aarch64")]
pub fn virt_to_phys(vaddr: u64) -> u64 {
let memstart = unsafe { read_volatile(_memstart_addr) };
vaddr - linear_map_base() + memstart
vaddr - linear_map_base(vaddr) + memstart
}
// The linear map sat at the top of the kernel half (PAGE_OFFSET = -1 << (VA_BITS - 1)) until the
// 5.4 VA-space flip moved it to the bottom (-1 << VA_BITS). Both candidates are 256 GB apart, so a
// linear-map address selects its layout by being at or above the higher (older) base.
#[cfg(target_arch = "aarch64")]
fn linear_map_base() -> u64 {
fn linear_map_base(vaddr: u64) -> u64 {
let tcr_el1: u64;
unsafe {
core::arch::asm!("mrs {}, tcr_el1", out(reg) tcr_el1, options(nomem, nostack));
}
let kernel_address_bits = 64 - ((tcr_el1 >> TCR_T1SZ_SHIFT) & TCR_T1SZ_MASK);
u64::MAX << kernel_address_bits
let flipped = u64::MAX << kernel_address_bits;
let classic = u64::MAX << (kernel_address_bits - 1);
if vaddr >= classic {
classic
} else {
flipped
}
}
#[cfg(target_arch = "arm")]
@@ -633,15 +832,25 @@ const WAIT_ENTRY_WAKE: usize = 2;
const WAIT_ENTRY_QUEUED: usize = 3;
const WAIT_QUEUE_WORDS: usize = 16;
const LOCK_KEY_WORDS: usize = 8;
#[cfg(target_arch = "aarch64")]
const DEVICE_MEMORY: u64 = 0x04;
const ORDINARY_MEMORY: u64 = 0xff;
#[cfg(target_arch = "aarch64")]
const ATTRIBUTES_KEPT: u64 = 8;
const VM_MAP: u64 = 0x4;
#[cfg(target_arch = "aarch64")]
const PTE_TYPE_PAGE: u64 = 0x3;
#[cfg(target_arch = "aarch64")]
const PTE_SHARED: u64 = 3 << 8;
#[cfg(target_arch = "aarch64")]
const PTE_AF: u64 = 1 << 10;
#[cfg(target_arch = "aarch64")]
const PTE_WRITE: u64 = 1 << 51;
#[cfg(target_arch = "aarch64")]
const PTE_DIRTY: u64 = 1 << 55;
#[cfg(target_arch = "aarch64")]
const PTE_PXN: u64 = 1 << 53;
#[cfg(target_arch = "aarch64")]
const PTE_UXN: u64 = 1 << 54;
const IRQF_SHARED: usize = 0x80;
const IRQ_NONE: c_int = 0;
@@ -672,15 +881,23 @@ unsafe extern "C" {
#[cfg(not(target_arch = "x86"))]
static _fget: unsafe extern "C" fn(c_uint) -> *mut c_void;
#[cfg(not(target_arch = "x86"))]
static _fget_task: unsafe extern "C" fn(*mut c_void, c_uint) -> *mut c_void;
#[cfg(not(target_arch = "x86"))]
static _find_task_by_vpid: unsafe extern "C" fn(c_uint) -> *mut c_void;
#[cfg(not(target_arch = "x86"))]
static _kernel_read: unsafe extern "C" fn(*mut c_void, *mut u8, usize, *mut i64) -> isize;
#[cfg(not(target_arch = "x86"))]
static _kernel_write: unsafe extern "C" fn(*mut c_void, *const u8, usize, *mut i64) -> isize;
#[cfg(not(target_arch = "x86"))]
static _kernel_write: unsafe extern "C" fn(*mut c_void, *const u8, usize, i64) -> isize;
// Allocation profiling renamed the entry points in 6.10; before that the
// size-plus-flags pair went to __kmalloc.
static ___kmalloc_noprof: Option<unsafe extern "C" fn(usize, u32) -> *mut u8>;
static _kmalloc: Option<unsafe extern "C" fn(usize, u32) -> *mut u8>;
// Kernels before ~5.x inline kmalloc, so kallsyms names only __kmalloc.
static ___kmalloc: Option<unsafe extern "C" fn(usize, u32) -> *mut u8>;
#[cfg(not(target_arch = "x86"))]
static _kfree: unsafe extern "C" fn(*mut u8);
static _vmalloc: Option<unsafe extern "C" fn(usize) -> *mut u8>;
// Executable memory moved out of the module loader in 6.12.
static _execmem_alloc: Option<unsafe extern "C" fn(u32, usize) -> *mut u8>;
static _set_memory_rw: Option<unsafe extern "C" fn(usize, c_int) -> c_int>;
@@ -688,8 +905,13 @@ unsafe extern "C" {
static _set_memory_x: Option<unsafe extern "C" fn(usize, c_int) -> c_int>;
static _set_memory_nx: Option<unsafe extern "C" fn(usize, c_int) -> c_int>;
#[cfg(target_arch = "aarch64")]
static _aarch64_insn_patch_text:
unsafe extern "C" fn(*mut *mut c_void, *const u32, c_int) -> c_int;
static _aarch64_insn_patch_text_nosync: unsafe extern "C" fn(*mut c_void, u32) -> c_int;
// Addresses of kernel functions of the same C signature as our kthread and IRQ
// callbacks; the kCFI type-id the kernel checks sits in the word before each.
#[cfg(target_arch = "aarch64")]
static _kthread_worker_fn: Option<unsafe extern "C" fn()>;
#[cfg(target_arch = "aarch64")]
static _vring_interrupt: Option<unsafe extern "C" fn()>;
#[cfg(target_arch = "arm")]
static _patch_text: unsafe extern "C" fn(*mut c_void, u32);
#[cfg(target_arch = "x86_64")]
@@ -712,7 +934,8 @@ unsafe extern "C" {
#[cfg(not(target_arch = "x86"))]
static _pci_get_domain_bus_and_slot: unsafe extern "C" fn(c_int, c_uint, c_uint) -> *mut c_void;
#[cfg(not(target_arch = "x86"))]
static _pci_irq_vector: unsafe extern "C" fn(*mut c_void, c_uint) -> c_int;
static _pci_irq_vector: Option<unsafe extern "C" fn(*mut c_void, c_uint) -> c_int>;
static _of_irq_parse_and_map_pci: Option<unsafe extern "C" fn(*mut c_void, u8, u8) -> c_int>;
#[cfg(not(target_arch = "x86"))]
static _pci_dev_put: unsafe extern "C" fn(*mut c_void);
#[cfg(not(target_arch = "x86"))]
@@ -720,7 +943,8 @@ unsafe extern "C" {
#[cfg(not(target_arch = "x86"))]
static _ktime_get_mono_fast_ns: unsafe extern "C" fn() -> u64;
#[cfg(not(target_arch = "x86"))]
static _ktime_get_real_ts64: unsafe extern "C" fn(*mut Timespec64);
static _ktime_get_real_ts64: Option<unsafe extern "C" fn(*mut Timespec64)>;
static _getnstimeofday64: Option<unsafe extern "C" fn(*mut Timespec64)>;
#[cfg(not(target_arch = "x86"))]
static _send_sig: unsafe extern "C" fn(c_int, *mut c_void, c_int) -> c_int;
#[cfg(not(target_arch = "x86"))]
@@ -733,7 +957,10 @@ unsafe extern "C" {
usize,
*const c_char,
*mut c_void,
) -> c_int; static _generic_ioremap_prot: unsafe extern "C" fn(u64, usize, usize) -> *mut c_void;
) -> c_int;
static _generic_ioremap_prot: Option<unsafe extern "C" fn(u64, usize, usize) -> *mut c_void>;
#[cfg(target_arch = "aarch64")]
static ___ioremap: Option<unsafe extern "C" fn(u64, usize, u64) -> *mut c_void>;
#[cfg(not(target_arch = "x86"))]
static _vmap: unsafe extern "C" fn(*mut c_void, u32, usize, usize) -> *mut c_void;
#[cfg(target_arch = "aarch64")]
@@ -757,14 +984,14 @@ unsafe extern "C" {
static _of_irq_get: unsafe extern "C" fn(*mut c_void, c_int) -> c_int;
#[cfg(target_arch = "arm")]
static _of_node_put: unsafe extern "C" fn(*mut c_void);
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[cfg(not(target_arch = "x86"))]
static _ioremap: unsafe extern "C" fn(usize, usize) -> *mut c_void;
#[cfg(target_arch = "x86_64")]
static _ioremap: Option<unsafe extern "C" fn(usize, usize) -> *mut c_void>;
#[cfg(target_arch = "x86_64")]
static _ioremap_nocache: Option<unsafe extern "C" fn(usize, usize) -> *mut c_void>;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
static ___default_kernel_pte_mask: *const usize;
}
#[cfg(target_arch = "x86")]
unsafe extern "C" {
fn frida_k_log(message: *const c_char);
@@ -782,6 +1009,10 @@ unsafe extern "C" {
fn _wake_up_process(a0: *mut c_void) -> c_int;
#[link_name = "frida_k_fget"]
fn _fget(a0: c_uint) -> *mut c_void;
#[link_name = "frida_k_fget_task"]
fn _fget_task(a0: *mut c_void, a1: c_uint) -> *mut c_void;
#[link_name = "frida_k_find_task_by_vpid"]
fn _find_task_by_vpid(a0: c_uint) -> *mut c_void;
#[link_name = "frida_k_kernel_read"]
fn _kernel_read(a0: *mut c_void, a1: *mut u8, a2: usize, a3: *mut i64) -> isize;
#[link_name = "frida_k_kernel_write"]
@@ -837,10 +1068,10 @@ unsafe extern "C" {
#[cfg(target_arch = "x86")]
const THREAD_ENTRY: unsafe extern "C" fn(*mut c_void) -> c_int = frida_kcb_thread;
#[cfg(not(target_arch = "x86"))]
#[cfg(not(any(target_arch = "x86", target_arch = "aarch64")))]
const THREAD_ENTRY: unsafe extern "C" fn(*mut c_void) -> c_int = frida_cb_thread;
#[cfg(target_arch = "x86")]
const INTERRUPT_ENTRY: unsafe extern "C" fn(c_int, *mut c_void) -> c_int = frida_kcb_interrupt;
#[cfg(not(target_arch = "x86"))]
#[cfg(not(any(target_arch = "x86", target_arch = "aarch64")))]
const INTERRUPT_ENTRY: unsafe extern "C" fn(c_int, *mut c_void) -> c_int = frida_cb_interrupt;
+242 -16
View File
@@ -1,3 +1,5 @@
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use core::ffi::{c_char, c_int, c_long, c_void};
use core::ptr;
@@ -7,6 +9,10 @@ pub struct ProcessInfo {
pub name: *const u8,
pub path: *const u8,
pub command_line: *const u8,
pub uid: u32,
pub user: *const u8,
pub ppid: u32,
pub started: i64,
}
pub fn enumerate_processes(found: &mut dyn FnMut(ProcessInfo)) {
@@ -16,14 +22,31 @@ pub fn enumerate_processes(found: &mut dyn FnMut(ProcessInfo)) {
let mut path = Vec::new();
path.resize(PATH_MAX, 0u8);
let mut cmdline = Vec::new();
cmdline.resize(CMDLINE_MAX, 0u8);
let mut label = Vec::new();
label.resize(NAME_MAX, 0u8);
let mut owner = Vec::new();
owner.resize(NAME_MAX, 0u8);
let (now_boottime, now_realtime) = current_time();
for task in take_task_snapshot(layout) {
found(ProcessInfo {
id: task.id,
name: task.name.as_ptr(),
path: path_of(task.executable, &mut path),
command_line: ptr::null(),
});
if !task.memory.is_null() {
let (name, command_line) = describe(task.memory, &mut cmdline, &mut label)
.unwrap_or((task.name.as_ptr(), ptr::null()));
found(ProcessInfo {
id: task.id,
name,
path: path_of(task.executable, &mut path),
command_line,
uid: task.uid,
user: format_user(task.uid, &mut owner),
ppid: task.ppid,
started: wall_time(task.started, now_boottime, now_realtime),
});
unsafe { _mmput(task.memory) };
}
if !task.executable.is_null() {
unsafe { _fput(task.executable) };
@@ -69,6 +92,10 @@ struct Task {
id: u32,
name: [u8; NAME_SIZE + 1],
executable: *mut c_void,
memory: *mut c_void,
uid: u32,
ppid: u32,
started: u64,
}
fn take_task_snapshot(layout: &Layout) -> Vec<Task> {
@@ -94,14 +121,162 @@ fn read_task(task: usize, layout: &Layout) -> Task {
id: read_id(task, layout),
name,
executable: unsafe { _get_task_exe_file(task as *mut c_void) },
memory: unsafe { _get_task_mm(task as *mut c_void) },
uid: read_uid(task, layout),
ppid: read_ppid(task, layout),
started: layout.started.and_then(|at| read_kernel_word(task + at)).unwrap_or(0) as u64,
}
}
fn read_id(task: usize, layout: &Layout) -> u32 {
let mut id = [0u8; 4];
read_kernel(task + layout.id, &mut id);
fn read_uid(task: usize, layout: &Layout) -> u32 {
let (Some(credentials), Some(uid)) = (layout.credentials, layout.uid) else {
return 0;
};
let Some(cred) = read_kernel_word(task + credentials) else {
return 0;
};
read_kernel_u32(cred + uid)
}
u32::from_ne_bytes(id)
fn read_ppid(task: usize, layout: &Layout) -> u32 {
let (Some(parent), Some(group)) = (layout.parent, layout.group) else {
return 0;
};
let Some(task) = read_kernel_word(task + parent) else {
return 0;
};
read_kernel_u32(task + group)
}
fn read_id(task: usize, layout: &Layout) -> u32 {
read_kernel_u32(task + layout.id)
}
fn read_kernel_u32(address: usize) -> u32 {
let mut bytes = [0u8; 4];
if !read_kernel(address, &mut bytes) {
return 0;
}
u32::from_ne_bytes(bytes)
}
#[cfg(target_arch = "aarch64")]
fn describe(memory: *mut c_void, cmdline: &mut [u8], label: &mut [u8]) -> Option<(*const u8, *const u8)> {
let read = read_cmdline(memory, cmdline)?;
let name = base_name(&cmdline[..read], label);
let command_line = join_arguments(cmdline, read);
Some((name, command_line))
}
#[cfg(not(target_arch = "aarch64"))]
fn describe(_memory: *mut c_void, _cmdline: &mut [u8], _label: &mut [u8]) -> Option<(*const u8, *const u8)> {
None
}
#[cfg(target_arch = "aarch64")]
fn read_cmdline(memory: *mut c_void, buffer: &mut [u8]) -> Option<usize> {
let start = read_kernel_word(memory as usize + super::layout::field_offset("mm_struct", "arg_start")?)?;
if start == 0 {
return None;
}
let room = buffer.len() - 1;
unsafe { _kthread_use_mm(memory) };
let missed = unsafe {
___arch_copy_from_user(buffer.as_mut_ptr() as *mut c_void, start as *const c_void, room)
};
unsafe { _kthread_unuse_mm(memory) };
let read = room - missed;
(read != 0).then_some(read)
}
#[cfg(target_arch = "aarch64")]
fn base_name(arguments: &[u8], label: &mut [u8]) -> *const u8 {
let first = &arguments[..arguments.iter().position(|byte| *byte == 0).unwrap_or(arguments.len())];
let start = first.iter().rposition(|byte| *byte == b'/').map_or(0, |slash| slash + 1);
let name = &first[start..];
let taken = name.len().min(label.len() - 1);
label[..taken].copy_from_slice(&name[..taken]);
label[taken] = 0;
label.as_ptr()
}
#[cfg(target_arch = "aarch64")]
fn join_arguments(cmdline: &mut [u8], read: usize) -> *const u8 {
for byte in &mut cmdline[..read] {
if *byte == 0 {
*byte = b' ';
}
}
cmdline[read] = 0;
cmdline.as_ptr()
}
fn format_user(uid: u32, buffer: &mut [u8]) -> *const u8 {
let text = user_label(uid);
let bytes = text.as_bytes();
let taken = bytes.len().min(buffer.len() - 1);
buffer[..taken].copy_from_slice(&bytes[..taken]);
buffer[taken] = 0;
buffer.as_ptr()
}
fn user_label(uid: u32) -> String {
let user = uid / AID_USER_OFFSET;
let app = uid % AID_USER_OFFSET;
if (AID_APP_START..AID_APP_END).contains(&app) {
return format!("u{}_a{}", user, app - AID_APP_START);
}
match app {
0 => "root".into(),
1000 => "system".into(),
1001 => "radio".into(),
2000 => "shell".into(),
_ => format!("{}", uid),
}
}
fn wall_time(started: u64, now_boottime: u64, now_realtime: u64) -> i64 {
if started == 0 || started > now_boottime {
return 0;
}
let age = now_boottime - started;
if age > now_realtime {
return 0;
}
((now_realtime - age) / 1_000_000_000) as i64
}
#[cfg(not(target_arch = "x86"))]
fn current_time() -> (u64, u64) {
let mut wall = Timespec { seconds: 0, nanoseconds: 0 };
unsafe { _ktime_get_real_ts64(&mut wall) };
let wallclock = (wall.seconds as u64) * 1_000_000_000 + (wall.nanoseconds as u64);
let uptime = unsafe { _ktime_get_mono_fast_ns() };
(uptime, wallclock)
}
#[cfg(target_arch = "x86")]
fn current_time() -> (u64, u64) {
(0, 0)
}
#[cfg(not(target_arch = "x86"))]
#[repr(C)]
struct Timespec {
seconds: i64,
nanoseconds: i64,
}
fn path_of(executable: *mut c_void, buffer: &mut [u8]) -> *const u8 {
@@ -175,6 +350,11 @@ struct Layout {
list: usize,
name: usize,
id: usize,
credentials: Option<usize>,
uid: Option<usize>,
parent: Option<usize>,
group: Option<usize>,
started: Option<usize>,
}
fn discover_layout() -> Option<Layout> {
@@ -189,16 +369,32 @@ fn discover_layout() -> Option<Layout> {
let name = find(&image, IDLE_TASK_NAME)?;
let (list, id) = find_task_list(init, &image, name)?;
Some(Layout { init, list, name, id })
Some(Layout {
init,
list,
name,
id,
credentials: None,
uid: None,
parent: None,
group: None,
started: None,
})
}
fn described_layout(init: usize) -> Option<Layout> {
use super::layout::field_offset;
Some(Layout {
init,
list: super::layout::field_offset("task_struct", "tasks")?,
name: super::layout::field_offset("task_struct", "comm")?,
id: super::layout::field_offset("task_struct", "pid")?,
list: field_offset("task_struct", "tasks")?,
name: field_offset("task_struct", "comm")?,
id: field_offset("task_struct", "pid")?,
credentials: field_offset("task_struct", "real_cred"),
uid: field_offset("cred", "uid"),
parent: field_offset("task_struct", "real_parent"),
group: field_offset("task_struct", "tgid"),
started: field_offset("task_struct", "start_time"),
})
}
@@ -406,6 +602,14 @@ const PIDTYPE_TGID: c_int = 1;
const PIDTYPE_MAX: usize = 4;
pub fn cloak_task(task: *mut c_void) {
// detach_pid's arity differs across kernels (the classic detach_pid(task, type)
// versus the batched detach_pid(pids, task, type) paired with free_pids); calling
// the wrong one drives a UBSAN/CFI trap on hardened kernels. Leave the thread
// visible rather than risk it.
if true {
let _ = task;
return;
}
let (Some(tasks), Some(sibling), Some(thread_node)) = (
super::layout::field_offset("task_struct", "tasks"),
super::layout::field_offset("task_struct", "sibling"),
@@ -495,6 +699,11 @@ fn write_unlock_tasklist(flags: usize) {
const IDLE_TASK_NAME: &[u8] = b"swapper";
const NAME_SIZE: usize = 16;
const PATH_MAX: usize = 4096;
const CMDLINE_MAX: usize = 512;
const NAME_MAX: usize = 256;
const AID_USER_OFFSET: u32 = 100000;
const AID_APP_START: u32 = 10000;
const AID_APP_END: u32 = 20000;
const MAX_TASK_SIZE: usize = 16 * 1024;
const READ_CHUNK: usize = 64;
@@ -509,7 +718,6 @@ const MAX_IDENTIFIER: u32 = 4 * 1024 * 1024;
const ERROR_POINTER_START: usize = usize::MAX - 4095;
unsafe extern "C" {
static _init_task: *const c_void;
static _tasklist_lock: *mut c_void;
@@ -538,9 +746,23 @@ unsafe extern "C" {
#[cfg(not(target_arch = "x86"))]
static _copy_from_kernel_nofault:
unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> c_long;
#[cfg(not(target_arch = "x86"))]
static _get_task_mm: unsafe extern "C" fn(*mut c_void) -> *mut c_void;
#[cfg(not(target_arch = "x86"))]
static _mmput: unsafe extern "C" fn(*mut c_void);
#[cfg(target_arch = "aarch64")]
static _kthread_use_mm: unsafe extern "C" fn(*mut c_void);
#[cfg(target_arch = "aarch64")]
static _kthread_unuse_mm: unsafe extern "C" fn(*mut c_void);
#[cfg(target_arch = "aarch64")]
static ___arch_copy_from_user:
unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> usize;
#[cfg(not(target_arch = "x86"))]
static _ktime_get_real_ts64: unsafe extern "C" fn(*mut Timespec);
#[cfg(not(target_arch = "x86"))]
static _ktime_get_mono_fast_ns: unsafe extern "C" fn() -> u64;
}
#[cfg(target_arch = "x86")]
unsafe extern "C" {
#[link_name = "frida_k_copy_from_kernel_nofault"]
@@ -561,4 +783,8 @@ unsafe extern "C" {
fn _file_path(a0: *mut c_void, a1: *mut c_char, a2: c_int) -> *const c_char;
#[link_name = "frida_k_fput"]
fn _fput(a0: *mut c_void);
#[link_name = "frida_k_get_task_mm"]
fn _get_task_mm(a0: *mut c_void) -> *mut c_void;
#[link_name = "frida_k_mmput"]
fn _mmput(a0: *mut c_void);
}
+91 -73
View File
@@ -1,4 +1,4 @@
extern void *_panic;
extern void * _panic;
void frida_k_panic (void * a0)
{
@@ -7,7 +7,7 @@ void frida_k_panic (void * a0)
((fn_t) _panic) (a0);
}
extern void *_wake_up_process;
extern void * _wake_up_process;
unsigned long frida_k_wake_up_process (void * a0)
{
@@ -16,7 +16,7 @@ unsigned long frida_k_wake_up_process (void * a0)
return ((fn_t) _wake_up_process) (a0);
}
extern void *_fget;
extern void * _fget;
void * frida_k_fget (unsigned long a0)
{
@@ -25,7 +25,25 @@ void * frida_k_fget (unsigned long a0)
return ((fn_t) _fget) (a0);
}
extern void *_kernel_read;
extern void * _fget_task;
void * frida_k_fget_task (void * a0, unsigned int a1)
{
typedef void * (__attribute__((regparm(3))) * fn_t) (void *, unsigned int);
return ((fn_t) _fget_task) (a0, a1);
}
extern void * _find_task_by_vpid;
void * frida_k_find_task_by_vpid (unsigned int a0)
{
typedef void * (__attribute__((regparm(3))) * fn_t) (unsigned int);
return ((fn_t) _find_task_by_vpid) (a0);
}
extern void * _kernel_read;
long frida_k_kernel_read (void * a0, void * a1, unsigned long a2, void * a3)
{
@@ -34,7 +52,7 @@ long frida_k_kernel_read (void * a0, void * a1, unsigned long a2, void * a3)
return ((fn_t) _kernel_read) (a0, a1, a2, a3);
}
extern void *_kernel_write;
extern void * _kernel_write;
long frida_k_kernel_write (void * a0, void * a1, unsigned long a2, void * a3)
{
@@ -43,7 +61,7 @@ long frida_k_kernel_write (void * a0, void * a1, unsigned long a2, void * a3)
return ((fn_t) _kernel_write) (a0, a1, a2, a3);
}
extern void *_kfree;
extern void * _kfree;
void frida_k_kfree (void * a0)
{
@@ -52,7 +70,7 @@ void frida_k_kfree (void * a0)
((fn_t) _kfree) (a0);
}
extern void *___init_waitqueue_head;
extern void * ___init_waitqueue_head;
void frida_k___init_waitqueue_head (void * a0, void * a1, void * a2)
{
@@ -61,7 +79,7 @@ void frida_k___init_waitqueue_head (void * a0, void * a1, void * a2)
((fn_t) ___init_waitqueue_head) (a0, a1, a2);
}
extern void *_prepare_to_wait_event;
extern void * _prepare_to_wait_event;
unsigned long frida_k_prepare_to_wait_event (void * a0, void * a1, unsigned long a2)
{
@@ -70,7 +88,7 @@ unsigned long frida_k_prepare_to_wait_event (void * a0, void * a1, unsigned long
return ((fn_t) _prepare_to_wait_event) (a0, a1, a2);
}
extern void *_finish_wait;
extern void * _finish_wait;
void frida_k_finish_wait (void * a0, void * a1)
{
@@ -79,7 +97,7 @@ void frida_k_finish_wait (void * a0, void * a1)
((fn_t) _finish_wait) (a0, a1);
}
extern void *___wake_up;
extern void * ___wake_up;
void frida_k___wake_up (void * a0, unsigned long a1, unsigned long a2, void * a3)
{
@@ -88,7 +106,7 @@ void frida_k___wake_up (void * a0, unsigned long a1, unsigned long a2, void * a3
((fn_t) ___wake_up) (a0, a1, a2, a3);
}
extern void *_schedule_hrtimeout_range;
extern void * _schedule_hrtimeout_range;
unsigned long frida_k_schedule_hrtimeout_range (void * a0, unsigned long long a1, unsigned long a2)
{
@@ -97,7 +115,7 @@ unsigned long frida_k_schedule_hrtimeout_range (void * a0, unsigned long long a1
return ((fn_t) _schedule_hrtimeout_range) (a0, a1, a2);
}
extern void *_pci_get_domain_bus_and_slot;
extern void * _pci_get_domain_bus_and_slot;
void * frida_k_pci_get_domain_bus_and_slot (unsigned long a0, unsigned long a1, unsigned long a2)
{
@@ -106,7 +124,7 @@ void * frida_k_pci_get_domain_bus_and_slot (unsigned long a0, unsigned long a1,
return ((fn_t) _pci_get_domain_bus_and_slot) (a0, a1, a2);
}
extern void *_pci_irq_vector;
extern void * _pci_irq_vector;
unsigned long frida_k_pci_irq_vector (void * a0, unsigned long a1)
{
@@ -115,7 +133,7 @@ unsigned long frida_k_pci_irq_vector (void * a0, unsigned long a1)
return ((fn_t) _pci_irq_vector) (a0, a1);
}
extern void *_pci_dev_put;
extern void * _pci_dev_put;
void frida_k_pci_dev_put (void * a0)
{
@@ -124,7 +142,7 @@ void frida_k_pci_dev_put (void * a0)
((fn_t) _pci_dev_put) (a0);
}
extern void *_schedule;
extern void * _schedule;
void frida_k_schedule (void)
{
@@ -133,7 +151,7 @@ void frida_k_schedule (void)
((fn_t) _schedule) ();
}
extern void *_ktime_get_mono_fast_ns;
extern void * _ktime_get_mono_fast_ns;
unsigned long long frida_k_ktime_get_mono_fast_ns (void)
{
@@ -142,7 +160,7 @@ unsigned long long frida_k_ktime_get_mono_fast_ns (void)
return ((fn_t) _ktime_get_mono_fast_ns) ();
}
extern void *_ktime_get_real_ts64;
extern void * _ktime_get_real_ts64;
void frida_k_ktime_get_real_ts64 (void * a0)
{
@@ -151,7 +169,7 @@ void frida_k_ktime_get_real_ts64 (void * a0)
((fn_t) _ktime_get_real_ts64) (a0);
}
extern void *_send_sig;
extern void * _send_sig;
unsigned long frida_k_send_sig (unsigned long a0, void * a1, unsigned long a2)
{
@@ -160,7 +178,7 @@ unsigned long frida_k_send_sig (unsigned long a0, void * a1, unsigned long a2)
return ((fn_t) _send_sig) (a0, a1, a2);
}
extern void *_free_irq;
extern void * _free_irq;
void * frida_k_free_irq (unsigned long a0, void * a1)
{
@@ -169,7 +187,7 @@ void * frida_k_free_irq (unsigned long a0, void * a1)
return ((fn_t) _free_irq) (a0, a1);
}
extern void *_vmap;
extern void * _vmap;
void * frida_k_vmap (void * a0, unsigned long a1, unsigned long a2, unsigned long a3)
{
@@ -178,7 +196,7 @@ void * frida_k_vmap (void * a0, unsigned long a1, unsigned long a2, unsigned lon
return ((fn_t) _vmap) (a0, a1, a2, a3);
}
extern void *_ioremap;
extern void * _ioremap;
void * frida_k_ioremap (unsigned long a0, unsigned long a1)
{
@@ -187,7 +205,7 @@ void * frida_k_ioremap (unsigned long a0, unsigned long a1)
return ((fn_t) _ioremap) (a0, a1);
}
extern void *_get_task_exe_file;
extern void * _get_task_exe_file;
void * frida_k_get_task_exe_file (void * a0)
{
@@ -196,7 +214,7 @@ void * frida_k_get_task_exe_file (void * a0)
return ((fn_t) _get_task_exe_file) (a0);
}
extern void *_file_path;
extern void * _file_path;
void * frida_k_file_path (void * a0, void * a1, unsigned long a2)
{
@@ -205,7 +223,7 @@ void * frida_k_file_path (void * a0, void * a1, unsigned long a2)
return ((fn_t) _file_path) (a0, a1, a2);
}
extern void *_fput;
extern void * _fput;
void frida_k_fput (void * a0)
{
@@ -214,7 +232,7 @@ void frida_k_fput (void * a0)
((fn_t) _fput) (a0);
}
extern void *_get_task_mm;
extern void * _get_task_mm;
void * frida_k_get_task_mm (void * a0)
{
@@ -223,7 +241,7 @@ void * frida_k_get_task_mm (void * a0)
return ((fn_t) _get_task_mm) (a0);
}
extern void *_kthread_use_mm;
extern void * _kthread_use_mm;
void frida_k_kthread_use_mm (void * a0)
{
@@ -232,7 +250,7 @@ void frida_k_kthread_use_mm (void * a0)
((fn_t) _kthread_use_mm) (a0);
}
extern void *_kthread_unuse_mm;
extern void * _kthread_unuse_mm;
void frida_k_kthread_unuse_mm (void * a0)
{
@@ -241,7 +259,7 @@ void frida_k_kthread_unuse_mm (void * a0)
((fn_t) _kthread_unuse_mm) (a0);
}
extern void *_mmput;
extern void * _mmput;
void frida_k_mmput (void * a0)
{
@@ -250,7 +268,7 @@ void frida_k_mmput (void * a0)
((fn_t) _mmput) (a0);
}
extern void *_vm_mmap;
extern void * _vm_mmap;
unsigned long frida_k_vm_mmap (void * a0, unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5)
{
@@ -259,7 +277,7 @@ unsigned long frida_k_vm_mmap (void * a0, unsigned long a1, unsigned long a2, un
return ((fn_t) _vm_mmap) (a0, a1, a2, a3, a4, a5);
}
extern void *_vm_munmap;
extern void * _vm_munmap;
unsigned long frida_k_vm_munmap (unsigned long a0, unsigned long a1)
{
@@ -268,7 +286,7 @@ unsigned long frida_k_vm_munmap (unsigned long a0, unsigned long a1)
return ((fn_t) _vm_munmap) (a0, a1);
}
extern void *_vunmap;
extern void * _vunmap;
void frida_k_vunmap (void * a0)
{
@@ -277,7 +295,7 @@ void frida_k_vunmap (void * a0)
((fn_t) _vunmap) (a0);
}
extern void *__copy_to_user;
extern void * __copy_to_user;
unsigned long frida_k__copy_to_user (void * a0, void * a1, unsigned long a2)
{
@@ -286,7 +304,7 @@ unsigned long frida_k__copy_to_user (void * a0, void * a1, unsigned long a2)
return ((fn_t) __copy_to_user) (a0, a1, a2);
}
extern void *_down_read;
extern void * _down_read;
void frida_k_down_read (void * a0)
{
@@ -295,7 +313,7 @@ void frida_k_down_read (void * a0)
((fn_t) _down_read) (a0);
}
extern void *_up_read;
extern void * _up_read;
void frida_k_up_read (void * a0)
{
@@ -304,7 +322,7 @@ void frida_k_up_read (void * a0)
((fn_t) _up_read) (a0);
}
extern void *_get_user_pages_remote;
extern void * _get_user_pages_remote;
long frida_k_get_user_pages_remote (void * a0, unsigned long a1, unsigned long a2, unsigned long a3, void * a4, void * a5)
{
@@ -313,7 +331,7 @@ long frida_k_get_user_pages_remote (void * a0, unsigned long a1, unsigned long a
return ((fn_t) _get_user_pages_remote) (a0, a1, a2, a3, a4, a5);
}
extern void *_do_futex;
extern void * _do_futex;
long frida_k_do_futex (unsigned long a0, unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5, unsigned long a6)
{
@@ -322,7 +340,7 @@ long frida_k_do_futex (unsigned long a0, unsigned long a1, unsigned long a2, uns
return ((fn_t) _do_futex) (a0, a1, a2, a3, a4, a5, a6);
}
extern void *_call_usermodehelper_exec;
extern void * _call_usermodehelper_exec;
unsigned long frida_k_call_usermodehelper_exec (void * a0, unsigned long a1)
{
@@ -331,26 +349,26 @@ unsigned long frida_k_call_usermodehelper_exec (void * a0, unsigned long a1)
return ((fn_t) _call_usermodehelper_exec) (a0, a1);
}
extern void *__printk;
extern void *_printk;
extern void *___kmalloc_noprof;
extern void *_kmalloc;
extern void *_execmem_alloc;
extern void *_module_alloc;
extern void *_set_memory_rw;
extern void *_execmem_free;
extern void *_module_memfree;
extern void *__raw_read_lock;
extern void *__raw_read_unlock;
extern void *__raw_read_lock_irqsave;
extern void *__raw_read_unlock_irqrestore;
extern void *__raw_write_lock;
extern void *__raw_write_unlock;
extern void *__raw_write_lock_irqsave;
extern void *__raw_write_unlock_irqrestore;
extern void *_detach_pid;
extern void *_free_pids;
extern void *_tasklist_lock;
extern void * __printk;
extern void * _printk;
extern void * ___kmalloc_noprof;
extern void * _kmalloc;
extern void * _execmem_alloc;
extern void * _module_alloc;
extern void * _set_memory_rw;
extern void * _execmem_free;
extern void * _module_memfree;
extern void * __raw_read_lock;
extern void * __raw_read_unlock;
extern void * __raw_read_lock_irqsave;
extern void * __raw_read_unlock_irqrestore;
extern void * __raw_write_lock;
extern void * __raw_write_unlock;
extern void * __raw_write_lock_irqsave;
extern void * __raw_write_unlock_irqrestore;
extern void * _detach_pid;
extern void * _free_pids;
extern void * _tasklist_lock;
typedef void (* frida_say_t) (const char *, ...);
typedef void * (__attribute__((regparm(3))) * frida_take_t) (unsigned long, unsigned long);
@@ -363,21 +381,21 @@ typedef void (__attribute__((regparm(3))) * frida_release_flags_t) (void *, unsi
void frida_k_log (const char * message)
{
void *say = __printk ? __printk : _printk;
void * say = __printk ? __printk : _printk;
((frida_say_t) say) (message);
}
void * frida_k_alloc (unsigned long size, unsigned long flags)
{
void *take = ___kmalloc_noprof ? ___kmalloc_noprof : _kmalloc;
void * take = ___kmalloc_noprof ? ___kmalloc_noprof : _kmalloc;
return ((frida_take_t) take) (size, flags);
}
void * frida_k_alloc_code (unsigned long kind, unsigned long size, unsigned long pages)
{
void *code;
void * code;
if (_execmem_alloc)
code = ((frida_take_t) _execmem_alloc) (kind, size);
@@ -392,7 +410,7 @@ void * frida_k_alloc_code (unsigned long kind, unsigned long size, unsigned long
void frida_k_free_code (void * code)
{
void *give = _execmem_free ? _execmem_free : _module_memfree;
void * give = _execmem_free ? _execmem_free : _module_memfree;
((frida_give_t) give) (code);
}
@@ -490,7 +508,7 @@ frida_kcb_thread_left (void * data, void * task, _Bool group_dead)
frida_cb_thread_left (data, task, group_dead);
}
extern void *_request_threaded_irq;
extern void * _request_threaded_irq;
int
frida_k_request_threaded_irq (unsigned int a0, void * a1, void * a2, unsigned long a3, const char * a4,
@@ -502,7 +520,7 @@ frida_k_request_threaded_irq (unsigned int a0, void * a1, void * a2, unsigned lo
return ((fn_t) _request_threaded_irq) (a0, a1, a2, a3, a4, a5);
}
extern void *_copy_from_kernel_nofault;
extern void * _copy_from_kernel_nofault;
long
frida_k_copy_from_kernel_nofault (void * a0, const void * a1, unsigned int a2)
@@ -512,10 +530,10 @@ frida_k_copy_from_kernel_nofault (void * a0, const void * a1, unsigned int a2)
return ((fn_t) _copy_from_kernel_nofault) (a0, a1, a2);
}
extern void *_tracepoint_probe_register;
extern void *_tracepoint_probe_unregister;
extern void *_user_mode_thread;
extern void *_call_usermodehelper_setup;
extern void * _tracepoint_probe_register;
extern void * _tracepoint_probe_unregister;
extern void * _user_mode_thread;
extern void * _call_usermodehelper_setup;
int
frida_k_tracepoint_probe_register (void * a0, void * a1, void * a2)
@@ -566,7 +584,7 @@ frida_kcb_release_words (void * info)
frida_cb_release_words (info);
}
extern void *_get_user_pages_unlocked;
extern void * _get_user_pages_unlocked;
long
frida_k_get_user_pages_unlocked (unsigned long a0, unsigned long a1, void * a2, int a3)
@@ -577,7 +595,7 @@ frida_k_get_user_pages_unlocked (unsigned long a0, unsigned long a1, void * a2,
return ((fn_t) _get_user_pages_unlocked) (a0, a1, a2, a3);
}
extern void *_text_poke_kgdb;
extern void * _text_poke_kgdb;
void * frida_k_text_poke (void * addr, const void * opcode, unsigned long len)
{
@@ -589,7 +607,7 @@ void * frida_k_text_poke (void * addr, const void * opcode, unsigned long len)
return ((fn_t) _text_poke_kgdb) (addr, opcode, len);
}
extern void *_register_module_notifier;
extern void * _register_module_notifier;
int frida_k_register_module_notifier (void * a0)
{
@@ -598,7 +616,7 @@ int frida_k_register_module_notifier (void * a0)
return ((fn_t) _register_module_notifier) (a0);
}
extern void *_unregister_module_notifier;
extern void * _unregister_module_notifier;
int frida_k_unregister_module_notifier (void * a0)
{
@@ -615,7 +633,7 @@ frida_kcb_module_state (void * nb, unsigned long action, void * data)
return frida_cb_module_state (nb, action, data);
}
extern void *_register_die_notifier;
extern void * _register_die_notifier;
int frida_k_register_die_notifier (void * a0)
{
@@ -624,7 +642,7 @@ int frida_k_register_die_notifier (void * a0)
return ((fn_t) _register_die_notifier) (a0);
}
extern void *_unregister_die_notifier;
extern void * _unregister_die_notifier;
int frida_k_unregister_die_notifier (void * a0)
{
+12
View File
@@ -67,6 +67,7 @@ fn send_what_waits(arena: u64) {
let Some(written) = piece else {
return;
};
publish_fence();
wake_the_copy(arena);
if written == frame.len() {
@@ -85,6 +86,7 @@ fn write_frame(ring: &Ring, arena: u64, frame: &[u8], tell: fn(u64)) -> bool {
match ring.write(arena, arena + ring.buffer, frame, written) {
Some(now) => {
written = now;
publish_fence();
tell(arena);
}
None => {
@@ -126,6 +128,16 @@ pub fn forget(arena: u64) {
unsafe { waiting() }.remove(&arena);
}
#[cfg(target_arch = "aarch64")]
fn publish_fence() {
unsafe { core::arch::asm!("dsb ish", options(nostack, preserves_flags)) };
}
#[cfg(not(target_arch = "aarch64"))]
fn publish_fence() {
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
}
fn wake_the_copy(arena: u64) {
super::injection::tell_the_copy_at(arena);
}
Loaded 100 of 363 files, more files were not shown because too many files have changed in this diff. Show more