Compare commits

...
Author SHA1 Message Date
Ole André Vadla Ravnås 813756fee9 ci: Build the Barebone agents
Build the agent images and the kext the way frida/frida does, each
agent against the SDK its GumJS devkit was built with, and only when
src/barebone/agent/ changes. Skip the rest of CI for changes that
touch nothing else.
2026-11-12 21:20:29 +01:00
Ole André Vadla Ravnås f8e9e800cf barebone: Wait without a time limit
A wait given no timeout gave up after a second on Linux, and GLib's
lock waits after a millisecond, so an idle agent kept waking for
nothing. Sleep until woken, as TASK_IDLE so a long sleep neither adds
to the load nor looks hung. Mark the agent in a process gone when its
pipe reaches EOF, which happens once its files are released and it no
longer touches its mapping, and wait for that. Give timed condition
waits their deadline.
2026-11-12 21:20:29 +01:00
Ole André Vadla Ravnås 2bcd1ffb7e barebone: Wake only the waiters of a token
Every waiter shared one wait queue and every wake woke all of them, so
each GLib lock released in a hook on a hot kernel path woke the idle
worker; on Android that came to some ninety times a second. Record the
token in the wait entry and pass it as the wake key, letting the wake
function skip waiters of other tokens. Have the spawn gate wake the
loop's token rather than counting on that broadcast.
2026-11-12 21:20:29 +01:00
Ole André Vadla Ravnås 008f3e25d2 barebone: Give injected threads the vector lengths
A thread started from the kernel side never goes through clone(), so
on arm64 it kept the vector lengths of the kernel thread it began as.
Once it used SVE, saving its state on a context switch found the
length in use did not match, and the kernel killed its thread group.
Copy the SVE and SME lengths from the leader, as arch_dup_task_struct()
would have.
2026-11-12 21:20:29 +01:00
Ole André Vadla Ravnås 24f68f0c60 barebone: Find x86 per-CPU variables in pcpu_hot
From 6.2 to 6.14 the current task, the preempt count and the top of
the stack are members of struct pcpu_hot rather than per-CPU
variables of their own, leaving the agent without a current task on
those kernels. Look them up there when the standalone ones are
missing; the loader supplies the member offsets.
2026-11-12 21:20:29 +01:00
Ole André Vadla Ravnås 0c7f6fae2a barebone: Move the kmod's injector into the agent
Port the prctl channel, process memory ops, thread spawning into a
target's thread group, and cloaking with its /proc hooks from
frida-kmod.c into the shared Linux agent, so both ways into the
kernel get them. The loader supplies struct offsets from the kernel
headers, falling back to BTF.

Choose the detach_pid() ABI at runtime from whether free_pids()
exists, which keeps pre-6.15 kernels from crashing on injection.

Take the more mature half of each primitive: bounded uninterruptible
waits that spin where sleeping is not allowed, inline queued locks
where the kernel has no out-of-line ones, module_mutex around walks of
the module list, module-region code memory, and a vmap alias for
writing kernel text.

Fixes #1292
2026-11-12 21:20:29 +01:00
Ole André Vadla Ravnås a751fbee2c barebone: Load the injected agent from the kmod
Turn frida-kmod.c into an in-kernel loader for the linux-injected
image: map and relocate it, fill .kernel_addrs from kallsyms and the
loader's own /dev/frida glue, and hand it the config the host would.
Drop the separate linux flavor and its prelink.
2026-11-12 21:20:29 +01:00
Ole André Vadla Ravnås 8118e7546f barebone: Only restore an overwritten prologue
kfault's install() leaves fixup_exception() alone when its prologue is
not the expected one, but uninstall() wrote that prologue over it
regardless, corrupting the kernel once the agent stopped.
2026-11-12 21:20:29 +01:00
Francesco Tamagni d62dce1c5f compat: Forward top-level builtin options
Meson keys top-level builtin options with subproject None, while
top-level project options use "". We only matched the latter, so
builtins such as strip never reached compat builds, which resulted in
e.g. a stripped arm64 gadget slice despite --enable-symbols.
2026-11-12 14:42:27 +01:00
Ole André Vadla Ravnås d9bbc37db3 compat: Always pass the resolved frida_version
The check for -Dfrida_version never matched the store's
-D:frida_version spelling. Append the version the parent resolved
unconditionally.
2026-11-12 10:13:17 +01:00
Ole André Vadla Ravnås a2c88d7b2e compat: Stop forwarding the parent's assets option
The override check missed the store's -D:assets spelling, so the
parent's assets=auto replaced the sub-build's assets=installed. Hold
assets back with the other options the sub-build sets itself, and drop
the separate override list.
2026-11-12 10:13:17 +01:00
Ole André Vadla Ravnås d65ee995ad Drop Efni catches that can never be reached
The calls they guard only throw Frida.Error.
2026-11-12 10:08:48 +01:00
Ole André Vadla Ravnås 8873e9d5b3 package-manager: Catch Efni errors too
Catch the Efni.Error that FS throws for a missing package.json in the
already-installed probe, which otherwise fails every fresh install.
Wrap Efni errors from tarball extraction as well.
2026-11-12 10:08:48 +01:00
Ole André Vadla Ravnås 2faca1f013 barebone: Enter agent code with an aligned stack
Wherever the agent is started without a call, the stack it is given has
to look the way a call would have left it. On x86 and x86_64 several
such entries were off: the win9x trampoline, NT user threads, the Linux
userland copy and the x86 Linux thread start. On arm, a host-side
invoke borrowed whatever SP the interrupted code had.

Guest code that relies on alignment then faults, e.g. musl's fstatat on
x86_64 Alpine, which made every lstat() from an injected agent crash.
2026-11-11 12:43:13 +01:00
Ole André Vadla Ravnås 588f79c5ec fruity: Install idund beside other assets
A shared build loads its assets from disk, so idund was neither
embedded nor installed -- installs skip subprojects -- and idun's guess
at its location next to the library found nothing. Install it into
the plugins directory and point the daemon locator there.
2026-11-11 11:43:22 +01:00
Ole André Vadla Ravnås 588ac3b3de subprojects: Bump outdated 2026-11-09 22:03:43 +01:00
Ole André Vadla Ravnås d0615e7574 submodules: Bump releng 2026-11-09 22:03:42 +01:00
Kaz Wolfe 1b169dec1c exit-monitor: Pass function sizes to Interceptor
On QNX 6.5 ARM, libc's _exit() is a lone BL into __exit(), and the
Interceptor cannot tell where it ends. Look up the symbol sizes and pass
them as redirect_space_hint so the redirect stays within the function.
2026-11-09 21:59:38 +01:00
Kaz Wolfe 0e61ae2762 qnx: Set PC when emitting the ARM payload
The writer assumed the code would run where it was generated, so calls
to libc functions within BL range of the local buffer were encoded with
offsets that are wrong in the target process.
2026-11-09 21:59:38 +01:00
Kaz Wolfe 6a445d9dea qnx: Add x86 support to the injector
Mirror the ARM payload and remote call using cdecl.
2026-11-09 20:34:01 +01:00
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
346 changed files with 39334 additions and 34669 deletions

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
name: Set up LLVM
description: Install the LLVM toolchain and put its tools on the path
inputs:
version:
required: false
default: "21"
description: The LLVM major version to install
runs:
using: composite
steps:
- name: Install LLVM
shell: bash
run: |
curl -sSL https://apt.llvm.org/llvm.sh -o llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh $VERSION
echo "/usr/lib/llvm-$VERSION/bin" >> $GITHUB_PATH
env:
VERSION: ${{ inputs.version }}
+245
View File
@@ -0,0 +1,245 @@
name: Barebone agent
on:
push:
paths:
- 'src/barebone/agent/**'
- '.github/workflows/barebone-agent.yml'
pull_request:
branches:
- main
paths:
- 'src/barebone/agent/**'
- '.github/workflows/barebone-agent.yml'
workflow_dispatch:
jobs:
gum-none:
strategy:
matrix:
machine:
- none-x86-softfloat_pic
- none-x86_64-softfloat_pic
- none-x86_64-softfloat_pic_msabi
- none-arm-softfloat_pic
- none-arm64-softfloat_pic
fail-fast: false
runs-on: ubuntu-latest
env:
FRIDA_DEPS: ${{ github.workspace }}/deps
steps:
- name: Check out repo
uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: 24
- name: Set up C toolchain
uses: ./.github/actions/setup-llvm
- name: Grab frida-gum
run: |
python releng/meson/meson.py subprojects download frida-gum
git -C subprojects/frida-gum submodule update --init --recursive --depth 1
- name: Configure Gum
working-directory: subprojects/frida-gum
run: >-
./configure
--host=${{ matrix.machine }}
--enable-gumjs
--with-devkits=gumjs
--with-devkit-symbol-scope=original
env:
CC: clang
CXX: clang++
- name: Compile
working-directory: subprojects/frida-gum
run: make
- name: Upload GumJS devkit
uses: actions/upload-artifact@v7
with:
name: frida-gumjs-devkit-${{ matrix.machine }}
path: subprojects/frida-gum/build/bindings/gumjs/devkit/
- name: Upload SDK
uses: actions/upload-artifact@v7
with:
name: sdk-${{ matrix.machine }}
path: ${{ env.FRIDA_DEPS }}/sdk-${{ matrix.machine }}/
gum-macos-kernel:
runs-on: macos-latest
env:
FRIDA_DEPS: ${{ github.workspace }}/deps
steps:
- name: Check out repo
uses: actions/checkout@v6
with:
submodules: recursive
- name: Grab frida-gum
run: |
python3 releng/meson/meson.py subprojects download frida-gum
git -C subprojects/frida-gum submodule update --init --recursive --depth 1
- name: Configure Gum
working-directory: subprojects/frida-gum
run: >-
./configure
--host=macos-arm64e-kernel
--enable-gumjs
--with-devkits=gumjs
--with-devkit-symbol-scope=original
- name: Compile
working-directory: subprojects/frida-gum
run: make
- name: Upload GumJS devkit
uses: actions/upload-artifact@v7
with:
name: frida-gumjs-devkit-macos-arm64e-kernel
path: subprojects/frida-gum/build/bindings/gumjs/devkit/
- name: Upload SDK
uses: actions/upload-artifact@v7
with:
name: sdk-macos-arm64e-kernel
path: ${{ env.FRIDA_DEPS }}/sdk-macos-arm64e-kernel/
blob:
needs: gum-none
strategy:
matrix:
include:
- flavor: win9x
feature: win9x
arch: x86
machine: none-x86-softfloat_pic
rust_target: i686-unknown-none-pic
- flavor: winnt
feature: winnt
arch: x86
machine: none-x86-softfloat_pic
rust_target: i686-unknown-none-pic
- flavor: winnt
feature: winnt
arch: x86_64
machine: none-x86_64-softfloat_pic_msabi
rust_target: x86_64-unknown-none-pic
- flavor: winnt
feature: winnt
arch: arm64
machine: none-arm64-softfloat_pic
rust_target: aarch64-unknown-none-pic
- flavor: xnu
feature: xnu
arch: arm64
machine: none-arm64-softfloat_pic
rust_target: aarch64-unknown-none-pic
- flavor: linux
feature: linux-injected
arch: x86
machine: none-x86-softfloat_pic
rust_target: i686-unknown-none-pic
- flavor: linux
feature: linux-injected
arch: x86_64
machine: none-x86_64-softfloat_pic
rust_target: x86_64-unknown-none-pic
- flavor: linux
feature: linux-injected
arch: arm
machine: none-arm-softfloat_pic
rust_target: armv7a-unknown-none-pic
- flavor: linux
feature: linux-injected
arch: arm64
machine: none-arm64-softfloat_pic
rust_target: aarch64-unknown-none-pic
fail-fast: false
runs-on: ubuntu-latest
env:
SDK: ${{ github.workspace }}/sdk-${{ matrix.machine }}
GUMJS_DEVKIT_DIR: ${{ github.workspace }}/gumjs-devkit
steps:
- name: Check out repo
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
- name: Detect version
run: |
version=$(releng/frida_version.py)
test -n "$version"
echo "FRIDA_VERSION=$version" >> $GITHUB_ENV
- name: Set up C toolchain
uses: ./.github/actions/setup-llvm
- name: Set up Rust
run: rustup component add rust-src
- name: Download SDK
uses: actions/download-artifact@v8
with:
name: sdk-${{ matrix.machine }}
path: ${{ env.SDK }}
- name: Download GumJS devkit
uses: actions/download-artifact@v8
with:
name: frida-gumjs-devkit-${{ matrix.machine }}
path: ${{ env.GUMJS_DEVKIT_DIR }}
- name: Build agent
working-directory: src/barebone/agent
run: >-
cargo build --release
--features ${{ matrix.feature }}
--target ./${{ matrix.rust_target }}.json -Z json-target-spec
-Z build-std=core,alloc,compiler_builtins
--target-dir target-${{ matrix.flavor }}
env:
RUSTC_BOOTSTRAP: 1
CC: >-
clang
-I${{ env.SDK }}/include
-I${{ env.SDK }}/include/glib-2.0
-I${{ env.SDK }}/lib/glib-2.0/include
-I${{ env.SDK }}/include/capstone
-I${{ env.SDK }}/include/json-glib-1.0
RUSTFLAGS: -L native=${{ env.SDK }}/lib
- name: Upload agent
uses: actions/upload-artifact@v7
with:
name: frida-barebone-agent-${{ matrix.flavor }}-${{ matrix.arch }}
path: src/barebone/agent/target-${{ matrix.flavor }}/${{ matrix.rust_target }}/release/frida-barebone-agent
kext:
needs: gum-macos-kernel
runs-on: macos-latest
env:
FRIDA_SDK: ${{ github.workspace }}/sdk-macos-arm64e-kernel
GUMJS_DEVKIT_DIR: ${{ github.workspace }}/gumjs-devkit
steps:
- name: Check out repo
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
- name: Detect version
run: |
version=$(releng/frida_version.py)
test -n "$version"
echo "FRIDA_VERSION=$version" >> $GITHUB_ENV
- name: Set up Rust
run: rustup component add rust-src
- name: Download SDK
uses: actions/download-artifact@v8
with:
name: sdk-macos-arm64e-kernel
path: ${{ env.FRIDA_SDK }}
- name: Download GumJS devkit
uses: actions/download-artifact@v8
with:
name: frida-gumjs-devkit-macos-arm64e-kernel
path: ${{ env.GUMJS_DEVKIT_DIR }}
- name: Build agent
working-directory: src/barebone/agent/macos
run: make
- name: Upload agent
uses: actions/upload-artifact@v7
with:
name: frida-barebone-agent-xnu-kext-arm64e
path: src/barebone/agent/macos/frida-agent.kext
+20 -5
View File
@@ -2,9 +2,13 @@ name: CI
on:
push:
paths-ignore:
- 'src/barebone/agent/**'
pull_request:
branches:
- main
paths-ignore:
- 'src/barebone/agent/**'
workflow_dispatch:
inputs:
tag:
@@ -21,7 +25,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 +455,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 +675,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
@@ -6,7 +6,7 @@
/src/barebone/agent/linux/*.mod
/src/barebone/agent/linux/*.mod.c
/src/barebone/agent/linux/*.o
/src/barebone/agent/linux/*.o.bin
/src/barebone/agent/linux/frida-agent.elf
/src/barebone/agent/linux/.*.cmd
/src/barebone/agent/linux/Module.symvers
/src/barebone/agent/linux/modules.order
+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
+23 -38
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")
@@ -510,9 +510,7 @@ def compile(privdir: Path, state: State):
if not (workdir / "build.ninja").exists():
if options is None:
options = load_meson_options(top_builddir, state.role, set(subprojects.keys()))
version_opt = next((opt for opt in options if opt.startswith("-Dfrida_version=")), None)
if version_opt is None:
options += [f"-Dfrida_version={state.frida_version}"]
options += [f"-D:frida_version={state.frida_version}"]
host_machine = MachineSpec(state.host_os, group.arch, state.host_config, group.triplet)
@@ -525,7 +523,7 @@ def compile(privdir: Path, state: State):
forwarded_options = [o for o in options if not is_stripped_compat_option(o)]
forwarded_options += ["-Ddefault_library=static"]
else:
forwarded_options = [o for o in options if not is_overridden_compat_option(o)]
forwarded_options = list(options)
if state.apple_tls == "enabled":
forwarded_options += ["-Dapple_tls=disabled"]
@@ -576,24 +574,19 @@ def compile(privdir: Path, state: State):
def is_stripped_compat_option(o: str) -> bool:
if is_overridden_compat_option(o):
return True
name = o.split("=", 1)[0].removeprefix("-D")
return name in STRIPPED_COMPAT_OPTIONS or name.endswith(":default_library")
def is_overridden_compat_option(o: str) -> bool:
name = o.split("=", 1)[0].removeprefix("-D")
return name in OVERRIDDEN_COMPAT_OPTIONS
def load_meson_options(top_builddir: Path,
role: Role,
subprojects: set[str]) -> Sequence[str]:
from mesonbuild import coredata
return [f"-D{adapt_key(k, role)}={v.value}" for k, v in coredata.load(top_builddir).options.items()
if option_should_be_forwarded(k, v, role, subprojects)]
optstore = coredata.load(top_builddir).optstore
return [f"-D{adapt_key(k, role)}={v.value}" for k, v in optstore.options.items()
if option_should_be_forwarded(k, v, optstore, role, subprojects)]
def adapt_key(k: "OptionKey", role: Role) -> "OptionKey":
@@ -603,44 +596,47 @@ 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
subproject = k.subproject or ""
our_project_id = "frida-core" if role == "subproject" else ""
is_for_us = k.subproject == our_project_id
is_for_child = k.subproject in subprojects
is_for_us = subproject == our_project_id
is_for_child = subproject in subprojects
if k.is_project():
if optstore.is_project_option(k):
if is_for_us:
if k.name == "compiler_backend":
if k.name in {"compiler_backend", "assets"}:
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:
return k.subproject == our_project_id
if not is_for_child and role == "subproject" and subproject in {"", our_project_id}:
if not options.BUILTIN_OPTIONS[k.evolve(subproject=None, machine=MachineChoice.HOST)].yielding:
return 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):
@@ -794,17 +790,6 @@ STRIPPED_COMPAT_OPTIONS = {
"frida-gum:inspector",
}
OVERRIDDEN_COMPAT_OPTIONS = {
"compiler_backend",
"assets",
"helper_modern",
"helper_legacy",
"agent_modern",
"agent_legacy",
"agent_emulated_modern",
"agent_emulated_legacy",
}
HELPER_TARGET = "frida-helper"
HELPER_FILE_WINDOWS = Path("src") / "frida-helper.exe"
HELPER_FILE_UNIX = Path("src") / "frida-helper"
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
+394 -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,37 @@ 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)
@@ -199,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;
@@ -285,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 {
@@ -326,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 ();
@@ -341,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);
});
@@ -381,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);
@@ -1042,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 ();
}
@@ -1087,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;
@@ -1295,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;
@@ -1367,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)
@@ -1406,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");
@@ -1445,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);
@@ -1453,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);
@@ -1500,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) {
@@ -1660,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;
+3 -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
@@ -93,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
-126
View File
@@ -1,126 +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 Bytes read_all_bytes_sync (File f) throws Error {
try {
uint8[] contents;
f.load_contents (null, out contents, null);
return new Bytes.take ((owned) contents);
} catch (GLib.Error 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 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);
}
+3 -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',
]
@@ -29,13 +24,6 @@ else
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'
@@ -89,14 +77,11 @@ endif
if host_os_family != 'windows'
base_vala_args += [
'--pkg=gio-unix-2.0',
'--pkg=posix',
]
endif
if host_os_family == 'darwin'
base_vala_args += [
'--pkg=corefoundation',
'--pkg=darwin-gcd',
'--pkg=darwin-xpc',
]
endif
if host_os_family == 'linux'
@@ -115,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],
)
@@ -123,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 {
+2 -2
View File
@@ -278,9 +278,9 @@ namespace Frida {
public uint exit_status;
public Posix.Signal term_signal;
public int term_signal;
public Posix.Signal stop_signal;
public int stop_signal;
public WaitResult (uint pid, int status) {
this.pid = pid;
+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);
+91 -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
@@ -1281,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;
@@ -1294,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;
@@ -1306,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;
@@ -2743,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";
@@ -2768,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)
{
+27 -86
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);
}
}
@@ -1117,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);
}
@@ -1166,7 +1092,7 @@ namespace Frida {
if (recv_queue.len == 0) {
#if HAVE_SOUP_WEBSOCKET_BACKPRESSURE
websocket.resume_input ();
resume_websocket_input ();
#endif
}
@@ -1183,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;
}
}
-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 {
-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 ();
+18 -2
View File
@@ -46,8 +46,24 @@ namespace Frida {
}
#else
var libc = Gum.Process.get_libc_module ();
foreach (var symbol in apis) {
interceptor.attach ((void *) libc.find_export_by_name (symbol), listener);
var targets = new Gum.Address[apis.length];
for (var i = 0; i != apis.length; i++)
targets[i] = libc.find_export_by_name (apis[i]);
var sizes = new uint[apis.length];
#if ARM
libc.enumerate_symbols (s => {
for (var i = 0; i != targets.length; i++) {
if (s.address == targets[i] && s.size > 0)
sizes[i] = (uint) s.size;
}
return true;
});
#endif
for (var i = 0; i != apis.length; i++) {
var options = Gum.AttachOptions ();
options.instrumentation.redirect_space_hint = sizes[i];
interceptor.attach ((void *) targets[i], listener, options);
}
#endif
#endif
+26 -20
View File
@@ -215,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);
@@ -252,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];
@@ -302,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;
}
@@ -1905,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);
@@ -1927,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);
@@ -1944,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);
+66 -49
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
@@ -349,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',
@@ -408,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',
@@ -428,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(
@@ -442,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
@@ -480,6 +504,7 @@ endif
if host_os == 'android'
minizip_dep = dependency('minizip', required: false, default_options: [
'default_library=static',
'zlib=enabled',
'lzma=disabled',
])
@@ -573,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'
@@ -697,54 +726,37 @@ 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 = 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')
have_idund = idun_sp.get_variable('build_daemon')
if have_idund
idund_raw = idun_sp.get_variable('idund')
if assets_mode == 'embedded'
vala_flags += '--define=HAVE_EMBEDDED_IDUND'
else
vala_flags += '--define=HAVE_INSTALLED_IDUND'
cdata.set_quoted('FRIDA_IDUND_NAME', 'idund' + exe_suffix)
endif
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()
@@ -774,8 +786,12 @@ 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
@@ -979,12 +995,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: 1089fe24c2...fe7d31dfbc.
+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>
+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() {
+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
+143 -109
View File
@@ -1,3 +1,5 @@
using Efni;
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
namespace Frida.Barebone {
public sealed class AgentConnection : Object, AsyncInitable {
@@ -26,6 +28,7 @@ namespace Frida.Barebone {
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;
@@ -39,9 +42,9 @@ namespace Frida.Barebone {
private Allocation config_allocation;
private Allocation? agent_stack;
private Promise<bool>? listening;
private Efni.Promise<bool>? listening;
private Gee.Map<uint16, Promise<Variant>> pending_requests = new Gee.HashMap<uint16, Promise<Variant>> ();
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;
@@ -55,8 +58,8 @@ namespace Frida.Barebone {
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,
Gee.Map<string, uint64?> kernel_notes) {
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;
@@ -64,16 +67,17 @@ namespace Frida.Barebone {
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 Error, IOError {
public async void open (Cancellable? cancellable) throws Efni.Error, IOError {
try {
yield init_async (Priority.DEFAULT, cancellable);
} catch (GLib.Error e) {
throw_api_error (e);
Efni.throw_api_error (e);
}
}
@@ -82,7 +86,7 @@ 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 {
throws Efni.Error, IOError {
var connection = new AgentConnection.resident ();
connection.adopt_hostlink_streams (stream);
@@ -103,7 +107,7 @@ namespace Frida.Barebone {
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 Error, IOError {
private async bool init_async (int io_priority, Cancellable? cancellable) throws Efni.Error, IOError {
progress ("Preparing the agent", 0.0);
var transport_tag = yield resolve_transport (cancellable);
@@ -171,12 +175,12 @@ 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");
flavor = new LinuxKernelFlavor (machine, kernel_base, allocator, symbols);
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);
} else if (kind == WINNT) {
@@ -191,7 +195,7 @@ 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 = debugger.make_buffer (new Bytes (elf.get_file_data ()));
@@ -238,7 +242,7 @@ namespace Frida.Barebone {
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")
@@ -296,12 +300,12 @@ 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 protect_unless_already (machine, allocator, config_allocation.virtual_address,
yield protect_unless_already (machine, data_allocator, config_allocation.virtual_address,
config_allocation.size, READ | WRITE, cancellable);
var ia32 = machine as IA32Machine;
@@ -316,11 +320,19 @@ namespace Frida.Barebone {
}
progress ("Starting the agent", 0.75);
yield machine.invoke (start_address, {
config_allocation.virtual_address,
config_allocation.size
},
cancellable);
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;
@@ -341,14 +353,14 @@ namespace Frida.Barebone {
return true;
}
private async void wait_to_be_greeted (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 Error.TIMED_OUT ("The agent never announced itself"));
listening.reject (new Efni.Error.TIMED_OUT ("The agent never announced itself"));
return false;
});
timeout.attach (MainContext.get_thread_default ());
@@ -360,7 +372,7 @@ namespace Frida.Barebone {
}
}
private async Variant resolve_transport (Cancellable? cancellable) throws Error, IOError {
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) {
@@ -397,11 +409,11 @@ namespace Frida.Barebone {
new Variant.variant (new Variant.string (config.socket_path))
});
}
throw new Error.NOT_SUPPORTED ("Unsupported transport config");
throw new Efni.Error.NOT_SUPPORTED ("Unsupported transport config");
}
private async void connect_serial_transport (BareboneSerialTransportConfig config,
Cancellable? cancellable) throws Error, IOError {
Cancellable? cancellable) throws Efni.Error, IOError {
try {
#if WINDOWS
adopt_hostlink_streams (WindowsNamedPipe.open_client (config.path));
@@ -411,20 +423,20 @@ namespace Frida.Barebone {
cancellable));
#endif
} catch (GLib.Error e) {
throw new Error.TRANSPORT ("Unable to reach the serial port of the guest: %s", e.message);
throw new Efni.Error.TRANSPORT ("Unable to reach the serial port of the guest: %s", e.message);
}
listening = new Promise<bool> ();
listening = new Efni.Promise<bool> ();
}
private async void open_pipe_listener (string path, Cancellable? cancellable) throws Error, IOError {
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 Error.TRANSPORT ("Unable to listen on %s: %s", path, e.message);
throw new Efni.Error.TRANSPORT ("Unable to listen on %s: %s", path, e.message);
}
service.incoming.connect ((connection) => {
if (pipe_stream == null) {
@@ -441,7 +453,7 @@ namespace Frida.Barebone {
pipe_service = service;
}
private async void establish_hostlink (Cancellable? cancellable) throws Error, IOError {
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;
@@ -472,7 +484,7 @@ namespace Frida.Barebone {
}
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;
@@ -510,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) {
@@ -549,7 +561,7 @@ 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 ());
@@ -573,7 +585,7 @@ 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;
@@ -605,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");
}
/**
@@ -619,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);
@@ -631,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)
@@ -649,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++) {
@@ -676,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++) {
@@ -727,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)
@@ -784,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++) {
@@ -810,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);
@@ -986,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);
@@ -1017,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 ());
@@ -1044,29 +1076,29 @@ namespace Frida.Barebone {
Variant response = null;
try {
response = yield promise.future.wait_async (cancellable);
} catch (Error e) {
if (!(e is Error.TIMED_OUT))
} 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 Error.TIMED_OUT ("%s; %s", e.message, fault);
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 Error, IOError {
private async string? describe_agent_fault (Cancellable? cancellable) throws Efni.Error, IOError {
if (fault_record == 0)
return null;
@@ -1113,7 +1145,7 @@ namespace Frida.Barebone {
}
private async void process_incoming_messages () {
reading = new Promise<bool> ();
reading = new Efni.Promise<bool> ();
try {
while (true) {
size_t header_size = 4;
@@ -1141,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;
@@ -1154,7 +1186,7 @@ namespace Frida.Barebone {
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;
@@ -1163,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;
@@ -1188,7 +1220,7 @@ 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);
@@ -1198,7 +1230,7 @@ namespace Frida.Barebone {
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);
}
@@ -1209,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)
@@ -1222,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 ();
@@ -1244,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;
@@ -1259,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);
@@ -1289,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;
@@ -1310,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);
@@ -1324,31 +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,
READY = 131;
public string to_nick () {
return Marshal.enum_to_nick<Command> (this);
return Efni.Marshal.enum_to_nick<Command> (this);
}
}
-4
View File
@@ -9,15 +9,12 @@ name = "frida_barebone_agent"
path = "src/lib.rs"
crate-type = ["rlib", "staticlib"]
# The XNU flavour is a freestanding executable the host injects and calls into.
# The Linux flavour is the staticlib above, linked into a .ko by linux/Kbuild.
[[bin]]
name = "frida-barebone-agent"
path = "src/main.rs"
required-features = ["blob"]
[features]
# Flavors the host injects and calls into, as opposed to the module Linux loads.
blob = []
# The XNU flavour itself, and the two ways it is packaged: a blob the host injects
# into a running kernel, and a kext that kernel loads.
@@ -26,7 +23,6 @@ xnu = ["xnu-core", "blob"]
xnu-kext = ["xnu-core"]
win9x = ["blob"]
winnt = ["blob"]
linux = []
linux-injected = ["blob"]
[profile.release]
+18 -24
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
@@ -58,7 +50,7 @@ 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` |
@@ -72,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
+17
View File
@@ -33,8 +33,22 @@ SECTIONS {
_set_memory_x = .; . += 4;
_set_memory_nx = .; . += 4;
_modules = .; . += 4;
_module_mutex = .; . += 4;
_mutex_lock = .; . += 4;
_mutex_unlock = .; . += 4;
_register_module_notifier = .; . += 4;
_unregister_module_notifier = .; . += 4;
_frida_kmod_link_open = .; . += 4;
_frida_kmod_link_close = .; . += 4;
_frida_kmod_link_send = .; . += 4;
_frida_kmod_link_recv = .; . += 4;
_frida_kmod_link_pending = .; . += 4;
_frida_kmod_exit_worker = .; . += 4;
_frida_kmod_layout = .; . += 4;
_frida_kmod_remap_writable = .; . += 4;
_frida_kmod_unmap_writable = .; . += 4;
_execmem_alloc_rw = .; . += 4;
_vmalloc_noprof = .; . += 4;
_execmem_free = .; . += 4;
_module_alloc = .; . += 4;
_module_memfree = .; . += 4;
@@ -89,6 +103,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;
@@ -97,6 +113,7 @@ SECTIONS {
_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;
+43
View File
@@ -33,8 +33,48 @@ SECTIONS {
_set_memory_x = .; . += 8;
_set_memory_nx = .; . += 8;
_modules = .; . += 8;
_module_mutex = .; . += 8;
_mutex_lock = .; . += 8;
_mutex_unlock = .; . += 8;
_register_module_notifier = .; . += 8;
_unregister_module_notifier = .; . += 8;
_frida_kmod_link_open = .; . += 8;
_frida_kmod_link_close = .; . += 8;
_frida_kmod_link_send = .; . += 8;
_frida_kmod_link_recv = .; . += 8;
_frida_kmod_link_pending = .; . += 8;
_frida_kmod_exit_worker = .; . += 8;
_frida_kmod_layout = .; . += 8;
_frida_kmod_remap_writable = .; . += 8;
_frida_kmod_unmap_writable = .; . += 8;
_execmem_alloc_rw = .; . += 8;
_vmalloc_noprof = .; . += 8;
_queued_spin_lock_slowpath = .; . += 8;
_queued_read_lock_slowpath = .; . += 8;
_queued_write_lock_slowpath = .; . += 8;
_find_get_task_by_vpid = .; . += 8;
___put_task_struct = .; . += 8;
_task_join_group_stop = .; . += 8;
___cleanup_sighand = .; . += 8;
_kmem_cache_free = .; . += 8;
_signal_cachep = .; . += 8;
_put_files_struct = .; . += 8;
_switch_task_namespaces = .; . += 8;
_free_fs_struct = .; . += 8;
___put_cred = .; . += 8;
__raw_spin_lock = .; . += 8;
__raw_spin_unlock = .; . += 8;
_capable = .; . += 8;
_show_map = .; . += 8;
_show_smap = .; . += 8;
_proc_fill_cache = .; . += 8;
_proc_pid_status = .; . += 8;
_do_task_stat = .; . += 8;
_release_task = .; . += 8;
_proc_task_instantiate = .; . += 8;
_proc_fd_instantiate = .; . += 8;
_proc_fdinfo_instantiate = .; . += 8;
___arm64_sys_prctl = .; . += 8;
_execmem_free = .; . += 8;
_module_alloc = .; . += 8;
_module_memfree = .; . += 8;
@@ -86,6 +126,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;
@@ -97,6 +139,7 @@ SECTIONS {
_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;
+16
View File
@@ -32,8 +32,22 @@ SECTIONS {
_set_memory_x = .; . += 4;
_set_memory_nx = .; . += 4;
_modules = .; . += 4;
_module_mutex = .; . += 4;
_mutex_lock = .; . += 4;
_mutex_unlock = .; . += 4;
_register_module_notifier = .; . += 4;
_unregister_module_notifier = .; . += 4;
_frida_kmod_link_open = .; . += 4;
_frida_kmod_link_close = .; . += 4;
_frida_kmod_link_send = .; . += 4;
_frida_kmod_link_recv = .; . += 4;
_frida_kmod_link_pending = .; . += 4;
_frida_kmod_exit_worker = .; . += 4;
_frida_kmod_layout = .; . += 4;
_frida_kmod_remap_writable = .; . += 4;
_frida_kmod_unmap_writable = .; . += 4;
_execmem_alloc_rw = .; . += 4;
_vmalloc_noprof = .; . += 4;
_text_poke_kgdb = .; . += 4;
_execmem_free = .; . += 4;
_module_alloc = .; . += 4;
@@ -55,6 +69,7 @@ SECTIONS {
_request_threaded_irq = .; . += 4;
_vmap = .; . += 4;
_current_task = .; . += 4;
_pcpu_hot = .; . += 4;
_ioremap = .; . += 4;
_ioremap_nocache = .; . += 4;
___default_kernel_pte_mask = .; . += 4;
@@ -100,6 +115,7 @@ SECTIONS {
__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;
+42
View File
@@ -33,8 +33,45 @@ SECTIONS {
_set_memory_x = .; . += 8;
_set_memory_nx = .; . += 8;
_modules = .; . += 8;
_module_mutex = .; . += 8;
_mutex_lock = .; . += 8;
_mutex_unlock = .; . += 8;
_register_module_notifier = .; . += 8;
_unregister_module_notifier = .; . += 8;
_frida_kmod_link_open = .; . += 8;
_frida_kmod_link_close = .; . += 8;
_frida_kmod_link_send = .; . += 8;
_frida_kmod_link_recv = .; . += 8;
_frida_kmod_link_pending = .; . += 8;
_frida_kmod_exit_worker = .; . += 8;
_frida_kmod_layout = .; . += 8;
_frida_kmod_remap_writable = .; . += 8;
_frida_kmod_unmap_writable = .; . += 8;
_execmem_alloc_rw = .; . += 8;
_vmalloc_noprof = .; . += 8;
_find_get_task_by_vpid = .; . += 8;
___put_task_struct = .; . += 8;
_task_join_group_stop = .; . += 8;
___cleanup_sighand = .; . += 8;
_kmem_cache_free = .; . += 8;
_signal_cachep = .; . += 8;
_put_files_struct = .; . += 8;
_switch_task_namespaces = .; . += 8;
_free_fs_struct = .; . += 8;
___put_cred = .; . += 8;
__raw_spin_lock = .; . += 8;
__raw_spin_unlock = .; . += 8;
_capable = .; . += 8;
_show_map = .; . += 8;
_show_smap = .; . += 8;
_proc_fill_cache = .; . += 8;
_proc_pid_status = .; . += 8;
_do_task_stat = .; . += 8;
_release_task = .; . += 8;
_proc_task_instantiate = .; . += 8;
_proc_fd_instantiate = .; . += 8;
_proc_fdinfo_instantiate = .; . += 8;
___x64_sys_prctl = .; . += 8;
_text_poke_kgdb = .; . += 8;
_execmem_free = .; . += 8;
_module_alloc = .; . += 8;
@@ -59,6 +96,8 @@ SECTIONS {
_page_offset_base = .; . += 8;
_current_task = .; . += 8;
_cpu_current_top_of_stack = .; . += 8;
___preempt_count = .; . += 8;
_pcpu_hot = .; . += 8;
_register_die_notifier = .; . += 8;
_unregister_die_notifier = .; . += 8;
_ioremap = .; . += 8;
@@ -91,6 +130,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;
@@ -106,6 +147,7 @@ SECTIONS {
__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;
+9 -2
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");
@@ -42,8 +47,6 @@ fn main() {
.write_to_file(out_dir.join("bindings.rs"))
.expect("Couldn't write bindings");
// The Linux flavour is a staticlib: kbuild owns the final link, so it — not
// us — decides where the devkit and libc archives come from.
if env::var("CARGO_FEATURE_BLOB").is_ok() {
println!(
"cargo:rustc-link-search=native={}",
@@ -84,6 +87,10 @@ fn main() {
// references the entrypoint the host calls; keep the linker from
// dropping it along with the rest of the unreferenced archive members.
println!("cargo:rustc-link-arg=--undefined=_start");
if env::var("CARGO_FEATURE_LINUX_INJECTED").is_ok() {
println!("cargo:rustc-link-arg=--undefined=frida_agent_wake");
println!("cargo:rustc-link-arg=--undefined=frida_agent_stop");
}
let note = compile_version_note(&out_dir, cc, &cc_args, arch);
println!("cargo:rustc-link-arg={}", note.to_string_lossy());
@@ -1,5 +1,6 @@
{
"agent": {
"type": "resident",
"transport": {
"type": "socket",
"path": "/tmp/frida-guest.sock"
+1
View File
@@ -1,5 +1,6 @@
{
"agent": {
"type": "resident",
"transport": {
"type": "device",
"path": "/dev/frida"
+9 -21
View File
@@ -2,27 +2,15 @@ MODULE_NAME ?= frida-agent
obj-m += $(MODULE_NAME).o
# frida-agent-prelinked.o is not built from source here: it is the Rust agent, the
# GumJS devkit and the C library collapsed into a single relocatable object by the prelink
# step in Makefile. Copy it in through if_changed rather than kbuild's _shipped rule,
# so the .cmd file modpost expects gets written.
$(MODULE_NAME)-y := frida-kmod.o frida-agent-prelinked.o
$(MODULE_NAME)-y := frida-kmod.o frida-agent-image.o
targets += frida-agent-prelinked.o
AFLAGS_frida-agent-image.o := -DFRIDA_AGENT_IMAGE='"$(FRIDA_AGENT_IMAGE)"'
quiet_cmd_frida_agent_prelinked = COPY $@
cmd_frida_agent_prelinked = cp $< $@
frida-header-has = $(if $(shell grep -ls '$(2)' $(srctree)/$(1)),-D$(3))
ccflags-y += $(call frida-header-has,include/linux/sched.h,list_head[[:space:]]*thread_group;,FRIDA_HAVE_TASK_THREAD_GROUP)
ccflags-y += $(call frida-header-has,include/linux/sched/signal.h,int[[:space:]]*quick_threads;,FRIDA_HAVE_SIGNAL_QUICK_THREADS)
ccflags-y += $(call frida-header-has,include/linux/fs_struct.h,spinlock_t[[:space:]]*lock;,FRIDA_HAVE_FS_STRUCT_LOCK)
ccflags-y += $(call frida-header-has,arch/$(SRCARCH)/include/asm/current.h,struct[[:space:]]*pcpu_hot[[:space:]]*{,FRIDA_HAVE_PCPU_HOT)
ccflags-y += $(call frida-header-has,arch/$(SRCARCH)/include/asm/processor.h,vl_onexec\[ARM64_VEC_MAX\],FRIDA_HAVE_VECTOR_LENGTHS)
$(obj)/frida-agent-prelinked.o: $(src)/frida-agent-prelinked.o.bin FORCE
$(call if_changed,frida_agent_prelinked)
# On x86 objtool validates every object going into a module and generates its ORC
# unwind tables. It was not built to read a JavaScript runtime linked against a
# freestanding libc: it gives up on the first Rust switch jump table it cannot
# follow, and the tables would only ever describe frames no kernel stack trace walks
# into. Both the blob and the module object it is linked into have to be excused,
# since objtool runs over each.
OBJECT_FILES_NON_STANDARD_frida-agent-prelinked.o := y
OBJECT_FILES_NON_STANDARD_$(MODULE_NAME).o := y
ccflags-y += $(FRIDA_KMOD_DEFS)
$(obj)/frida-agent-image.o: $(FRIDA_AGENT_IMAGE)
+28 -160
View File
@@ -1,14 +1,3 @@
# Builds frida-agent.ko in two steps, because kbuild insists on owning the final
# link:
#
# 1. Collapse the Rust staticlib plus everything it needs — the GumJS devkit and
# the C library — into one relocatable object, and localize every symbol except
# the entry points frida-kmod.c calls. Localizing matters: Gum and the C library
# define names like memcpy and malloc that must not leak into the kernel's
# namespace.
# 2. Hand that object to kbuild as a shipped object, so vermagic, modinfo and
# CRCs all come from the target kernel's own build system.
#
# See README.md for usage.
# KDIR is the kernel to build against. When it is a source tree that was configured
@@ -17,142 +6,44 @@
KDIR ?= /lib/modules/$(shell uname -r)/build
PROFILE ?= release
# The kernel's own name for the architecture, and the one Rust knows it by. Both
# follow from the machine this is built for, which defaults to the one building.
ARCH ?= $(shell uname -m \
| sed -e 's/^i[3-6]86$$/x86/' -e 's/^x86_64$$/x86/' -e 's/^aarch64$$/arm64/')
BITS ?= $(shell uname -m \
| sed -e 's/^i[3-6]86$$/32/' -e 's/^x86_64$$/64/' -e 's/^aarch64$$/64/')
ifeq ($(ARCH)-$(BITS),x86-32)
RUST_TARGET ?= i686-unknown-none
AGENT_TARGET_FLAGS ?= --target $(AGENT_DIR)/$(RUST_TARGET).json -Z json-target-spec
RUST_TARGET ?= i686-unknown-none-pic
SDK_ARCH ?= x86
TOOLPREFIX ?=
AGENT_LD_EMULATION ?= elf_i386
AGENT_RUSTFLAGS ?=
AGENT_CFLAGS ?= --target=i386-none-elf
else ifeq ($(ARCH)-$(BITS),x86-64)
RUST_TARGET ?= x86_64-unknown-none
RUST_TARGET ?= x86_64-unknown-none-pic
SDK_ARCH ?= x86_64
TOOLPREFIX ?=
AGENT_LD_EMULATION ?= elf_x86_64
AGENT_RUSTFLAGS ?= -Zcf-protection=branch -Cllvm-args=--min-jump-table-entries=65536
AGENT_CFLAGS ?= --target=x86_64-none-elf -fcf-protection=branch -fno-jump-tables
else ifeq ($(ARCH)-$(BITS),arm64-64)
RUST_TARGET ?= aarch64-unknown-none
RUST_TARGET ?= aarch64-unknown-none-pic
SDK_ARCH ?= arm64
TOOLPREFIX ?= aarch64-none-elf-
AGENT_RUSTFLAGS ?=
AGENT_CFLAGS ?= --target=aarch64-none-elf -ffixed-x18
else
$(error Unsupported ARCH "$(ARCH)" and BITS "$(BITS)"; this flavour covers \
$(error Unsupported ARCH "$(ARCH)" and BITS "$(BITS)"; this flavor covers \
x86/32, x86/64 and arm64/64)
endif
AGENT_TARGET_FLAGS ?= --target $(RUST_TARGET)
AGENT_DIR := $(CURDIR)/..
AGENT_TARGET_DIR := $(AGENT_DIR)/target-linux
AGENT_LIB := $(AGENT_TARGET_DIR)/$(RUST_TARGET)/$(PROFILE)/libfrida_barebone_agent.a
FRIDA_SDK ?= $(error Set FRIDA_SDK to a none-$(SDK_ARCH)-softfloat_pic SDK prefix)
GUMJS_DEVKIT_DIR ?= $(error Set GUMJS_DEVKIT_DIR to the directory holding libfrida-gumjs.a)
# Any binutils that speak the target's ELF will do; nothing here is compiled with
# them. Building for the host's own architecture, that is the host's binutils.
AGENT_LD ?= $(TOOLPREFIX)ld
AGENT_AR ?= $(TOOLPREFIX)ar
AGENT_NM ?= $(TOOLPREFIX)nm
AGENT_OBJCOPY ?= $(TOOLPREFIX)objcopy
# The ABI the kernel imposes on anything linked into it. The flags only reach crates
# Cargo compiles, and the toolchain ships core, alloc and compiler_builtins prebuilt
# without them, so those three have to be rebuilt from source as well. That needs the
# rust-src component and an unstable flag, hence RUSTC_BOOTSTRAP.
#
# On x86_64 that is endbr64 on every address-taken function, since a kernel with
# CONFIG_X86_KERNEL_IBT faults an indirect call landing anywhere else, and switches
# compiled as comparisons rather than jump tables, which objtool cannot follow into.
# Soft-float, the kernel code model and the absent red zone come with the target
# itself. On arm64
# it is x18, which the kernel reserves for the shadow call stack, so nothing linked in
# may use it as a general-purpose register.
ifeq ($(ARCH)-$(BITS),x86-32)
AGENT_RUSTFLAGS ?=
AGENT_CFLAGS ?= --target=i386-none-elf \
-Xclang -target-feature -Xclang +soft-float -mno-mmx -mno-sse \
-mlong-double-64 -fno-pic --sysroot=$(FRIDA_SDK)
else ifeq ($(ARCH)-$(BITS),x86-64)
AGENT_RUSTFLAGS ?= -Zcf-protection=branch \
-Cllvm-args=--min-jump-table-entries=65536 \
-Crelocation-model=static -Zplt=yes
AGENT_CFLAGS ?= --target=x86_64-none-elf \
-Xclang -target-feature -Xclang +soft-float -mno-mmx -mno-sse -mno-red-zone \
-mlong-double-64 -mcmodel=kernel -fcf-protection=branch -fno-jump-tables \
-fno-pic --sysroot=$(FRIDA_SDK)
else
AGENT_RUSTFLAGS ?= -Zfixed-x18
AGENT_CFLAGS ?= --target=aarch64-none-elf -mabi=aapcs-soft -mgeneral-regs-only \
-ffixed-x18 -fno-pic --sysroot=$(FRIDA_SDK)
endif
AGENT_BUILD_STD ?= -Z build-std=core,alloc,compiler_builtins
# The devkit header reaches for GLib's and capstone's, which a devkit built against
# an SDK leaves in that SDK rather than beside itself. The sysroot's own include
# directory is spelled out because only clang's bare-metal driver, which it selects
# for arm64 but not for x86_64, searches there on its own.
AGENT_CFLAGS += -isystem $(FRIDA_SDK)/include \
AGENT_CC ?= clang
AGENT_CFLAGS += -I$(FRIDA_SDK)/include \
-I$(FRIDA_SDK)/include/glib-2.0 \
-I$(FRIDA_SDK)/lib/glib-2.0/include \
-I$(FRIDA_SDK)/include/capstone \
-I$(FRIDA_SDK)/include/json-glib-1.0
# The soft-float SDK, whose libc is picolibc and whose compiler runtime is
# compiler-rt. GCC's own newlib and libgcc are hard-float and cannot be used here.
FRIDA_SDK ?= $(error Set FRIDA_SDK to a none-$(SDK_ARCH)-softfloat_nopic SDK prefix)
LIBM = $(FRIDA_SDK)/lib/libm.a
LIBBUILTINS = $(FRIDA_SDK)/lib/libclang_rt.builtins.a
# bindgen reads the devkit header through whatever this compiler reports as its
# include path, so it has to be one that knows where the SDK keeps its libc. It has
# to be clang either way: -mabi=aapcs-soft landed there in 19 and GCC has it at no
# version, and on x86_64 the soft-float ABI is reachable only as an -Xclang feature.
AGENT_CC ?= clang
PRELINKED := frida-agent-prelinked.o.bin
AGENT_LIB_SOFTFLOAT := $(AGENT_TARGET_DIR)/libfrida-barebone-agent-softfloat.a
# On arm64 the toolchain's own compiler_builtins is compiled for the ordinary AAPCS,
# where a double travels in d0: its __muldf3 opens with "fmov x14, d0". Everything
# else there is -mabi=aapcs-soft and passes that same double in x0. Whole-archiving
# the staticlib puts those definitions in front of the SDK's compiler-rt, which has
# the soft-float ones, and every double the C library computes then comes back
# garbage — quietly, since the arithmetic still runs. Drop them and let compiler-rt
# answer instead. x86_64 needs none of this: soft-float is part of the Rust target
# there, so the copy build-std compiles already passes doubles the way the C half
# expects.
ifeq ($(ARCH),arm64)
DROP_HARDFLOAT_BUILTINS = \
$(AGENT_AR) d $@ $$($(AGENT_NM) --print-armap $@ | sed -n 's/^__muldf3 in //p') && \
$(AGENT_AR) s $@
else
DROP_HARDFLOAT_BUILTINS = :
endif
# The agent puts malloc, the C library's locks and its process stubs on the kernel's
# own facilities, so picolibc's versions are duplicate definitions rather than
# fallbacks. Work out which members they live in instead of naming them: the set
# follows from what the agent defines, and picolibc moves things between releases.
LIBC := $(AGENT_TARGET_DIR)/libc-trimmed.a
ENTRY_POINTS := frida_agent_start frida_agent_stop \
gum_interceptor_obtain gum_interceptor_begin_transaction \
gum_interceptor_end_transaction gum_interceptor_replace \
gum_interceptor_revert
KEEP_GLOBAL := $(foreach s,$(ENTRY_POINTS),--keep-global-symbol=$(s))
# Two structures the reparenting path reaches into lost a member upstream, and
# neither change announces itself as anything frida-kmod.c could test for. The
# headers it is compiled against are the authority on both, so ask them.
FRIDA_KMOD_DEFS := \
$(if $(shell grep -l 'list_head[[:space:]]*thread_group;' \
$(KDIR)/include/linux/sched.h),-DFRIDA_HAVE_TASK_THREAD_GROUP) \
$(if $(shell grep -l 'spinlock_t[[:space:]]*lock;' \
$(KDIR)/include/linux/fs_struct.h),-DFRIDA_HAVE_FS_STRUCT_LOCK)
AGENT_DIR := $(CURDIR)/..
AGENT_TARGET_DIR := $(AGENT_DIR)/target-linux-$(SDK_ARCH)
AGENT_IMAGE := frida-agent.elf
all: modules
@@ -160,47 +51,24 @@ all: modules
# next boot, so bring-up wants a fresh one per attempt.
MODULE_NAME ?= frida-agent
modules: $(PRELINKED)
modules: $(AGENT_IMAGE)
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) \
FRIDA_KMOD_DEFS="$(FRIDA_KMOD_DEFS)" MODULE_NAME=$(MODULE_NAME) modules
MODULE_NAME=$(MODULE_NAME) FRIDA_AGENT_IMAGE=$(CURDIR)/$(AGENT_IMAGE) modules
$(PRELINKED): $(AGENT_LIB_SOFTFLOAT) $(LIBC) prelink.lds
$(AGENT_LD) -r $(if $(AGENT_LD_EMULATION),-m $(AGENT_LD_EMULATION)) \
-T prelink.lds -o $@.tmp \
--whole-archive $(AGENT_LIB_SOFTFLOAT) --no-whole-archive \
--start-group \
$(GUMJS_DEVKIT_DIR)/libfrida-gumjs.a \
$(LIBC) \
$(LIBM) \
$(LIBBUILTINS) \
--end-group
$(AGENT_OBJCOPY) $(KEEP_GLOBAL) --strip-debug \
--remove-section=.llvmbc --remove-section=.llvmcmd $@.tmp $@
rm -f $@.tmp
$(AGENT_LIB_SOFTFLOAT): agent
cp $(AGENT_LIB) $@
$(DROP_HARDFLOAT_BUILTINS)
$(LIBC): export LC_ALL = C
$(LIBC): $(AGENT_LIB_SOFTFLOAT)
cp $(FRIDA_SDK)/lib/libc.a $@
$(AGENT_NM) --defined-only $(AGENT_LIB_SOFTFLOAT) \
| awk 'NF == 3 && $$2 ~ /^[TDBR]$$/ { print $$3 }' | sort -u > $@.agent-syms
$(AGENT_NM) --print-armap $@ | sed -n 's/^\([^ ]*\) in \(.*\)$$/\1 \2/p' | sort -u > $@.members
$(AGENT_AR) d $@ $$(join $@.agent-syms $@.members | awk '{ print $$2 }' | sort -u)
$(AGENT_AR) s $@
rm -f $@.agent-syms $@.members
$(AGENT_IMAGE): agent
cp $(AGENT_TARGET_DIR)/$(RUST_TARGET)/$(PROFILE)/frida-barebone-agent $@
agent:
cd $(AGENT_DIR) && RUSTC_BOOTSTRAP=1 RUSTFLAGS="$(AGENT_RUSTFLAGS)" \
CC_$(subst -,_,$(RUST_TARGET))="$(AGENT_CC) $(AGENT_CFLAGS)" \
cargo build --$(PROFILE) \
$(AGENT_TARGET_FLAGS) --features linux \
--target-dir $(AGENT_TARGET_DIR) $(AGENT_BUILD_STD)
cd $(AGENT_DIR) && RUSTC_BOOTSTRAP=1 \
RUSTFLAGS="-L native=$(FRIDA_SDK)/lib $(AGENT_RUSTFLAGS)" \
CC="$(AGENT_CC) $(AGENT_CFLAGS)" \
GUMJS_DEVKIT_DIR=$(GUMJS_DEVKIT_DIR) \
cargo build --$(PROFILE) --features linux-injected \
--target ./$(RUST_TARGET).json -Z json-target-spec \
--target-dir $(AGENT_TARGET_DIR) -Z build-std=core,alloc,compiler_builtins
clean:
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) clean
rm -f $(PRELINKED) $(PRELINKED).tmp $(AGENT_LIB_SOFTFLOAT) $(LIBC)
rm -f $(AGENT_IMAGE)
.PHONY: all modules agent clean
+127 -217
View File
@@ -1,291 +1,201 @@
# Linux kernel module
The same agent that the Barebone backend injects into XNU from the outside, but
loaded from the inside as an ordinary kernel module.
The agent the Barebone backend injects into a Linux guest from the outside,
loaded from the inside instead. The module is a loader and nothing more: it
carries the same `linux-injected` image a hypervisor would write into guest
memory, maps and relocates it, and starts it. Everything past that point is the
one implementation, whichever way the agent got in: the GumJS runtime, process
and module enumeration, cloaking, the userspace injector.
## How it differs from the XNU flavor
## What the loader does
| | XNU | Linux |
|-------------------|--------------------------------------------------|-------------------------------------------|
| Delivery | remote stub injects a freestanding ELF | `insmod frida-agent.ko` |
| Kernel primitives | addresses patched into `.kernel_addrs` by the host | direct calls into `frida-kmod.c` |
| Bootstrap config | GVariant blob written into guest memory | module parameters |
| Transport | virtio hostlink or vsock | `/dev/frida` character device |
| Page permissions | host rewrites descriptors over the hostlink | `set_memory_*()` |
| Writable alias | shadow copy, committed via physical-memory bridge | `vmap()` of the same pages |
| Symbols | table supplied by the host | kallsyms |
| | From the outside | From the inside (`frida-agent.ko`) |
|--------------------|-----------------------------------------------|--------------------------------------------------|
| Delivery | host writes the image into guest memory | `insmod`; the image is embedded in the module |
| Relocation | host applies `.rela.dyn` | loader applies `.rela.dyn` (`.rel.dyn` on x86) |
| `.kernel_addrs` | host fills the slots from its symbol source | loader fills them from kallsyms |
| Struct layouts | BTF | `offsetof()` table from the module's headers, BTF behind it |
| Symbol table | supplied by the host | built by the loader from kallsyms |
| Transport | virtio hostlink or vsock | `/dev/frida` character device |
| Writable alias | host rewrites page tables | `vmap()` of the same pages |
| Lifetime | ends when the host detaches | resident until `rmmod` |
Because the Linux agent performs its own memory work, it never issues the
`RemapWritablePages`, `MemoryProtect` or `PatchCode` host RPCs. The wire protocol
is otherwise identical, so the host side sees the same `(yqv)` frames.
The configuration the agent starts from is the same GVariant either way; the
loader writes one naming the character device as its transport. A slot the
loader fills that the host never does — the link functions, the layout table,
the writable alias — is what tells the agent it was loaded from the inside.
## Building
Four inputs: this repository, a GumJS devkit built for
`none-<arch>-softfloat_nopic`, the SDK it was built against, and the kernel the module is for. `Makefile`
covers x86_64 and arm64, and defaults to the architecture of the machine building.
Building for the running kernel is a good deal shorter than cross-building for a
device, so that walkthrough comes first; the arm64 one below it is the same steps
with the kernel obtained from elsewhere.
Three inputs besides this repository: a GumJS devkit for
`none-<arch>-softfloat_pic`, the SDK it was built against, and the kernel the
module is for. Building the image needs no kernel and runs anywhere; only the
kbuild step needs the kernel's build tree.
### Toolchain
rustup target add x86_64-unknown-none # or aarch64-unknown-none
rustup component add rust-src
sudo dnf install clang binutils kernel-devel-$(uname -r)
rustup toolchain install nightly
rustup component add rust-src --toolchain nightly
clang must be 19 or newer, since that is where `-mabi=aapcs-soft` landed; GCC does
not implement it at any version, nor does it reach x86_64's soft-float ABI. The
binutils are for the prelink, which reads the archive map that only GNU `nm` prints —
the LLVM one does not; cross-building wants the target's, natively the host's own
will do. The host compiler is for Cargo's own build scripts, which run on the machine
doing the building.
The image links with `rust-lld`; nothing GNU is involved. clang has to be 19 or
newer, which is where `-mabi=aapcs-soft` landed. The kbuild step wants whatever
compiler the kernel was built with.
The commands below are run from the top of this repository, with `releng` checked
out — `git submodule update --init releng` if the clone did not recurse.
### The devkit and the SDK
### For the running x86_64 kernel
version=17.26.2
arch=arm64 # or x86_64
base=https://github.com/frida/frida/releases/download/$version
curl -LO $base/frida-gumjs-devkit-$version-none-$arch-softfloat_pic.tar.xz
mkdir -p ~/gumjs-devkit
tar -C ~/gumjs-devkit -xf frida-gumjs-devkit-$version-none-$arch-softfloat_pic.tar.xz
No prebuilt bundles are published for `none-x86_64-softfloat_nopic`, so both inputs are
built locally, against the same SDK:
releng/deps.py sync sdk none-$arch-softfloat_pic ~/sdk-none-$arch-softfloat_pic
releng/deps.py build --bundle=sdk --host=none-x86_64-softfloat_nopic
mkdir -p ~/sdk-none-x86_64-softfloat_nopic
tar -C ~/sdk-none-x86_64-softfloat_nopic -xf deps/sdk-none-x86_64-softfloat_nopic.tar.xz
`releng/deps.toml` pins which SDK a devkit was built against, and mixing the two
leaves symbols undefined at link time.
gum=subprojects/frida-gum
mkdir -p $gum/build/none-x86_64-softfloat_nopic
(cd $gum/build/none-x86_64-softfloat_nopic \
&& FRIDA_DEPS=$PWD/../../../../deps ../../configure \
--host=none-x86_64-softfloat_nopic \
--enable-gumjs \
--with-devkits=gumjs \
--with-devkit-symbol-scope=original \
&& make)
### For the running kernel
make -C src/barebone/agent/linux \
FRIDA_SDK=$HOME/sdk-none-x86_64-softfloat_nopic \
GUMJS_DEVKIT_DIR=$PWD/$gum/build/none-x86_64-softfloat_nopic/bindings/gumjs/devkit
FRIDA_SDK=$HOME/sdk-none-$arch-softfloat_pic \
GUMJS_DEVKIT_DIR=$HOME/gumjs-devkit
Everything else — `KDIR`, the Rust target, the binutils, the kernel's own name for the
architecture — follows from the running kernel.
`KDIR`, the Rust target and the kernel's name for the architecture all follow
from the running kernel. `make frida-agent.elf` stops after the image, which is
also what the Barebone backend injects from the outside.
### For an arm64 device
### For an Android device
Here there are published bundles to start from, and the kernel has to be assembled.
#### The devkit and the SDK
version=17.17.0
base=https://github.com/frida/frida/releases/download/$version
curl -LO $base/frida-gumjs-devkit-$version-none-arm64-softfloat_nopic.tar.xz
mkdir -p ~/gumjs-devkit
tar -C ~/gumjs-devkit -xf frida-gumjs-devkit-$version-none-arm64-softfloat_nopic.tar.xz
releng/deps.py sync sdk none-arm64-softfloat_nopic ~/sdk-none-arm64-softfloat_nopic
The SDK carries picolibc and the compiler-rt builtins the devkit expects to be linked
against, and `releng/deps.toml` pins which one. Mixing a devkit with an SDK built for
a different libc leaves undefined symbols at prelink time.
#### The kernel
Nothing here needs the device's own kernel to be built, but the config alone is not
enough either. `uname -r` ends in the GKI commit and the build number:
The image is built the same way; it is the kernel tree that takes assembling.
`uname -r` ends in the GKI commit and the build number:
adb shell uname -r
6.1.145-android14-11-gc1de4747ac59-ab14219743
6.6.102-android15-8-g6eb5b2a8c46b-ab14739656-4k
which is commit `c1de4747ac59` and build `14219743`. Three pieces are keyed to them:
which is commit `6eb5b2a8c46b` and build `14739656`. The source comes from the
commit, the CRCs `CONFIG_MODVERSIONS` checks from what CI published for the
build, and the config from the device itself:
commit=c1de4747ac59
build=14219743
commit=6eb5b2a8c46b
build=14739656
ci=https://ci.android.com/builds/submitted/$build/kernel_aarch64/latest/raw
mkdir -p ~/kernel-prepared ~/kernel-source
curl -sSL $ci/modules_prepare_outdir.tar.gz | tar -xz -C ~/kernel-prepared
curl -sSL $ci/kernel_aarch64_Module.symvers -o ~/kernel-prepared/Module.symvers
mkdir -p ~/kernel
curl -sSL https://android.googlesource.com/kernel/common/+archive/$commit.tar.gz \
| tar -xz -C ~/kernel-source
| tar -xz -C ~/kernel
curl -sSL $ci/kernel_aarch64_Module.symvers -o ~/kernel/Module.symvers
adb shell "su -c 'cat /proc/config.gz'" | gunzip > ~/kernel/.config
make -C ~/kernel ARCH=arm64 LLVM=1 olddefconfig modules_prepare
`modules_prepare_outdir.tar.gz` is the configured build tree with `scripts/` already
compiled as x86-64 binaries, so the kbuild half has to run on an x86-64 Linux host —
an arm64 one gets as far as `CC [M]` and then fails to run `fixdep`. `Module.symvers`
carries the CRCs `CONFIG_MODVERSIONS` checks. The top-level `Makefile` in that prepared tree is a stub
that includes the source's, hence both `KDIR` and `KOUT` below.
A different GKI build of the same branch does not substitute for these: vermagic is
compared verbatim, and it names the exact build.
#### Build
That wants a Linux host, of either architecture, with clang and lld. With
`CONFIG_MODVERSIONS` the kernel matches a module by its CRCs rather than by the
release vermagic names, so the release this tree reports does not have to be
the device's; the vendor's own modules do not carry it either.
make -C src/barebone/agent/linux \
FRIDA_SDK=$HOME/sdk-none-arm64-softfloat_nopic \
ARCH=arm64 BITS=64 \
FRIDA_SDK=$HOME/sdk-none-arm64-softfloat_pic \
GUMJS_DEVKIT_DIR=$HOME/gumjs-devkit \
AGENT_LD=aarch64-linux-gnu-ld \
AGENT_AR=aarch64-linux-gnu-ar \
AGENT_NM=aarch64-linux-gnu-nm \
AGENT_OBJCOPY=aarch64-linux-gnu-objcopy \
KDIR=$HOME/kernel-source \
KOUT=$HOME/kernel-prepared \
KDIR=$HOME/kernel \
LLVM=1
`make` builds the Rust staticlib itself, prelinks it against the devkit and the SDK,
then hands the result to kbuild. The `AGENT_*` overrides are only needed because the
defaults name an `aarch64-none-elf-` GNU toolchain; drop them if that is what is
installed.
The prelink half runs anywhere; the kbuild half needs the x86-64 Linux host above.
Cross-building from macOS means running that step in a container holding the kernel
tree.
adb push src/barebone/agent/linux/frida-agent.ko /data/local/tmp/
## Loading
insmod frida-agent.ko
The module's init path returns as soon as the worker thread is running — watch
`dmesg` for `frida: listening on /dev/frida`. `rmmod frida-agent` unwinds in the
reverse order and does not return until the worker is off the module's text.
Init returns once the agent's worker is running and `/dev/frida` is there.
`rmmod frida-agent` stops the agent, waits for its worker to leave the image,
and only then frees it.
## Talking to it
The agent exposes `/dev/frida` and waits: one client at a time, `read`/`write` of the
same length-prefixed frames the other transports carry, with `poll` for readiness.
It never dials out, so there is no network namespace to get right and no ordering
requirement between loading the module and attaching a client.
The agent waits on `/dev/frida`: one client at a time, `read`/`write` of the
same length-prefixed frames the other transports carry, with `poll` for
readiness. It never dials out, so there is no network namespace to get right
and no ordering between loading the module and attaching a client.
On Android the node is created by devtmpfs without a policy-specific label, so an
SELinux-enforcing system needs the client's domain granted access to it before it can
open the device. A socket-based transport was the other option considered and is a
worse fit: `AF_VSOCK` only reaches a peer when a hypervisor is on the other side, and
having the kernel connect out to loopback needs something already listening before
the module loads.
On Android devtmpfs creates the node without a policy-specific label, so an
SELinux-enforcing system needs the client's domain granted access to it.
### Reaching it from a host
Serve the Barebone device from a frida-server running on the target, so the `open()`
happens next to the device node and the host talks to an ordinary frida-server:
Serve the Barebone device from a frida-server running on the target, so the
`open()` happens next to the device node:
FRIDA_BAREBONE_CONFIG=/data/local/tmp/linux-kmod.json \
frida-server --device barebone -l 127.0.0.1:27042
`etc/linux-kmod.json` is that config. From the host, with `adb forward tcp:27042
tcp:27042` in place:
`etc/linux-kmod.json` is that config. Its `"type": "resident"` is what keeps a
client leaving from stopping the agent. From the host, with
`adb forward tcp:27042 tcp:27042` in place:
frida -H 127.0.0.1:27042 -p 0
## Injecting into userspace
Loaded on a live system the module doubles as a ptrace-free injector for ordinary
processes — how the Linux local backend reaches its targets when the module is
present, falling back to `ptrace` when it is not. There is no second device node;
the channel is `prctl()` with a magic option and a token in the fifth argument,
On x86_64 and arm64 a resident agent doubles as a ptrace-free injector for
ordinary processes — how the Linux local backend reaches its targets when the
agent is there, falling back to `ptrace` when it is not. The channel is
`prctl()` with a magic option and a token in the fifth argument,
prctl(0x46524944, op, arg1, arg2, 0x1d5f9e6b2c7a4038)
and any call whose option or token does not match falls through to the real
prctl, so probing for the module looks like a stock kernel. `op` 32 pings and
prctl, so probing for the agent looks like a stock kernel. `op` 32 pings and
returns the magic.
Allocate, free, read, write and spawn — the primitives the injector drives
against a target pid — require `CAP_SYS_ADMIN`. A spawned thread is a real
`CLONE_THREAD` sibling that adopts the target's mm, files, credentials,
namespaces, fs and SysV semaphore undo list, so it is a native member of the
group rather than a kernel thread wearing its address space.
Allocate, free, read, write and spawn — what the injector drives against a
target pid — require `CAP_SYS_ADMIN`. A spawned thread is a real member of the
target's thread group that adopts its mm, files, credentials, namespaces, fs
and SysV semaphore undo list, rather than a kernel thread wearing its address
space.
Cloaking is Gum's own registry mirrored into the kernel so it holds against
`/proc` too: the module intercepts the `maps`, `smaps`, task and status readers
Cloaking is Gum's own registry mirrored into the kernel, so it holds against
`/proc` too: the agent intercepts the `maps`, `smaps`, task and status readers
and drops cloaked threads, ranges and fds while keeping the counts consistent. A
process cloaks its own resources with the token alone — no capability — so an
injected agent self-cloaks by forwarding Gum's updates over the same channel,
and the loader adds the footprint the agent itself never sees. A cloaked thread
still sees through the cloak when it reads `/proc`, so the agent is not blind to
its own process.
process cloaks its own resources with the token alone, so an injected agent
self-cloaks by forwarding Gum's updates over the same channel. A cloaked thread
still sees through the cloak when it reads `/proc`.
## What the target kernel has to provide
- **x86_64 or arm64.** The register-level pieces — the page size, cache maintenance,
where the syscall ABI leaves prctl()'s arguments, and how a spawned thread's
registers are set up for its return to userspace — have a half for each. The XNU
flavor remains arm64-only.
- **`CONFIG_KPROBES` and `CONFIG_KALLSYMS_ALL`.** A good deal of what the shim
needs is compiled in but not exported — `kallsyms_lookup_name`,
`kallsyms_on_each_symbol` and `set_memory_*` all are, and GKI trims its export
table to the KMI symbol list on top of that. The shim resolves them by name
through a kprobe, which is the standard way back in. Without kallsyms the module
still loads but cannot look up symbols or change page permissions.
- **x86_64 or arm64** for everything above; 32-bit x86 and ARM load the agent
but have no injector.
- **`CONFIG_KPROBES` and `CONFIG_KALLSYMS_ALL`.** Most of what the agent calls
is compiled in but not exported, and GKI trims its export table to the KMI
symbol list on top of that. The loader finds `kallsyms_lookup_name` through a
kprobe and resolves the rest by name.
- **Unsigned modules.** `CONFIG_MODULE_SIG_FORCE` must be off. Android's
`CONFIG_MODULE_SIG_PROTECT` is fine: it only demands signatures for modules on
the GKI list.
- **No BTI on arm64.** The image has no landing pads, so a CPU implementing BTI
under a kernel built with `CONFIG_ARM64_BTI_KERNEL` faults the first indirect
call into it.
## Kernel ABI constraints
A hardened kernel imposes an ABI on anything linked into it, and the FP constraint
below is why this flavor is built against a soft-float SDK
(`none-arm64-softfloat_nopic`, `none-x86_64-softfloat_nopic`) rather than an ordinary
bare-metal one. The remaining constraints are per-architecture.
A hardened kernel imposes an ABI on anything running inside it, which the image
honors whichever way it got in:
- **FP/SIMD.** JavaScript numbers are doubles, so a hardfloat build would use FP
for as long as the runtime lives, and neither architecture lets a kernel thread
hold it for free — x86 wants every stretch bracketed by `kernel_fpu_begin()`, and
on arm64 kernels before 6.9 there is no correct way to hold it at all:
`kernel_neon_begin()` runs with preemption disabled so we cannot sleep under it,
and the scheduler does not save a kernel thread's FP registers — live values are
lost on preemption and the FP state of whichever user task was interrupted is
corrupted. Building everything soft-float removes the question rather than
answering it: `-mabi=aapcs-soft -mgeneral-regs-only` on arm64, and on x86_64
LLVM's `+soft-float`, which is also what Rust's own `x86_64-unknown-none` turns
on, so the two halves agree on where a double travels. `-mno-sse` alone does not
reach the ABI; and `long double` has to be narrowed to 64 bits with
`-mlong-double-64`, since soft-float has no lowering for x87's 80-bit format and
LLVM crashes rather than calling out to a builtin.
- **Indirect branch tracking (x86_64).** With `CONFIG_X86_KERNEL_IBT` an indirect
call landing on anything but an `endbr64` faults, so everything linked in is built
`-fcf-protection=branch` and the Rust half `-Zcf-protection=branch`.
- **Kernel code model and red zone (x86_64).** Modules are loaded into the top 2GB
of the address space and their relocations are resolved as if by `-mcmodel=kernel`,
and there is no red zone below the stack pointer for an interrupt to step on. Both
come with the Rust target; the C half is told explicitly.
- **objtool (x86_64).** It validates every object entering a module and builds its
ORC unwind tables, and was not written to read a JavaScript runtime linked against
a freestanding libc. `Kbuild` marks the prelinked blob `OBJECT_FILES_NON_STANDARD`.
- **Shadow call stack (arm64).** With `CONFIG_SHADOW_CALL_STACK` the kernel reserves x18, so
the SDK and Gum are built `-ffixed-x18` and the Rust half with `-Zfixed-x18`.
Anything linked in that uses x18 as a scratch register overwrites the kernel's SCS
pointer, and the thread then returns through whatever that address happens to hold.
The Rust flag only reaches crates Cargo compiles, so `core`, `alloc` and
`compiler_builtins` — which the toolchain ships prebuilt, and which between them
account for every x18 write that used to reach the module — are rebuilt from source
via `-Z build-std`. That needs `rustup component add rust-src`.
## The soft-float libc
The libc is picolibc and the compiler runtime is compiler-rt, both from the
soft-float SDK that `FRIDA_SDK` points at. `Makefile` trims a copy of its
`libc.a` before the prelink: the agent implements `malloc`, the C library's locks and
its process stubs over the kernel's own facilities, so picolibc's are duplicate
definitions rather than fallbacks. The set is derived from what the agent defines
rather than listed, since picolibc moves things between releases.
On arm64 those compiler-rt builtins are load-bearing in a way that is easy to miss.
Rust's toolchain carries its own `compiler_builtins`, compiled for the ordinary AAPCS
where a double travels in `d0`, and the prelink whole-archives the Rust staticlib — so
without the filtering step in `Makefile` those definitions answer the C library's calls
to `__muldf3` and friends, which pass the same double in `x0`. Nothing crashes. The
arithmetic simply returns garbage, and the first thing to notice is GLib deciding a
hash table need not grow. x86_64 needs none of that filtering: soft-float is part of
the Rust target there, so the copy `-Z build-std` compiles already agrees with the C
half.
## What a script may look like
The runtime compiles scripts on a kernel thread's stack, and QuickJS parses
expressions by recursion, so the ceiling is nesting rather than length: roughly
fourteen levels of parentheses or fifteen of object literals on arm64 Linux, whose
kernel stack is 16 KiB — as is x86_64's. A script that exceeds it fails to compile rather than taking
anything down, though the message is not always `stack overflow` — an over-deep
`if`/`else` chain reports `expecting ';'`. Long scripts are fine; deep ones are not.
for as long as the runtime lives, and neither architecture lets a kernel
thread hold it for free — x86 wants every stretch bracketed by
`kernel_fpu_begin()`, and on arm64 kernels before 6.9 there is no correct way
to hold it at all. Building everything soft-float removes the question: the
SDK, the devkit and the Rust target all use the soft-float ABI.
- **Indirect branch tracking (x86_64).** With `CONFIG_X86_KERNEL_IBT` an
indirect call landing on anything but an `endbr64` faults, so the image is
built `-fcf-protection=branch` and `-Zcf-protection=branch`, with switches
compiled as comparisons rather than `notrack` jump tables. The kernel seals
the `endbr64` of every function it saw no indirect call to, so the loader
restores it on any it hands the agent to call.
- **Shadow call stack (arm64).** With `CONFIG_SHADOW_CALL_STACK` the kernel
reserves x18, so the SDK, the devkit and the Rust target all leave it alone.
`core`, `alloc` and `compiler_builtins` are rebuilt from source with
`-Z build-std` so that holds for them too.
- **Stack.** The runtime runs on a kernel thread's stack, and QuickJS parses
expressions by recursion, so the ceiling is nesting rather than length: around
fourteen levels of parentheses or fifteen of object literals on a 16 KiB
kernel stack. A script that exceeds it fails to compile rather than taking
anything down, though the message is not always `stack overflow`.
@@ -0,0 +1,7 @@
.section .rodata, "a"
.balign 16
.globl frida_agent_image_start
frida_agent_image_start:
.incbin FRIDA_AGENT_IMAGE
.globl frida_agent_image_end
frida_agent_image_end:
File diff suppressed because it is too large. Load diff
-8
View File
@@ -1,8 +0,0 @@
SECTIONS
{
.init_array 0 : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
}
+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
+15 -21
View File
@@ -236,7 +236,7 @@ unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl) -> bool {
unsafe fn rec_mutex_wait(impl_: &mut RecMutexImpl) {
while impl_.count.load(Ordering::Relaxed) != 0 {
wait_on(&impl_.count, &mut || impl_.count.load(Ordering::Relaxed) == 0);
wait_on(&impl_.count, None, &mut || impl_.count.load(Ordering::Relaxed) == 0);
}
}
@@ -361,7 +361,7 @@ unsafe fn rw_lock_reader_try_acquire(impl_: &mut RWLockImpl) -> bool {
unsafe fn rw_lock_wait_for_writers(impl_: &mut RWLockImpl) {
while impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT != 0 {
wait_on(&impl_.state, &mut || {
wait_on(&impl_.state, None, &mut || {
impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT == 0
});
}
@@ -369,7 +369,7 @@ unsafe fn rw_lock_wait_for_writers(impl_: &mut RWLockImpl) {
unsafe fn rw_lock_wait_for_all(impl_: &mut RWLockImpl) {
while impl_.state.load(Ordering::Relaxed) != 0 {
wait_on(&impl_.state, &mut || impl_.state.load(Ordering::Relaxed) == 0);
wait_on(&impl_.state, None, &mut || impl_.state.load(Ordering::Relaxed) == 0);
}
}
@@ -395,7 +395,7 @@ pub extern "C" fn g_cond_wait(cond: *mut GCond, mutex: *mut GMutex) {
let impl_ = cond_impl(cond);
while impl_.generation.load(Ordering::Acquire) == seen {
wait_on(&impl_.generation, &mut || {
wait_on(&impl_.generation, None, &mut || {
impl_.generation.load(Ordering::Acquire) != seen
});
}
@@ -431,11 +431,13 @@ pub extern "C" fn g_cond_wait_until(cond: *mut GCond, mutex: *mut GMutex, end_ti
let impl_ = cond_impl(cond);
let mut signalled = impl_.generation.load(Ordering::Acquire) != seen;
while !signalled && crate::kernel::monotonic_micros() < end_time {
wait_on(&impl_.generation, &mut || {
let mut now = crate::kernel::monotonic_micros();
while !signalled && now < end_time {
wait_on(&impl_.generation, Some((end_time - now) as u64), &mut || {
impl_.generation.load(Ordering::Acquire) != seen
});
signalled = impl_.generation.load(Ordering::Acquire) != seen;
now = crate::kernel::monotonic_micros();
}
g_mutex_lock(mutex);
@@ -453,6 +455,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) };
}
@@ -619,7 +622,7 @@ pub extern "C" fn _g_system_thread_wait(thread: *mut GSystemThread) {
let system_thread = thread as *mut SystemThreadImpl;
while (*system_thread).thread_id.load(Ordering::Acquire) == 0 {
wait_on(&(*system_thread).thread_id, &mut || {
wait_on(&(*system_thread).thread_id, None, &mut || {
(*system_thread).thread_id.load(Ordering::Acquire) != 0
});
}
@@ -644,7 +647,7 @@ unsafe fn cleanup_wrapper_data(wrapper_data: *mut ThreadWrapperData) {
unsafe fn lock_acquire(lock: &AtomicU32) {
while !unsafe { lock_try_acquire(lock) } {
wait_on(lock, &mut || lock.load(Ordering::Relaxed) == 0);
wait_on(lock, None, &mut || lock.load(Ordering::Relaxed) == 0);
}
}
@@ -658,19 +661,11 @@ unsafe fn lock_release(lock: &AtomicU32) {
wake_waiters(lock);
}
// Sleeps until `ready` holds or the slice expires, whichever comes first.
//
// These used to spin on wfe/sev. A kernel thread that spins never reaches the
// scheduler, so a lost wakeup — or a genuine deadlock, which is how we found this —
// starves the CPU it is on until the hardware watchdog takes the machine down. The
// bounded sleep turns both into a stall that can be observed instead.
//
// The address of the word being waited on names the wait channel: XNU pairs
// assert_wait()/thread_wakeup() on it directly, while the Linux shim has a single
// channel and treats it as a hint. Every caller re-checks its own condition in a
// loop, so a spurious wakeup costs nothing either way.
fn wait_on<T>(word: &T, ready: &mut dyn FnMut() -> bool) {
crate::kernel::wait(token_of(word), Some(WAIT_SLICE_US), ready);
// assert_wait()/thread_wakeup() on it directly. Every caller re-checks its own
// condition in a loop, so a spurious wakeup costs nothing either way.
fn wait_on<T>(word: &T, timeout_us: Option<u64>, ready: &mut dyn FnMut() -> bool) {
crate::kernel::wait(token_of(word), timeout_us, ready);
// The wait above returns without sleeping whenever `ready` already holds, so on
// its own it is not enough to keep a contended loop off the CPU: every caller
@@ -687,4 +682,3 @@ fn token_of<T>(word: &T) -> *const u8 {
word as *const T as *const u8
}
const WAIT_SLICE_US: u64 = 1000;
+57 -32
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,
};
@@ -21,12 +19,17 @@ use alloc::vec::Vec;
use core::ptr;
use core::sync::atomic::{AtomicU32, Ordering};
#[cfg(feature = "linux")]
use crate::gum_linux::enumerate_exports_in_range;
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[cfg(feature = "linux-injected")]
use crate::bindings::{
GumFoundSymbolFunc, GumSymbolDetails, GumSymbolType_GUM_SYMBOL_FUNCTION,
GumSymbolType_GUM_SYMBOL_OBJECT, GumSymbolType_GUM_SYMBOL_UNKNOWN, gboolean, gssize,
};
#[cfg(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,8 +184,13 @@ 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"))]
#[cfg(feature = "linux-injected")]
{
(*iface).enumerate_symbols =
Some(crate::signed_to_be_called_back(gum_native_module_enumerate_symbols, 0));
@@ -314,7 +322,33 @@ unsafe extern "C" fn gum_native_module_enumerate_exports(
}
}
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[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(feature = "linux-injected")]
unsafe extern "C" fn gum_native_module_enumerate_symbols(
self_: *mut GumModule,
func: GumFoundSymbolFunc,
@@ -352,12 +386,12 @@ unsafe extern "C" fn gum_native_module_enumerate_symbols(
/// What the backends implement for `gum_native_module_enumerate_symbols`: call
/// `callback` for every symbol in `[start, end)`, stopping early when it returns
/// false.
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[cfg(feature = "linux-injected")]
pub(crate) type FoundSymbolCallback<'a> = dyn FnMut(*const gchar, u64, u64, u8, bool) -> bool + 'a;
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[cfg(feature = "linux-injected")]
const SYMBOL_IS_OBJECT: u8 = 1;
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[cfg(feature = "linux-injected")]
const SYMBOL_IS_FUNCTION: u8 = 2;
/// What the backends implement for `gum_native_module_enumerate_exports`: call
@@ -365,6 +399,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 +438,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 +454,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 +503,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) {
+223 -48
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,
GumThreadFlags, 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")]
@@ -115,6 +116,11 @@ pub extern "C" fn gum_memory_try_remap_writable_pages(
return remap_agent_pages(first_page, n_pages);
}
#[cfg(feature = "linux-injected")]
if let Some(alias) = kernel::remap_writable(first_page as u64, n_pages) {
return alias as gpointer;
}
#[cfg(all(feature = "linux-injected", not(target_arch = "arm")))]
if writable_in_place(first_page, n_pages) {
return first_page;
@@ -137,7 +143,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 +181,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 +272,18 @@ 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;
}
#[cfg(feature = "linux-injected")]
if kernel::unmap_writable(writable as *mut core::ffi::c_void) {
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 +291,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 +416,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 +432,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 +453,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 +462,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 +476,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 +556,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);
}
@@ -548,16 +601,14 @@ pub extern "C" fn gum_memory_allocate(
#[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);
let ptr = kernel::alloc_code(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"))]
@@ -663,7 +714,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"))]
@@ -672,12 +723,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() {
@@ -706,17 +757,97 @@ 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,
_flags: GumThreadFlags) {
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"))]
@@ -730,7 +861,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;
}
@@ -864,3 +995,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() })
}
-217
View File
@@ -1,217 +0,0 @@
// Linux half of Gum's platform backend. Everything is done locally: we are the
// kernel, so page permissions go through set_memory_*(), writable aliases of
// write-protected text come from vmap(), and symbols come from kallsyms.
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,
g_pattern_spec_new, g_strdup, gboolean, gchar, gconstpointer, gpointer, gsize, guint,
gum_barebone_register_module, gum_mprotect,
},
gum::{self, FoundExportCallback},
kernel,
};
use alloc::format;
use core::ffi::CStr;
use core::ptr;
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_platform() -> *const crate::bindings::gchar {
c"linux".as_ptr() as *const crate::bindings::gchar
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_stack_size() -> gsize {
crate::linux::stack_headroom() as gsize
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_query_rwx_support() -> GumRwxSupport {
_GumRwxSupport_GUM_RWX_NONE
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_can_remap_writable() -> gboolean {
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_try_remap_writable_pages(
first_page: gpointer,
n_pages: guint,
) -> gpointer {
kernel::remap_writable(first_page as u64, n_pages) as gpointer
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages: guint) {
kernel::unmap_writable(writable as *mut core::ffi::c_void);
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_try_mprotect(
address: gpointer,
size: gsize,
prot: GumPageProtection,
) -> gboolean {
if kernel::protect(address as u64, size as usize, prot as u32) {
1
} else {
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_allocate(
_address: gpointer,
size: gsize,
_alignment: gsize,
prot: GumPageProtection,
) -> gpointer {
let ptr = kernel::alloc_code(size as usize);
unsafe {
core::ptr::write_bytes(ptr, 0, size as usize);
if (prot & _GumPageProtection_GUM_PAGE_EXECUTE) != 0 {
gum::register_slab(ptr as u64, size as usize);
gum_mprotect(ptr as gpointer, size, prot);
}
}
ptr as gpointer
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_free(address: gpointer, size: gsize) -> gboolean {
// vfree() wants the range writable again: the pages go back to the vmalloc
// allocator, and leaving them RX would fault the next consumer.
if gum::is_agent_slab(address as u64) {
unsafe {
gum_mprotect(
address,
size,
(_GumPageProtection_GUM_PAGE_READ | _GumPageProtection_GUM_PAGE_WRITE)
as GumPageProtection,
);
}
gum::unregister_slab(address as u64);
}
kernel::free_code(address as *mut u8, size as usize);
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
crate::gum_modules::publish(registry);
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_on_registry_deactivating(_registry: *mut GumModuleRegistry) {
crate::gum_modules::unpublish();
}
pub(crate) unsafe fn enumerate_exports_in_range(
start_address: u64,
end_address: u64,
callback: &mut FoundExportCallback<'_>,
) {
if crate::gum_modules::enumerate_exports_in_module(start_address, callback) {
return;
}
kernel::enumerate_symbols(&mut |name, address| {
if address < start_address || address >= end_address {
return true;
}
callback(name.as_ptr() as *const gchar, address)
});
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_symbol_details_from_address(
address: gpointer,
details: *mut GumDebugSymbolDetails,
) -> gboolean {
let Some((name, symbol_address)) = kernel::symbol_from_address(address as u64) else {
return 0;
};
unsafe {
let name_bytes = name.as_bytes();
let copy_len = core::cmp::min(name_bytes.len(), (*details).symbol_name.len() - 1);
core::ptr::copy_nonoverlapping(
name_bytes.as_ptr(),
(*details).symbol_name.as_mut_ptr() as *mut u8,
copy_len,
);
(*details).symbol_name[copy_len] = 0;
(*details).address = symbol_address;
(*details).module_name[0] = 0;
(*details).file_name[0] = 0;
(*details).line_number = 0;
(*details).column = 0;
}
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_symbol_name_from_address(address: gpointer) -> *mut gchar {
let Some((name, _)) = kernel::symbol_from_address(address as u64) else {
return ptr::null_mut();
};
let mut name = name;
name.push('\0');
unsafe { g_strdup(name.as_ptr() as *const gchar) }
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_function(name: *const gchar) -> gpointer {
let address = kernel::find_function(unsafe { CStr::from_ptr(name) });
address as gpointer
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_functions_named(name: *const gchar) -> *mut GArray {
let wanted = unsafe { CStr::from_ptr(name) };
collect_addresses(&mut |candidate, _address| candidate == wanted)
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_functions_matching(pattern: *const gchar) -> *mut GArray {
unsafe {
let spec = g_pattern_spec_new(pattern);
let array = collect_addresses(&mut |candidate, _address| {
g_pattern_spec_match_string(spec, candidate.as_ptr() as *const gchar) != 0
});
g_pattern_spec_free(spec);
array
}
}
fn collect_addresses(predicate: &mut dyn FnMut(&CStr, u64) -> bool) -> *mut GArray {
unsafe {
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
kernel::enumerate_symbols(&mut |name, address| {
if predicate(name, address) {
let addr = address as gpointer;
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
}
true
});
array
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
0
}
+230 -12
View File
@@ -3,26 +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,
_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() {
@@ -170,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;
@@ -194,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 }
}
@@ -412,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() {
@@ -657,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,
+35 -1
View File
@@ -6,6 +6,7 @@
// stream; whoever opens the device decides how it reaches the host.
use core::cell::UnsafeCell;
use core::sync::atomic::{AtomicBool, Ordering};
use core::ffi::{c_int, c_void};
use alloc::vec::Vec;
@@ -30,6 +31,7 @@ impl Hostlink {
if unsafe { frida_kmod_link_open() } != 0 {
return Err(());
}
LINK_OPEN.store(true, Ordering::Release);
Ok(Hostlink {
state: UnsafeCell::new(Inner {
@@ -97,19 +99,23 @@ impl Hostlink {
impl Drop for Hostlink {
fn drop(&mut self) {
LINK_OPEN.store(false, Ordering::Release);
unsafe { frida_kmod_link_close() };
}
}
pub fn a_turn_is_wanted() -> bool {
unsafe { frida_kmod_link_pending() }
LINK_OPEN.load(Ordering::Acquire) && unsafe { frida_kmod_link_pending() }
}
static LINK_OPEN: AtomicBool = AtomicBool::new(false);
fn recv_nonblocking(dst: &mut [u8]) -> usize {
let n = unsafe { frida_kmod_link_recv(dst.as_mut_ptr() as *mut c_void, dst.len()) };
if n <= 0 { 0 } else { n as usize }
}
#[cfg(feature = "xnu-kext")]
unsafe extern "C" {
fn frida_kmod_link_open() -> c_int;
fn frida_kmod_link_close();
@@ -117,3 +123,31 @@ unsafe extern "C" {
fn frida_kmod_link_recv(data: *mut c_void, size: usize) -> isize;
fn frida_kmod_link_pending() -> bool;
}
#[cfg(all(feature = "linux-injected", not(target_arch = "x86")))]
unsafe extern "C" {
#[link_name = "_frida_kmod_link_open"]
static frida_kmod_link_open: unsafe extern "C" fn() -> c_int;
#[link_name = "_frida_kmod_link_close"]
static frida_kmod_link_close: unsafe extern "C" fn();
#[link_name = "_frida_kmod_link_send"]
static frida_kmod_link_send: unsafe extern "C" fn(*const c_void, usize) -> c_int;
#[link_name = "_frida_kmod_link_recv"]
static frida_kmod_link_recv: unsafe extern "C" fn(*mut c_void, usize) -> isize;
#[link_name = "_frida_kmod_link_pending"]
static frida_kmod_link_pending: unsafe extern "C" fn() -> bool;
}
#[cfg(all(feature = "linux-injected", target_arch = "x86"))]
unsafe extern "C" {
#[link_name = "frida_k_frida_kmod_link_open"]
fn frida_kmod_link_open() -> c_int;
#[link_name = "frida_k_frida_kmod_link_close"]
fn frida_kmod_link_close();
#[link_name = "frida_k_frida_kmod_link_send"]
fn frida_kmod_link_send(data: *const c_void, size: usize) -> c_int;
#[link_name = "frida_k_frida_kmod_link_recv"]
fn frida_kmod_link_recv(data: *mut c_void, size: usize) -> isize;
#[link_name = "frida_k_frida_kmod_link_pending"]
fn frida_kmod_link_pending() -> bool;
}
@@ -404,15 +404,12 @@ 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 EXECUTIVE: u32 = 0;
const KERNEL_MODE: u32 = 0;
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 FILE_SYNCHRONOUS_IO_NONALERT: u32 = 0x20;
const OBJ_CASE_INSENSITIVE: usize = 0x40;
const OBJ_KERNEL_HANDLE: usize = 0x200;
+20 -4
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;
@@ -867,6 +873,7 @@ extern "C" fn isr_wake(token: *mut c_void, _refcon: *mut c_void, _nub: *mut c_vo
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)
}
@@ -903,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;
}
}
@@ -1021,6 +1026,7 @@ impl Regs {
#[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,
@@ -1035,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"))]
@@ -1046,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) },
}
@@ -1055,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,
@@ -1207,6 +1221,7 @@ impl PciDevice {
.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,
@@ -1232,6 +1247,7 @@ 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);
-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);
+2 -7
View File
@@ -1,14 +1,8 @@
// Facade over the host kernel the agent is running inside of. Both backends
// expose the same primitives, so the rest of the agent never names a specific
// kernel.
//
// A kernel is reached from the outside -- a remote stub injects the agent and
// patches the addresses it needs into .kernel_addrs -- or from the inside,
// where the agent is a kernel module whose glue is an ordinary C translation
// unit compiled against the target kernel's headers. Linux is reached either
// way; the others only from the outside.
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[cfg(feature = "linux-injected")]
pub use crate::linux::*;
#[cfg(feature = "win9x")]
pub use crate::win9x::*;
@@ -37,6 +31,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>,
}
+224 -179
View File
@@ -4,16 +4,12 @@
feature = "xnu-core",
feature = "win9x",
feature = "winnt",
feature = "linux",
feature = "linux-injected"
)))]
compile_error!(
"pick a flavor: --features xnu, --features win9x, --features winnt, --features linux or --features linux-injected"
"pick a flavor: --features xnu, --features win9x, --features winnt or --features linux-injected"
);
#[cfg(all(feature = "blob", feature = "linux"))]
compile_error!("pick one flavour, not both");
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::format;
@@ -25,19 +21,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,27 +36,33 @@ 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;
#[cfg(feature = "linux")]
mod gum_linux;
#[cfg(feature = "linux-injected")]
mod gum_btf;
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[cfg(feature = "linux-injected")]
mod gum_modules;
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
#[cfg(any(feature = "linux-injected", feature = "xnu-kext"))]
mod hostlink_chardev;
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
mod heap;
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[cfg(feature = "linux-injected")]
mod linux;
#[cfg(any(feature = "win9x", feature = "winnt"))]
@@ -143,25 +138,27 @@ 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,
@@ -184,6 +181,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"),
@@ -264,6 +263,19 @@ mod entrypoint_blob {
unsafe { enter(config_data, config_size) }
}
#[cfg(feature = "linux-injected")]
#[unsafe(no_mangle)]
pub extern "C" fn frida_agent_wake() {
nudge_the_loop(ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8);
}
#[cfg(feature = "linux-injected")]
#[unsafe(no_mangle)]
pub extern "C" fn frida_agent_stop() {
STOP_REQUESTED.store(true, Ordering::Release);
frida_agent_wake();
}
unsafe fn enter(config_data: *const u8, config_size: usize) {
unsafe {
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected", feature = "xnu-core"))]
@@ -307,6 +319,12 @@ mod entrypoint_blob {
#[cfg(feature = "linux-injected")]
crate::gum_btf::publish();
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
{
kernel::learn_when_to_sleep();
kernel::open_control_channel();
}
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
// can receive a handle to the same event.
@@ -337,6 +355,10 @@ mod entrypoint_blob {
linux::hostlink_pipe_goldfish::Hostlink::init(&path, Some(on_frame_from_host), wake_token)
.unwrap(),
),
#[cfg(feature = "linux-injected")]
TransportConfig::CharDevice => Transport::CharDevice(
hostlink_chardev::Hostlink::init(Some(on_frame_from_host)).unwrap(),
),
#[cfg(feature = "xnu-core")]
TransportConfig::Vsock { host_port } => Transport::Vsock(
hostlink_vsock::Hostlink::init(host_port, Some(on_frame_from_host), wake_token)
@@ -356,6 +378,9 @@ mod entrypoint_blob {
destroy_all_scripts(context);
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
kernel::close_control_channel();
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected",
feature = "xnu-core"))]
{
@@ -379,6 +404,8 @@ mod entrypoint_blob {
(&raw mut frida_agent_left).write_volatile(1);
#[cfg(feature = "linux-injected")]
kernel::hand_back_to_the_loader();
}
}
@@ -470,6 +497,12 @@ mod entrypoint_blob {
#[cfg(not(feature = "linux-injected"))]
{ panic!("pipe-goldfish is linux-injected's") }
}
6 => {
#[cfg(feature = "linux-injected")]
{ TransportConfig::CharDevice }
#[cfg(not(feature = "linux-injected"))]
{ panic!("the character device is linux-injected's") }
}
_ => panic!("Unsupported transport kind: {}", transport_kind),
};
g_variant_unref(transport_cfg_inner);
@@ -623,7 +656,7 @@ mod entrypoint_xnu_kext {
kernel::wake(ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8);
kernel::wait(
ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8,
Some(10_000),
None,
&mut || WORKER_EXITED.load(Ordering::Acquire),
);
}
@@ -673,98 +706,6 @@ mod entrypoint_xnu_kext {
}
}
#[cfg(feature = "linux")]
mod entrypoint_linux {
use super::*;
use core::ffi::c_int;
use core::sync::atomic::AtomicBool;
static STOP_REQUESTED: AtomicBool = AtomicBool::new(false);
static WORKER_EXITED: AtomicBool = AtomicBool::new(false);
/// Called from the module's init path. Everything interesting happens on our
/// own kernel thread: `insmod` runs with the module mutex held, and bringing
/// up Gum plus connecting to the host both block.
#[unsafe(no_mangle)]
pub extern "C" fn frida_agent_start(own_base: u64, own_size: u64) -> c_int {
kernel::log("frida: agent starting\n\0");
unsafe {
OWN_RANGE = GumMemoryRange {
base_address: own_base,
size: own_size as gsize,
};
}
if kernel::spawn_thread(worker, ptr::null_mut()) != 0 {
kernel::log("frida: failed to spawn worker\n\0");
return -1;
}
0
}
/// Called from the module's exit path, which must not return until the worker
/// is off our text — the module's pages go away right after.
#[unsafe(no_mangle)]
pub extern "C" fn frida_agent_stop() {
kernel::log("frida: agent stopping\n\0");
STOP_REQUESTED.store(true, Ordering::Release);
while !WORKER_EXITED.load(Ordering::Acquire) {
kernel::wake(ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8);
kernel::wait(
ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8,
Some(10_000),
&mut || WORKER_EXITED.load(Ordering::Acquire),
);
}
kernel::log("frida: agent stopped\n\0");
}
pub fn stop_requested() -> bool {
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");
crate::run_constructors();
init_gum();
kernel::install_hooks();
match hostlink_chardev::Hostlink::init(Some(on_frame_from_host)) {
Ok(hostlink) => transport_set(Transport::CharDevice(hostlink)),
Err(_) => {
kernel::log("frida: failed to connect to peer\n\0");
WORKER_EXITED.store(true, Ordering::Release);
return;
}
}
let main_context = adopt_js_context();
run_main_loop(main_context);
destroy_all_scripts(main_context);
transport_teardown();
WORKER_EXITED.store(true, Ordering::Release);
kernel::wake(ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8);
}
}
}
#[cfg(feature = "linux")]
pub use entrypoint_linux::{frida_agent_start, frida_agent_stop};
#[cfg(feature = "xnu-kext")]
pub use entrypoint_xnu_kext::{frida_agent_start, frida_agent_stop, frida_agent_wake};
#[cfg(feature = "blob")]
@@ -781,7 +722,7 @@ pub enum Transport {
PipeVsock(linux::hostlink_pipe_vsock::Hostlink),
#[cfg(feature = "linux-injected")]
PipeGoldfish(linux::hostlink_pipe_goldfish::Hostlink),
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
#[cfg(any(feature = "linux-injected", feature = "xnu-kext"))]
CharDevice(hostlink_chardev::Hostlink),
}
@@ -798,7 +739,7 @@ impl Transport {
Transport::PipeVsock(h) => h.send(payload),
#[cfg(feature = "linux-injected")]
Transport::PipeGoldfish(h) => h.send(payload),
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
#[cfg(any(feature = "linux-injected", feature = "xnu-kext"))]
Transport::CharDevice(h) => h.send(payload),
}
}
@@ -815,7 +756,7 @@ impl Transport {
Transport::PipeVsock(h) => h.shutdown(),
#[cfg(feature = "linux-injected")]
Transport::PipeGoldfish(h) => h.shutdown(),
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
#[cfg(any(feature = "linux-injected", feature = "xnu-kext"))]
Transport::CharDevice(_) => {}
}
}
@@ -832,7 +773,7 @@ impl Transport {
Transport::PipeVsock(h) => h.process(),
#[cfg(feature = "linux-injected")]
Transport::PipeGoldfish(h) => h.process(),
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
#[cfg(any(feature = "linux-injected", feature = "xnu-kext"))]
Transport::CharDevice(h) => h.process(),
}
}
@@ -840,7 +781,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};
@@ -853,7 +794,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
}
@@ -871,6 +812,8 @@ pub enum TransportConfig {
PipeVsock { path: alloc::string::String },
#[cfg(feature = "linux-injected")]
PipeGoldfish { path: alloc::string::String },
#[cfg(feature = "linux-injected")]
CharDevice,
}
static mut TRANSPORT_DRIVER: *mut Transport = core::ptr::null_mut();
@@ -883,6 +826,7 @@ fn transport_set(driver: Transport) {
}
}
#[cfg(feature = "blob")]
fn tell_the_host_we_are_listening() {
unsafe {
let message = g_variant_new(
@@ -939,7 +883,7 @@ 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);
@@ -1032,9 +976,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"))]
@@ -1067,6 +1013,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 {
@@ -1078,17 +1025,19 @@ 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 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;
@@ -1111,14 +1060,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();
}
@@ -1186,6 +1148,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
@@ -1197,6 +1160,7 @@ 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;
@@ -1240,11 +1204,6 @@ fn run_main_loop(main_context: *mut GMainContext) {
#[cfg(not(any(feature = "blob", feature = "xnu-core")))]
transport_get_unchecked().process();
#[cfg(feature = "linux")]
if entrypoint_linux::stop_requested() {
return;
}
if stop_requested() {
return;
}
@@ -1276,7 +1235,7 @@ fn kernel_half_has_work() -> bool {
return true;
}
#[cfg(feature = "xnu-kext")]
#[cfg(any(feature = "linux-injected", feature = "xnu-kext"))]
if hostlink_chardev::a_turn_is_wanted() {
return true;
}
@@ -1299,7 +1258,14 @@ 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;
}
@@ -1308,6 +1274,11 @@ fn kernel_half_has_work() -> bool {
return true;
}
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
if kernel::an_operation_waits() {
return true;
}
kernel::injected_arenas()
.iter()
.any(|arena| kernel::holds_a_frame_from_target(*arena))
@@ -1318,13 +1289,16 @@ 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();
@@ -1332,12 +1306,19 @@ fn serve_the_kernel_half() {
#[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();
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
kernel::carry_out_operations();
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);
@@ -1353,31 +1334,22 @@ pub(crate) fn watch_for_work(main_context: *mut GMainContext, ready: fn() -> boo
}
}
#[cfg(any(feature = "blob", feature = "xnu-core"))]
static mut WORK_SOURCE: *mut GSource = ptr::null_mut();
#[repr(C)]
pub struct GPollFd {
pub fd: i32,
pub events: u16,
pub revents: u16,
pub user_data: *mut c_void,
}
#[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() };
pub type GPollFunc = unsafe extern "C" fn(*mut GPollFd, u32, i32) -> i32;
unsafe extern "C" {
fn g_source_add_poll(source: *mut GSource, fd: *mut GPollFd);
fn g_main_context_set_poll_func(context: *mut GMainContext, func: GPollFunc);
}
static mut WAKEUP_POLLFD: GPollFd = GPollFd { fd: -1, events: 0, revents: 0, user_data: ptr::null_mut() };
pub(crate) fn watch_a_descriptor(context: *mut GMainContext, fd: i32, poll: GPollFunc) {
#[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 = GPollFd { fd, events: G_IO_IN, revents: 0, user_data: ptr::null_mut() };
g_source_add_poll(WORK_SOURCE, ptr::addr_of_mut!(WAKEUP_POLLFD));
g_main_context_set_poll_func(context, poll);
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));
}
}
@@ -1404,9 +1376,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;
@@ -1415,6 +1390,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;
@@ -1423,6 +1399,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() } {
@@ -1432,6 +1409,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),
@@ -1452,6 +1430,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() {
@@ -1494,7 +1473,7 @@ unsafe extern "C" fn on_script_unloaded(
static UNLOADS_IN_FLIGHT: AtomicU32 = AtomicU32::new(0);
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
#[cfg(feature = "xnu-kext")]
fn transport_teardown() {
unsafe {
let driver = TRANSPORT_DRIVER;
@@ -1585,6 +1564,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")]
@@ -1592,6 +1575,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(
@@ -1862,6 +1847,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 {
@@ -1878,7 +1873,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
};
@@ -1902,15 +1897,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 }
}
@@ -1923,12 +1920,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;
@@ -1996,6 +1995,22 @@ fn handle_gate_spawns(payload: *mut GVariant) -> HandlerResponse {
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) })
}
#[cfg(any(feature = "win9x", feature = "linux-injected", feature = "xnu-core"))]
pub(crate) fn tell_the_host_of_a_spawn(pid: u32, command_line: *const u8) {
unsafe {
@@ -2030,8 +2045,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);
@@ -2042,6 +2057,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);
@@ -2063,6 +2082,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() {
@@ -2093,11 +2116,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;
@@ -2124,7 +2159,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
}
@@ -2134,6 +2169,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(
@@ -2151,7 +2192,6 @@ fn text_or_empty(text: *const u8) -> *const core::ffi::c_char {
}
fn send_command_reply(request_id: u16, response: HandlerResponse) {
unsafe {
let message = g_variant_new(
c"(yquv)".as_ptr(),
@@ -2337,9 +2377,14 @@ fn handle_stop() -> HandlerResponse {
kernel::show_our_threads_again();
}
#[cfg(feature = "blob")]
#[cfg(all(feature = "blob", not(feature = "linux-injected")))]
STOP_REQUESTED.store(true, Ordering::Release);
#[cfg(feature = "linux-injected")]
if !kernel::loaded_from_the_inside() {
STOP_REQUESTED.store(true, Ordering::Release);
}
HandlerResponse::success(unsafe { g_variant_new_uint32(0) })
}
@@ -2478,7 +2523,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 }
}
+2 -2
View File
@@ -50,7 +50,7 @@ pub extern "C" fn sysconf(_name: i32) -> isize {
#[unsafe(no_mangle)]
pub extern "C" fn __clear_cache(_start: *const u8, _end: *const u8) {}
#[cfg(all(target_arch = "arm", not(any(feature = "linux", feature = "linux-injected"))))]
#[cfg(all(target_arch = "arm", not(feature = "linux-injected")))]
#[unsafe(no_mangle)]
pub extern "C" fn __clear_cache(start: *const u8, end: *const u8) {
unsafe {
@@ -68,7 +68,7 @@ pub extern "C" fn __clear_cache(start: *const u8, end: *const u8) {
}
}
#[cfg(all(target_arch = "arm", any(feature = "linux", feature = "linux-injected")))]
#[cfg(all(target_arch = "arm", feature = "linux-injected"))]
#[unsafe(no_mangle)]
pub extern "C" fn __clear_cache(start: *const u8, end: *const u8) {
unsafe {
+52 -16
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,7 @@ 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);
@@ -133,10 +119,12 @@ impl Arena {
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
}
@@ -149,12 +137,53 @@ impl Arena {
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() }
}
@@ -190,4 +219,11 @@ 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;
+402
View File
@@ -0,0 +1,402 @@
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use core::ffi::{c_int, c_void};
use core::sync::atomic::{AtomicBool, Ordering};
use super::layout::field_offset;
use super::native;
pub fn add_thread(tgid: i32, tid: i32) {
let fresh = Cloak::of(tgid);
registry().with(|cloaks| {
let threads = &mut cloaks.entry(tgid).or_insert(fresh).threads;
if !threads.contains(&tid) {
threads.push(tid);
}
});
}
pub fn remove_thread(tgid: i32, tid: i32) {
registry().with(|cloaks| {
if let Some(cloak) = cloaks.get_mut(&tgid) {
cloak.threads.retain(|t| *t != tid);
}
});
}
pub fn add_range(tgid: i32, base: usize, size: usize) {
let fresh = Cloak::of(tgid);
registry().with(|cloaks| {
cloaks.entry(tgid).or_insert(fresh).ranges.push((base, base + size));
});
}
pub fn remove_range(tgid: i32, base: usize, size: usize) {
registry().with(|cloaks| {
if let Some(cloak) = cloaks.get_mut(&tgid) {
if let Some(index) = cloak.ranges.iter().position(|r| *r == (base, base + size)) {
cloak.ranges.swap_remove(index);
}
}
});
}
pub fn add_fd(tgid: i32, fd: i32) {
let fresh = Cloak::of(tgid);
registry().with(|cloaks| {
let fds = &mut cloaks.entry(tgid).or_insert(fresh).fds;
if !fds.contains(&fd) {
fds.push(fd);
}
});
}
pub fn remove_fd(tgid: i32, fd: i32) {
registry().with(|cloaks| {
if let Some(cloak) = cloaks.get_mut(&tgid) {
cloak.fds.retain(|f| *f != fd);
}
});
}
pub fn install_hooks() {
let Some(layout) = describe_readers() else {
return;
};
unsafe { LAYOUT = Some(layout) };
let interceptor = unsafe { crate::bindings::gum_interceptor_obtain() };
unsafe {
crate::bindings::gum_interceptor_begin_transaction(interceptor);
replace(interceptor, _show_map, frida_cb_show_map as *mut c_void, &raw mut SHOW_MAP);
replace(interceptor, _show_smap, frida_cb_show_smap as *mut c_void, &raw mut SHOW_SMAP);
replace(interceptor, _proc_fill_cache, frida_cb_proc_fill_cache as *mut c_void,
&raw mut PROC_FILL_CACHE);
replace(interceptor, _proc_pid_status, frida_cb_proc_pid_status as *mut c_void,
&raw mut PROC_PID_STATUS);
replace(interceptor, _do_task_stat, frida_cb_do_task_stat as *mut c_void, &raw mut DO_TASK_STAT);
replace(interceptor, _release_task, frida_cb_release_task as *mut c_void, &raw mut RELEASE_TASK);
crate::bindings::gum_interceptor_end_transaction(interceptor);
}
}
pub fn remove_hooks() {
if unsafe { (*(&raw const LAYOUT)).is_none() } {
return;
}
let interceptor = unsafe { crate::bindings::gum_interceptor_obtain() };
unsafe {
crate::bindings::gum_interceptor_begin_transaction(interceptor);
for target in [_show_map, _show_smap, _proc_fill_cache, _proc_pid_status, _do_task_stat, _release_task] {
if target != 0 {
crate::bindings::gum_interceptor_revert(interceptor, target as *mut c_void);
}
}
crate::bindings::gum_interceptor_end_transaction(interceptor);
}
registry().with(|cloaks| cloaks.clear());
}
unsafe fn replace(interceptor: *mut crate::bindings::GumInterceptor, target: usize, replacement: *mut c_void,
original: *mut usize) {
if target == 0 {
return;
}
unsafe {
crate::bindings::gum_interceptor_replace(interceptor, target as *mut c_void, replacement,
original as *mut *mut c_void, core::ptr::null_mut());
}
}
unsafe extern "C" fn frida_cb_show_map(m: *mut c_void, vma: *mut c_void) -> c_int {
if hides_the_area(vma) {
return 0;
}
unsafe { call::<ShowFn>(SHOW_MAP)(m, vma) }
}
unsafe extern "C" fn frida_cb_show_smap(m: *mut c_void, vma: *mut c_void) -> c_int {
if hides_the_area(vma) {
return 0;
}
unsafe { call::<ShowFn>(SHOW_SMAP)(m, vma) }
}
unsafe extern "C" fn frida_cb_proc_fill_cache(file: *mut c_void, ctx: *mut c_void, name: *const u8, len: u32,
instantiate: usize, task: *mut c_void, ptr: *const c_void) -> bool {
if hides_the_entry(instantiate, task as usize, ptr as usize) {
return true;
}
unsafe { call::<FillCacheFn>(PROC_FILL_CACHE)(file, ctx, name, len, instantiate, task, ptr) }
}
unsafe extern "C" fn frida_cb_proc_pid_status(m: *mut c_void, ns: *mut c_void, pid: *mut c_void,
task: *mut c_void) -> c_int {
let layout = layout();
let start = read_word(m as usize + layout.seq_count);
let result = unsafe { call::<StatusFn>(PROC_PID_STATUS)(m, ns, pid, task) };
if let Some(hidden) = threads_hidden_from_the_reader(task as usize) {
let said = said_since(m as usize, start);
if let Some(at) = find(said, b"Threads:\t") {
take_from_the_number_at(m as usize, start + at + b"Threads:\t".len(), hidden);
}
}
result
}
unsafe extern "C" fn frida_cb_do_task_stat(m: *mut c_void, ns: *mut c_void, pid: *mut c_void,
task: *mut c_void, whole: c_int) -> c_int {
let layout = layout();
let start = read_word(m as usize + layout.seq_count);
let result = unsafe { call::<StatFn>(DO_TASK_STAT)(m, ns, pid, task, whole) };
if let Some(hidden) = threads_hidden_from_the_reader(task as usize) {
if let Some(at) = where_the_thread_count_is(said_since(m as usize, start)) {
take_from_the_number_at(m as usize, start + at, hidden);
}
}
result
}
unsafe extern "C" fn frida_cb_release_task(task: *mut c_void) {
let layout = layout();
let task = task as usize;
let tgid = read_i32(task + layout.task_tgid);
remove_thread(tgid, read_i32(task + layout.task_pid));
if read_word(task + layout.task_group_leader) == task {
registry().with(|cloaks| {
cloaks.remove(&tgid);
});
}
unsafe { call::<ReleaseFn>(RELEASE_TASK)(task as *mut c_void) };
}
fn hides_the_area(vma: *mut c_void) -> bool {
let layout = layout();
let vma = vma as usize;
let mm = read_word(vma + layout.vma_mm);
let start = read_word(vma + layout.vma_start);
let end = read_word(vma + layout.vma_end);
!the_reader_is_cloaked() && registry().with(|cloaks| {
cloaks.values().any(|cloak| {
cloak.mm == mm && cloak.ranges.iter().any(|(s, e)| start < *e && *s < end)
})
})
}
fn hides_the_entry(instantiate: usize, task: usize, ptr: usize) -> bool {
if the_reader_is_cloaked() {
return false;
}
let layout = layout();
if instantiate == unsafe { _proc_task_instantiate } {
let tid = read_i32(task + layout.task_pid);
return registry().with(|cloaks| cloaks.values().any(|cloak| cloak.threads.contains(&tid)));
}
if instantiate == unsafe { _proc_fd_instantiate } || instantiate == unsafe { _proc_fdinfo_instantiate } {
let tgid = read_i32(task + layout.task_tgid);
let fd = read_i32(ptr + layout.fd_data_fd);
return registry().with(|cloaks| cloaks.get(&tgid).is_some_and(|cloak| cloak.fds.contains(&fd)));
}
false
}
fn threads_hidden_from_the_reader(task: usize) -> Option<i32> {
if the_reader_is_cloaked() {
return None;
}
let tgid = read_i32(task + layout().task_tgid);
let hidden = registry().with(|cloaks| cloaks.get(&tgid).map_or(0, |cloak| cloak.threads.len()));
(hidden != 0).then_some(hidden as i32)
}
// A cloaked thread is one of ours, so let it see through the cloak; RASP threads in the same
// process are not cloaked and keep getting the censored view.
fn the_reader_is_cloaked() -> bool {
let tid = read_i32(native::current_task() as usize + layout().task_pid);
registry().with(|cloaks| cloaks.values().any(|cloak| cloak.threads.contains(&tid)))
}
fn said_since(m: usize, start: usize) -> &'static [u8] {
let layout = layout();
let count = read_word(m + layout.seq_count);
if start >= count || count >= read_word(m + layout.seq_size) {
return &[];
}
unsafe { core::slice::from_raw_parts((read_word(m + layout.seq_buf) + start) as *const u8, count - start) }
}
fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack.windows(needle.len()).position(|window| window == needle)
}
fn where_the_thread_count_is(said: &[u8]) -> Option<usize> {
let name_end = said.iter().rposition(|b| *b == b')')?;
said[name_end..]
.iter()
.enumerate()
.filter(|(_, b)| **b == b' ')
.nth(17)
.map(|(at, _)| name_end + at + 1)
}
fn take_from_the_number_at(m: usize, at: usize, hidden: i32) {
let layout = layout();
let buf = read_word(m + layout.seq_buf);
let count = read_word(m + layout.seq_count);
let said = unsafe { core::slice::from_raw_parts_mut(buf as *mut u8, count) };
let digits = said[at..].iter().take_while(|b| b.is_ascii_digit()).count();
let value = said[at..at + digits].iter().fold(0i32, |n, b| n * 10 + i32::from(b - b'0'));
let shown = if value > hidden { value - hidden } else { 1 };
let rendered = alloc::format!("{shown}");
said.copy_within(at + digits..count, at + rendered.len());
said[at..at + rendered.len()].copy_from_slice(rendered.as_bytes());
write_word(m + layout.seq_count, count - digits + rendered.len());
}
struct Cloak {
mm: usize,
threads: Vec<i32>,
ranges: Vec<(usize, usize)>,
fds: Vec<i32>,
}
impl Cloak {
fn of(tgid: i32) -> Self {
let mm = super::processes::HeldTask::with_id(tgid as u32)
.map_or(0, |task| read_word(task.address() + layout().task_mm));
Self { mm, threads: Vec::new(), ranges: Vec::new(), fds: Vec::new() }
}
}
struct Registry {
busy: AtomicBool,
cloaks: core::cell::UnsafeCell<BTreeMap<i32, Cloak>>,
}
unsafe impl Sync for Registry {}
impl Registry {
fn with<R>(&self, action: impl FnOnce(&mut BTreeMap<i32, Cloak>) -> R) -> R {
native::disable_preemption();
while self.busy.swap(true, Ordering::Acquire) {
core::hint::spin_loop();
}
let result = action(unsafe { &mut *self.cloaks.get() });
self.busy.store(false, Ordering::Release);
native::enable_preemption();
result
}
}
fn registry() -> &'static Registry {
&REGISTRY
}
static REGISTRY: Registry = Registry {
busy: AtomicBool::new(false),
cloaks: core::cell::UnsafeCell::new(BTreeMap::new()),
};
struct ReaderLayout {
task_pid: usize,
task_tgid: usize,
task_mm: usize,
task_group_leader: usize,
vma_mm: usize,
vma_start: usize,
vma_end: usize,
seq_buf: usize,
seq_size: usize,
seq_count: usize,
fd_data_fd: usize,
}
fn describe_readers() -> Option<ReaderLayout> {
Some(ReaderLayout {
task_pid: field_offset("task_struct", "pid")?,
task_tgid: field_offset("task_struct", "tgid")?,
task_mm: field_offset("task_struct", "mm")?,
task_group_leader: field_offset("task_struct", "group_leader")?,
vma_mm: field_offset("vm_area_struct", "vm_mm")?,
vma_start: field_offset("vm_area_struct", "vm_start")?,
vma_end: field_offset("vm_area_struct", "vm_end")?,
seq_buf: field_offset("seq_file", "buf")?,
seq_size: field_offset("seq_file", "size")?,
seq_count: field_offset("seq_file", "count")?,
fd_data_fd: field_offset("fd_data", "fd")?,
})
}
fn layout() -> &'static ReaderLayout {
unsafe { (*(&raw const LAYOUT)).as_ref().unwrap() }
}
static mut LAYOUT: Option<ReaderLayout> = None;
unsafe fn call<F: Copy>(original: usize) -> F {
unsafe { core::mem::transmute_copy(&original) }
}
fn read_word(address: usize) -> usize {
unsafe { (address as *const usize).read() }
}
fn write_word(address: usize, value: usize) {
unsafe { (address as *mut usize).write(value) };
}
fn read_i32(address: usize) -> i32 {
unsafe { (address as *const i32).read() }
}
type ShowFn = unsafe extern "C" fn(*mut c_void, *mut c_void) -> c_int;
type FillCacheFn =
unsafe extern "C" fn(*mut c_void, *mut c_void, *const u8, u32, usize, *mut c_void, *const c_void) -> bool;
type StatusFn = unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void, *mut c_void) -> c_int;
type StatFn = unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void, *mut c_void, c_int) -> c_int;
type ReleaseFn = unsafe extern "C" fn(*mut c_void);
static mut SHOW_MAP: usize = 0;
static mut SHOW_SMAP: usize = 0;
static mut PROC_FILL_CACHE: usize = 0;
static mut PROC_PID_STATUS: usize = 0;
static mut DO_TASK_STAT: usize = 0;
static mut RELEASE_TASK: usize = 0;
unsafe extern "C" {
static _show_map: usize;
static _show_smap: usize;
static _proc_fill_cache: usize;
static _proc_pid_status: usize;
static _do_task_stat: usize;
static _release_task: usize;
static _proc_task_instantiate: usize;
static _proc_fd_instantiate: usize;
static _proc_fdinfo_instantiate: usize;
}
+362
View File
@@ -0,0 +1,362 @@
use alloc::vec;
use core::ffi::{c_int, c_long, c_void};
use core::mem::size_of;
use core::ptr;
use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
use super::layout::field_offset;
use super::{cloak, native, process_memory, user_threads};
pub fn open() {
cloak::install_hooks();
let Some(arguments) = describe_arguments() else {
return;
};
let target = unsafe { SYS_PRCTL_TARGET };
if target == 0 {
return;
}
unsafe { ARGUMENTS = Some(arguments) };
let interceptor = unsafe { crate::bindings::gum_interceptor_obtain() };
unsafe {
crate::bindings::gum_interceptor_begin_transaction(interceptor);
crate::bindings::gum_interceptor_replace(interceptor, target as *mut c_void,
frida_cb_sys_prctl as *mut c_void, (&raw mut SYS_PRCTL) as *mut *mut c_void, ptr::null_mut());
crate::bindings::gum_interceptor_end_transaction(interceptor);
}
}
pub fn close() {
if unsafe { (*(&raw const ARGUMENTS)).is_some() } {
let interceptor = unsafe { crate::bindings::gum_interceptor_obtain() };
unsafe {
crate::bindings::gum_interceptor_begin_transaction(interceptor);
crate::bindings::gum_interceptor_revert(interceptor, SYS_PRCTL_TARGET as *mut c_void);
crate::bindings::gum_interceptor_end_transaction(interceptor);
}
}
cloak::remove_hooks();
}
pub fn an_operation_waits() -> bool {
!PENDING.load(Ordering::Acquire).is_null()
}
pub fn carry_out_operations() {
let operation = PENDING.swap(ptr::null_mut(), Ordering::AcqRel);
if operation.is_null() {
return;
}
unsafe {
(*operation).result = match (*operation).request {
Request::Allocate { id, size, protection } => process_memory::allocate(id, size, protection) as c_long,
Request::Release { id, address, size } => process_memory::release(id, address, size) as c_long,
Request::StartThread { id, entry, stack, argument, tls } => {
user_threads::start_in_process(id, entry, stack, argument, tls) as c_long
}
};
(*operation).done.store(true, Ordering::Release);
}
native::wake(wake_token());
}
unsafe extern "C" fn frida_cb_sys_prctl(regs: *const c_void) -> c_long {
let argument = |index: usize| read_word(regs as usize + unsafe { arguments().at[index] });
if argument(0) as c_int != CONTROL_MAGIC || argument(4) as u64 != CONTROL_TOKEN {
let original: unsafe extern "C" fn(*const c_void) -> c_long = unsafe { core::mem::transmute(SYS_PRCTL) };
return unsafe { original(regs) };
}
let op = argument(1) as u32;
let (first, second) = (argument(2), argument(3));
if op == OP_PING {
return CONTROL_MAGIC as c_long;
}
if op >= OP_ALLOC && !unsafe { _capable(CAP_SYS_ADMIN) } {
return -(EPERM as c_long);
}
let tgid = read_i32(native::current_task() as usize + unsafe { arguments().task_tgid });
match op {
OP_ADD_THREAD => cloak::add_thread(tgid, first as i32),
OP_REMOVE_THREAD => cloak::remove_thread(tgid, first as i32),
OP_ADD_RANGE => cloak::add_range(tgid, first, second),
OP_REMOVE_RANGE => cloak::remove_range(tgid, first, second),
OP_ADD_FD => cloak::add_fd(tgid, first as i32),
OP_REMOVE_FD => cloak::remove_fd(tgid, first as i32),
OP_ALLOC => return allocate(first),
OP_FREE => return release(first),
OP_WRITE => return write(first),
OP_READ => return read(first),
OP_SPAWN => return start_thread(first),
OP_CLOAK_THREAD => return cloak_thread(first),
OP_CLOAK_RANGE => return cloak_range(first),
_ => return -(EINVAL as c_long),
}
0
}
fn allocate(arguments: usize) -> c_long {
let Some(args) = arguments_at::<AllocArgs>(arguments) else {
return -(EFAULT as c_long);
};
hand_to_the_worker(Request::Allocate { id: args.pid, size: args.size as usize, protection: args.prot as usize })
}
fn release(arguments: usize) -> c_long {
let Some(args) = arguments_at::<FreeArgs>(arguments) else {
return -(EFAULT as c_long);
};
hand_to_the_worker(Request::Release { id: args.pid, address: args.address as usize, size: args.size as usize })
}
fn write(arguments: usize) -> c_long {
let Some(args) = arguments_at::<RwArgs>(arguments) else {
return -(EFAULT as c_long);
};
let mut bounce = vec![0u8; args.size as usize];
if !process_memory::read_own(args.buffer as usize, &mut bounce) {
return -(EFAULT as c_long);
}
process_memory::write(args.pid, args.address as usize, &bounce) as c_long
}
fn read(arguments: usize) -> c_long {
let Some(args) = arguments_at::<RwArgs>(arguments) else {
return -(EFAULT as c_long);
};
let mut bounce = vec![0u8; args.size as usize];
let transferred = process_memory::read(args.pid, args.address as usize, &mut bounce);
if !process_memory::write_own(args.buffer as usize, &bounce) {
return -(EFAULT as c_long);
}
transferred as c_long
}
fn start_thread(arguments: usize) -> c_long {
let Some(args) = arguments_at::<SpawnArgs>(arguments) else {
return -(EFAULT as c_long);
};
hand_to_the_worker(Request::StartThread {
id: args.pid,
entry: args.entry as usize,
stack: args.stack as usize,
argument: args.arg as usize,
tls: args.tls as usize,
})
}
fn cloak_thread(arguments: usize) -> c_long {
let Some(args) = arguments_at::<CloakThreadArgs>(arguments) else {
return -(EFAULT as c_long);
};
cloak::add_thread(args.tgid as i32, args.tid as i32);
0
}
fn cloak_range(arguments: usize) -> c_long {
let Some(args) = arguments_at::<CloakRangeArgs>(arguments) else {
return -(EFAULT as c_long);
};
cloak::add_range(args.tgid as i32, args.base as usize, args.size as usize);
0
}
fn arguments_at<T: Default>(address: usize) -> Option<T> {
let mut value = T::default();
let raw = unsafe { core::slice::from_raw_parts_mut(&mut value as *mut T as *mut u8, size_of::<T>()) };
process_memory::read_own(address, raw).then_some(value)
}
fn hand_to_the_worker(request: Request) -> c_long {
let token = wake_token();
while BUSY.swap(true, Ordering::Acquire) {
native::wait(token, None, &mut || !BUSY.load(Ordering::Acquire));
}
let mut operation = Operation { request, result: 0, done: AtomicBool::new(false) };
PENDING.store(&mut operation, Ordering::Release);
crate::nudge_the_loop(token);
while !operation.done.load(Ordering::Acquire) {
native::wait(token, None, &mut || operation.done.load(Ordering::Acquire));
}
BUSY.store(false, Ordering::Release);
native::wake(token);
operation.result
}
fn wake_token() -> *const u8 {
(&raw const crate::glib::WAKEUP_TOKEN) as *const u8
}
struct Operation {
request: Request,
result: c_long,
done: AtomicBool,
}
enum Request {
Allocate { id: u32, size: usize, protection: usize },
Release { id: u32, address: usize, size: usize },
StartThread { id: u32, entry: usize, stack: usize, argument: usize, tls: usize },
}
static PENDING: AtomicPtr<Operation> = AtomicPtr::new(ptr::null_mut());
static BUSY: AtomicBool = AtomicBool::new(false);
#[repr(C)]
#[derive(Default)]
struct AllocArgs {
size: u64,
pid: u32,
prot: u32,
}
#[repr(C)]
#[derive(Default)]
struct FreeArgs {
address: u64,
size: u64,
pid: u32,
}
#[repr(C)]
#[derive(Default)]
struct RwArgs {
address: u64,
buffer: u64,
size: u64,
pid: u32,
}
#[repr(C)]
#[derive(Default)]
struct SpawnArgs {
entry: u64,
stack: u64,
arg: u64,
tls: u64,
pid: u32,
}
#[repr(C)]
#[derive(Default)]
struct CloakThreadArgs {
tgid: u32,
tid: u32,
}
#[repr(C)]
#[derive(Default)]
struct CloakRangeArgs {
base: u64,
size: u64,
tgid: u32,
}
struct ArgumentLayout {
at: [usize; 5],
task_tgid: usize,
}
#[cfg(target_arch = "aarch64")]
fn describe_arguments() -> Option<ArgumentLayout> {
let regs = field_offset("pt_regs", "regs")?;
Some(ArgumentLayout {
at: core::array::from_fn(|index| regs + index * size_of::<usize>()),
task_tgid: field_offset("task_struct", "tgid")?,
})
}
#[cfg(target_arch = "x86_64")]
fn describe_arguments() -> Option<ArgumentLayout> {
Some(ArgumentLayout {
at: [
field_offset("pt_regs", "di")?,
field_offset("pt_regs", "si")?,
field_offset("pt_regs", "dx")?,
field_offset("pt_regs", "r10")?,
field_offset("pt_regs", "r8")?,
],
task_tgid: field_offset("task_struct", "tgid")?,
})
}
unsafe fn arguments() -> &'static ArgumentLayout {
unsafe { (*(&raw const ARGUMENTS)).as_ref().unwrap() }
}
static mut ARGUMENTS: Option<ArgumentLayout> = None;
static mut SYS_PRCTL: usize = 0;
fn read_word(address: usize) -> usize {
unsafe { (address as *const usize).read() }
}
fn read_i32(address: usize) -> i32 {
unsafe { (address as *const i32).read() }
}
const CONTROL_MAGIC: c_int = 0x4652_4944;
const CONTROL_TOKEN: u64 = 0x1d5f_9e6b_2c7a_4038;
const OP_ADD_THREAD: u32 = 1;
const OP_REMOVE_THREAD: u32 = 2;
const OP_ADD_RANGE: u32 = 3;
const OP_REMOVE_RANGE: u32 = 4;
const OP_ADD_FD: u32 = 5;
const OP_REMOVE_FD: u32 = 6;
const OP_PING: u32 = 32;
const OP_ALLOC: u32 = 64;
const OP_FREE: u32 = 65;
const OP_WRITE: u32 = 66;
const OP_READ: u32 = 67;
const OP_SPAWN: u32 = 68;
const OP_CLOAK_THREAD: u32 = 69;
const OP_CLOAK_RANGE: u32 = 70;
const CAP_SYS_ADMIN: c_int = 21;
const EPERM: c_int = 1;
const EFAULT: c_int = 14;
const EINVAL: c_int = 22;
#[cfg(target_arch = "aarch64")]
use self::___arm64_sys_prctl as SYS_PRCTL_TARGET;
#[cfg(target_arch = "x86_64")]
use self::___x64_sys_prctl as SYS_PRCTL_TARGET;
unsafe extern "C" {
#[cfg(target_arch = "aarch64")]
static ___arm64_sys_prctl: usize;
#[cfg(target_arch = "x86_64")]
static ___x64_sys_prctl: usize;
static _capable: unsafe extern "C" fn(c_int) -> bool;
}
@@ -191,7 +191,8 @@ unsafe fn hook_data_ready(so: Socket) {
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 usize, sock_def_readable_ref());
let thunk =
super::native::kcfi_thunk(on_data_ready as *const () as usize, sock_def_readable_ref());
unsafe { *slot = thunk };
}
@@ -257,11 +258,11 @@ unsafe fn send_all(so: Socket, bytes: &[u8]) {
}
unsafe fn _init_net_addr() -> *mut c_void {
unsafe { ptr::addr_of!(_init_net) as *mut c_void }
ptr::addr_of!(_init_net) as *mut c_void
}
unsafe fn _sock_def_readable_addr() -> usize {
_sock_def_readable.map_or(0, |f| f as usize)
unsafe { _sock_def_readable }.map_or(0, |f| f as usize)
}
unsafe extern "C" {
+416 -88
View File
@@ -1,10 +1,18 @@
use core::ffi::{c_int, c_void};
use core::ptr;
use alloc::boxed::Box;
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;
@@ -37,10 +45,8 @@ pub fn inject_into_process(id: u32) -> u32 {
let placed = Placement {
task,
copy_pid: home.copy_pid,
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(),
@@ -58,25 +64,34 @@ fn take_what_the_copy_opened(placed: &mut Placement) {
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);
listen_to_the_copy(placed);
}
fn listen_to_the_copy(says: *mut c_void) {
native::spawn_thread(carry_what_the_copy_says, says);
fn listen_to_the_copy(placed: &Placement) {
let listener = Box::into_raw(Box::new(Listener { says: placed.says, arena: placed.arena }));
native::spawn_thread(carry_what_the_copy_says, listener as *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) {
let listener = unsafe { Box::from_raw(argument as *mut Listener) };
while native::wait_for_a_word(listener.says) {
crate::nudge_the_loop(crate::glib::wakeup_token());
}
Arena::at(listener.arena).gone();
crate::nudge_the_loop(crate::glib::wakeup_token());
}
struct Listener {
says: *mut c_void,
arena: usize,
}
struct Placement {
task: usize,
copy_pid: u32,
base: usize,
arena: usize,
seen_by_the_copy: usize,
stack: usize,
says: *mut c_void,
hears: *mut c_void,
@@ -89,10 +104,8 @@ fn pid_reported_by(placed: &Placement) -> u32 {
// The copy is given a task of its own that shares the address space it was mapped into, so
// nothing of the target is borrowed and a target that never runs is no obstacle.
struct Home {
copy_pid: u32,
base: usize,
arena: usize,
seen_by_the_copy: usize,
stack: usize,
}
@@ -130,19 +143,14 @@ fn map_and_start(id: u32) -> Option<Home> {
let arena = view_of(where_the_copy_sees_it)?;
let copy_pid = start(base, where_the_copy_sees_it, stack);
start(base, where_the_copy_sees_it, stack);
Some(Home {
copy_pid,
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")?;
@@ -207,6 +215,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);
@@ -221,6 +232,13 @@ fn map_a_copy() -> Option<usize> {
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;
@@ -287,10 +305,6 @@ fn give(destination: usize, source: usize, size: usize) -> Option<()> {
unsafe { copy_to_user(destination as *mut c_void, source as *const c_void, size) };
unsafe { close_user_access(opened) };
if left != 0 {
if left == size {
} else if left < size {
} else {
}
return None;
}
@@ -330,7 +344,7 @@ unsafe fn open_user_access() -> usize {
#[cfg(not(target_arch = "arm"))]
unsafe fn close_user_access(_domains: usize) {}
fn start(base: usize, arena: usize, stack: usize) -> u32 {
fn start(base: usize, arena: usize, stack: usize) {
let entry = native::alloc(size_of::<Entry>()) as *mut Entry;
unsafe {
entry.write(Entry {
@@ -339,7 +353,7 @@ fn start(base: usize, arena: usize, stack: usize) -> u32 {
stack,
});
_user_mode_thread(USER_ENTRY, entry as *mut c_void, CLONE_VM | CLONE_FS | CLONE_FILES) as u32
_user_mode_thread(USER_ENTRY, entry as *mut c_void, CLONE_VM | CLONE_FS | CLONE_FILES);
}
}
@@ -360,33 +374,37 @@ pub unsafe extern "C" fn frida_cb_user(argument: *mut c_void) -> c_int {
COPY_TASK.store(native::current_task() as usize, core::sync::atomic::Ordering::Release);
hand_userland_to(bootstrap, hand_over_argument(stack, arena), arena);
0
}
pub(crate) fn hand_userland_to(pc: usize, stack: usize, first_argument: usize) {
let Some(places) = describe_registers() else {
return 0;
return;
};
let registers = registers_of_this_task(&places);
let stack = hand_over_argument(stack, arena);
unsafe {
((registers + places.pc) as *mut usize).write(bootstrap & !THUMB_BIT);
((registers + places.pc) as *mut usize).write(pc & !THUMB_BIT);
((registers + places.stack) as *mut usize).write(stack);
((registers + places.flags) as *mut usize).write(
USER_EXECUTION | (bootstrap & THUMB_BIT) * THUMB_STATE);
((registers + places.first) as *mut usize).write(arena);
USER_EXECUTION | (pc & THUMB_BIT) * THUMB_STATE);
((registers + places.first) as *mut usize).write(first_argument);
write_selectors(registers, &places);
}
0
}
// 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 + places.stack_span - TOP_OF_STACK_PADDING - places.size
}
#[cfg(target_arch = "x86_64")]
@@ -408,11 +426,279 @@ 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 {
((registers + 136) as *mut usize).write(0x33);
((registers + 160) as *mut usize).write(0x2b);
((registers + 120) as *mut isize).write(-1);
}
}
@@ -432,15 +718,25 @@ unsafe fn write_selectors(_registers: usize, _places: &Places) {}
#[cfg(target_arch = "x86")]
fn hand_over_argument(stack: usize, arena: usize) -> usize {
let frame = [0usize, arena];
let sp = stack - size_of::<[usize; 2]>();
let aligned_argument = stack - 16;
let sp = aligned_argument - size_of::<usize>();
match give(sp, frame.as_ptr() as usize, size_of::<[usize; 2]>()) {
Some(()) => sp,
None => stack,
}
give(sp, frame.as_ptr() as usize, size_of::<[usize; 2]>());
sp
}
#[cfg(not(target_arch = "x86"))]
#[cfg(target_arch = "x86_64")]
fn hand_over_argument(stack: usize, _arena: usize) -> usize {
let return_address = 0usize;
let sp = stack - size_of::<usize>();
give(sp, &return_address as *const usize as usize, size_of::<usize>());
sp
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn hand_over_argument(stack: usize, _arena: usize) -> usize {
stack
}
@@ -452,6 +748,7 @@ struct Places {
pc: usize,
flags: usize,
stack_of_task: usize,
stack_span: usize,
}
#[cfg(target_arch = "aarch64")]
@@ -463,6 +760,7 @@ fn describe_registers() -> Option<Places> {
pc: field_offset("user_pt_regs", "pc")?,
flags: field_offset("user_pt_regs", "pstate")?,
stack_of_task: field_offset("task_struct", "stack")?,
stack_span: struct_size("thread_union").unwrap_or(STACK_SPAN),
})
}
@@ -475,6 +773,7 @@ fn describe_registers() -> Option<Places> {
pc: 15 * 4,
flags: 16 * 4,
stack_of_task: 0,
stack_span: STACK_SPAN,
})
}
@@ -487,6 +786,7 @@ fn describe_registers() -> Option<Places> {
pc: 48,
flags: 56,
stack_of_task: 0,
stack_span: STACK_SPAN,
})
}
@@ -499,6 +799,7 @@ fn describe_registers() -> Option<Places> {
pc: 128,
flags: 144,
stack_of_task: 0,
stack_span: STACK_SPAN,
})
}
@@ -561,33 +862,30 @@ fn make_a_range_executable(task: usize, base: usize, size: usize) -> bool {
granted
}
pub fn report_what_the_copies_hit() {
pub fn a_copy_is_asking() -> bool {
unsafe { placements() }.values().any(|placed| {
let arena = Arena::at(placed.arena);
arena.wants_registers().is_some() || arena.wants_executable().is_some()
})
}
pub fn serve_register_requests() {
for placed in unsafe { placements() }.values() {
let Some(hit) = what_it_says(placed) else {
let arena = Arena::at(placed.arena);
let Some(thread) = arena.wants_registers() else {
continue;
};
bump_to(placed.arena + super::arena::PROGRESS, NOTHING_MORE);
let words = capture_registers(thread).map(|state| words_from_registers(&state));
arena.deliver_registers(words.as_ref().map(|w| w.as_slice()));
}
}
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
));
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);
}
}
@@ -605,16 +903,6 @@ 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() {
@@ -664,7 +952,50 @@ pub unsafe extern "C" fn frida_cb_exec(_data: *mut c_void, task: *mut c_void, _w
((program as usize + at) as *const *const u8).read()
});
native::wake(task as *const u8);
native::wake(crate::glib::wakeup_token());
}
#[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(crate::glib::wakeup_token());
}
}
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);
@@ -715,7 +1046,7 @@ static COPY_TASK: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUs
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;
};
@@ -751,9 +1082,8 @@ fn ask_it_to_leave(placed: &Placement) {
arena.tell_it_to_go();
wake_the_copy(placed);
let waited_on = &placed.arena as *const usize as *const u8;
while !arena.has_gone() {
native::wait(waited_on, Some(LEAVING_GRACE_US), &mut || arena.has_gone());
native::wait(crate::glib::wakeup_token(), None, &mut || arena.has_gone());
}
}
@@ -780,7 +1110,6 @@ 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(&id) else {
@@ -797,10 +1126,6 @@ fn wake_the_copy(placed: &Placement) {
}
}
fn read_address(address: usize) -> u64 {
unsafe { (address as *const u64).read_volatile() }
}
fn read_word(address: usize) -> u32 {
unsafe { (address as *const u32).read_volatile() }
}
@@ -843,12 +1168,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;
@@ -879,6 +1203,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;
@@ -921,6 +1247,8 @@ unsafe extern "C" {
>;
#[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")]
+25 -7
View File
@@ -1,5 +1,10 @@
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;
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
use core::sync::atomic::{AtomicBool, Ordering};
use crate::bindings::{
gum_barebone_handle_exception, GumCpuContext,
@@ -90,7 +95,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,13 +321,18 @@ 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);
}
ACTIVATED.store(true, Ordering::Release);
}
#[cfg(target_arch = "arm")]
pub fn uninstall() {
if !ACTIVATED.swap(false, Ordering::AcqRel) {
return;
}
let target = unsafe { _fixup_exception } as *mut u32;
unsafe {
_patch_text(target.add(1) as *mut c_void, PROLOGUE[1]);
@@ -434,6 +444,9 @@ unsafe extern "C" {
#[cfg(target_arch = "aarch64")]
const PROLOGUE: [u32; 4] = [0xd503233f, 0xa9be7bfd, 0x910003fd, 0xa90153f3];
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
static ACTIVATED: AtomicBool = AtomicBool::new(false);
#[cfg(target_arch = "aarch64")]
#[unsafe(no_mangle)]
static mut FRIDA_ARM64_ORIGINAL_CONT: u64 = 0;
@@ -449,7 +462,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 = redirect_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]);
@@ -457,10 +470,15 @@ pub fn install() {
_aarch64_insn_patch_text_nosync(addr.add(1) as *mut c_void, jump[1]);
_aarch64_insn_patch_text_nosync(addr as *mut c_void, jump[0]);
}
ACTIVATED.store(true, Ordering::Release);
}
#[cfg(target_arch = "aarch64")]
pub fn uninstall() {
if !ACTIVATED.swap(false, Ordering::AcqRel) {
return;
}
let target = unsafe { _fixup_exception } as *mut u32;
unsafe {
for slot in 0..4 {
@@ -470,7 +488,7 @@ pub fn uninstall() {
}
#[cfg(target_arch = "aarch64")]
fn detour_to(destination: usize) -> [u32; 4] {
fn redirect_to(destination: usize) -> [u32; 4] {
[0x58000050, 0xd61f0200, destination as u32, (destination >> 32) as u32]
}
@@ -526,7 +544,7 @@ unsafe fn recovered(regs: *mut c_void) -> bool {
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 usize as u64;
context.pc = frida_arm64_park as *const () as usize as u64;
unsafe { restore_context(regs, &context) };
return true;
}
@@ -539,7 +557,7 @@ unsafe fn recovered(regs: *mut c_void) -> bool {
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);
crate::kernel::wait(core::ptr::addr_of!(PARK_TOKEN), None, &mut || false);
}
}
-357
View File
@@ -1,357 +0,0 @@
// The agent as a kernel module: every primitive is an ordinary call into
// linux/frida-kmod.c, compiled by kbuild against the target kernel's headers,
// which is what keeps us out of the business of guessing struct layouts,
// config-dependent macros and per-version symbol names.
use core::ffi::{CStr, c_char, c_int, c_void};
use alloc::string::String;
use alloc::vec::Vec;
use super::{LoadedModule, ModuleEvent};
use crate::kernel::ThreadEntry;
pub fn log(msg: &str) {
unsafe { frida_kmod_log(msg.as_ptr() as *const c_char) };
}
pub fn panic(msg: &str) -> ! {
unsafe { frida_kmod_panic(msg.as_ptr() as *const c_char) }
}
pub fn run_when_ready(action: fn()) {
action();
}
pub fn spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> isize {
unsafe { frida_kmod_spawn_thread(entry, parameter) as isize }
}
pub fn install_hooks() {
unsafe { frida_kmod_install_hooks() };
}
pub fn alloc(size: usize) -> *mut u8 {
unsafe { frida_kmod_alloc(size) }
}
pub fn free(ptr: *mut u8, size: usize) {
unsafe { frida_kmod_free(ptr, size) };
}
pub fn alloc_code(size: usize) -> *mut u8 {
unsafe { frida_kmod_alloc_code(size) }
}
pub fn free_code(ptr: *mut u8, size: usize) {
unsafe { frida_kmod_free_code(ptr, size) };
}
pub fn page_size() -> usize {
crate::gum::page_size_the_kernel_runs_with()
}
pub fn alloc_heap(size: usize) -> *mut u8 {
alloc(size)
}
pub fn alloc_dma(size: usize) -> *mut u8 {
alloc_code(size)
}
pub fn free_dma(ptr: *mut u8, size: usize) {
free_code(ptr, size);
}
pub fn own_range() -> (u64, u64) {
let mut base: u64 = 0;
let mut size: u64 = 0;
unsafe { frida_kmod_own_range(&mut base, &mut size) };
(base, size)
}
// The sequence number is taken before `check` runs, so a wakeup racing with the
// condition we are about to observe still makes the commit return immediately.
pub fn wait(_token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut() -> bool) {
let seq = unsafe { frida_kmod_wait_prepare() };
if check() {
return;
}
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() };
}
/// Gives the scheduler a chance to run something else. A kernel thread that loops
/// without doing this is a hard lockup waiting to happen.
pub fn yield_now() {
unsafe { frida_kmod_yield() };
}
pub fn monotonic_micros() -> i64 {
unsafe { frida_kmod_monotonic_micros() }
}
pub fn wall_clock_micros() -> (u32, u32) {
let mut secs: u32 = 0;
let mut micros: u32 = 0;
unsafe { frida_kmod_wall_clock_micros(&mut secs, &mut micros) };
(secs, micros)
}
pub fn current_process_id() -> u32 {
KERNEL_PROCESS
}
pub fn current_thread_id() -> u64 {
unsafe { frida_kmod_current_thread_id() }
}
pub fn protect(address: u64, size: usize, gum_prot: u32) -> bool {
unsafe { frida_kmod_protect(address, size, gum_prot) != 0 }
}
pub fn remap_writable(first_page: u64, n_pages: u32) -> *mut c_void {
unsafe { frida_kmod_remap_writable(first_page, n_pages) }
}
/// Tears down an alias handed out by [`remap_writable`] and flushes the icache
/// over the range it aliased; the shim remembers which range that was.
pub fn unmap_writable(mapping: *mut c_void) {
unsafe { frida_kmod_unmap_writable(mapping) };
}
pub fn get_kernel_base() -> u64 {
unsafe { frida_kmod_kernel_base() }
}
pub fn get_kernel_size() -> u64 {
unsafe { frida_kmod_kernel_size() }
}
pub fn enumerate_modules() -> Vec<LoadedModule> {
unsafe extern "C" fn on_module(
name: *const c_char,
version: *const c_char,
base: u64,
size: u64,
user_data: *mut c_void,
) -> c_int {
unsafe {
let modules = &mut *(user_data as *mut Vec<LoadedModule>);
modules.push(LoadedModule {
name: String::from(CStr::from_ptr(name).to_str().unwrap_or("?")),
version: String::from(CStr::from_ptr(version).to_str().unwrap_or("")),
base,
size,
});
1
}
}
let mut modules: Vec<LoadedModule> = Vec::new();
unsafe {
frida_kmod_enumerate_modules(on_module, &mut modules as *mut _ as *mut c_void);
}
modules
}
pub fn watch_modules(on_event: fn(ModuleEvent, LoadedModule)) {
unsafe extern "C" fn on_module_event(
loaded: c_int,
name: *const c_char,
version: *const c_char,
base: u64,
size: u64,
user_data: *mut c_void,
) {
unsafe {
let on_event: fn(ModuleEvent, LoadedModule) = core::mem::transmute(user_data);
on_event(
if loaded != 0 {
ModuleEvent::Loaded
} else {
ModuleEvent::Unloaded
},
LoadedModule {
name: String::from(CStr::from_ptr(name).to_str().unwrap_or("?")),
version: String::from(CStr::from_ptr(version).to_str().unwrap_or("")),
base,
size,
},
);
}
}
unsafe {
frida_kmod_watch_modules(on_module_event, on_event as *mut c_void);
}
}
pub fn unwatch_modules() {
unsafe { frida_kmod_unwatch_modules() };
}
pub fn enumerate_module_symbols(
base: u64,
on_symbol: &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool,
) -> bool {
unsafe extern "C" fn on_module_symbol(
name: *const c_char,
address: u64,
size: u64,
kind: u8,
is_global: c_int,
user_data: *mut c_void,
) -> c_int {
unsafe {
let callback =
&mut *(user_data as *mut &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool);
if callback(name, address, size, kind, is_global != 0) { 1 } else { 0 }
}
}
let mut boxed: &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool = on_symbol;
unsafe {
frida_kmod_enumerate_module_symbols(base, on_module_symbol, &mut boxed as *mut _ as *mut c_void) != 0
}
}
pub fn enumerate_module_exports(
base: u64,
on_export: &mut dyn FnMut(*const c_char, u64) -> bool,
) -> bool {
unsafe extern "C" fn on_module_export(
name: *const c_char,
address: u64,
user_data: *mut c_void,
) -> c_int {
unsafe {
let callback = &mut *(user_data as *mut &mut dyn FnMut(*const c_char, u64) -> bool);
if callback(name, address) { 1 } else { 0 }
}
}
let mut boxed: &mut dyn FnMut(*const c_char, u64) -> bool = on_export;
unsafe {
frida_kmod_enumerate_module_exports(base, on_module_export, &mut boxed as *mut _ as *mut c_void) != 0
}
}
pub fn find_symbol(name: &CStr) -> u64 {
unsafe { frida_kmod_find_symbol(name.as_ptr()) }
}
pub fn find_function(name: &CStr) -> u64 {
unsafe { frida_kmod_find_function(name.as_ptr()) }
}
/// Resolves `address` to `(name, symbol_address)`, or `None` when it falls
/// outside every known symbol.
pub fn symbol_from_address(address: u64) -> Option<(String, u64)> {
const NAME_CAPACITY: usize = 256;
let mut buffer = [0u8; NAME_CAPACITY];
let mut symbol_address: u64 = 0;
let found = unsafe {
frida_kmod_symbol_name_from_address(
address,
buffer.as_mut_ptr() as *mut c_char,
NAME_CAPACITY,
&mut symbol_address,
)
};
if found == 0 {
return None;
}
let name = CStr::from_bytes_until_nul(&buffer).unwrap();
Some((String::from(name.to_str().unwrap()), symbol_address))
}
/// Visits every symbol the running kernel knows about. Returns false when
/// kallsyms is unavailable, which is the case on kernels built without
/// CONFIG_KALLSYMS or when the shim could not resolve its entry points.
pub fn enumerate_symbols(callback: &mut dyn FnMut(&CStr, u64) -> bool) -> bool {
unsafe extern "C" fn on_symbol(
name: *const c_char,
address: u64,
user_data: *mut c_void,
) -> c_int {
unsafe {
let callback = &mut *(user_data as *mut &mut dyn FnMut(&CStr, u64) -> bool);
if callback(CStr::from_ptr(name), address) { 1 } else { 0 }
}
}
let mut boxed: &mut dyn FnMut(&CStr, u64) -> bool = callback;
unsafe { frida_kmod_enumerate_symbols(on_symbol, &mut boxed as *mut _ as *mut c_void) != 0 }
}
const KERNEL_PROCESS: u32 = 0;
type FoundSymbolFunc =
unsafe extern "C" fn(name: *const c_char, address: u64, user_data: *mut c_void) -> c_int;
type ModuleEventFunc = unsafe extern "C" fn(c_int, *const c_char, *const c_char, u64, u64, *mut c_void);
type FoundModuleSymbolFunc =
unsafe extern "C" fn(*const c_char, u64, u64, u8, c_int, *mut c_void) -> c_int;
type FoundModuleExportFunc = unsafe extern "C" fn(*const c_char, u64, *mut c_void) -> c_int;
type FoundModuleFunc = unsafe extern "C" fn(
name: *const c_char,
version: *const c_char,
base: u64,
size: u64,
user_data: *mut c_void,
) -> c_int;
unsafe extern "C" {
fn frida_kmod_log(message: *const c_char);
fn frida_kmod_panic(message: *const c_char) -> !;
fn frida_kmod_spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> c_int;
fn frida_kmod_install_hooks();
fn frida_kmod_alloc(size: usize) -> *mut u8;
fn frida_kmod_free(ptr: *mut u8, size: usize);
fn frida_kmod_alloc_code(size: usize) -> *mut u8;
fn frida_kmod_free_code(ptr: *mut u8, size: usize);
fn frida_kmod_own_range(base: *mut u64, size: *mut u64);
fn frida_kmod_wait_prepare() -> u32;
fn frida_kmod_wait_commit(seq: u32, timeout_us: i64);
fn frida_kmod_wake();
fn frida_kmod_yield();
fn frida_kmod_monotonic_micros() -> i64;
fn frida_kmod_wall_clock_micros(secs: *mut u32, micros: *mut u32);
fn frida_kmod_current_thread_id() -> u64;
fn frida_kmod_protect(address: u64, size: usize, gum_prot: u32) -> c_int;
fn frida_kmod_remap_writable(first_page: u64, n_pages: u32) -> *mut c_void;
fn frida_kmod_unmap_writable(mapping: *mut c_void);
fn frida_kmod_kernel_base() -> u64;
fn frida_kmod_kernel_size() -> u64;
fn frida_kmod_enumerate_modules(func: FoundModuleFunc, user_data: *mut c_void);
fn frida_kmod_watch_modules(func: ModuleEventFunc, user_data: *mut c_void);
fn frida_kmod_unwatch_modules();
fn frida_kmod_enumerate_module_symbols(
base: u64,
func: FoundModuleSymbolFunc,
user_data: *mut c_void,
) -> c_int;
fn frida_kmod_enumerate_module_exports(
base: u64,
func: FoundModuleExportFunc,
user_data: *mut c_void,
) -> c_int;
fn frida_kmod_find_symbol(name: *const c_char) -> u64;
fn frida_kmod_find_function(name: *const c_char) -> u64;
fn frida_kmod_symbol_name_from_address(
address: u64,
buffer: *mut c_char,
size: usize,
symbol_address: *mut u64,
) -> c_int;
fn frida_kmod_enumerate_symbols(func: FoundSymbolFunc, user_data: *mut c_void) -> c_int;
}
+58 -2
View File
@@ -1,13 +1,17 @@
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use core::ffi::c_void;
use core::ffi::{CStr, c_char, c_void};
pub fn field_offset(container: &str, field: &str) -> Option<usize> {
if let Some(told) = told_by_the_loader(container, field) {
return Some(told);
}
let types = described_types()?;
let described = types.named(container)?;
types.offset_of(&described, field).map(|bits| bits / 8)
types.offset_of_path(&described, field).map(|bits| bits / 8)
}
pub fn resolve_struct(container: &str) -> Option<usize> {
@@ -96,11 +100,43 @@ pub fn types_are_described() -> bool {
}
pub fn struct_size(container: &str) -> Option<usize> {
if let Some(told) = told_by_the_loader(container, "") {
return Some(told);
}
let types = described_types()?;
Some(types.named(container)?.size as usize)
}
fn told_by_the_loader(container: &str, field: &str) -> Option<usize> {
let mut entry = unsafe { _frida_kmod_layout } as *const ToldLayout;
if entry.is_null() {
return None;
}
unsafe {
while !(*entry).container.is_null() {
let told = &*entry;
if CStr::from_ptr(told.container).to_bytes() == container.as_bytes()
&& CStr::from_ptr(told.field).to_bytes() == field.as_bytes()
{
return Some(told.value);
}
entry = entry.add(1);
}
}
None
}
#[repr(C)]
struct ToldLayout {
container: *const c_char,
field: *const c_char,
value: usize,
}
struct DescribedTypes {
types: &'static [u8],
names: &'static [u8],
@@ -437,6 +473,25 @@ impl DescribedTypes {
described.kind == KIND_ENUM || described.kind == KIND_WIDE_ENUM
}
fn offset_of_path(&self, described: &Described, path: &str) -> Option<usize> {
let Some((member, rest)) = path.split_once('.') else {
return self.offset_of(described, path);
};
let (placement, type_id) = self.member_named(described, member)?;
let within = self.with_id(type_id)?;
Some(placement + self.offset_of_path(&within, rest)?)
}
fn member_named(&self, described: &Described, wanted: &str) -> Option<(usize, u32)> {
(0..described.members).find_map(|index| {
let member = described.body + (index * MEMBER_SIZE);
(self.name_of(word_at(self.types, member)) == wanted)
.then(|| (self.placement_of(described, member), word_at(self.types, member + 4)))
})
}
// A structure the kernel lays out itself keeps its fields in a member with no name of its
// own, so a field is looked for through those as well, and answers where it sits in the
// structure the question was asked about.
@@ -619,4 +674,5 @@ const MAX_DEPTH: usize = 8;
unsafe extern "C" {
static ___start_BTF: *const c_void;
static ___stop_BTF: *const c_void;
static _frida_kmod_layout: usize;
}
+18 -2
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::{
@@ -83,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);
}
}
@@ -112,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;
+18 -15
View File
@@ -1,10 +1,3 @@
// Linux backend. The agent reaches the kernel in one of two ways, and the rest
// of the agent cannot tell which: loaded from the inside as a kernel module,
// where every primitive is a call into a shim kbuild compiled against the
// target's headers; or injected from the outside like XNU and Windows, where
// the host patches the addresses it needs into .kernel_addrs and the memory it
// cannot touch is reached over the hostlink.
use alloc::string::String;
pub fn stack_headroom() -> usize {
@@ -20,8 +13,6 @@ pub(crate) const STACK_SPAN: usize = 16 * 1024;
#[cfg(any(target_arch = "arm", target_arch = "x86"))]
pub(crate) const STACK_SPAN: usize = 8 * 1024;
#[cfg(feature = "linux")]
mod kmod;
#[cfg(feature = "linux-injected")]
mod arena;
#[cfg(feature = "linux-injected")]
@@ -52,9 +43,15 @@ mod threads;
pub(crate) mod user;
#[cfg(feature = "linux-injected")]
mod processes;
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
mod cloak;
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
mod control;
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
mod process_memory;
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
mod user_threads;
#[cfg(feature = "linux")]
pub use self::kmod::*;
#[cfg(feature = "linux-injected")]
pub use self::injection::*;
#[cfg(feature = "linux-injected")]
@@ -63,17 +60,23 @@ pub use self::relay::*;
pub use self::facade::*;
#[cfg(feature = "linux-injected")]
pub use self::native::{
alloc_dma, free_dma, get_kernel_base, get_kernel_size, install_interrupt_handler, map_io,
map_pages,
mmio_interrupt, pci_interrupt, release_fault_reporter, release_interrupt, run_when_ready,
patch_text, set_kernel_base, set_protection, virt_to_phys,
alloc_dma, free_dma, get_kernel_base, get_kernel_size, hand_back_to_the_loader,
install_interrupt_handler, loaded_from_the_inside, map_io, map_pages, mmio_interrupt, pci_interrupt,
release_fault_reporter, release_interrupt, remap_writable, run_when_ready, patch_text,
set_kernel_base, set_protection, unmap_writable, virt_to_phys,
};
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
pub use self::native::learn_when_to_sleep;
#[cfg(feature = "linux-injected")]
pub use self::mapped::*;
#[cfg(feature = "linux-injected")]
pub use self::modules::*;
#[cfg(feature = "linux-injected")]
pub use self::processes::*;
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
pub use self::control::{
an_operation_waits, carry_out_operations, close as close_control_channel, open as open_control_channel,
};
#[cfg(feature = "linux-injected")]
pub use self::spawn::*;
#[cfg(feature = "linux-injected")]
+54 -5
View File
@@ -9,6 +9,7 @@ use crate::bindings::{g_free, gsize, gum_memory_read};
use super::{LoadedModule, ModuleEvent};
pub fn enumerate_modules() -> Vec<LoadedModule> {
let _held = ModuleListLock::take();
let Some(layout) = module_layout() else {
return Vec::new();
};
@@ -38,6 +39,7 @@ pub fn enumerate_module_symbols(
base: u64,
on_symbol: &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool,
) -> bool {
let _held = ModuleListLock::take();
let Some(layout) = module_layout() else {
return false;
};
@@ -87,6 +89,7 @@ pub fn enumerate_module_exports(
base: u64,
on_export: &mut dyn FnMut(*const c_char, u64) -> bool,
) -> bool {
let _held = ModuleListLock::take();
let Some(layout) = module_layout() else {
return false;
};
@@ -152,6 +155,52 @@ fn relative_target(at: usize) -> usize {
at.wrapping_add_signed(offset as isize)
}
struct ModuleListLock {
mutex: *mut c_void,
}
impl ModuleListLock {
fn take() -> Self {
let mutex = unsafe { _module_mutex } as *mut c_void;
let lockable = !mutex.is_null() && unsafe { _mutex_lock.is_some() && _mutex_unlock.is_some() };
if !lockable {
return Self { mutex: ptr::null_mut() };
}
unsafe { lock_mutex(mutex) };
Self { mutex }
}
}
impl Drop for ModuleListLock {
fn drop(&mut self) {
if !self.mutex.is_null() {
unsafe { unlock_mutex(self.mutex) };
}
}
}
#[cfg(not(target_arch = "x86"))]
unsafe fn lock_mutex(mutex: *mut c_void) {
unsafe { _mutex_lock.unwrap()(mutex) };
}
#[cfg(not(target_arch = "x86"))]
unsafe fn unlock_mutex(mutex: *mut c_void) {
unsafe { _mutex_unlock.unwrap()(mutex) };
}
#[cfg(target_arch = "x86")]
unsafe fn lock_mutex(mutex: *mut c_void) {
unsafe { frida_k_mutex_lock(mutex) };
}
#[cfg(target_arch = "x86")]
unsafe fn unlock_mutex(mutex: *mut c_void) {
unsafe { frida_k_mutex_unlock(mutex) };
}
pub fn watch_modules(on_event: fn(ModuleEvent, LoadedModule)) {
if module_layout().is_none() {
return;
@@ -651,10 +700,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 +812,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;
@@ -783,10 +827,15 @@ unsafe extern "C" {
) -> c_int;
fn frida_k_register_module_notifier(block: *mut c_void) -> c_int;
fn frida_k_unregister_module_notifier(block: *mut c_void) -> c_int;
fn frida_k_mutex_lock(mutex: *mut c_void);
fn frida_k_mutex_unlock(mutex: *mut c_void);
}
unsafe extern "C" {
static _modules: *const c_void;
static _module_mutex: *const c_void;
static _mutex_lock: Option<unsafe extern "C" fn(*mut c_void)>;
static _mutex_unlock: Option<unsafe extern "C" fn(*mut c_void)>;
static _register_module_notifier: Option<unsafe extern "C" fn(*mut c_void) -> c_int>;
#[allow(dead_code)]
static _unregister_module_notifier: Option<unsafe extern "C" fn(*mut c_void) -> c_int>;
+479 -57
View File
@@ -1,13 +1,10 @@
// The agent injected from the outside: the host resolves the kernel functions
// below and patches their addresses into .kernel_addrs, the same way it does
// for XNU and Windows. Everything the kernel would otherwise have to lend us --
// its symbol table, its module list, the right to write to its pages -- comes
// from the host instead, over the hostlink.
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, AtomicUsize, Ordering};
use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
use core::sync::atomic::{AtomicI32, fence};
#[cfg(not(target_arch = "arm"))]
use core::sync::atomic::AtomicUsize;
use crate::kernel::ThreadEntry;
@@ -45,6 +42,16 @@ pub fn release_fault_reporter() {
super::kfault::uninstall();
}
pub fn loaded_from_the_inside() -> bool {
unsafe { _frida_kmod_exit_worker != 0 }
}
pub fn hand_back_to_the_loader() {
if loaded_from_the_inside() {
unsafe { frida_kmod_exit_worker() };
}
}
pub fn send_signal(signal: c_int, task: usize) {
unsafe { _send_sig(signal, task as *mut c_void, 1) };
}
@@ -57,14 +64,14 @@ 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) {
unsafe { super::processes::let_go_of(file) };
}
@@ -150,6 +157,17 @@ pub(crate) fn kcfi_thunk(callback: usize, _type_ref: Option<usize>) -> usize {
callback
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn kernel_thread_function(cache: &AtomicUsize, callback: KthreadFn) -> KthreadFn {
let ref_fn = unsafe { _kthread_worker_fn.map(|f| f as usize) };
unsafe { core::mem::transmute(kcfi_thunk_cached(cache, callback as usize, ref_fn)) }
}
#[cfg(target_arch = "x86_64")]
pub(crate) fn kernel_thread_function(_cache: &AtomicUsize, callback: KthreadFn) -> KthreadFn {
callback
}
#[cfg(target_arch = "aarch64")]
fn kcfi_thunk_cached(cache: &AtomicUsize, callback: usize, type_ref: Option<usize>) -> usize {
let existing = cache.load(Ordering::Acquire);
@@ -164,7 +182,10 @@ fn kcfi_thunk_cached(cache: &AtomicUsize, callback: usize, type_ref: Option<usiz
#[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 usize, ref_fn)) }
unsafe {
core::mem::transmute(kcfi_thunk_cached(&THREAD_THUNK, frida_cb_thread as *const () as usize,
ref_fn))
}
}
#[cfg(not(target_arch = "aarch64"))]
@@ -176,7 +197,8 @@ fn thread_entry() -> unsafe extern "C" fn(*mut c_void) -> c_int {
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 usize, ref_fn))
core::mem::transmute(kcfi_thunk_cached(&INTERRUPT_THUNK,
frida_cb_interrupt as *const () as usize, ref_fn))
}
}
@@ -206,28 +228,38 @@ pub fn spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> isize {
#[cfg(target_arch = "x86")]
pub fn alloc(size: usize) -> *mut u8 {
unsafe { frida_k_alloc(size, GFP_KERNEL as usize) }
unsafe { frida_k_alloc(size, allocation_flags() as usize) }
}
#[cfg(not(target_arch = "x86"))]
pub fn alloc(size: usize) -> *mut u8 {
let flags = allocation_flags();
unsafe {
if let Some(f) = ___kmalloc_noprof {
f(size, GFP_KERNEL)
f(size, flags)
} else if let Some(f) = _kmalloc {
f(size, GFP_KERNEL)
f(size, flags)
} else {
___kmalloc.unwrap()(size, GFP_KERNEL)
___kmalloc.unwrap()(size, flags)
}
}
}
fn allocation_flags() -> u32 {
if can_sleep() { GFP_KERNEL } else { GFP_ATOMIC }
}
pub fn free(ptr: *mut u8, _size: usize) {
unsafe { _kfree(ptr) };
}
pub fn alloc_heap(size: usize) -> *mut u8 {
unsafe {
#[cfg(not(target_arch = "x86"))]
if let Some(f) = _vmalloc_noprof {
return f(size);
}
if let Some(f) = _vmalloc {
return f(size);
}
@@ -236,7 +268,6 @@ pub fn alloc_heap(size: usize) -> *mut u8 {
alloc(size)
}
#[cfg(target_arch = "x86")]
pub fn alloc_code(size: usize) -> *mut u8 {
unsafe {
@@ -247,7 +278,9 @@ pub fn alloc_code(size: usize) -> *mut u8 {
#[cfg(not(target_arch = "x86"))]
pub fn alloc_code(size: usize) -> *mut u8 {
unsafe {
let code = if let Some(f) = _execmem_alloc {
let code = if let Some(f) = _execmem_alloc_rw {
f(EXECMEM_MODULE_TEXT, size)
} else if let Some(f) = _execmem_alloc {
f(EXECMEM_MODULE_TEXT, size)
} else {
_module_alloc.unwrap()(size)
@@ -277,6 +310,29 @@ pub fn free_code(ptr: *mut u8, _size: usize) {
}
}
#[cfg(not(target_arch = "x86"))]
pub fn remap_writable(first_page: u64, n_pages: u32) -> Option<*mut c_void> {
let remap = unsafe { _frida_kmod_remap_writable }?;
let alias = unsafe { remap(first_page, n_pages) };
(!alias.is_null()).then_some(alias)
}
#[cfg(target_arch = "x86")]
pub fn remap_writable(_first_page: u64, _n_pages: u32) -> Option<*mut c_void> {
None
}
#[cfg(not(target_arch = "x86"))]
pub fn unmap_writable(alias: *mut c_void) -> bool {
unsafe { _frida_kmod_unmap_writable }.is_some_and(|unmap| unsafe { unmap(alias) })
}
#[cfg(target_arch = "x86")]
pub fn unmap_writable(_alias: *mut c_void) -> bool {
false
}
pub fn page_size() -> usize {
crate::gum::page_size_the_kernel_runs_with()
}
@@ -303,14 +359,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")]
@@ -368,7 +425,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 {
@@ -383,13 +442,18 @@ pub fn free_dma(ptr: *mut u8, size: usize) {
free(ptr, size);
}
pub fn wait(_token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut() -> bool) {
pub fn wait(token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut() -> bool) {
if !can_sleep() {
spin_until(check, timeout_us);
return;
}
let waiters = waiters();
let mut waiter = [0usize; WAIT_ENTRY_WORDS];
let waiter = queue_this_thread(&mut waiter);
let waiter = queue_this_thread(&mut waiter, token);
unsafe { _prepare_to_wait_event(waiters, waiter, TASK_INTERRUPTIBLE) };
unsafe { _prepare_to_wait_event(waiters, waiter, TASK_IDLE) };
if !check() {
match timeout_us {
@@ -404,18 +468,19 @@ pub fn wait(_token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut()
unsafe { _finish_wait(waiters, waiter) };
}
pub fn wake(_token: *const u8) {
unsafe { ___wake_up(waiters(), TASK_NORMAL, WAKE_EVERY_WAITER, ptr::null_mut()) };
pub fn wake(token: *const u8) {
unsafe { ___wake_up(waiters(), TASK_NORMAL, WAKE_EVERY_WAITER, token as *mut c_void) };
}
pub fn poke_loop_wakeup() {}
fn queue_this_thread(entry: &mut [usize; WAIT_ENTRY_WORDS]) -> *mut c_void {
fn queue_this_thread(entry: &mut [usize; WAIT_ENTRY_WORDS], token: *const u8) -> *mut c_void {
let entry = entry.as_mut_ptr();
unsafe {
entry.add(WAIT_ENTRY_THREAD).write(current_task() as usize);
entry.add(WAIT_ENTRY_WAKE).write(_autoremove_wake_function as usize);
entry.add(WAIT_ENTRY_WAKE).write(waking_function());
entry.add(WAIT_ENTRY_TOKEN).write(token as usize);
let queued = entry.add(WAIT_ENTRY_QUEUED);
queued.write(queued as usize);
@@ -425,6 +490,37 @@ fn queue_this_thread(entry: &mut [usize; WAIT_ENTRY_WORDS]) -> *mut c_void {
entry as *mut c_void
}
#[cfg(target_arch = "aarch64")]
fn waking_function() -> usize {
let ref_fn = unsafe { _autoremove_wake_function } as usize;
kcfi_thunk_cached(&WAKE_THUNK, frida_cb_wake as *const () as usize, Some(ref_fn))
}
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86")))]
fn waking_function() -> usize {
frida_cb_wake as *const () as usize
}
#[cfg(target_arch = "x86")]
fn waking_function() -> usize {
unsafe { _autoremove_wake_function as usize }
}
#[cfg(not(target_arch = "x86"))]
unsafe extern "C" fn frida_cb_wake(entry: *mut c_void, mode: c_uint, flags: c_int, key: *mut c_void) -> c_int {
let awaited = unsafe { (entry as *const usize).add(WAIT_ENTRY_TOKEN).read() };
if key as usize != awaited {
return 0;
}
let wake_this_one: unsafe extern "C" fn(*mut c_void, c_uint, c_int, *mut c_void) -> c_int =
unsafe { core::mem::transmute(_autoremove_wake_function) };
unsafe { wake_this_one(entry, mode, flags, key) }
}
#[cfg(target_arch = "aarch64")]
static WAKE_THUNK: AtomicUsize = AtomicUsize::new(0);
fn waiters() -> *mut c_void {
let queue = (&raw mut WAITERS) as *mut c_void;
@@ -483,7 +579,7 @@ fn pci_irq_vector() -> Option<unsafe extern "C" fn(*mut c_void, c_uint) -> c_int
#[cfg(target_arch = "arm")]
pub fn mmio_interrupt(mmio_base: u64) -> Option<u32> {
let path = alloc::format!("/virtio_mmio@{:x}\0", mmio_base);
let node = unsafe { _of_find_node_opts_by_path(path.as_ptr() as *const c_char, ptr::null_mut()) };
let node = unsafe { _of_find_node_opts_by_path(path.as_ptr() as *const c_char, core::ptr::null_mut()) };
if node.is_null() {
return None;
}
@@ -504,7 +600,240 @@ pub fn mmio_interrupt(_mmio_base: u64) -> Option<u32> {
}
pub fn yield_now() {
unsafe { _schedule() };
if can_sleep() {
unsafe { _schedule() };
} else {
core::hint::spin_loop();
}
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
pub fn learn_when_to_sleep() {
let above = |found: Option<usize>| found.map_or(usize::MAX, |at| at + 1);
RCU_DEPTH_AT.store(above(super::layout::field_offset("task_struct", "rcu_read_lock_nesting")), Ordering::Relaxed);
PREEMPT_COUNT_AT.store(above(where_the_preempt_count_is()), Ordering::Release);
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
fn can_sleep() -> bool {
let Some(places) = sleeping_places() else {
return true;
};
let rcu_depth = places.rcu_read_lock_nesting
.map_or(0, |at| unsafe { ((current_task() as usize + at) as *const i32).read_volatile() });
current_preempt_count(places.preempt_count) == 0 && !interrupts_masked() && rcu_depth == 0
}
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
fn can_sleep() -> bool {
true
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
fn sleeping_places() -> Option<SleepingPlaces> {
let found = |at: usize| (at != 0 && at != usize::MAX).then(|| at - 1);
Some(SleepingPlaces {
preempt_count: found(PREEMPT_COUNT_AT.load(Ordering::Acquire))?,
rcu_read_lock_nesting: found(RCU_DEPTH_AT.load(Ordering::Relaxed)),
})
}
#[cfg(target_arch = "aarch64")]
fn where_the_preempt_count_is() -> Option<usize> {
super::layout::field_offset("task_struct", "thread_info.preempt_count")
}
#[cfg(target_arch = "x86_64")]
fn where_the_preempt_count_is() -> Option<usize> {
let standalone = unsafe { ___preempt_count };
if standalone != 0 {
return Some(standalone);
}
hot_per_cpu_place("preempt_count")
}
#[cfg(target_arch = "aarch64")]
fn current_preempt_count(at: usize) -> u32 {
unsafe { ((current_task() as usize + at) as *const u32).read_volatile() }
}
#[cfg(target_arch = "x86_64")]
fn current_preempt_count(at: usize) -> u32 {
let count: u32;
unsafe { core::arch::asm!("mov {:e}, gs:[{}]", out(reg) count, in(reg) at, options(nostack, readonly)) };
count & !PREEMPT_NEED_RESCHED
}
#[cfg(target_arch = "aarch64")]
fn interrupts_masked() -> bool {
let daif: usize;
unsafe { core::arch::asm!("mrs {}, daif", out(reg) daif, options(nomem, nostack)) };
daif & DAIF_IRQ_MASKED != 0
}
#[cfg(target_arch = "x86_64")]
fn interrupts_masked() -> bool {
let flags: usize;
unsafe { core::arch::asm!("pushfq", "pop {}", out(reg) flags, options(nomem, preserves_flags)) };
flags & RFLAGS_IF == 0
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
struct SleepingPlaces {
preempt_count: usize,
rcu_read_lock_nesting: Option<usize>,
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
static PREEMPT_COUNT_AT: AtomicUsize = AtomicUsize::new(0);
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
static RCU_DEPTH_AT: AtomicUsize = AtomicUsize::new(0);
fn spin_until(check: &mut dyn FnMut() -> bool, timeout_us: Option<u64>) {
let deadline = timeout_us.map(|us| monotonic_micros() + us as i64);
while !check() && deadline.is_none_or(|deadline| monotonic_micros() < deadline) {
core::hint::spin_loop();
}
}
#[cfg(target_arch = "x86_64")]
pub(crate) fn lock_spinlock(lock: usize) {
unsafe { __raw_spin_lock.unwrap()(lock as *mut c_void) };
}
#[cfg(target_arch = "x86_64")]
pub(crate) fn unlock_spinlock(lock: usize) {
unsafe { __raw_spin_unlock.unwrap()(lock as *mut c_void) };
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn lock_spinlock(lock: usize) {
if let Some(lock_it) = unsafe { __raw_spin_lock } {
return unsafe { lock_it(lock as *mut c_void) };
}
disable_preemption();
if let Err(seen) = counter_at(lock).compare_exchange(0, SPIN_LOCKED, Ordering::Acquire, Ordering::Relaxed) {
unsafe { _queued_spin_lock_slowpath(lock as *mut c_void, seen as u32) };
}
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn unlock_spinlock(lock: usize) {
if let Some(unlock_it) = unsafe { __raw_spin_unlock } {
return unsafe { unlock_it(lock as *mut c_void) };
}
unsafe { core::sync::atomic::AtomicU8::from_ptr(lock as *mut u8) }.store(0, Ordering::Release);
enable_preemption();
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn read_lock_irqsave(lock: usize) -> usize {
let flags = mask_interrupts();
disable_preemption();
if counter_at(lock).fetch_add(READER_BIAS, Ordering::Acquire) & WRITER_MASK != 0 {
unsafe { _queued_read_lock_slowpath(lock as *mut c_void) };
}
flags
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn read_unlock_irqrestore(lock: usize, flags: usize) {
counter_at(lock).fetch_sub(READER_BIAS, Ordering::Release);
enable_preemption();
restore_interrupts(flags);
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn write_lock_irqsave(lock: usize) -> usize {
let flags = mask_interrupts();
disable_preemption();
if counter_at(lock).compare_exchange(0, WRITER_LOCKED, Ordering::Acquire, Ordering::Relaxed).is_err() {
unsafe { _queued_write_lock_slowpath(lock as *mut c_void) };
}
flags
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn write_unlock_irqrestore(lock: usize, flags: usize) {
unsafe { core::sync::atomic::AtomicU8::from_ptr(lock as *mut u8) }.store(0, Ordering::Release);
enable_preemption();
restore_interrupts(flags);
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn disable_preemption() {
let count = preempt_count();
unsafe { count.write_volatile(count.read_volatile() + 1) };
core::sync::atomic::compiler_fence(Ordering::SeqCst);
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn enable_preemption() {
core::sync::atomic::compiler_fence(Ordering::SeqCst);
let count = preempt_count();
unsafe { count.write_volatile(count.read_volatile() - 1) };
}
#[cfg(target_arch = "x86_64")]
pub(crate) fn disable_preemption() {
let at = sleeping_places().unwrap().preempt_count;
unsafe { core::arch::asm!("add dword ptr gs:[{}], 1", in(reg) at, options(nostack)) };
core::sync::atomic::compiler_fence(Ordering::SeqCst);
}
#[cfg(target_arch = "x86_64")]
pub(crate) fn enable_preemption() {
core::sync::atomic::compiler_fence(Ordering::SeqCst);
let at = sleeping_places().unwrap().preempt_count;
unsafe { core::arch::asm!("sub dword ptr gs:[{}], 1", in(reg) at, options(nostack)) };
}
#[cfg(target_arch = "aarch64")]
fn preempt_count() -> *mut u32 {
let at = sleeping_places().unwrap().preempt_count;
(current_task() as usize + at) as *mut u32
}
#[cfg(target_arch = "aarch64")]
fn mask_interrupts() -> usize {
let flags: usize;
unsafe {
core::arch::asm!("mrs {}, daif", "msr daifset, #3", out(reg) flags, options(nostack));
}
flags
}
#[cfg(target_arch = "aarch64")]
fn restore_interrupts(flags: usize) {
unsafe { core::arch::asm!("msr daif, {}", in(reg) flags, options(nostack)) };
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub(crate) fn hold(counter: usize) {
counter_at(counter).fetch_add(1, Ordering::Relaxed);
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub(crate) fn let_go(counter: usize) -> bool {
if counter_at(counter).fetch_sub(1, Ordering::Release) != 1 {
return false;
}
fence(Ordering::Acquire);
true
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
fn counter_at(address: usize) -> &'static AtomicI32 {
unsafe { AtomicI32::from_ptr(address as *mut i32) }
}
pub fn monotonic_micros() -> i64 {
@@ -558,33 +887,64 @@ pub fn current_task() -> u64 {
task
}
#[cfg(target_arch = "x86_64")]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub fn current_task() -> u64 {
let task: u64;
unsafe {
core::arch::asm!("mov {}, gs:[{}]", out(reg) task, in(reg) _current_task, options(nostack));
}
task
let place = per_cpu_place(unsafe { _current_task }, "current_task", &CURRENT_TASK_AT);
read_per_cpu_word(place) as u64
}
#[cfg(target_arch = "x86_64")]
pub fn top_of_stack() -> usize {
let top: usize;
unsafe {
core::arch::asm!("mov {}, gs:[{}]", out(reg) top, in(reg) _cpu_current_top_of_stack, options(nostack));
let place = per_cpu_place(unsafe { _cpu_current_top_of_stack }, "top_of_stack", &TOP_OF_STACK_AT);
read_per_cpu_word(place)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn per_cpu_place(standalone: usize, hot_member: &str, cache: &AtomicUsize) -> usize {
let known = cache.load(Ordering::Relaxed);
if known != 0 {
return known;
}
top
let place = if standalone != 0 { standalone } else { hot_per_cpu_place(hot_member).unwrap() };
cache.store(place, Ordering::Relaxed);
place
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn hot_per_cpu_place(member: &str) -> Option<usize> {
let hot = unsafe { _pcpu_hot };
if hot == 0 {
return None;
}
Some(hot + super::layout::field_offset("pcpu_hot", member)?)
}
#[cfg(target_arch = "x86_64")]
fn read_per_cpu_word(place: usize) -> usize {
let value: usize;
unsafe { core::arch::asm!("mov {}, gs:[{}]", out(reg) value, in(reg) place, options(nostack, readonly)) };
value
}
#[cfg(target_arch = "x86")]
pub fn current_task() -> u64 {
let task: u32;
unsafe {
core::arch::asm!("mov {}, fs:[{}]", out(reg) task, in(reg) _current_task as u32, options(nostack));
}
task as u64
fn read_per_cpu_word(place: usize) -> usize {
let value: usize;
unsafe { core::arch::asm!("mov {}, fs:[{}]", out(reg) value, in(reg) place, options(nostack, readonly)) };
value
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
static CURRENT_TASK_AT: AtomicUsize = AtomicUsize::new(0);
#[cfg(target_arch = "x86_64")]
static TOP_OF_STACK_AT: AtomicUsize = AtomicUsize::new(0);
pub fn get_kernel_base() -> u64 {
KERNEL_BASE.load(Ordering::Relaxed)
}
@@ -805,34 +1165,59 @@ const KERNEL_PROCESS: u32 = 0;
const JS_SAFE_THREAD_ID_MASK: u64 = (1 << 48) - 1;
const GFP_KERNEL: u32 = 0xcc0;
const GFP_ATOMIC: u32 = 0x820;
const NUMA_NO_NODE: c_int = -1;
const EXECMEM_MODULE_TEXT: u32 = 1;
const EXECMEM_MODULE_TEXT: u32 = 0;
const PAGE_SIZE: usize = 4096;
#[cfg(target_arch = "aarch64")]
const TCR_T1SZ_SHIFT: u64 = 16;
#[cfg(target_arch = "aarch64")]
const TCR_T1SZ_MASK: u64 = 0x3f;
const TASK_INTERRUPTIBLE: c_uint = 1;
const TASK_IDLE: c_uint = 0x402;
const TASK_NORMAL: c_uint = 3;
const WAKE_EVERY_WAITER: c_int = 0;
const WAKE_SLACK_NS: u64 = 50_000;
#[cfg(target_arch = "aarch64")]
const SPIN_LOCKED: i32 = 1;
#[cfg(target_arch = "aarch64")]
const READER_BIAS: i32 = 1 << 9;
#[cfg(target_arch = "aarch64")]
const WRITER_LOCKED: i32 = 0xff;
#[cfg(target_arch = "aarch64")]
const WRITER_MASK: i32 = 0x1ff;
#[cfg(target_arch = "aarch64")]
const DAIF_IRQ_MASKED: usize = 1 << 7;
#[cfg(target_arch = "x86_64")]
const PREEMPT_NEED_RESCHED: u32 = 1 << 31;
#[cfg(target_arch = "x86_64")]
const RFLAGS_IF: usize = 1 << 9;
const HRTIMER_MODE_REL: c_int = 1;
const WAIT_ENTRY_WORDS: usize = 8;
const WAIT_ENTRY_THREAD: usize = 1;
const WAIT_ENTRY_WAKE: usize = 2;
const WAIT_ENTRY_QUEUED: usize = 3;
const WAIT_ENTRY_TOKEN: usize = WAIT_ENTRY_WORDS - 1;
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;
@@ -846,7 +1231,33 @@ struct Timespec64 {
tv_nsec: i64,
}
type KthreadFn = unsafe extern "C" fn(data: *mut c_void) -> c_int;
pub(crate) type KthreadFn = unsafe extern "C" fn(data: *mut c_void) -> c_int;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
unsafe extern "C" {
static __raw_spin_lock: Option<unsafe extern "C" fn(*mut c_void)>;
static __raw_spin_unlock: Option<unsafe extern "C" fn(*mut c_void)>;
}
#[cfg(target_arch = "aarch64")]
unsafe extern "C" {
static _queued_spin_lock_slowpath: unsafe extern "C" fn(*mut c_void, u32);
static _queued_read_lock_slowpath: unsafe extern "C" fn(*mut c_void);
static _queued_write_lock_slowpath: unsafe extern "C" fn(*mut c_void);
}
unsafe extern "C" {
static _frida_kmod_exit_worker: usize;
#[cfg(target_arch = "x86")]
#[link_name = "frida_k_frida_kmod_exit_worker"]
fn frida_kmod_exit_worker() -> !;
}
#[cfg(not(target_arch = "x86"))]
unsafe fn frida_kmod_exit_worker() -> ! {
let exit: unsafe extern "C" fn() -> ! = unsafe { core::mem::transmute(_frida_kmod_exit_worker) };
unsafe { exit() }
}
type IrqHandlerFn = unsafe extern "C" fn(irq: c_int, cookie: *mut c_void) -> c_int;
unsafe extern "C" {
@@ -882,13 +1293,20 @@ unsafe extern "C" {
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>;
#[cfg(not(target_arch = "x86"))]
static _execmem_alloc_rw: Option<unsafe extern "C" fn(u32, usize) -> *mut u8>;
#[cfg(not(target_arch = "x86"))]
static _vmalloc_noprof: Option<unsafe extern "C" fn(usize) -> *mut u8>;
#[cfg(not(target_arch = "x86"))]
static _frida_kmod_remap_writable: Option<unsafe extern "C" fn(u64, u32) -> *mut c_void>;
#[cfg(not(target_arch = "x86"))]
static _frida_kmod_unmap_writable: Option<unsafe extern "C" fn(*mut c_void) -> bool>;
static _set_memory_rw: Option<unsafe extern "C" fn(usize, c_int) -> c_int>;
static _set_memory_ro: Option<unsafe extern "C" fn(usize, c_int) -> c_int>;
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")]
@@ -954,6 +1372,10 @@ unsafe extern "C" {
static _current_task: usize;
#[cfg(target_arch = "x86_64")]
static _cpu_current_top_of_stack: usize;
#[cfg(target_arch = "x86_64")]
static ___preempt_count: usize;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
static _pcpu_hot: usize;
#[cfg(target_arch = "arm")]
static ___pv_offset: *const u64;
#[cfg(target_arch = "arm")]
@@ -1051,10 +1473,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;
@@ -0,0 +1,99 @@
use core::ffi::{c_int, c_void};
use super::processes::HeldTask;
pub fn allocate(id: u32, size: usize, protection: usize) -> usize {
let Some(mm) = BorrowedMemory::of(id) else {
return 0;
};
let address = unsafe { _vm_mmap(core::ptr::null_mut(), 0, size, protection, MAP_PRIVATE | MAP_ANONYMOUS, 0) };
drop(mm);
if address >= FIRST_ERROR_ADDRESS { 0 } else { address }
}
pub fn release(id: u32, address: usize, size: usize) -> c_int {
let Some(mm) = BorrowedMemory::of(id) else {
return -ESRCH;
};
let result = unsafe { _vm_munmap(address, size) };
drop(mm);
result
}
pub fn write(id: u32, address: usize, data: &[u8]) -> usize {
transfer(id, address, data.as_ptr() as *mut c_void, data.len(), FOLL_WRITE)
}
pub fn read(id: u32, address: usize, buffer: &mut [u8]) -> usize {
transfer(id, address, buffer.as_mut_ptr() as *mut c_void, buffer.len(), 0)
}
pub fn read_own(address: usize, buffer: &mut [u8]) -> bool {
let len = buffer.len();
transfer_with(super::native::current_task() as usize, address, buffer.as_mut_ptr() as *mut c_void,
len, 0) == len
}
pub fn write_own(address: usize, data: &[u8]) -> bool {
transfer_with(super::native::current_task() as usize, address, data.as_ptr() as *mut c_void,
data.len(), FOLL_WRITE) == data.len()
}
fn transfer(id: u32, address: usize, buffer: *mut c_void, size: usize, flags: u32) -> usize {
let Some(task) = HeldTask::with_id(id) else {
return 0;
};
transfer_with(task.address(), address, buffer, size, flags)
}
fn transfer_with(task: usize, address: usize, buffer: *mut c_void, size: usize, flags: u32) -> usize {
let transferred = unsafe { _access_process_vm(task as *mut c_void, address, buffer, size as c_int, flags) };
transferred.max(0) as usize
}
struct BorrowedMemory(*mut c_void);
impl BorrowedMemory {
fn of(id: u32) -> Option<Self> {
let task = HeldTask::with_id(id)?;
let mm = unsafe { _get_task_mm(task.address() as *mut c_void) };
if mm.is_null() {
return None;
}
unsafe { _kthread_use_mm(mm) };
Some(Self(mm))
}
}
impl Drop for BorrowedMemory {
fn drop(&mut self) {
unsafe {
_kthread_unuse_mm(self.0);
_mmput(self.0);
}
}
}
const MAP_PRIVATE: usize = 0x02;
const MAP_ANONYMOUS: usize = 0x20;
const FOLL_WRITE: u32 = 0x01;
const FIRST_ERROR_ADDRESS: usize = usize::MAX - 4095;
const ESRCH: c_int = 3;
unsafe extern "C" {
static _get_task_mm: unsafe extern "C" fn(*mut c_void) -> *mut c_void;
static _mmput: unsafe extern "C" fn(*mut c_void);
static _kthread_use_mm: unsafe extern "C" fn(*mut c_void);
static _kthread_unuse_mm: unsafe extern "C" fn(*mut c_void);
static _vm_mmap: unsafe extern "C" fn(*mut c_void, usize, usize, usize, usize, usize) -> usize;
static _vm_munmap: unsafe extern "C" fn(usize, usize) -> c_int;
static _access_process_vm: unsafe extern "C" fn(*mut c_void, usize, *mut c_void, c_int, u32) -> c_int;
}
+359 -43
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) };
@@ -49,6 +72,53 @@ pub unsafe fn let_go_of(file: *mut c_void) {
unsafe { _fput(file) };
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub(crate) struct HeldTask(usize);
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
impl HeldTask {
pub(crate) fn with_id(id: u32) -> Option<Self> {
let task = unsafe { _find_get_task_by_vpid(id as c_int) } as usize;
(task != 0).then(|| Self(task))
}
pub(crate) fn leader(&self) -> Option<Self> {
let group_leader = super::layout::field_offset("task_struct", "group_leader")?;
let usage = super::layout::field_offset("task_struct", "usage")?;
let flags = lock_tasklist();
let leader = unsafe { ((self.0 + group_leader) as *const usize).read() };
super::native::hold(leader + usage);
unlock_tasklist(flags);
Some(Self(leader))
}
pub(crate) fn address(&self) -> usize {
self.0
}
pub(crate) fn into_address(self) -> usize {
let task = self.0;
core::mem::forget(self);
task
}
pub(crate) unsafe fn from_address(task: usize) -> Self {
Self(task)
}
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
impl Drop for HeldTask {
fn drop(&mut self) {
let usage = super::layout::field_offset("task_struct", "usage").unwrap();
if super::native::let_go(self.0 + usage) {
unsafe { ___put_task_struct(self.0 as *mut c_void) };
}
}
}
pub fn task_with_id(id: u32) -> Option<usize> {
let layout = task_layout()?;
@@ -69,6 +139,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 +168,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 +397,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 +416,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"),
})
}
@@ -377,12 +620,16 @@ fn lock_tasklist() -> usize {
#[cfg(not(target_arch = "x86"))]
fn lock_tasklist() -> usize {
unsafe {
match (__raw_read_lock_irqsave, __raw_read_unlock_irqrestore) {
(Some(lock), Some(_)) => lock(_tasklist_lock),
_ => {
__raw_read_lock.unwrap()(_tasklist_lock);
match (__raw_read_lock_irqsave, __raw_read_unlock_irqrestore, __raw_read_lock) {
(Some(lock), Some(_), _) => lock(_tasklist_lock),
(_, _, Some(lock)) => {
lock(_tasklist_lock);
0
}
#[cfg(target_arch = "aarch64")]
_ => super::native::read_lock_irqsave(_tasklist_lock as usize),
#[cfg(not(target_arch = "aarch64"))]
_ => panic!("no way to lock the task list"),
}
}
}
@@ -395,9 +642,13 @@ fn unlock_tasklist(flags: usize) {
#[cfg(not(target_arch = "x86"))]
fn unlock_tasklist(flags: usize) {
unsafe {
match (__raw_read_lock_irqsave, __raw_read_unlock_irqrestore) {
(Some(_), Some(unlock)) => unlock(_tasklist_lock, flags),
_ => __raw_read_unlock.unwrap()(_tasklist_lock),
match (__raw_read_lock_irqsave, __raw_read_unlock_irqrestore, __raw_read_unlock) {
(Some(_), Some(unlock), _) => unlock(_tasklist_lock, flags),
(_, _, Some(unlock)) => unlock(_tasklist_lock),
#[cfg(target_arch = "aarch64")]
_ => super::native::read_unlock_irqrestore(_tasklist_lock as usize, flags),
#[cfg(not(target_arch = "aarch64"))]
_ => panic!("no way to unlock the task list"),
}
}
}
@@ -406,10 +657,6 @@ 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;
@@ -442,8 +689,17 @@ unsafe fn detach_pid(pids: *mut *mut c_void, task: *mut c_void, kind: c_int) {
}
#[cfg(not(target_arch = "x86"))]
unsafe fn detach_pid(pids: *mut *mut c_void, task: *mut c_void, kind: c_int) {
unsafe { _detach_pid(pids, task, kind) };
pub(crate) unsafe fn detach_pid(pids: *mut *mut c_void, task: *mut c_void, kind: c_int) {
type ReleasingDetachPid = unsafe extern "C" fn(*mut c_void, c_int);
unsafe {
if _free_pids.is_some() {
_detach_pid(pids, task, kind);
} else {
let releasing: ReleasingDetachPid = core::mem::transmute(_detach_pid);
releasing(task, kind);
}
}
}
#[cfg(target_arch = "x86")]
@@ -452,11 +708,13 @@ unsafe fn free_pids(pids: *mut *mut c_void) {
}
#[cfg(not(target_arch = "x86"))]
unsafe fn free_pids(pids: *mut *mut c_void) {
unsafe { _free_pids(pids) };
pub(crate) unsafe fn free_pids(pids: *mut *mut c_void) {
if let Some(free) = unsafe { _free_pids } {
unsafe { free(pids) };
}
}
unsafe fn unlink(node: usize) {
pub(crate) unsafe fn unlink(node: usize) {
let next = unsafe { (node as *const usize).read() };
let prev = unsafe { ((node + WORD_SIZE) as *const usize).read() };
unsafe {
@@ -468,34 +726,64 @@ unsafe fn unlink(node: usize) {
}
#[cfg(target_arch = "x86")]
fn write_lock_tasklist() -> usize {
pub(crate) fn write_lock_tasklist() -> usize {
unsafe { frida_k_write_lock_tasklist() }
}
#[cfg(not(target_arch = "x86"))]
fn write_lock_tasklist() -> usize {
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub(crate) unsafe fn unlink_for_readers(node: usize) {
let next = unsafe { (node as *const usize).read() };
let prev = unsafe { ((node + WORD_SIZE) as *const usize).read() };
unsafe {
match (__raw_write_lock_irqsave, __raw_write_unlock_irqrestore) {
(Some(lock), Some(_)) => lock(_tasklist_lock),
_ => {
__raw_write_lock.unwrap()(_tasklist_lock);
((next + WORD_SIZE) as *mut usize).write(prev);
(prev as *mut usize).write(next);
}
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub(crate) unsafe fn append_for_readers(node: usize, head: usize) {
let prev = unsafe { ((head + WORD_SIZE) as *const usize).read() };
unsafe {
(node as *mut usize).write(head);
((node + WORD_SIZE) as *mut usize).write(prev);
core::sync::atomic::AtomicUsize::from_ptr(prev as *mut usize)
.store(node, core::sync::atomic::Ordering::Release);
((head + WORD_SIZE) as *mut usize).write(node);
}
}
#[cfg(not(target_arch = "x86"))]
pub(crate) fn write_lock_tasklist() -> usize {
unsafe {
match (__raw_write_lock_irqsave, __raw_write_unlock_irqrestore, __raw_write_lock) {
(Some(lock), Some(_), _) => lock(_tasklist_lock),
(_, _, Some(lock)) => {
lock(_tasklist_lock);
0
}
#[cfg(target_arch = "aarch64")]
_ => super::native::write_lock_irqsave(_tasklist_lock as usize),
#[cfg(not(target_arch = "aarch64"))]
_ => panic!("no way to lock the task list"),
}
}
}
#[cfg(target_arch = "x86")]
fn write_unlock_tasklist(flags: usize) {
pub(crate) fn write_unlock_tasklist(flags: usize) {
unsafe { frida_k_write_unlock_tasklist(flags) };
}
#[cfg(not(target_arch = "x86"))]
fn write_unlock_tasklist(flags: usize) {
pub(crate) fn write_unlock_tasklist(flags: usize) {
unsafe {
match (__raw_write_lock_irqsave, __raw_write_unlock_irqrestore) {
(Some(_), Some(unlock)) => unlock(_tasklist_lock, flags),
_ => __raw_write_unlock.unwrap()(_tasklist_lock),
match (__raw_write_lock_irqsave, __raw_write_unlock_irqrestore, __raw_write_unlock) {
(Some(_), Some(unlock), _) => unlock(_tasklist_lock, flags),
(_, _, Some(unlock)) => unlock(_tasklist_lock),
#[cfg(target_arch = "aarch64")]
_ => super::native::write_unlock_irqrestore(_tasklist_lock as usize, flags),
#[cfg(not(target_arch = "aarch64"))]
_ => panic!("no way to unlock the task list"),
}
}
}
@@ -503,6 +791,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;
@@ -527,7 +820,11 @@ unsafe extern "C" {
#[cfg(not(target_arch = "x86"))]
static _detach_pid: unsafe extern "C" fn(*mut *mut c_void, *mut c_void, c_int);
#[cfg(not(target_arch = "x86"))]
static _free_pids: unsafe extern "C" fn(*mut *mut c_void);
static _free_pids: Option<unsafe extern "C" fn(*mut *mut c_void)>;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
static _find_get_task_by_vpid: unsafe extern "C" fn(c_int) -> *mut c_void;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
static ___put_task_struct: unsafe extern "C" fn(*mut c_void);
#[cfg(not(target_arch = "x86"))]
static __raw_write_lock: Option<unsafe extern "C" fn(*mut c_void)>;
#[cfg(not(target_arch = "x86"))]
@@ -545,6 +842,21 @@ 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")]
@@ -567,4 +879,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);
}
+81
View File
@@ -34,6 +34,15 @@ void * frida_k_fget_task (void * a0, unsigned int a1)
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)
@@ -616,6 +625,24 @@ int frida_k_unregister_module_notifier (void * a0)
return ((fn_t) _unregister_module_notifier) (a0);
}
extern void * _mutex_lock;
void frida_k_mutex_lock (void * a0)
{
typedef void (__attribute__ ((regparm (3))) * fn_t) (void *);
((fn_t) _mutex_lock) (a0);
}
extern void * _mutex_unlock;
void frida_k_mutex_unlock (void * a0)
{
typedef void (__attribute__ ((regparm (3))) * fn_t) (void *);
((fn_t) _mutex_unlock) (a0);
}
extern int frida_cb_module_state (void * nb, unsigned long action, void * data);
__attribute__ ((regparm (3))) int
@@ -649,3 +676,57 @@ frida_kcb_die (void * nb, unsigned long action, void * data)
{
return frida_cb_die (nb, action, data);
}
extern void * _frida_kmod_link_open;
int frida_k_frida_kmod_link_open (void)
{
typedef int (__attribute__ ((regparm (3))) * fn_t) (void);
return ((fn_t) _frida_kmod_link_open) ();
}
extern void * _frida_kmod_link_close;
void frida_k_frida_kmod_link_close (void)
{
typedef void (__attribute__ ((regparm (3))) * fn_t) (void);
((fn_t) _frida_kmod_link_close) ();
}
extern void * _frida_kmod_link_send;
int frida_k_frida_kmod_link_send (const void * data, unsigned long size)
{
typedef int (__attribute__ ((regparm (3))) * fn_t) (const void *, unsigned long);
return ((fn_t) _frida_kmod_link_send) (data, size);
}
extern void * _frida_kmod_link_recv;
long frida_k_frida_kmod_link_recv (void * data, unsigned long size)
{
typedef long (__attribute__ ((regparm (3))) * fn_t) (void *, unsigned long);
return ((fn_t) _frida_kmod_link_recv) (data, size);
}
extern void * _frida_kmod_link_pending;
_Bool frida_k_frida_kmod_link_pending (void)
{
typedef _Bool (__attribute__ ((regparm (3))) * fn_t) (void);
return ((fn_t) _frida_kmod_link_pending) ();
}
extern void * _frida_kmod_exit_worker;
void frida_k_frida_kmod_exit_worker (void)
{
typedef void (__attribute__ ((regparm (3))) * fn_t) (void);
((fn_t) _frida_kmod_exit_worker) ();
}
+60
View File
@@ -3,6 +3,7 @@ use alloc::vec::Vec;
use core::ffi::{c_int, c_void};
use core::mem::size_of;
use core::ptr;
use core::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
use super::layout::field_offset;
use super::native;
@@ -77,6 +78,65 @@ pub fn holds_this_one(task: usize) -> bool {
held != 0 && held == id
}
pub fn arm_named_spawn(name: &[u8]) {
let end = name.iter().position(|byte| *byte == 0).unwrap_or(name.len());
let name = &name[..end];
let start = name.len().saturating_sub(COMM_LEN);
let held = &name[start..];
unsafe { (&raw mut WANTED_COMM).as_mut().unwrap()[..held.len()].copy_from_slice(held) };
WANTED_LEN.store(held.len(), Ordering::Release);
super::injection::watch_for_named_spawns();
}
pub fn disarm_named_spawn(_name: &[u8]) {
WANTED_LEN.store(0, Ordering::Release);
}
// Runs in the rename tracepoint's atomic context: no allocation, no sleeping lock, no glib.
pub fn claim_if_wanted(comm: &[u8]) -> bool {
let len = WANTED_LEN.load(Ordering::Acquire);
if len == 0 || comm.len() != len {
return false;
}
let wanted = unsafe { core::slice::from_raw_parts((&raw const WANTED_COMM) as *const u8, len) };
if comm != wanted {
return false;
}
WANTED_LEN.compare_exchange(len, 0, Ordering::AcqRel, Ordering::Acquire).is_ok()
}
pub fn note_pending_spawn(id: u32, comm: &[u8]) {
let len = comm.len().min(COMM_LEN);
unsafe { (&raw mut PENDING_COMM).as_mut().unwrap()[..len].copy_from_slice(&comm[..len]) };
PENDING_LEN.store(len, Ordering::Release);
PENDING_HELD.store(id, Ordering::Release);
}
pub fn a_pending_spawn_waits() -> bool {
PENDING_HELD.load(Ordering::Acquire) != 0
}
pub fn drain_pending_spawn() {
let id = PENDING_HELD.swap(0, Ordering::AcqRel);
if id == 0 {
return;
}
let len = PENDING_LEN.load(Ordering::Acquire);
let name = unsafe { core::slice::from_raw_parts((&raw const PENDING_COMM) as *const u8, len).to_vec() };
unsafe { held_spawns() }.push((id, name));
}
static mut WANTED_COMM: [u8; COMM_LEN] = [0; COMM_LEN];
static WANTED_LEN: AtomicUsize = AtomicUsize::new(0);
static mut PENDING_COMM: [u8; COMM_LEN] = [0; COMM_LEN];
static PENDING_LEN: AtomicUsize = AtomicUsize::new(0);
static PENDING_HELD: AtomicU32 = AtomicU32::new(0);
const COMM_LEN: usize = 15;
// A spawn is held in the kernel's own exec path, where telling the host is not this thread's
// to do; the loop says it once it is back where sending a frame belongs.
pub fn note_a_held_spawn(id: u32, program: *const u8) {
+33 -3
View File
@@ -2,20 +2,50 @@ use alloc::ffi::CString;
use alloc::format;
use alloc::vec::Vec;
use crate::bindings::{
GumThreadState, GumThreadState_GUM_THREAD_HALTED, GumThreadState_GUM_THREAD_RUNNING,
GumThreadState_GUM_THREAD_STOPPED, GumThreadState_GUM_THREAD_UNINTERRUPTIBLE,
GumThreadState_GUM_THREAD_WAITING,
};
use crate::kernel::ThreadInfo;
use super::facade::{home_process_id, in_copy};
use super::processes::running_task_ids;
use super::user::names_in;
use super::user::{contents_of, names_in};
pub fn enumerate_threads(found: &mut dyn FnMut(ThreadInfo)) {
pub fn enumerate_threads(found: &mut dyn FnMut(ThreadInfo), with_registers: bool) {
let copy = in_copy();
let ids = if copy { running_threads() } else { running_task_ids() };
for id in ids {
if copy && super::user::thread_is_ours(id) {
continue;
}
found(ThreadInfo { id, cpu_state: None });
let (state, cpu_state) = if !with_registers {
(None, None)
} else if copy {
(state_from_proc(id), super::user::ask_the_kernel_half_for_registers(id))
} else {
(super::injection::thread_state(id), super::injection::capture_registers(id))
};
found(ThreadInfo { id, state, cpu_state });
}
}
fn state_from_proc(id: u32) -> Option<GumThreadState> {
let path = CString::new(format!("/proc/{}/task/{}/stat", home_process_id(), id)).ok()?;
let stat = contents_of(&path);
let last_paren = stat.iter().rposition(|&byte| byte == b')')?;
let state_letter = *stat.get(last_paren + 2)?;
Some(gum_thread_state(state_letter))
}
fn gum_thread_state(letter: u8) -> GumThreadState {
match letter {
b'R' => GumThreadState_GUM_THREAD_RUNNING,
b'S' => GumThreadState_GUM_THREAD_WAITING,
b'D' => GumThreadState_GUM_THREAD_UNINTERRUPTIBLE,
b'T' | b't' => GumThreadState_GUM_THREAD_STOPPED,
_ => GumThreadState_GUM_THREAD_HALTED,
}
}
+36 -18
View File
@@ -8,7 +8,7 @@ use super::arena::{Arena, FAULT_ADDRESS, FAULT_KIND, FAULT_LR, FAULT_PC, WOKEN};
use super::facade::Primitives;
pub fn entry_offset() -> usize {
(frida_linux_user_entry as usize) - crate::own_range().0
(frida_linux_user_entry as *const () as usize) - crate::own_range().0
}
// The copy's first word in the address space it was placed in: it says it is up, and from then
@@ -97,10 +97,6 @@ pub fn thread_is_ours(id: u32) -> bool {
id != 0 && unsafe { CLOAK_THREADS }.contains(&id)
}
pub fn fd_is_ours(fd: u32) -> bool {
fd != 0 && unsafe { CLOAK_FDS }.contains(&fd)
}
fn serve_the_copy() {
drain_loop_wakeup();
let arena = Arena::at(unsafe { ARENA });
@@ -129,7 +125,6 @@ fn leave() -> ! {
syscall(CLOSE, descriptor as usize, 0, 0, 0, 0, 0);
}
Arena::at(unsafe { ARENA }).gone();
syscall(EXIT_GROUP, 0, 0, 0, 0, 0, 0);
loop {}
@@ -230,6 +225,21 @@ fn ask_the_kernel_half_to_make_executable(address: u64, size: usize) -> bool {
arena.executable_was_granted()
}
pub fn ask_the_kernel_half_for_registers(id: u32) -> Option<crate::kernel::CpuState> {
let arena = Arena::at(unsafe { ARENA });
let seq = arena.request_registers(id);
arena.tell_the_kernel_half();
while !arena.registers_settled(seq) {
yield_now();
}
if !arena.registers_were_captured() {
return None;
}
let mut words = [0u64; super::injection::REGISTER_WORDS];
arena.captured_registers(&mut words);
Some(super::injection::registers_from_words(&words))
}
pub fn map_writable(size: usize) -> *mut u8 {
map(size, READABLE | WRITABLE)
}
@@ -314,6 +324,7 @@ fn stand_on_a_thread_pointer() {
unsafe { core::arch::asm!("mov gs, {:e}", in(reg) selector, options(nomem, nostack)) };
}
#[cfg(target_arch = "x86")]
#[repr(C)]
struct UserDesc {
entry_number: u32,
@@ -447,9 +458,10 @@ unsafe extern "C" {
#[cfg(target_arch = "x86")]
unsafe fn start_thread(stack: usize, carried: usize) -> isize {
let launch = stack - 8;
let aligned_argument = stack - 16;
let launch = aligned_argument - 4;
unsafe {
(launch as *mut usize).write(enter_thread as usize);
(launch as *mut usize).write(enter_thread as *const () as usize);
((launch + 4) as *mut usize).write(carried);
frida_start_thread(CLONE, THREAD_FLAGS, launch)
@@ -583,7 +595,8 @@ struct TimeSpec {
nanoseconds: i64,
}
unsafe extern "C" fn frida_poll(ufds: *mut crate::GPollFd, nfds: u32, timeout: i32) -> i32 {
pub(crate) unsafe extern "C" fn frida_poll(ufds: *mut crate::bindings::GPollFD, nfds: u32,
timeout: i32) -> i32 {
const MAX_FDS: usize = 16;
let fds = unsafe { core::slice::from_raw_parts_mut(ufds, nfds as usize) };
@@ -712,7 +725,7 @@ fn current_thread_id() -> u64 {
fn install_fault_reporter() {
for signal in [ILLEGAL, ABORTED, BUS, SEGMENT, ARITHMETIC, TRAPPED, BAD_CALL] {
let action = Action {
handler: report_fault as usize,
handler: report_fault as *const () as usize,
flags: SIGINFO | RESTORER_FLAG,
restorer: restorer(),
blocked: 0,
@@ -1086,7 +1099,7 @@ const RESTORER_FLAG: usize = 0;
target_arch = "arm"
))]
fn restorer() -> usize {
sigreturn_trampoline as usize
sigreturn_trampoline as *const () as usize
}
#[cfg(not(any(
target_arch = "aarch64",
@@ -1191,6 +1204,18 @@ pub fn names_in(directory: &core::ffi::CStr) -> Vec<String> {
names
}
pub(crate) fn read(fd: i32, buffer: *mut u8, count: usize) -> isize {
syscall(READ, fd as usize, buffer as usize, count, 0, 0, 0)
}
pub(crate) fn write(fd: i32, buffer: *const u8, count: usize) -> isize {
syscall(WRITE, fd as usize, buffer as usize, count, 0, 0, 0)
}
pub(crate) fn close(fd: i32) -> isize {
syscall(CLOSE, fd as usize, 0, 0, 0, 0, 0)
}
pub fn contents_of(path: &core::ffi::CStr) -> Vec<u8> {
let file = syscall(OPENAT, WORKING_DIRECTORY, path.as_ptr() as usize, READ_ONLY, 0, 0, 0);
if file < 0 {
@@ -1457,12 +1482,6 @@ const GETDENTS: usize = 220;
const GETDENTS: usize = 217;
#[cfg(target_arch = "aarch64")]
const GETDENTS: usize = 61;
#[cfg(target_arch = "x86")]
const POLL: usize = 168;
#[cfg(target_arch = "x86_64")]
const POLL: usize = 7;
#[cfg(target_arch = "aarch64")]
const POLL: usize = 73;
#[cfg(target_arch = "arm")]
const READ: usize = 3;
#[cfg(target_arch = "x86")]
@@ -1568,7 +1587,6 @@ const MPROTECT: usize = 10;
#[cfg(target_arch = "aarch64")]
const MPROTECT: usize = 226;
const STANDARD_ERROR: usize = 2;
const WORKING_DIRECTORY: usize = -100isize as usize;
const READ_ONLY: usize = 0;
const POLL_IN: i32 = 1;
@@ -0,0 +1,419 @@
use core::ffi::{c_int, c_void};
use core::mem::size_of;
use core::ptr;
use core::sync::atomic::{AtomicUsize, Ordering};
use super::layout::field_offset;
use super::native::{self, hold, let_go, lock_spinlock, unlock_spinlock};
use super::processes::{
HeldTask, append_for_readers, detach_pid, free_pids, unlink, unlink_for_readers,
write_lock_tasklist, write_unlock_tasklist,
};
pub fn start_in_process(id: u32, entry: usize, stack: usize, argument: usize, tls: usize) -> c_int {
let Some(layout) = describe_thread_group() else {
return -ENOSYS;
};
let Some(task) = HeldTask::with_id(id) else {
return -ESRCH;
};
let Some(leader) = task.leader() else {
return -ESRCH;
};
let mm = unsafe { _get_task_mm(task.address() as *mut c_void) };
if mm.is_null() {
return -ESRCH;
}
let birth = native::alloc(size_of::<Birth>()) as *mut Birth;
unsafe {
birth.write(Birth { entry, stack, argument, tls, leader: leader.into_address(), layout });
_kthread_use_mm(mm);
let started = _user_mode_thread(thread_entry(), birth as *mut c_void, 0);
_kthread_unuse_mm(mm);
_mmput(mm);
if started < 0 {
drop(HeldTask::from_address((*birth).leader));
native::free(birth as *mut u8, size_of::<Birth>());
}
started
}
}
struct Birth {
entry: usize,
stack: usize,
argument: usize,
tls: usize,
leader: usize,
layout: ThreadGroupLayout,
}
fn thread_entry() -> native::KthreadFn {
native::kernel_thread_function(&THREAD_ENTRY, frida_cb_user_thread)
}
static THREAD_ENTRY: AtomicUsize = AtomicUsize::new(0);
unsafe extern "C" fn frida_cb_user_thread(argument: *mut c_void) -> c_int {
let birth = unsafe { (argument as *const Birth).read() };
native::free(argument as *mut u8, size_of::<Birth>());
let leader = unsafe { HeldTask::from_address(birth.leader) };
let child = native::current_task() as usize;
join_thread_group(child, leader.address(), &birth.layout);
adopt_context_of(child, leader.address(), &birth.layout);
drop(leader);
super::injection::hand_userland_to(birth.entry, birth.stack, birth.argument);
use_thread_pointer(child, birth.tls, &birth.layout);
0
}
fn join_thread_group(child: usize, leader: usize, layout: &ThreadGroupLayout) {
let task = &layout.task;
let old_signal = read_word(child + task.signal);
let old_sighand = read_word(child + task.sighand);
let signal = read_word(leader + task.signal);
let sighand = read_word(leader + task.sighand);
let mut freed = [ptr::null_mut::<c_void>(); PIDTYPE_MAX];
let flags = write_lock_tasklist();
unsafe {
detach_pid(freed.as_mut_ptr(), child as *mut c_void, PIDTYPE_SID);
detach_pid(freed.as_mut_ptr(), child as *mut c_void, PIDTYPE_PGID);
detach_pid(freed.as_mut_ptr(), child as *mut c_void, PIDTYPE_TGID);
unlink_for_readers(child + task.tasks);
unlink(child + task.sibling);
unlink_for_readers(child + task.thread_node);
}
let parent = read_word(leader + task.real_parent);
write_word(child + task.real_parent, parent);
write_word(child + task.parent, parent);
write_word(child + task.group_leader, leader);
write_i32(child + task.tgid, read_i32(leader + task.tgid));
write_i32(child + task.exit_signal, -1);
write_word(child + task.signal, signal);
write_word(child + task.sighand, sighand);
hold(sighand + layout.sighand.count);
lock_spinlock(sighand + layout.sighand.siglock);
write_i32(signal + layout.signal.nr_threads, read_i32(signal + layout.signal.nr_threads) + 1);
if let Some(quick_threads) = layout.signal.quick_threads {
write_i32(signal + quick_threads, read_i32(signal + quick_threads) + 1);
}
hold(signal + layout.signal.live);
hold(signal + layout.signal.sigcnt);
unsafe {
(layout.calls.task_join_group_stop)(child as *mut c_void);
if let Some(thread_group) = task.thread_group {
append_for_readers(child + thread_group, leader + thread_group);
}
append_for_readers(child + task.thread_node, signal + layout.signal.thread_head);
}
unlock_spinlock(sighand + layout.sighand.siglock);
write_unlock_tasklist(flags);
unsafe {
free_pids(freed.as_mut_ptr());
(layout.calls.cleanup_sighand)(old_sighand as *mut c_void);
if let_go(old_signal + layout.signal.sigcnt) {
_kmem_cache_free(*_signal_cachep, old_signal as *mut c_void);
}
}
}
fn adopt_context_of(child: usize, leader: usize, layout: &ThreadGroupLayout) {
let task = &layout.task;
let files = read_word(leader + task.files);
let old_files = read_word(child + task.files);
hold(files + layout.files_count);
lock_spinlock(child + task.alloc_lock);
write_word(child + task.files, files);
unlock_spinlock(child + task.alloc_lock);
unsafe { (layout.calls.put_files_struct)(old_files as *mut c_void) };
let fs = read_word(leader + task.fs);
let old_fs = read_word(child + task.fs);
lock_spinlock(fs + layout.fs.lock);
write_i32(fs + layout.fs.users, read_i32(fs + layout.fs.users) + 1);
unlock_spinlock(fs + layout.fs.lock);
lock_spinlock(child + task.alloc_lock);
write_word(child + task.fs, fs);
unlock_spinlock(child + task.alloc_lock);
lock_spinlock(old_fs + layout.fs.lock);
let users_left = read_i32(old_fs + layout.fs.users) - 1;
write_i32(old_fs + layout.fs.users, users_left);
unlock_spinlock(old_fs + layout.fs.lock);
if users_left == 0 {
unsafe { (layout.calls.free_fs_struct)(old_fs as *mut c_void) };
}
let real_cred = read_word(leader + task.real_cred);
let cred = read_word(leader + task.cred);
let old_real_cred = read_word(child + task.real_cred);
let old_cred = read_word(child + task.cred);
hold(real_cred + layout.cred_usage);
write_word(child + task.real_cred, real_cred);
hold(cred + layout.cred_usage);
unsafe { AtomicUsize::from_ptr((child + task.cred) as *mut usize).store(cred, Ordering::Release) };
release_cred(old_cred, layout);
release_cred(old_real_cred, layout);
let namespaces = read_word(leader + task.nsproxy);
hold(namespaces + layout.nsproxy_count);
unsafe { (layout.calls.switch_task_namespaces)(child as *mut c_void, namespaces as *mut c_void) };
if let Some(undo) = &layout.semaphore_undo {
let undo_list = read_word(leader + undo.list);
if undo_list != 0 {
hold(undo_list + undo.refcnt);
write_word(child + undo.list, undo_list);
}
}
#[cfg(target_arch = "aarch64")]
if let Some(lengths) = &layout.vector_lengths {
adopt_vector_lengths(child, leader, lengths);
}
}
fn release_cred(cred: usize, layout: &ThreadGroupLayout) {
if let_go(cred + layout.cred_usage) {
unsafe { (layout.calls.put_cred)(cred as *mut c_void) };
}
}
#[cfg(target_arch = "aarch64")]
fn adopt_vector_lengths(child: usize, leader: usize, lengths: &VectorLengthLayout) {
for array in [lengths.current, lengths.on_exec] {
for kind in 0..VECTOR_KINDS {
let at = array + kind * size_of::<u32>();
write_i32(child + at, read_i32(leader + at));
}
}
}
#[cfg(target_arch = "x86_64")]
fn use_thread_pointer(child: usize, tls: usize, layout: &ThreadGroupLayout) {
const MSR_FS_BASE: u32 = 0xc000_0100;
write_word(child + layout.task.thread_pointer, tls);
unsafe {
core::arch::asm!("wrmsr", in("ecx") MSR_FS_BASE, in("eax") tls as u32,
in("edx") (tls >> 32) as u32, options(nostack));
}
}
#[cfg(target_arch = "aarch64")]
fn use_thread_pointer(child: usize, tls: usize, layout: &ThreadGroupLayout) {
write_word(child + layout.task.thread_pointer, tls);
unsafe { core::arch::asm!("msr tpidr_el0, {}", in(reg) tls, options(nostack)) };
}
struct ThreadGroupLayout {
calls: ThreadGroupCalls,
task: TaskLayout,
signal: SignalLayout,
sighand: SighandLayout,
fs: FsLayout,
files_count: usize,
cred_usage: usize,
nsproxy_count: usize,
semaphore_undo: Option<SemaphoreUndoLayout>,
#[cfg(target_arch = "aarch64")]
vector_lengths: Option<VectorLengthLayout>,
}
struct ThreadGroupCalls {
task_join_group_stop: unsafe extern "C" fn(*mut c_void),
cleanup_sighand: unsafe extern "C" fn(*mut c_void),
put_files_struct: unsafe extern "C" fn(*mut c_void),
free_fs_struct: unsafe extern "C" fn(*mut c_void),
put_cred: unsafe extern "C" fn(*mut c_void),
switch_task_namespaces: unsafe extern "C" fn(*mut c_void, *mut c_void),
}
struct TaskLayout {
tgid: usize,
real_parent: usize,
parent: usize,
group_leader: usize,
exit_signal: usize,
signal: usize,
sighand: usize,
tasks: usize,
sibling: usize,
thread_node: usize,
thread_group: Option<usize>,
files: usize,
fs: usize,
cred: usize,
real_cred: usize,
nsproxy: usize,
alloc_lock: usize,
thread_pointer: usize,
}
struct SignalLayout {
nr_threads: usize,
quick_threads: Option<usize>,
live: usize,
sigcnt: usize,
thread_head: usize,
}
struct SighandLayout {
count: usize,
siglock: usize,
}
struct FsLayout {
users: usize,
lock: usize,
}
struct SemaphoreUndoLayout {
list: usize,
refcnt: usize,
}
#[cfg(target_arch = "aarch64")]
struct VectorLengthLayout {
current: usize,
on_exec: usize,
}
fn describe_thread_group() -> Option<ThreadGroupLayout> {
Some(ThreadGroupLayout {
calls: unsafe {
ThreadGroupCalls {
task_join_group_stop: _task_join_group_stop?,
cleanup_sighand: ___cleanup_sighand?,
put_files_struct: _put_files_struct?,
free_fs_struct: _free_fs_struct?,
put_cred: ___put_cred?,
switch_task_namespaces: _switch_task_namespaces?,
}
},
task: TaskLayout {
tgid: field_offset("task_struct", "tgid")?,
real_parent: field_offset("task_struct", "real_parent")?,
parent: field_offset("task_struct", "parent")?,
group_leader: field_offset("task_struct", "group_leader")?,
exit_signal: field_offset("task_struct", "exit_signal")?,
signal: field_offset("task_struct", "signal")?,
sighand: field_offset("task_struct", "sighand")?,
tasks: field_offset("task_struct", "tasks")?,
sibling: field_offset("task_struct", "sibling")?,
thread_node: field_offset("task_struct", "thread_node")?,
thread_group: field_offset("task_struct", "thread_group"),
files: field_offset("task_struct", "files")?,
fs: field_offset("task_struct", "fs")?,
cred: field_offset("task_struct", "cred")?,
real_cred: field_offset("task_struct", "real_cred")?,
nsproxy: field_offset("task_struct", "nsproxy")?,
alloc_lock: field_offset("task_struct", "alloc_lock")?,
thread_pointer: field_offset("task_struct", THREAD_POINTER_FIELD)?,
},
signal: SignalLayout {
nr_threads: field_offset("signal_struct", "nr_threads")?,
quick_threads: field_offset("signal_struct", "quick_threads"),
live: field_offset("signal_struct", "live")?,
sigcnt: field_offset("signal_struct", "sigcnt")?,
thread_head: field_offset("signal_struct", "thread_head")?,
},
sighand: SighandLayout {
count: field_offset("sighand_struct", "count")?,
siglock: field_offset("sighand_struct", "siglock")?,
},
fs: FsLayout {
users: field_offset("fs_struct", "users")?,
lock: field_offset("fs_struct", "lock").or_else(|| field_offset("fs_struct", "seq.lock"))?,
},
files_count: field_offset("files_struct", "count")?,
cred_usage: field_offset("cred", "usage")?,
nsproxy_count: field_offset("nsproxy", "count")?,
semaphore_undo: describe_semaphore_undo(),
#[cfg(target_arch = "aarch64")]
vector_lengths: describe_vector_lengths(),
})
}
fn describe_semaphore_undo() -> Option<SemaphoreUndoLayout> {
Some(SemaphoreUndoLayout {
list: field_offset("task_struct", "sysvsem.undo_list")?,
refcnt: field_offset("sem_undo_list", "refcnt")?,
})
}
#[cfg(target_arch = "aarch64")]
fn describe_vector_lengths() -> Option<VectorLengthLayout> {
Some(VectorLengthLayout {
current: field_offset("task_struct", "thread.vl")?,
on_exec: field_offset("task_struct", "thread.vl_onexec")?,
})
}
#[cfg(target_arch = "x86_64")]
const THREAD_POINTER_FIELD: &str = "thread.fsbase";
#[cfg(target_arch = "aarch64")]
const THREAD_POINTER_FIELD: &str = "thread.uw.tp_value";
#[cfg(target_arch = "aarch64")]
const VECTOR_KINDS: usize = 2;
fn read_word(address: usize) -> usize {
unsafe { (address as *const usize).read() }
}
fn write_word(address: usize, value: usize) {
unsafe { (address as *mut usize).write(value) };
}
fn read_i32(address: usize) -> i32 {
unsafe { (address as *const i32).read() }
}
fn write_i32(address: usize, value: i32) {
unsafe { (address as *mut i32).write(value) };
}
const PIDTYPE_TGID: c_int = 1;
const PIDTYPE_PGID: c_int = 2;
const PIDTYPE_SID: c_int = 3;
const PIDTYPE_MAX: usize = 4;
const ESRCH: c_int = 3;
const ENOSYS: c_int = 38;
unsafe extern "C" {
static _get_task_mm: unsafe extern "C" fn(*mut c_void) -> *mut c_void;
static _mmput: unsafe extern "C" fn(*mut c_void);
static _kthread_use_mm: unsafe extern "C" fn(*mut c_void);
static _kthread_unuse_mm: unsafe extern "C" fn(*mut c_void);
static _user_mode_thread: unsafe extern "C" fn(native::KthreadFn, *mut c_void, usize) -> c_int;
static _task_join_group_stop: Option<unsafe extern "C" fn(*mut c_void)>;
static ___cleanup_sighand: Option<unsafe extern "C" fn(*mut c_void)>;
static _kmem_cache_free: unsafe extern "C" fn(*mut c_void, *mut c_void);
static _signal_cachep: *const *mut c_void;
static _put_files_struct: Option<unsafe extern "C" fn(*mut c_void)>;
static _free_fs_struct: Option<unsafe extern "C" fn(*mut c_void)>;
static ___put_cred: Option<unsafe extern "C" fn(*mut c_void)>;
static _switch_task_namespaces: Option<unsafe extern "C" fn(*mut c_void, *mut c_void)>;
}
-3
View File
@@ -2,9 +2,6 @@
// image and calls its `_start`. All of the agent lives in the library crate;
// this target exists only to produce that link. build.rs passes `--undefined
// _start` so the entrypoint survives --gc-sections.
//
// The Linux flavour is built as a static library instead and linked into a
// kernel module by kbuild — see linux/Kbuild.
#![no_std]
#![no_main]
+8 -6
View File
@@ -37,7 +37,7 @@ impl SymbolTable {
self.symbol_count
}
pub fn find_symbol_by_name(&self, name: &str) -> Option<SymbolRef> {
pub fn find_symbol_by_name(&self, name: &str) -> Option<SymbolRef<'_>> {
let (_, entry) = self.binary_search_by_name(name)?;
Some(SymbolRef {
symbol_table: self,
@@ -46,7 +46,7 @@ impl SymbolTable {
})
}
pub fn find_symbols_by_name(&self, name: &str) -> SymbolsByNameIterator {
pub fn find_symbols_by_name(&self, name: &str) -> SymbolsByNameIterator<'_> {
let (found_index, _) = match self.binary_search_by_name(name) {
Some(result) => result,
None => return SymbolsByNameIterator::empty(self),
@@ -83,7 +83,7 @@ impl SymbolTable {
}
}
pub fn find_symbols_matching_glob(&self, pattern: &str) -> SymbolsMatchingIterator {
pub fn find_symbols_matching_glob(&self, pattern: &str) -> SymbolsMatchingIterator<'_> {
if self.is_empty() {
return SymbolsMatchingIterator::empty(self);
}
@@ -100,7 +100,7 @@ impl SymbolTable {
}
}
pub fn find_symbol_by_address(&self, address: u64) -> Option<SymbolRef> {
pub fn find_symbol_by_address(&self, address: u64) -> Option<SymbolRef<'_>> {
let target_offset = address - get_kernel_base();
let (_, entry) = self.binary_search_by_address(target_offset)?;
Some(SymbolRef {
@@ -119,7 +119,7 @@ impl SymbolTable {
}
}
pub fn find_closest_symbol_by_address(&self, address: u64) -> Option<SymbolRef> {
pub fn find_closest_symbol_by_address(&self, address: u64) -> Option<SymbolRef<'_>> {
let kernel_base = get_kernel_base();
let target_offset = address - kernel_base;
let (_, entry) = self.binary_search_closest_by_address(target_offset)?;
@@ -130,7 +130,9 @@ impl SymbolTable {
})
}
pub fn iter_symbols_in_range(&self, start_address: u64, end_address: u64) -> SymbolsInRangeIterator {
pub fn iter_symbols_in_range(&self, start_address: u64, end_address: u64)
-> SymbolsInRangeIterator<'_>
{
if self.is_empty() {
return SymbolsInRangeIterator {
symbol_table: self,
Loaded 100 of 346 files, more files were not shown because too many files have changed in this diff. Show more